#!/usr/bin/env python3 # Conformance + cross-language harness for the Fiat-Shamir DUPLEX CHALLENGER (component (f)) of the PQ # STARK-verify precompile 0x0AE8. It: # 1. runs the Python challenger conformance KAT against the real ground truth emitted by the pinned # p3-challenger / p3-fri 0.4.3-succinct crates (challenger-extractor -> challenger_ground_truth.json); # 2. asserts Python, Node, and standalone Java produce byte-identical shared vectors (three # independent implementations of observe / sample / sample_bits / check_witness); # 3. closes the loop END TO END: derive the FRI betas + query indices FROM THE TRANSCRIPT, then feed # them into the CONFIRMED FRI verify_query (component (d), fri_verify_reference) and confirm it # ACCEPTS the real FRI ground-truth proof and REJECTS a tampered derived beta. This is # transcript -> betas/indices -> FRI verify, the loop the FRI KAT left open. # # It writes ../results/kat-results-challenger.json. The top-level 0x0AE8 stays FAIL-CLOSED regardless # (component (e), the AIR, is un-ported); this harness confirms component (f) only. import json import os import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) RESULTS = os.path.join(HERE, "..", "results", "kat-results-challenger.json") sys.path.insert(0, HERE) import challenger_reference as C import fri_verify_reference as FV # component (d), CONFIRMED passed = 0 failed = 0 notes = [] def check(name, cond): global passed, failed if cond: passed += 1 else: failed += 1 notes.append("FAIL: " + name) print(("PASS " if cond else "FAIL ") + name) # ---- 1) Python conformance KAT ---- p, t, det = C.conformance_kats() for d in det: check("py." + d["check"], d["pass"]) # ---- 2) cross-language byte-identity ---- py = C.shared_vectors() node = json.loads(subprocess.check_output(["node", os.path.join(HERE, "challenger_reference.mjs")])) java_out = subprocess.check_output( ["java", "-cp", os.path.join(HERE, "out"), "ChallengerSelfTest", "--emit", os.path.join(HERE, "challenger_ground_truth.json")], cwd=HERE) java = json.loads(java_out) def norm(v): return json.loads(json.dumps(v)) check("xlang.permZeros.py==node", norm(py["permZeros"]) == norm(node["permZeros"])) check("xlang.permZeros.py==java", norm(py["permZeros"]) == norm(java["permZeros"])) check("xlang.duplex.py==node", norm(py["duplex"]) == norm(node["duplex"])) check("xlang.duplex.py==java", norm(py["duplex"]) == norm(java["duplex"])) check("xlang.fri.py==node", norm(py["fri"]) == norm(node["fri"])) check("xlang.fri.py==java", norm(py["fri"]) == norm(java["fri"])) # ---- 3) end-to-end loop closure: transcript -> betas/indices -> CONFIRMED FRI verify_query ---- with open(os.path.join(HERE, "challenger_ground_truth.json")) as fp: ch_gt = json.load(fp) with open(os.path.join(HERE, "fri_ground_truth.json")) as fp: fri_gt = json.load(fp) fri_by_name = {c["name"]: c for c in fri_gt["cases"]} for tc in ch_gt["fri_transcript"]: name = tc["name"] betas, indices, pow_accept = C.derive_fri_challenges( tc["commit_phase_commits"], tc["final_poly"], tc["pow_witness"], tc["pow_bits"], tc["log_blowup"], tc["num_queries"]) fc = fri_by_name[name] # Build a FRI case that uses the TRANSCRIPT-DERIVED betas (indices already match fc queries). check("e2e[%s].derived_indices==fri_gt" % name, indices == [q["index"] for q in fc["queries"]]) synthetic = { "log_blowup": tc["log_blowup"], "log_max_height": len(tc["commit_phase_commits"]) + tc["log_blowup"], "commit_phase_commits": tc["commit_phase_commits"], "final_poly": fc["final_poly"], "betas": betas, # <-- derived in-circuit from the transcript "num_queries": tc["num_queries"], "queries": fc["queries"], # real openings from the confirmed FRI ground truth } # transcript -> derived betas/indices -> CONFIRMED FRI verify_query must ACCEPT the honest proof. check("e2e[%s].fri_verify_accepts_with_derived_betas" % name, FV.verify_case(synthetic) is True) # and a tampered derived beta must be REJECTED (soundness of the closed loop). check("e2e[%s].fri_verify_rejects_tampered_beta" % name, FV.verify_case(synthetic, "beta") is False) # ---- write results ---- total = passed + failed result = { "component": "Fiat-Shamir transcript / Poseidon2 DuplexChallenger (port spec component (f))", "precompile": "0x0000000000000000000000000000000000000ae8", "scope": "port spec section 7; the duplex-sponge challenger DuplexChallenger and the GrindingChallenger check_witness: observe (overwrite-mode absorb), sample (squeeze, pop from the end of the rate buffer), sample_bits (low-bits query-index reduction), check_witness (observe witness then sample_bits==0). Wired behind the still-fail-closed top level.", "validates": "CONFORMANCE: ground truth emitted by executing the pinned Plonky3 p3-challenger 0.4.3-succinct DuplexChallenger + GrindingChallenger (and the p3-fri verify_shape_and_sample_challenges transcript ORDER). (1) a fully-scripted observe/sample transcript reproduces every sampled base + F_{p^4} ext value, the full 16-lane sponge state, and a check_witness accept/reject table byte-for-byte; (2) the FRI betas + query indices + grinding acceptance are DERIVED from the transcript (commit_phase_commits + final_poly + pow_witness) and match the pinned p3-fri challenger AND the confirmed FRI ground truth; (3) END TO END: those derived betas/indices, fed into the CONFIRMED FRI verify_query (component (d)), accept the real FRI proof and reject a tampered beta. Python/Node/standalone-Java are byte-identical.", "does_not_validate": "the SP1 recursion-AIR constraint evaluation (component (e)): the reduced openings, DEEP zeta/alpha challenges, quotient consistency, and vk binding. That is un-ported (StarkConstraints.evaluateAtZeta = UNAVAILABLE). A real exported SP1 v6.1.0 proof's transcript (vkey digest, trace/quotient commitments, opened values order) is still [MEASURE].", "top_level_verifier": "unchanged, still FAIL-CLOSED (returns EMPTY for every input). Even with component (f) confirmed and Challenger.spongePorted flipped true, StarkConstraints.evaluateAtZeta returns UNAVAILABLE (component (e) un-ported), so verify() returns UNAVAILABLE before any query loop and computePrecompile returns EMPTY. ACCEPT is unreachable. DO NOT ACTIVATE.", "confirmed_config": "DuplexChallenger, WIDTH=16, RATE=8>; Witness=BabyBear; Challenge=BinomialExtensionField; sample pops from the END of the rate buffer (Vec::pop); sample_bits masks the LOW `bits` of as_canonical_u64; check_witness = observe(witness) then sample_bits(bits)==0.", "pinned_conformance_target": "p3-challenger 0.4.3-succinct (duplex_challenger.rs, grinding_challenger.rs), transcript order p3-fri 0.4.3-succinct verifier.rs (verify_shape_and_sample_challenges), sponge = confirmed Poseidon2-BabyBear (component (b)); byte-identical to the repo Cargo.lock. Extractor: pq-stark/challenger-extractor -> pq-stark/challenger_ground_truth.json.", "cross_language": "Python (challenger_reference.py), Node (challenger_reference.mjs), standalone Java (ChallengerSelfTest.java) emit byte-identical shared vectors.", "flags": {"Challenger.spongePorted": True, "note": "flipped true: component (f) confirmed. StarkConstraints.evaluateAtZeta stays UNAVAILABLE (component (e)), so the top level stays fail-closed and ACCEPT is unreachable."}, "notes": notes, "passed": passed, "failed": failed, "total": total, } os.makedirs(os.path.dirname(RESULTS), exist_ok=True) with open(RESULTS, "w") as fp: json.dump(result, fp, indent=1) print("\nchallenger conformance: PASS=%d FAIL=%d TOTAL=%d" % (passed, failed, total)) print("results -> %s" % os.path.normpath(RESULTS)) sys.exit(0 if failed == 0 else 1)