Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
245 lines
13 KiB
Python
245 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# Test harness for the FRI VERIFIER sub-component (port spec section 5, component (d)). Runs:
|
|
# 1. CONFORMANCE (the real gate): the ground-truth extractor (pq-stark/fri-extractor) ran the pinned
|
|
# Plonky3 p3-fri 0.4.3-succinct PROVER to emit real FRI proofs over known low-degree polynomials
|
|
# and confirmed the pinned p3-fri VERIFIER accepts them. This reference reproduces the ACCEPT for
|
|
# every genuine proof and REJECTS four tamper variants per case: a corrupted opened value, a
|
|
# corrupted MMCS sibling digest, a wrong fold challenge (beta), and a non-matching final polynomial.
|
|
# 2. a perm + two-adic-generator sanity check tying the ground truth to the CONFIRMED Poseidon2
|
|
# permutation (component (b)) and component (a)'s two-adic generators (this closes the [VERIFY] on
|
|
# twoAdicGenerator: the fold's coset points come out right only if the generators match Plonky3).
|
|
# 3. cross-language agreement: Python vs Node vs Java produce byte-identical per-query FOLDED values
|
|
# (three independent implementations of the F_{p^4} fold arithmetic) and identical accept/reject flags.
|
|
# Writes ../results/kat-results-fri-verify.json and exits non-zero on any failure.
|
|
#
|
|
# ============================ HONEST SCOPE ============================
|
|
# This confirms the FOLD + OPENING relations (verify_query) against the pinned p3-fri. Within this KAT the
|
|
# betas and query indices are taken as INPUTS (from the pinned p3-fri challenger in the extractor). The
|
|
# transcript BINDING that derives them in-circuit is component (f) (the duplex-sponge challenger), now
|
|
# CONFIRMED separately (test_challenger.py), whose derived betas/indices close the loop into this
|
|
# verify_query. So even with the FRI relation (d) AND the challenger (f) confirmed, the top-level 0x0AE8
|
|
# stays FAIL-CLOSED on the AIR (component (e), StarkConstraints=UNAVAILABLE): ACCEPT is unreachable and
|
|
# computePrecompile returns EMPTY for every input. DO NOT ACTIVATE.
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from shutil import which
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
|
|
import fri_verify_reference as R # noqa: E402
|
|
|
|
RESULTS = os.path.normpath(os.path.join(HERE, "..", "results", "kat-results-fri-verify.json"))
|
|
GROUND_TRUTH = os.path.join(HERE, "fri_ground_truth.json")
|
|
|
|
_pass = 0
|
|
_fail = 0
|
|
_notes = []
|
|
|
|
|
|
def check(name, cond):
|
|
global _pass, _fail
|
|
if cond:
|
|
_pass += 1
|
|
else:
|
|
_fail += 1
|
|
print(f"FAIL {name}")
|
|
|
|
|
|
# ---- 1/2. conformance (accept genuine, reject tampered) + sanity -----------------------------------
|
|
def conformance():
|
|
gt = R.load_ground_truth()
|
|
check("sanity.perm_zeros", R.M.permute([0] * 16) == gt["perm_zeros"])
|
|
for b in range(28):
|
|
check(f"sanity.gen.{b}", R.F.two_adic_generator(b) == gt["two_adic_generators"][b])
|
|
for case in gt["cases"]:
|
|
check(f"accept.{case['name']}", R.verify_case(case))
|
|
check(f"reject.sibling.{case['name']}", not R.verify_case(case, "sibling"))
|
|
check(f"reject.proof.{case['name']}", not R.verify_case(case, "proof"))
|
|
check(f"reject.beta.{case['name']}", not R.verify_case(case, "beta"))
|
|
check(f"reject.final.{case['name']}", not R.verify_case(case, "final"))
|
|
_notes.append(f"CONFORMANCE: {len(gt['cases'])} FRI proofs from the pinned p3-fri 0.4.3-succinct "
|
|
f"prover accepted; 4 tamper variants each rejected (opening, MMCS proof, beta, final poly)")
|
|
|
|
|
|
# ---- 3. cross-language agreement (Python vs Node vs Java) -------------------------------------------
|
|
def canonicalize(v):
|
|
def ia(x):
|
|
return [int(e) for e in x]
|
|
|
|
def ia2(x):
|
|
return [ia(e) for e in x]
|
|
|
|
return {
|
|
"permZeros": ia(v["permZeros"]),
|
|
"twoAdicGenerators": ia(v["twoAdicGenerators"]),
|
|
"cases": [
|
|
{
|
|
"name": c["name"],
|
|
"logBlowup": int(c["logBlowup"]),
|
|
"logMaxHeight": int(c["logMaxHeight"]),
|
|
"numQueries": int(c["numQueries"]),
|
|
"folded": ia2(c["folded"]),
|
|
"finalPoly": ia(c["finalPoly"]),
|
|
"accept": bool(c["accept"]),
|
|
"rejectSibling": bool(c["rejectSibling"]),
|
|
"rejectProof": bool(c["rejectProof"]),
|
|
"rejectBeta": bool(c["rejectBeta"]),
|
|
"rejectFinal": bool(c["rejectFinal"]),
|
|
}
|
|
for c in v["cases"]
|
|
],
|
|
}
|
|
|
|
|
|
def cross_language():
|
|
py = canonicalize(R.shared_vectors())
|
|
# every Python-side flag must hold, and each query's folded value must equal the final poly
|
|
for c in py["cases"]:
|
|
check(f"py.accept.{c['name']}", c["accept"])
|
|
check(f"py.rejects.{c['name']}",
|
|
c["rejectSibling"] and c["rejectProof"] and c["rejectBeta"] and c["rejectFinal"])
|
|
check(f"py.folded_eq_final.{c['name']}", all(fe == c["finalPoly"] for fe in c["folded"]))
|
|
langs = {"python": py}
|
|
|
|
node = which("node")
|
|
if node:
|
|
try:
|
|
out = subprocess.run(
|
|
[node, os.path.join(HERE, "fri_verify_reference.mjs")],
|
|
capture_output=True, text=True, timeout=180, check=True,
|
|
).stdout.strip()
|
|
langs["node"] = canonicalize(json.loads(out))
|
|
except Exception as e: # noqa: BLE001
|
|
_notes.append(f"node cross-check unavailable: {e}")
|
|
else:
|
|
_notes.append("node not found on PATH; node cross-check skipped")
|
|
|
|
javac = which("javac")
|
|
java = which("java")
|
|
if javac and java:
|
|
try:
|
|
subprocess.run(
|
|
[javac, "-d", os.path.join(HERE, "out"),
|
|
os.path.join(HERE, "FriVerifySelfTest.java")],
|
|
capture_output=True, text=True, timeout=180, check=True,
|
|
)
|
|
out = subprocess.run(
|
|
[java, "-cp", os.path.join(HERE, "out"), "FriVerifySelfTest", "--emit", GROUND_TRUTH],
|
|
capture_output=True, text=True, timeout=120, check=True,
|
|
).stdout.strip()
|
|
langs["java"] = canonicalize(json.loads(out))
|
|
except Exception as e: # noqa: BLE001
|
|
_notes.append(f"java cross-check unavailable: {e}")
|
|
else:
|
|
_notes.append("javac/java not found on PATH; java cross-check skipped")
|
|
|
|
for name, data in langs.items():
|
|
if name == "python":
|
|
continue
|
|
check(f"crosslang.{name}.agree", data == py)
|
|
_notes.append("cross-language implementations compared: " + ", ".join(sorted(langs)))
|
|
return sorted(langs)
|
|
|
|
|
|
def main():
|
|
conformance()
|
|
langs = cross_language()
|
|
|
|
report = {
|
|
"component": "FRI low-degree test / query verification over BabyBear (port spec component (d))",
|
|
"precompile": "0x0000000000000000000000000000000000000ae8",
|
|
"scope": "port spec section 5; the FRI verifier's FOLD + OPENING relations (verify_query / "
|
|
"verify_challenges): arity-2 folding in F_{p^4}, per-layer MMCS openings against the "
|
|
"commit-phase roots, down to a single constant equal to the committed final polynomial.",
|
|
"validates": "CONFORMANCE: real FRI proofs emitted by the pinned Plonky3 p3-fri 0.4.3-succinct "
|
|
"PROVER (and accepted by its own verifier) are re-verified byte-for-byte by this "
|
|
"reference (accept), and four tamper variants are rejected (corrupted opened value, "
|
|
"corrupted MMCS sibling digest, wrong fold challenge beta, non-matching final poly); "
|
|
"Python/Node/Java produce byte-identical per-query folded values.",
|
|
"does_not_validate": "the SP1 recursion-AIR constraint evaluation (component (e)), which supplies "
|
|
"the reduced openings and is UN-PORTED. Within THIS FRI KAT the betas / query "
|
|
"indices are supplied as inputs; deriving them in-circuit is component (f) (the "
|
|
"duplex-sponge challenger), now CONFIRMED separately (test_challenger.py), whose "
|
|
"derived betas/indices close the loop into this verify_query.",
|
|
"top_level_verifier": "unchanged, still FAIL-CLOSED (returns EMPTY for every input). Even with the FRI "
|
|
"fold+opening (d) and the challenger (f) confirmed, "
|
|
"StarkConstraints.evaluateAtZeta=UNAVAILABLE (component (e)), so verify() never "
|
|
"reaches ACCEPT and computePrecompile returns EMPTY. ACCEPT is unreachable. DO NOT ACTIVATE.",
|
|
"confirmed_config": {
|
|
"folding_arity": 2,
|
|
"sibling_rule": "index_sibling = index ^ 1, index_pair = index >> 1 (p3-fri verifier.rs)",
|
|
"num_fold_rounds": "log_max_height - log_blowup (final poly is a single F_{p^4} CONSTANT)",
|
|
"log_max_height": "commit_phase_commits.len() + log_blowup",
|
|
"commit_phase_mmcs": "ExtensionMmcs<Val,Challenge,ValMmcs> over the CONFIRMED FieldMerkleTreeMmcs "
|
|
"(component (c)); a commit-phase leaf is a pair of F_{p^4} evals flattened to "
|
|
"8 BabyBear coords, hashed by the PaddingFreeSponge",
|
|
"coset_point": "x = two_adic_generator(log_max_height)^reverse_bits_len(index, log_max_height), "
|
|
"in F_{p^4}; the sibling point is -x (two_adic_generator(1))",
|
|
"fold_relation": "folded = e0 + (beta - x0) * (e1 - e0) / (x1 - x0) (linear interpolation through "
|
|
"beta), plus reduced_openings[log_folded_height+1] injected before each fold",
|
|
"final_poly_check": "every query's folded constant must equal proof.final_poly",
|
|
},
|
|
"confirmed_by_kat": [
|
|
"CONFIRMED arity-2 fold (sibling=index^1, index_pair=index>>1) vs p3-fri verifier.rs",
|
|
"CONFIRMED num_fold_rounds = log_max_height - log_blowup; final poly is a single F_{p^4} constant",
|
|
"CONFIRMED reverse_bits_len index-to-coset-point mapping (x = g^rev(index))",
|
|
"CONFIRMED two_adic_generator(bits) matches Plonky3 (the fold coset points are correct)",
|
|
"CONFIRMED the F_{p^4} non-residue W = 11 is Plonky3's (the EF fold arithmetic reproduces the fold)",
|
|
"CONFIRMED the ExtensionMmcs flatten (each F_{p^4} eval -> 4 base coords, pair -> 8) + base MMCS open",
|
|
],
|
|
"cross_language": langs,
|
|
"conformance": {
|
|
"status": "CONFIRMED (real known-answer test PASSED: accept genuine + reject tampered)",
|
|
"method": "pq-stark/fri-extractor (a Rust binary depending on the pinned crates; lockfile "
|
|
"checksums matched) ran p3_fri::prover::prove over known low-degree codewords with the "
|
|
"seed_from_u64(1) Poseidon2, then p3_fri::verifier::{verify_shape_and_sample_challenges,"
|
|
"verify_challenges} to confirm the library accepts; its output is fri_ground_truth.json",
|
|
"cases": [c["name"] for c in R.load_ground_truth()["cases"]],
|
|
},
|
|
"pinned_conformance_target": {
|
|
"revision": "Succinct Plonky3 fork, crates.io version 0.4.3-succinct",
|
|
"p3_fri_checksum": "5cbc4965ee488f3247867b7ec4bb005b8afa72cb0d461a4dcb1387ecab6426d5",
|
|
"p3_challenger_checksum": "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77",
|
|
"p3_dft_checksum": "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0",
|
|
"p3_commit_checksum": "50acacc7219fce6c01db938f82c1b21b5e7133990b7fff861f91534aeb569419",
|
|
"found_in": "aerenew/zk-circuits/*/Cargo.lock and aerenew/rollup-evm-validity/*/Cargo.lock",
|
|
},
|
|
"flags": [
|
|
"[VERIFY]->CONFIRMED here: arity-2 fold, num_fold_rounds, reverse-bits mapping, "
|
|
"two_adic_generator, W=11 (all exercised by the accept-KAT over the pinned p3-fri proof)",
|
|
"[MEASURE] conformance against a REAL exported SP1 v6.1.0 inner proof's FRI section (needs the "
|
|
"exported proof + the AIR reduced-openings, component (e))",
|
|
"component (f) transcript binding is now CONFIRMED (Challenger.spongePorted=true; the derived "
|
|
"betas/indices close the loop into this verify_query, see test_challenger.py). The top level "
|
|
"stays fail-closed on the AIR (component (e), StarkConstraints=UNAVAILABLE)",
|
|
],
|
|
"notes": _notes,
|
|
"passed": _pass,
|
|
"failed": _fail,
|
|
"total": _pass + _fail,
|
|
}
|
|
os.makedirs(os.path.dirname(RESULTS), exist_ok=True)
|
|
with open(RESULTS, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
|
|
print("\n=== FRI verifier (low-degree test) KAT ===")
|
|
print(f"cross-language: {', '.join(langs)}")
|
|
for nnote in _notes:
|
|
print(f"note: {nnote}")
|
|
print(f"PASS={_pass} FAIL={_fail} TOTAL={_pass + _fail}")
|
|
print(f"results -> {RESULTS}")
|
|
if _fail != 0:
|
|
print("FRI_VERIFY_KAT_FAILED")
|
|
sys.exit(1)
|
|
print("CONFORMANCE to Plonky3 FRI verify: CONFIRMED (real KAT PASSED vs p3-fri 0.4.3-succinct; "
|
|
"accept genuine + reject tampered). Top level stays fail-closed on the AIR (component (e)).")
|
|
print("FRI_VERIFY_CONFORMANCE_OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|