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.
313 lines
15 KiB
Python
313 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
# Test harness for the Poseidon2-over-BabyBear width-16 permutation sub-component (port spec section
|
|
# 3, component (b)). Runs:
|
|
# 1. S-box structure: 7 is the smallest d>1 with gcd(d, p-1)=1 (so x^7 is a bijection on F_p); the
|
|
# monomial inverse round-trips over many random cases,
|
|
# 2. determinism: permute(s) == permute(s),
|
|
# 3. bijection: permute_inverse(permute(s)) == s over the fixed set + many random states (a genuine
|
|
# permutation on F_p^16), and no output collisions on a large sampled input set,
|
|
# 4. linear-layer / MDS structure: the external layer equals its explicit 16x16 matrix and that
|
|
# matrix is invertible; M4 is MDS (every square submatrix nonsingular); the internal layer equals
|
|
# R^{-1}*(J + diag(D)) and is invertible,
|
|
# 5. cross-language agreement: Python vs Node vs Java produce byte-identical permutation outputs on a
|
|
# shared input set (three independent implementations of the SAME permutation),
|
|
# 6. CONFORMANCE: the permutation reproduces real known-answer vectors (zeros / iota / testvec)
|
|
# emitted by executing the pinned p3-baby-bear / p3-poseidon2 0.4.3-succinct crates.
|
|
# Writes ../results/kat-results-poseidon2-babybear.json and exits non-zero on any failure.
|
|
#
|
|
# ============================ HONEST SCOPE (CONFIRMED 2026-07-19) ============================
|
|
# This validates the permutation LOGIC/arithmetic AND conformance to Plonky3. The port spec warned of
|
|
# the "two wrong copies agree" trap (a structurally-correct permutation with wrong constants is
|
|
# self-consistent yet disagrees with the prover). That trap is now closed: the constants + structure
|
|
# reproduce real known-answer vectors extracted from the exact pinned crates (checksums matched).
|
|
# - The 141 round constants are CONFIRMED (Xoroshiro128Plus::seed_from_u64(1) via new_from_rng_128).
|
|
# - The internal layer is CONFIRMED as R^{-1}*(J + diag(D)), R^{-1} = 943718400 (the Montgomery-form
|
|
# artifact recovered from the pinned library's exact 16x16 canonical matrix; the prior textbook
|
|
# state[i]*D[i] + sum was self-consistent but WRONG, exactly the two-wrong-copies trap).
|
|
# - ROUNDS_F=8, ROUNDS_P=13, M4, and the S-box degree are CONFIRMED.
|
|
# What is still NOT validated: the FULL STARK verifier. The duplex-sponge challenger, FRI folding,
|
|
# MMCS/Merkle openings, and the SP1 recursion-AIR are un-ported, so the top-level 0x0AE8 precompile
|
|
# stays fail-closed (returns EMPTY for every input) regardless of this component being confirmed.
|
|
|
|
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 poseidon2_babybear_reference as R # noqa: E402
|
|
|
|
RESULTS = os.path.normpath(os.path.join(HERE, "..", "results", "kat-results-poseidon2-babybear.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. S-box structure -----------------------------------------------------------------------------
|
|
def sbox_structure():
|
|
p = R.P
|
|
from math import gcd
|
|
# 7 is the smallest d>1 with gcd(d, p-1)=1 (p-1 = 2^27*3*5, so 2,3,4,5,6 all share a factor)
|
|
check("sbox.deg.is7", R.SBOX_DEGREE == 7)
|
|
for d in range(2, 7):
|
|
check(f"sbox.deg.notcoprime.d{d}", gcd(d, p - 1) != 1)
|
|
check("sbox.deg7.coprime", gcd(7, p - 1) == 1)
|
|
_notes.append("S-box x^7 CONFIRMED: 7 is the smallest d>1 with gcd(d, p-1)=1 (p-1 = 2^27*3*5), "
|
|
"so x^7 is a bijection on F_p")
|
|
rnd = random.Random(0xC0FFEE)
|
|
for _ in range(20000):
|
|
x = rnd.randrange(p)
|
|
check("sbox.roundtrip", R.sbox_mono_inv(R.sbox_mono(x)) == x)
|
|
check("sbox.value", R.sbox_mono(x) == pow(x, 7, p)) # x^7 vs an independent bignum power
|
|
|
|
|
|
# ---- 2/3. determinism + bijection + collision-freeness ----------------------------------------------
|
|
def permutation_bijection():
|
|
rnd = random.Random(0xBEEF01)
|
|
seen = {}
|
|
collisions = 0
|
|
for _ in range(3000):
|
|
st = [rnd.randrange(R.P) for _ in range(R.WIDTH)]
|
|
out = R.permute(st)
|
|
check("permute.deterministic", R.permute(st) == out)
|
|
check("permute.bijection", R.permute_inverse(out) == [x % R.P for x in st])
|
|
key = tuple(out)
|
|
if key in seen and seen[key] != tuple(st):
|
|
collisions += 1
|
|
seen[key] = tuple(st)
|
|
check("permute.no.collisions", collisions == 0)
|
|
_notes.append(f"permutation is a bijection: inverse round-trips on 3000 random states + fixed set; "
|
|
f"{len(seen)} distinct outputs, {collisions} collisions")
|
|
|
|
|
|
# ---- 4. linear-layer / MDS structure ----------------------------------------------------------------
|
|
def _minor_det(m, rows, cols):
|
|
"""Determinant mod p of the submatrix on the given rows/cols, via Gaussian elimination."""
|
|
n = len(rows)
|
|
a = [[m[r][c] % R.P for c in cols] for r in rows]
|
|
det = 1
|
|
for col in range(n):
|
|
piv = next((r for r in range(col, n) if a[r][col] % R.P != 0), None)
|
|
if piv is None:
|
|
return 0
|
|
if piv != col:
|
|
a[col], a[piv] = a[piv], a[col]
|
|
det = (-det) % R.P
|
|
det = (det * a[col][col]) % R.P
|
|
invp = pow(a[col][col], -1, R.P)
|
|
a[col] = [(x * invp) % R.P for x in a[col]]
|
|
for r in range(col + 1, n):
|
|
if a[r][col] % R.P != 0:
|
|
f = a[r][col]
|
|
a[r] = [(a[r][k] - f * a[col][k]) % R.P for k in range(n)]
|
|
return det
|
|
|
|
|
|
def _is_mds(m, size):
|
|
"""A matrix is MDS iff every square submatrix is nonsingular. Feasible for the 4x4 M4."""
|
|
from itertools import combinations
|
|
for k in range(1, size + 1):
|
|
for rows in combinations(range(size), k):
|
|
for cols in combinations(range(size), k):
|
|
if _minor_det(m, rows, cols) == 0:
|
|
return False
|
|
return True
|
|
|
|
|
|
def linear_layers():
|
|
rnd = random.Random(0xD00D)
|
|
ext = R.external_matrix()
|
|
inte = R.internal_matrix()
|
|
# layers equal their explicit matrices
|
|
for _ in range(500):
|
|
v = [rnd.randrange(R.P) for _ in range(R.WIDTH)]
|
|
check("ext.layer.eq.matrix", R.external_layer(v) == R.mat_vec(ext, v))
|
|
check("int.layer.eq.matrix", R.internal_layer(v) == R.mat_vec(inte, v))
|
|
# invertibility (necessary for the permutation to be a bijection)
|
|
ext_inv = R.mat_inverse(ext)
|
|
int_inv = R.mat_inverse(inte)
|
|
ident = [[1 if i == j else 0 for j in range(R.WIDTH)] for i in range(R.WIDTH)]
|
|
prod_ext = [[sum(ext[i][k] * ext_inv[k][j] for k in range(R.WIDTH)) % R.P for j in range(R.WIDTH)]
|
|
for i in range(R.WIDTH)]
|
|
check("ext.matrix.invertible", prod_ext == ident)
|
|
prod_int = [[sum(inte[i][k] * int_inv[k][j] for k in range(R.WIDTH)) % R.P for j in range(R.WIDTH)]
|
|
for i in range(R.WIDTH)]
|
|
check("int.matrix.invertible", prod_int == ident)
|
|
# M4 is MDS (every square submatrix nonsingular); M_E MDS then follows from the Poseidon2 construction
|
|
m4 = R.M4
|
|
check("m4.is.mds", _is_mds(m4, 4))
|
|
_notes.append("M4 is MDS (all 69 square submatrices nonsingular); external layer M_E and internal "
|
|
"layer M_I = R^{-1}*(J + diag(D)) are invertible over F_p (both CONFIRMED vs p3)")
|
|
|
|
|
|
# ---- 6. CONFORMANCE: reproduce the pinned-library known-answer vectors ------------------------------
|
|
def conformance():
|
|
passed, total = R.conformance_kats()
|
|
for kat in R.CONFORMANCE_KATS:
|
|
check(f"conformance.{kat['name']}", R.permute(list(kat["in"])) == kat["out"])
|
|
_notes.append(f"CONFORMANCE: {passed}/{total} known-answer vectors from p3-baby-bear/p3-poseidon2 "
|
|
f"0.4.3-succinct reproduced exactly (zeros, iota [0..15], testvec)")
|
|
|
|
|
|
# ---- 5. cross-language agreement (Python vs Node vs Java) -------------------------------------------
|
|
def canonicalize(v):
|
|
def ia(x):
|
|
return [int(e) for e in x]
|
|
|
|
return {
|
|
"constants": {k: (ia(x) if isinstance(x, list) else int(x)) for k, x in v["constants"].items()},
|
|
"permute": [(ia(r["in"]), ia(r["out"]), bool(r["roundtrip"])) for r in v["permute"]],
|
|
"conformanceKats": [(k["name"], ia(k["in"]), ia(k["out"]), bool(k["match"]))
|
|
for k in v.get("conformanceKats", [])],
|
|
}
|
|
|
|
|
|
def cross_language():
|
|
py = canonicalize(R.shared_vectors())
|
|
# every Python-side round-trip must be true (self-check before comparing languages)
|
|
check("py.all.roundtrip", all(r[2] for r in py["permute"]))
|
|
langs = {"python": py}
|
|
|
|
node = which("node")
|
|
if node:
|
|
try:
|
|
out = subprocess.run(
|
|
[node, os.path.join(HERE, "poseidon2_babybear_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, "Poseidon2BabyBearSelfTest.java")],
|
|
capture_output=True, text=True, timeout=180, check=True,
|
|
)
|
|
out = subprocess.run(
|
|
[java, "-cp", os.path.join(HERE, "out"), "Poseidon2BabyBearSelfTest", "--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():
|
|
sbox_structure()
|
|
permutation_bijection()
|
|
linear_layers()
|
|
conformance()
|
|
langs = cross_language()
|
|
|
|
report = {
|
|
"component": "Poseidon2 permutation over BabyBear, width 16 (port spec component (b))",
|
|
"precompile": "0x0000000000000000000000000000000000000ae8",
|
|
"scope": "port spec section 3; the hash Plonky3/SP1 uses for Merkle/MMCS compression and the "
|
|
"Fiat-Shamir duplex challenger",
|
|
"validates": "SELF-CONSISTENCY + CONFORMANCE: x^7 is a bijection (7 = smallest coprime degree, "
|
|
"proven); the permutation is deterministic and a genuine bijection (inverse "
|
|
"round-trip, no collisions); M4 is MDS and the external/internal linear layers "
|
|
"are invertible; Python/Node/Java produce byte-identical outputs; AND the "
|
|
"permutation reproduces real known-answer vectors from the pinned p3-baby-bear / "
|
|
"p3-poseidon2 0.4.3-succinct crates (the 'two wrong copies agree' trap is closed).",
|
|
"does_not_validate": "the FULL STARK verifier. Only component (b) (the Poseidon2 permutation) is "
|
|
"confirmed. The duplex-sponge challenger, FRI folding/consistency, "
|
|
"MMCS/Merkle openings, and the SP1 recursion-AIR are un-ported.",
|
|
"top_level_verifier": "unchanged, still fail-closed (returns EMPTY for every input). "
|
|
"Poseidon2Bb.available is now true (the permutation is confirmed), but the "
|
|
"challenger/FRI/AIR are still un-ported (Challenger.spongePorted=false, "
|
|
"StarkConstraints=UNAVAILABLE), so the top level still returns EMPTY.",
|
|
"constants": {
|
|
"P": R.P,
|
|
"r_inv": R.R_INV,
|
|
"width": R.WIDTH,
|
|
"sbox_degree": R.SBOX_DEGREE,
|
|
"rounds_f": R.ROUNDS_F,
|
|
"rounds_p": R.ROUNDS_P,
|
|
"internal_diag_m1_16": R.INTERNAL_DIAG_M1_16,
|
|
"internal_layer_form": "M_I = R_INV * (J + diag(D)), R_INV = 943718400 (Montgomery artifact)",
|
|
"m4": [R.M4[r][c] for r in range(4) for c in range(4)],
|
|
"round_constants": "141 CONFIRMED constants (128 external + 13 internal) from "
|
|
"Xoroshiro128Plus::seed_from_u64(1) via new_from_rng_128",
|
|
},
|
|
"cross_language": langs,
|
|
"conformance": {
|
|
"status": "CONFIRMED (real known-answer test PASSED)",
|
|
"method": "the exact pinned crates were executed via cargo (lockfile checksums matched) to "
|
|
"emit constants + 3 permutation vectors; this reference reproduces all 3 exactly",
|
|
"vectors": [k["name"] for k in R.CONFORMANCE_KATS],
|
|
},
|
|
"pinned_conformance_target": {
|
|
"revision": "Succinct Plonky3 fork, crates.io version 0.4.3-succinct",
|
|
"p3_poseidon2_checksum": "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0",
|
|
"p3_baby_bear_checksum": "d69e6e9af4eaaaa60f7bb9f0e0f73ebcbaefe7e00974d97ad0fa542d6a4f0890",
|
|
"found_in": "aerenew/zk-circuits/*/Cargo.lock and aerenew/rollup-evm-validity/*/Cargo.lock",
|
|
},
|
|
"confirmed": [
|
|
"CONFIRMED round constants: 128 external + 13 internal from seed_from_u64(1) / new_from_rng_128",
|
|
"CONFIRMED ROUNDS_F=8, ROUNDS_P=13 (poseidon2_round_numbers_128(16, 7))",
|
|
"CONFIRMED M4 = [[2,3,1,1],[1,2,3,1],[1,1,2,3],[3,1,1,2]] (recovered from Poseidon2ExternalMatrixGeneral)",
|
|
"CONFIRMED INTERNAL_DIAG_M1_16 (canonical) and the R^{-1}*(J+diag(D)) internal-layer form "
|
|
"(Montgomery artifact, recovered exactly from DiffusionMatrixBabyBear's 16x16 canonical matrix)",
|
|
"CONFORMANCE KAT PASSED against p3-baby-bear / p3-poseidon2 0.4.3-succinct (checksums matched)",
|
|
],
|
|
"flags": [
|
|
"[VERIFY] the SP1 v6.1.0 Merkle/MMCS compression convention (padding/rate, which 8 lanes) "
|
|
"for compress2to1 (component (c)) is not yet pinned",
|
|
],
|
|
"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=== Poseidon2-BabyBear (width 16) 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}")
|
|
print("CONFORMANCE to Plonky3 constants: CONFIRMED (real KAT PASSED vs p3-* 0.4.3-succinct; "
|
|
"checksums matched)")
|
|
if _fail != 0:
|
|
print("POSEIDON2_BABYBEAR_KAT_FAILED")
|
|
sys.exit(1)
|
|
print("POSEIDON2_BABYBEAR_CONFORMANCE_OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|