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.
251 lines
14 KiB
Python
251 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# Test harness for the GENERIC AIR constraint / quotient-consistency check (port spec section 6,
|
|
# component (e)). Runs:
|
|
# 1. CONFORMANCE (the real gate): the ground-truth extractor (pq-stark/airquotient-extractor) ran the
|
|
# pinned Plonky3 p3-uni-stark 0.4.3-succinct PROVER + VERIFIER on two KNOWN example AIRs (the
|
|
# Fibonacci AIR from Plonky3's OWN tests/fib_air.rs, and a degree-3 multiply AIR matching
|
|
# tests/mul_air.rs) and confirmed the library ACCEPTS (so the identity folded_constraints(zeta) ==
|
|
# Z_H(zeta)*quotient(zeta) holds for the emitted alpha/zeta/openings). This reference reproduces the
|
|
# ACCEPT for every genuine case and REJECTS three tamper variants per case: a corrupted trace opening,
|
|
# a wrong quotient chunk, a wrong alpha.
|
|
# 2. a perm + generator sanity check tying the ground truth to the CONFIRMED Poseidon2 permutation
|
|
# (component (b)) and BabyBear's multiplicative generator (component (a)).
|
|
# 3. cross-language agreement: Python vs Node vs Java produce byte-identical reconstructed quotient(zeta)
|
|
# and folded_constraints(zeta) (three independent F_{p^4} implementations) and identical flags.
|
|
# Writes ../results/kat-results-air-quotient.json and exits non-zero on any failure.
|
|
#
|
|
# ============================ HONEST SCOPE (read first) ============================
|
|
# This confirms the GENERIC quotient-consistency MECHANISM (component (e)) for a SUPPLIED AIR constraint
|
|
# evaluator. It does NOT port the SP1 RECURSION AIR (its specific multi-thousand-constraint set,
|
|
# interactions, public-value layout) or the verifying-key digest that commits to it: that is a large,
|
|
# program-specific, multi-week piece needing the SP1 toolchain. So this passing GENERIC KAT does NOT let
|
|
# the precompile verify a real SP1 proof: the top-level 0x0AE8 STAYS FAIL-CLOSED for real proofs
|
|
# (StarkConstraints.SP1_RECURSION_AIR_PORTED = false keeps evaluateAtZeta = UNAVAILABLE on the real path;
|
|
# the generic check is exposed only for a supplied example AIR under this KAT). 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 air_quotient_reference as R # noqa: E402
|
|
import mmcs_babybear_reference as M # noqa: E402
|
|
|
|
RESULTS = os.path.normpath(os.path.join(HERE, "..", "results", "kat-results-air-quotient.json"))
|
|
GROUND_TRUTH = os.path.join(HERE, "air_quotient_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", M.permute([0] * 16) == gt["perm_zeros"])
|
|
check("sanity.val_generator", gt["val_generator"] == R.F.GENERATOR)
|
|
for case in gt["cases"]:
|
|
check(f"library_accept.{case['name']}", case["library_accept"])
|
|
check(f"accept.{case['name']}", R.check_case(case))
|
|
check(f"reject.trace.{case['name']}", not R.check_case(case, "trace"))
|
|
check(f"reject.quotient.{case['name']}", not R.check_case(case, "quotient"))
|
|
check(f"reject.alpha.{case['name']}", not R.check_case(case, "alpha"))
|
|
_notes.append(f"CONFORMANCE: {len(gt['cases'])} p3-uni-stark 0.4.3-succinct proofs (Fibonacci + "
|
|
f"degree-3 mul AIR) accepted by the library; the generic quotient identity reproduces "
|
|
f"the accept and rejects 3 tamper variants each (trace opening, quotient chunk, alpha)")
|
|
|
|
|
|
# ---- 3. cross-language agreement (Python vs Node vs Java) -------------------------------------------
|
|
def canonicalize(v):
|
|
def ia(x):
|
|
return [int(e) for e in x]
|
|
|
|
return {
|
|
"valGenerator": int(v["valGenerator"]),
|
|
"cases": [
|
|
{
|
|
"name": c["name"],
|
|
"air": c["air"],
|
|
"degreeBits": int(c["degreeBits"]),
|
|
"quotientDegree": int(c["quotientDegree"]),
|
|
"quotient": ia(c["quotient"]),
|
|
"folded": ia(c["folded"]),
|
|
"invZeroifier": ia(c["invZeroifier"]),
|
|
"accept": bool(c["accept"]),
|
|
"rejectTrace": bool(c["rejectTrace"]),
|
|
"rejectQuotient": bool(c["rejectQuotient"]),
|
|
"rejectAlpha": bool(c["rejectAlpha"]),
|
|
}
|
|
for c in v["cases"]
|
|
],
|
|
}
|
|
|
|
|
|
def cross_language():
|
|
py = canonicalize(R.shared_vectors())
|
|
for c in py["cases"]:
|
|
check(f"py.accept.{c['name']}", c["accept"])
|
|
check(f"py.rejects.{c['name']}", c["rejectTrace"] and c["rejectQuotient"] and c["rejectAlpha"])
|
|
langs = {"python": py}
|
|
|
|
node = which("node")
|
|
if node:
|
|
try:
|
|
out = subprocess.run(
|
|
[node, os.path.join(HERE, "air_quotient_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, "AirQuotientSelfTest.java")],
|
|
capture_output=True, text=True, timeout=180, check=True,
|
|
)
|
|
out = subprocess.run(
|
|
[java, "-cp", os.path.join(HERE, "out"), "AirQuotientSelfTest", "--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": "GENERIC AIR constraint evaluation + quotient-consistency check over BabyBear "
|
|
"(port spec component (e), the GENERIC mechanism only)",
|
|
"precompile": "0x0000000000000000000000000000000000000ae8",
|
|
"scope": "port spec section 6; the GENERIC quotient-consistency check a p3-uni-stark verifier does "
|
|
"AFTER the PCS opening (verifier.rs lines 90-141): given the out-of-domain trace openings "
|
|
"at zeta (trace_local) and g*zeta (trace_next), the random challenge alpha, the "
|
|
"quotient-chunk openings, and the trace-domain degree, fold the AIR constraints with alpha "
|
|
"(Horner), reconstruct quotient(zeta) from the chunks + split-domain zps, compute Z_H(zeta) "
|
|
"and the first/last/transition selectors, and check "
|
|
"folded_constraints(zeta) == Z_H(zeta) * quotient(zeta), over F_{p^4}.",
|
|
"validates": "CONFORMANCE: real p3-uni-stark 0.4.3-succinct proofs of two KNOWN example AIRs (the "
|
|
"Fibonacci AIR from Plonky3's own tests/fib_air.rs, single quotient chunk; and a "
|
|
"degree-3 multiply AIR matching tests/mul_air.rs, TWO quotient chunks, exercising the "
|
|
"split-domain zps product) are ACCEPTED by the pinned library verifier; this reference "
|
|
"reproduces the accept via the generic quotient identity and rejects three tamper "
|
|
"variants (corrupted trace opening, wrong quotient chunk, wrong alpha); Python/Node/Java "
|
|
"produce byte-identical reconstructed quotient(zeta) and folded_constraints(zeta).",
|
|
"does_not_validate": "the SP1 RECURSION AIR itself (its specific multi-thousand-constraint set, "
|
|
"interactions/permutation argument, public-value layout) and the verifying-key "
|
|
"digest that commits to it. That is a large, program-specific, multi-week port "
|
|
"needing the SP1 toolchain, and is NOT done here. The GENERIC mechanism working "
|
|
"on a small example AIR does NOT mean the precompile can verify a real SP1 proof.",
|
|
"top_level_verifier": "unchanged, still FAIL-CLOSED (returns EMPTY for every input). The generic "
|
|
"quotient check is enabled ONLY for a supplied example AIR under this KAT; on "
|
|
"the real path StarkConstraints.SP1_RECURSION_AIR_PORTED = false keeps "
|
|
"evaluateAtZeta = UNAVAILABLE at stage 4 (before the query loop and before the "
|
|
"single return ACCEPT), so computePrecompile returns EMPTY for a real SP1 "
|
|
"proof and ACCEPT is unreachable. DO NOT ACTIVATE.",
|
|
"confirmed_mechanism": {
|
|
"constraint_fold": "VerifierConstraintFolder (p3-air folder.rs): acc = acc*alpha + constraint, "
|
|
"in the AIR eval() emission order; when_first_row/when_transition/when_last_row "
|
|
"multiply the constraint by the corresponding selector (p3-air air.rs).",
|
|
"trace_domain": "TwoAdicMultiplicativeCoset { log_n = degree_bits, shift = 1 } "
|
|
"(TwoAdicFriPcs::natural_domain_for_degree).",
|
|
"selectors_at_zeta": "z_h = zeta^(2^degree_bits) - 1; is_first = z_h/(zeta-1); "
|
|
"is_last = z_h/(zeta - g^-1); is_transition = zeta - g^-1; "
|
|
"inv_zeroifier = z_h^-1 (p3-commit domain.rs selectors_at_point).",
|
|
"quotient_domain": "create_disjoint_domain: log_n = degree_bits + log_quotient_degree, "
|
|
"shift = trace.shift * Val::generator() = GENERATOR (= 31, CONFIRMED).",
|
|
"chunk_domains": "split_domains(quotient_degree): chunk i has log_n = degree_bits, "
|
|
"shift = GENERATOR * two_adic_generator(degree_bits+log_quotient_degree)^i.",
|
|
"zps": "zps[i] = prod_{j!=i} zp_j(zeta) * zp_j(first_point_i)^-1, "
|
|
"zp_D(pt) = (pt*shift^-1)^(2^log_n) - 1 (p3-commit zp_at_point).",
|
|
"quotient_reconstruction": "quotient = sum_i zps[i] * sum_e monomial(e) * chunk[i][e], "
|
|
"monomial(e) = x^e in F_{p^4} (verifier.rs lines 106-116).",
|
|
"identity": "folded_constraints * inv_zeroifier == quotient "
|
|
"(i.e. folded_constraints(zeta) == Z_H(zeta) * quotient(zeta)); "
|
|
"OodEvaluationMismatch otherwise (verifier.rs lines 137-141).",
|
|
},
|
|
"cross_language": langs,
|
|
"conformance": {
|
|
"status": "CONFIRMED (real known-answer test PASSED: accept genuine + reject tampered)",
|
|
"method": "pq-stark/airquotient-extractor (a Rust binary depending on the pinned p3-uni-stark / "
|
|
"p3-air / p3-baby-bear / p3-commit 0.4.3-succinct crates; lockfile-pinned) ran "
|
|
"p3_uni_stark::prove then p3_uni_stark::verify on the Fibonacci AIR (tests/fib_air.rs, "
|
|
"n=8, pis=[0,1,21]) and a degree-3 multiply AIR (matching tests/mul_air.rs), asserted "
|
|
"the library accepts, and emitted degree_bits / quotient_degree / trace openings / "
|
|
"quotient-chunk openings / alpha (sample_ext) / zeta (sample) by replaying the exact "
|
|
"verifier transcript prefix; its output is air_quotient_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",
|
|
"crates": ["p3-uni-stark", "p3-air", "p3-baby-bear", "p3-commit", "p3-fri", "p3-matrix"],
|
|
"source_traced": ["p3-uni-stark verifier.rs (quotient-consistency check)",
|
|
"p3-commit domain.rs (selectors_at_point / zp_at_point / split_domains)",
|
|
"p3-air folder.rs + air.rs (VerifierConstraintFolder Horner fold; when_* filters)"],
|
|
},
|
|
"remaining": {
|
|
"sp1_recursion_air": "[MEASURE] the SP1 recursion AIR (exact constraint set + interactions + "
|
|
"public-value binding) and the vkey digest that commits to it; needs the SP1 "
|
|
"toolchain and a real exported SP1 v6.1.0 inner proof. This is the large "
|
|
"program-specific piece; ~3 to 4 person-weeks + audit (spec section 9).",
|
|
"end_to_end_kat": "[MEASURE] an end-to-end ACCEPT/REJECT KAT on a real exported SP1 proof, which "
|
|
"also needs the WireReader proof-body parser and the whole stack wired together.",
|
|
},
|
|
"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=== GENERIC AIR quotient-consistency check KAT (component (e), generic mechanism) ===")
|
|
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("AIR_QUOTIENT_KAT_FAILED")
|
|
sys.exit(1)
|
|
print("CONFORMANCE of the GENERIC AIR quotient-consistency mechanism to Plonky3 p3-uni-stark: "
|
|
"CONFIRMED (real KAT PASSED vs p3-uni-stark 0.4.3-succinct; accept genuine + reject tampered).")
|
|
print("The SP1-recursion-specific AIR + vkey remain UN-PORTED [MEASURE]; the top level stays "
|
|
"FAIL-CLOSED for real SP1 proofs. DO NOT ACTIVATE.")
|
|
print("AIR_QUOTIENT_CONFORMANCE_OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|