aere-research/pq-stark/fri_query_index_reference.py
Aere Network 6cb0140fae Republished from a clean root: the compiled artifact is gone from history, and the local line of work joins the sanitized public line
The public history carried kat/__pycache__/mlkem768_reference.cpython-314.pyc,
a compiled Python artifact embedding the operator's absolute local path. Text
secret scanners do not read compiled binaries, which is exactly how it slipped
through, and removing it from the tip would have left it reachable through the
old root commits. So this repository is republished from a single clean root.

This root also carries, from the previously unpublished line of work:
- corrected LICENSE year, LICENSING.md, VERIFY-POLICY.md, and
  CITATIONS-UNRESOLVED.md remeasured 2026-08-11 (101 paths, README aligned)
- O-018: run_consensus_verification.py ran 19 of 29 models and reported PASS;
  it now runs all 29, and computemarket_smt.py gains resolveByTimeout /
  reclaimUnsettled cases plus a negative control
- O-006: the word 'audited' removed from next to Bouncy Castle, twice, after a
  concurrent edit resurrected it
- O-014: prior art named and dated - Algorand's native falcon_verify shipped
  about ten months before AERE's precompiles; the primacy claim is withdrawn
  where it was implied
- bench/ scripts parametrized so they actually run for an outsider (the
  earlier textual sanitization left $STAGING unexpanded inside Python strings)
- AIP-2/AIP-3 errata with measured figures, spec remeasurements at 2026-08-01,
  and the spec-zk-stack retractions (owner is an operational key, not the
  Foundation; 'maximally sound' withdrawn; aggregator V1 deprecated)
The redacted bench-host environment files from the sanitized line are kept
exactly as published; the unredacted local variants are not carried.
2026-08-15 13:52:14 +03:00

110 lines
5.7 KiB
Python

#!/usr/bin/env python3
# Independent reference for the FRI query-index derivation used by the PQ STARK-verify
# precompile 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify). This module implements ONE
# self-contained, constant-free slice of component (d) in
# docs/AERE-STARK-VERIFIER-PORT-SPEC.md: turning Fiat-Shamir challenger output into FRI query
# indices, and the per-layer folding-index walk each query follows.
#
# HONEST SCOPE. This is NOT a STARK verifier and does not verify anything. It reproduces the
# public Plonky3 p3-fri index arithmetic (sample_bits + the arity-2 folding walk). It depends on
# NO secret round constants, so it can be validated offline against hand-computed golden vectors
# and cross-checked against independent implementations (Node and Java). Conformance to a real
# SP1 v6.1.0 trace is [MEASURE] (needs the Poseidon2 challenger + an exported proof); see the
# port spec section 8 for the exact validation command. The top-level 0x0AE8 verifier stays
# fail-closed regardless of this module.
#
# Plonky3 semantics reproduced (mark [VERIFY] against p3-fri / p3-challenger at the pinned SP1
# v6.1.0 Plonky3 revision):
# - sample_bits(bits): low `bits` bits of a sampled base-field element's canonical u32
# representative. [VERIFY] LSB (not MSB) and that one BabyBear sample supplies enough bits
# (log_max_height < 31 for the pinned config).
# - folding walk: arity 2, index_sibling = index ^ 1, index_pair = index >> 1 per layer.
# [VERIFY] against p3-fri verifier.rs verify_query.
# - num_fold_rounds = log_max_height - log_final_poly_len; after that many folds the index
# collapses into the final polynomial's domain [0, 2^log_final_poly_len). [VERIFY] the final
# poly length for the pinned config.
from dataclasses import dataclass, asdict
from typing import List
# BabyBear prime, for range-checking a canonical field representative fed to sample_bits.
BABYBEAR_P = 2013265921 # 2^31 - 2^27 + 1 = 0x78000001
def sample_bits(canonical_u32: int, bits: int) -> int:
"""Reduce a sampled BabyBear field element (its canonical u32 representative, in [0, p)) to a
query index in [0, 2^bits) by taking the low `bits` bits. Mirrors Plonky3
CanSampleBits::sample_bits for a DuplexChallenger over BabyBear.
[VERIFY] Plonky3 masks the LOW bits of as_canonical_u32(); confirm LSB vs MSB against
p3-challenger. bits must be < 31 so a single 31-bit BabyBear sample supplies them."""
if bits < 0:
raise ValueError("bits must be non-negative")
if bits >= 31:
# A single BabyBear element only carries ~31 bits; larger indices would need multiple
# samples. The pinned SP1 configs keep log_max_height well under 31, so we guard here
# rather than silently truncate.
raise ValueError("bits >= 31 needs multiple field samples; not supported for pinned config")
if canonical_u32 < 0 or canonical_u32 >= BABYBEAR_P:
raise ValueError("canonical_u32 must be a canonical BabyBear representative in [0, p)")
return canonical_u32 & ((1 << bits) - 1)
@dataclass(frozen=True)
class FoldStep:
layer: int # 0-based FRI folding round
index: int # this query's index within the current (folded) domain
sibling: int # index ^ 1, the paired evaluation opened alongside `index`
index_pair: int # index >> 1, the Merkle-open position in the half-size next layer
parity: int # index & 1, which of the sibling pair is this query's own evaluation
def walk(index: int, log_max_height: int, log_final_poly_len: int = 0) -> List[FoldStep]:
"""Produce the per-layer folding-index walk for one FRI query.
Given the sampled `index` in [0, 2^log_max_height) and the final polynomial log-length, return
the sequence of FoldStep records, one per folding round. num_fold_rounds =
log_max_height - log_final_poly_len. This is the index bookkeeping a FRI verifier follows to
know, at every layer, which sibling pair to open and which folded value is its own. It does NOT
perform the fold arithmetic or any Merkle/Poseidon2 opening (those are delegated, port spec
section 5)."""
if log_max_height < 0 or log_final_poly_len < 0:
raise ValueError("log heights must be non-negative")
if log_final_poly_len > log_max_height:
raise ValueError("log_final_poly_len must not exceed log_max_height")
if index < 0 or index >= (1 << log_max_height):
raise ValueError("index out of range for log_max_height")
num_fold_rounds = log_max_height - log_final_poly_len
steps: List[FoldStep] = []
cur = index
for layer in range(num_fold_rounds):
steps.append(
FoldStep(
layer=layer,
index=cur,
sibling=cur ^ 1,
index_pair=cur >> 1,
parity=cur & 1,
)
)
cur = cur >> 1
return steps
def final_index(index: int, log_max_height: int, log_final_poly_len: int = 0) -> int:
"""The terminal index after all folds; must land in the final polynomial domain
[0, 2^log_final_poly_len)."""
num_fold_rounds = log_max_height - log_final_poly_len
return index >> num_fold_rounds
def derive_query_indices(sampled_canonical_u32: List[int], log_max_height: int) -> List[int]:
"""Turn a list of challenger-sampled field representatives (one per query) into query indices.
In a real verifier the samples come out of the Poseidon2 duplex challenger (delegated); here
they are supplied so the index reduction can be tested independently of the sponge."""
return [sample_bits(s, log_max_height) for s in sampled_canonical_u32]
def walk_as_dicts(index: int, log_max_height: int, log_final_poly_len: int = 0):
return [asdict(s) for s in walk(index, log_max_height, log_final_poly_len)]