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.
110 lines
5.7 KiB
Python
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)]
|