aere-research/pq-stark/test_babybear_field.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

288 lines
13 KiB
Python

#!/usr/bin/env python3
# Test harness for the BabyBear field + degree-4 extension sub-component (port spec section 2, the
# FOUNDATION component (a)). Runs:
# 1. F_p field axioms over many random cases (comm/assoc/distrib/identities/inverses),
# 2. inverse cross-check: Fermat a^(p-2) == an INDEPENDENT extended-Euclid bignum inverse
# (Python pow(a, -1, p)), for many a; and the (p-1) == -1 identities,
# 3. exponent / Frobenius consistency: a^(p-1) == 1, a^p == a, pow == naive repeated mul,
# 4. constants (proven offline, NO external fetch): generator has order exactly p-1; the two-adic
# generator of the order-2^k subgroup has order exactly 2^k; W is a quadratic non-residue and
# p == 1 mod 4, so x^4 - W is irreducible and F_{p^4} is a real field; x^4 == W in F_{p^4},
# 5. F_{p^4} axioms: ring laws, base embedding is a homomorphism, a * inv(a) == 1 for many a
# (including non-base elements), Frobenius^4 == id and Frobenius fixes the base field,
# 6. cross-language agreement: Python vs Node vs Java produce byte-identical results on a shared
# vector set (three independent implementations of the same public field definition).
# Writes ../results/kat-results-babybear-field.json and exits non-zero on any failure.
#
# HONEST SCOPE. This validates the field ARITHMETIC against the public BabyBear definition and an
# independent bignum reference. It is the bottom layer of a six-component STARK port; it does NOT
# verify any real STARK proof, and the top-level 0x0AE8 precompile stays fail-closed (returns EMPTY
# for every input). GENERATOR = 31 and W = 11 are proven here to be a real generator and a real
# non-residue; that they are Plonky3's EXACT chosen constants is [VERIFY]/[MEASURE] against
# p3-baby-bear at the pinned SP1 v6.1.0 revision (see docs/AERE-STARK-VERIFIER-PORT-SPEC.md sec 2).
import json
import os
import random
import subprocess
import sys
from shutil import which
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import babybear_field_reference as F # noqa: E402
RESULTS = os.path.normpath(os.path.join(HERE, "..", "results", "kat-results-babybear-field.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. F_p field axioms + 2. inverse cross-check + 3. exponent/Frobenius --------------------------
def base_field_axioms():
p = F.P
rnd = random.Random(0xBEEF)
for _ in range(20000):
a = rnd.randrange(p)
b = rnd.randrange(p)
c = rnd.randrange(p)
check("add.comm", F.add(a, b) == F.add(b, a))
check("mul.comm", F.mul(a, b) == F.mul(b, a))
check("add.assoc", F.add(F.add(a, b), c) == F.add(a, F.add(b, c)))
check("mul.assoc", F.mul(F.mul(a, b), c) == F.mul(a, F.mul(b, c)))
check("distrib", F.mul(a, F.add(b, c)) == F.add(F.mul(a, b), F.mul(a, c)))
check("add.id", F.add(a, 0) == a)
check("mul.id", F.mul(a, 1) == a)
check("sub.def", F.add(F.sub(a, b), b) == a)
check("neg.def", F.add(a, F.neg(a)) == 0)
check("mul.vs.bignum", F.mul(a, b) == (a * b) % p) # independent bignum cross-check
if a != 0:
# Fermat inverse vs INDEPENDENT extended-Euclid inverse (Python pow(a, -1, p)).
check("inv.fermat.vs.euclid", F.inv(a) == pow(a, -1, p))
check("inv.def", F.mul(a, F.inv(a)) == 1)
check("fermat.little", F.pow_(a, p - 1) == 1)
check("frobenius.base", F.pow_(a, p) == a) # a^p == a on the base field
# (p-1) behaves exactly as -1
check("minus1.is.neg1", F.P - 1 == F.neg(1))
check("minus1.sq", F.mul(F.P - 1, F.P - 1) == 1)
check("minus1.add", F.add(F.P - 1, 1) == 0)
check("inv.zero.convention", F.inv(0) == 0)
# pow vs naive repeated multiplication for small exponents
for a in (2, 3, 31, 1000, F.P - 1):
acc = 1
for e in range(0, 40):
check(f"pow.naive.a{a}.e{e}", F.pow_(a, e) == acc)
acc = F.mul(acc, a)
# ---- 4. constants: generator, two-adic subgroup, W / irreducibility -------------------------------
def constants_offline_proofs():
p = F.P
check("p.value", p == 2**31 - 2**27 + 1 == 15 * 2**27 + 1 == 0x78000001)
check("p.minus1.factor", (p - 1) == (2**27) * 3 * 5)
check("p.mod4", p % 4 == 1)
# generator has order exactly p-1 (real proof via full factorization)
check("generator.order", F.multiplicative_order(F.GENERATOR) == p - 1)
_notes.append(f"multiplicative generator {F.GENERATOR} proven order = p-1 = {p-1} [VERIFY it is Plonky3's]")
# two-adic generator of order-2^k subgroup has order exactly 2^k, for k = 1..27
tag = F.two_adic_generator(27)
check("twoadic27.order", F.pow_(tag, 2**27) == 1 and F.pow_(tag, 2**26) != 1)
for k in range(1, 28):
g = F.two_adic_generator(k)
check(f"twoadic.order.k{k}", F.pow_(g, 2**k) == 1 and (k == 0 or F.pow_(g, 2 ** (k - 1)) != 1))
_notes.append(f"two_adic_generator(27) = {tag}, order proven 2^27 [VERIFY exact Plonky3 value]")
# W = 11: quadratic non-residue + p == 1 mod 4 => x^4 - W irreducible => F_{p^4} is a field
check("W.qnr", F.is_quadratic_non_residue(F.W))
_notes.append(
f"W = {F.W}: 11^((p-1)/2) == p-1 (QNR) and p == 1 mod 4 => x^4 - {F.W} irreducible "
f"(Lidl-Niederreiter Thm 3.75) => F_(p^4) is a genuine field [VERIFY it is Plonky3's W]"
)
# defining relation x^4 == W in the extension
x = [0, 1, 0, 0]
check("x4.eq.W", F.ext_eq(F.ext_pow(x, 4), F.ext_from_base(F.W)))
# order of x = 4 * order(W); must divide 4*(p-1)
ordW = F.multiplicative_order(F.W)
ordx = 4 * ordW
check("ordx.divides", (4 * (p - 1)) % ordx == 0)
_notes.append(f"ord(W=11) = {ordW} = 2^27 * 5; ord(x) = 4*ord(W) = {ordx} divides 4(p-1)")
# ---- 5. F_{p^4} extension axioms -------------------------------------------------------------------
def extension_axioms():
rnd = random.Random(0xF00D)
one = [1, 0, 0, 0]
zero = [0, 0, 0, 0]
for _ in range(10000):
a = [rnd.randrange(F.P) for _ in range(4)]
b = [rnd.randrange(F.P) for _ in range(4)]
c = [rnd.randrange(F.P) for _ in range(4)]
check("ext.add.comm", F.ext_eq(F.ext_add(a, b), F.ext_add(b, a)))
check("ext.mul.comm", F.ext_eq(F.ext_mul(a, b), F.ext_mul(b, a)))
check("ext.add.assoc", F.ext_eq(F.ext_add(F.ext_add(a, b), c), F.ext_add(a, F.ext_add(b, c))))
check("ext.mul.assoc", F.ext_eq(F.ext_mul(F.ext_mul(a, b), c), F.ext_mul(a, F.ext_mul(b, c))))
check("ext.distrib", F.ext_eq(F.ext_mul(a, F.ext_add(b, c)),
F.ext_add(F.ext_mul(a, b), F.ext_mul(a, c))))
check("ext.mul.id", F.ext_eq(F.ext_mul(a, one), a))
check("ext.sub.def", F.ext_eq(F.ext_add(F.ext_sub(a, b), b), a))
check("ext.neg.def", F.ext_eq(F.ext_add(a, F.ext_neg(a)), zero))
if not F.ext_eq(a, zero):
check("ext.inv.def", F.ext_is_one(F.ext_mul(a, F.ext_inv(a))))
# Frobenius^4 == id; Frobenius fixes the base field; Frobenius is a ring homomorphism
f = a
for _ in range(4):
f = F.ext_frobenius(f)
check("ext.frob4.id", F.ext_eq(f, a))
check("ext.mul.vs.frob", F.ext_eq(F.ext_frobenius(F.ext_mul(a, b)),
F.ext_mul(F.ext_frobenius(a), F.ext_frobenius(b))))
# base embedding is a ring homomorphism, and is fixed by Frobenius
for _ in range(2000):
u = rnd.randrange(F.P)
v = rnd.randrange(F.P)
check("embed.add", F.ext_eq(F.ext_add(F.ext_from_base(u), F.ext_from_base(v)),
F.ext_from_base(F.add(u, v))))
check("embed.mul", F.ext_eq(F.ext_mul(F.ext_from_base(u), F.ext_from_base(v)),
F.ext_from_base(F.mul(u, v))))
check("embed.frob.fix", F.ext_eq(F.ext_frobenius(F.ext_from_base(u)), F.ext_from_base(u)))
check("ext.inv.zero.convention", F.ext_eq(F.ext_inv([0, 0, 0, 0]), [0, 0, 0, 0]))
# ---- 6. cross-language agreement (Python vs Node vs Java) -----------------------------------------
def canonicalize(v):
# Structural comparison ignoring key order / whitespace / int-vs-float JSON encoding.
def i(x):
return int(x)
def ia(x):
return [int(e) for e in x]
return {
"constants": {k: i(x) for k, x in v["constants"].items()},
"base_unary": [(i(r["a"]), i(r["neg"]), i(r["inv"]), i(r["pow"])) for r in v["base_unary"]],
"base_binary": [(i(r["a"]), i(r["b"]), i(r["add"]), i(r["sub"]), i(r["mul"]))
for r in v["base_binary"]],
"ext_unary": [(ia(r["a"]), ia(r["neg"]), ia(r["inv"]), ia(r["frob"])) for r in v["ext_unary"]],
"ext_binary": [(ia(r["a"]), ia(r["b"]), ia(r["add"]), ia(r["sub"]), ia(r["mul"]))
for r in v["ext_binary"]],
}
def cross_language():
py = canonicalize(F.shared_vectors())
langs = {"python": py}
node = which("node")
if node:
try:
out = subprocess.run(
[node, os.path.join(HERE, "babybear_field_reference.mjs")],
capture_output=True, text=True, timeout=120, 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, "BabyBearFieldSelfTest.java")],
capture_output=True, text=True, timeout=180, check=True,
)
out = subprocess.run(
[java, "-cp", os.path.join(HERE, "out"), "BabyBearFieldSelfTest", "--emit"],
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():
base_field_axioms()
constants_offline_proofs()
extension_axioms()
langs = cross_language()
report = {
"component": "BabyBear field F_p + degree-4 extension F_{p^4} (port spec component (a), FOUNDATION)",
"precompile": "0x0000000000000000000000000000000000000ae8",
"scope": "port spec section 2; the field-arithmetic foundation the whole STARK verifier is built on",
"validates": "F_p and F_{p^4} field axioms; Fermat inverse vs independent extended-Euclid "
"bignum; generator has order p-1; two-adic generator order 2^k; W is a QNR so "
"x^4-W is irreducible (real field); across Python/Node/Java",
"does_not_validate": "any real STARK proof (this is only component (a) of six); and that "
"GENERATOR=31 / W=11 / two_adic_generator(27) are Plonky3's EXACT "
"constants ([VERIFY]/[MEASURE] vs p3-baby-bear at pinned SP1 v6.1.0)",
"top_level_verifier": "unchanged, still fail-closed (returns EMPTY for every input)",
"constants": {
"P": F.P,
"P_expr": "2^31 - 2^27 + 1 = 15*2^27 + 1 = 0x78000001",
"generator": F.GENERATOR,
"two_adicity": F.TWO_ADICITY,
"two_adic_generator_27": F.two_adic_generator(27),
"W": F.W,
},
"cross_language": langs,
"flags": [
"[VERIFY] GENERATOR = 31 is Plonky3 p3-baby-bear's chosen multiplicative generator "
"(order = p-1 is PROVEN here; the identity as Plonky3's is [VERIFY])",
"[VERIFY] W = 11 is Plonky3's BabyBear BinomialExtensionField<_,4> non-residue "
"(x^4-11 irreducibility is PROVEN here; the identity as Plonky3's W is [VERIFY]/[MEASURE])",
"[VERIFY] two_adic_generator(27) exact value vs Plonky3 (order 2^27 is PROVEN here)",
"[MEASURE] byte-for-byte conformance of Fp/Fp4 outputs vs p3-baby-bear / p3-field unit "
"vectors at the pinned revision (needs the pinned Plonky3 source)",
],
"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=== BabyBear field + F_{p^4} 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("BABYBEAR_FIELD_KAT_FAILED")
sys.exit(1)
print("BABYBEAR_FIELD_KAT_OK")
if __name__ == "__main__":
main()