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

341 lines
12 KiB
JavaScript

// Independent (second-language) reference for the Poseidon2 permutation over BabyBear at width 16,
// component (b) of the PQ STARK-verify precompile 0x0AE8. Mirrors poseidon2_babybear_reference.py
// and the standalone Java Poseidon2BabyBearSelfTest. It serves two purposes: cross-language agreement
// (three independent implementations produce byte-identical output) AND conformance (it reproduces
// real known-answer vectors from the pinned Plonky3 crates; see CONFORMANCE_KATS). It verifies
// NOTHING about a real proof and the top-level 0x0AE8 verifier stays fail-closed. See
// docs/AERE-STARK-VERIFIER-PORT-SPEC.md section 3.
//
// HONEST SCOPE (CONFIRMED 2026-07-19): the 141 round constants, internal diagonal, M4, round counts,
// x^7 S-box, and layer order all match p3-baby-bear / p3-poseidon2 0.4.3-succinct (the exact crates
// pinned in aerenew Cargo.lock; lockfile checksums matched). A real known-answer test PASSES.
// MONTGOMERY SUBTLETY (resolved): the internal layer as a canonical map is R^{-1} * (J + diag(D)),
// R^{-1} = 943718400; a prior pass omitted the R^{-1} (textbook state[i]*D[i] + sum) which is
// self-consistent but disagrees with the prover. See the .py header for the full source citation.
//
// Uses BigInt throughout (x^7 and the linear layers exceed 2^53). Run standalone to emit JSON:
// node poseidon2_babybear_reference.mjs
export const P = 2013265921n;
// Montgomery inverse factor: p3-baby-bear stores field elements in Montgomery form with R = 2^32; its
// internal diffusion layer, as a canonical linear map, carries a factor of R^{-1} = (2^32)^{-1} mod p.
export const R_INV = 943718400n; // = (2^32)^{-1} mod p, verified against the pinned library's matrix
export const WIDTH = 16;
export const SBOX_DEGREE = 7n;
export const ROUNDS_F = 8;
export const ROUNDS_P = 13;
export const M4 = [
[2n, 3n, 1n, 1n],
[1n, 2n, 3n, 1n],
[1n, 1n, 2n, 3n],
[3n, 1n, 1n, 2n],
];
export const INTERNAL_DIAG_M1_16 = [
P - 2n, 1n, 2n, 4n, 8n, 16n, 32n, 64n, 128n, 256n, 512n, 1024n, 2048n, 4096n, 8192n, 32768n,
];
// ---- base-field helpers ----
const mod = (a) => ((a % P) + P) % P;
export const add = (a, b) => mod(a + b);
export const mul = (a, b) => mod(a * b);
export function sboxMono(x) {
x = mod(x);
const x2 = mul(x, x);
const x4 = mul(x2, x2);
return mul(x4, mul(x2, x)); // x^7
}
function sboxInvExponent() {
// 7^{-1} mod (p-1) via BigInt extended Euclid (p-1 = 2013265920).
const m = P - 1n;
let [oldR, r] = [SBOX_DEGREE % m, m];
let [oldS, s] = [1n, 0n];
while (r !== 0n) {
const q = oldR / r;
[oldR, r] = [r, oldR - q * r];
[oldS, s] = [s, oldS - q * s];
}
return ((oldS % m) + m) % m;
}
const SBOX_INV_E = sboxInvExponent();
export function powF(base, e) {
let b = mod(base);
let acc = 1n;
let ee = e;
while (ee > 0n) {
if (ee & 1n) acc = mul(acc, b);
b = mul(b, b);
ee >>= 1n;
}
return acc;
}
export const sboxMonoInv = (y) => powF(mod(y), SBOX_INV_E);
// ---- CONFIRMED round constants (from p3-baby-bear/p3-poseidon2 0.4.3-succinct via seed_from_u64(1)) ----
export const RC_EXTERNAL = [
[
1321363468n, 285374923n, 858595076n, 131742120n, 550898981n, 109281027n,
1548327248n, 299186948n, 1198120888n, 1302311359n, 568137078n, 1484856917n,
1301979945n, 725688886n, 941758026n, 323341913n,
],
[
1049323172n, 822409348n, 1406080127n, 1279024384n, 214862539n, 904628921n,
1320747287n, 11578228n, 1036373712n, 1474430466n, 1430509860n, 111174484n,
1124450171n, 85382027n, 679880882n, 243277213n,
],
[
1338495990n, 1523013347n, 1841068573n, 578194469n, 47683837n, 1790441672n,
1628061601n, 1716216090n, 1635810049n, 1115145248n, 1117524270n, 678640014n,
1962751651n, 1367401392n, 11688709n, 1950824358n,
],
[
528649031n, 1937116923n, 1460949223n, 1193074357n, 1221801411n, 1183923117n,
433505619n, 1928933309n, 505759755n, 285671663n, 1047265910n, 909281502n,
1258966486n, 864761693n, 307024510n, 504858517n,
],
[
1467478033n, 1754565867n, 432187324n, 1452390672n, 881974300n, 550050336n,
1447309270n, 939419487n, 1783112406n, 1166910332n, 107514714n, 580516863n,
2003318760n, 854475946n, 934896823n, 994783668n,
],
[
1841107561n, 438269126n, 1550523825n, 913322122n, 600932628n, 583000098n,
1262690949n, 105797869n, 277542016n, 170491952n, 365854467n, 1479645308n,
1457660602n, 1635879552n, 499155053n, 741227047n,
],
[
651389942n, 464828001n, 89696107n, 360044673n, 230330371n, 1773129416n,
1380150763n, 745014723n, 793475694n, 1361274828n, 1443741698n, 51616650n,
731414218n, 1087554954n, 1273943885n, 311581717n,
],
[
702702762n, 1473247301n, 132108357n, 1348260424n, 476775430n, 1438949459n,
2434448n, 1349232398n, 1954471898n, 1762138591n, 1271221795n, 1593266476n,
864488771n, 139147729n, 1053373910n, 422842363n,
],
];
export const RC_INTERNAL = [
402771160n, 320708227n, 1122772462n, 100431997n, 202594011n, 1226485372n,
1088619034n, 64118538n, 109828860n, 724723599n, 1662837151n, 797753907n,
1075635743n,
];
// ---- linear layers ----
function m4Apply(t) {
const out = [];
for (let row = 0; row < 4; row++) {
out.push(mod(M4[row][0] * t[0] + M4[row][1] * t[1] + M4[row][2] * t[2] + M4[row][3] * t[3]));
}
return out;
}
export function externalLayer(state) {
const blocks = [];
for (let b = 0; b < 4; b++) blocks.push(m4Apply(state.slice(4 * b, 4 * b + 4)));
const colSum = [];
for (let j = 0; j < 4; j++) colSum.push(mod(blocks[0][j] + blocks[1][j] + blocks[2][j] + blocks[3][j]));
const out = new Array(WIDTH);
for (let b = 0; b < 4; b++) for (let j = 0; j < 4; j++) out[4 * b + j] = mod(blocks[b][j] + colSum[j]);
return out;
}
export function internalLayer(state) {
// p3-baby-bear DiffusionMatrixBabyBear as a canonical map: M_I = R^{-1} * (J + diag(D)),
// i.e. out[i] = R_INV * (sum + D[i]*state[i]). The R_INV factor is the Montgomery-form artifact.
let s = 0n;
for (const v of state) s += v;
s = mod(s);
return state.map((v, i) => mod(R_INV * mod(s + INTERNAL_DIAG_M1_16[i] * v)));
}
// ---- explicit matrices (for invertibility / inverse round-trip only) ----
export function externalMatrix() {
const m = Array.from({ length: WIDTH }, () => new Array(WIDTH).fill(0n));
for (let bi = 0; bi < 4; bi++)
for (let bj = 0; bj < 4; bj++) {
const coef = bi === bj ? 2n : 1n;
for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) m[4 * bi + r][4 * bj + c] = mod(coef * M4[r][c]);
}
return m;
}
export function internalMatrix() {
// M_I = R^{-1} * (J + diag(D)): off-diagonal = R_INV, diagonal = R_INV*(1 + D[i]).
const m = Array.from({ length: WIDTH }, () => new Array(WIDTH).fill(R_INV));
for (let i = 0; i < WIDTH; i++) m[i][i] = mod(R_INV * (1n + INTERNAL_DIAG_M1_16[i]));
return m;
}
export function matVec(m, v) {
return m.map((row) => {
let acc = 0n;
for (let j = 0; j < WIDTH; j++) acc = mod(acc + mul(row[j], v[j]));
return acc;
});
}
export function matInverse(m) {
const n = WIDTH;
const a = m.map((row, i) => [...row.map(mod), ...Array.from({ length: n }, (_, j) => (j === i ? 1n : 0n))]);
for (let col = 0; col < n; col++) {
let piv = -1;
for (let r = col; r < n; r++) if (mod(a[r][col]) !== 0n) { piv = r; break; }
if (piv < 0) throw new Error("singular matrix");
[a[col], a[piv]] = [a[piv], a[col]];
const invP = powF(a[col][col], P - 2n);
a[col] = a[col].map((x) => mul(x, invP));
for (let r = 0; r < n; r++) {
if (r !== col && mod(a[r][col]) !== 0n) {
const f = a[r][col];
a[r] = a[r].map((x, k) => mod(x - mul(f, a[col][k])));
}
}
}
return a.map((row) => row.slice(n));
}
// ---- the permutation ----
export function permute(state) {
if (state.length !== WIDTH) throw new Error("state must have WIDTH elements");
let s = state.map(mod);
s = externalLayer(s);
const half = ROUNDS_F / 2;
for (let r = 0; r < half; r++) {
s = s.map((v, i) => add(v, RC_EXTERNAL[r][i]));
s = s.map(sboxMono);
s = externalLayer(s);
}
for (let r = 0; r < ROUNDS_P; r++) {
s[0] = add(s[0], RC_INTERNAL[r]);
s[0] = sboxMono(s[0]);
s = internalLayer(s);
}
for (let r = half; r < ROUNDS_F; r++) {
s = s.map((v, i) => add(v, RC_EXTERNAL[r][i]));
s = s.map(sboxMono);
s = externalLayer(s);
}
return s;
}
export function permuteInverse(state) {
let s = state.map(mod);
const extInv = matInverse(externalMatrix());
const intInv = matInverse(internalMatrix());
const half = ROUNDS_F / 2;
for (let r = ROUNDS_F - 1; r >= half; r--) {
s = matVec(extInv, s);
s = s.map(sboxMonoInv);
s = s.map((v, i) => mod(v - RC_EXTERNAL[r][i]));
}
for (let r = ROUNDS_P - 1; r >= 0; r--) {
s = matVec(intInv, s);
s[0] = sboxMonoInv(s[0]);
s[0] = mod(s[0] - RC_INTERNAL[r]);
}
for (let r = half - 1; r >= 0; r--) {
s = matVec(extInv, s);
s = s.map(sboxMonoInv);
s = s.map((v, i) => mod(v - RC_EXTERNAL[r][i]));
}
s = matVec(extInv, s);
return s;
}
// ---- CONFIRMED conformance KATs (from the pinned p3-baby-bear/p3-poseidon2 0.4.3-succinct crates) ----
export const CONFORMANCE_KATS = [
{
name: "zeros",
in: new Array(16).fill(0n),
out: [1787823396n, 953829438n, 89382455n, 347481625n, 1754527224n, 916217775n, 1056029082n,
410644796n, 1169123478n, 1854704276n, 1195829987n, 1485264906n, 1824644035n, 1948268315n,
847945433n, 190591038n],
},
{
name: "iota",
in: Array.from({ length: 16 }, (_, i) => BigInt(i)),
out: [157639285n, 1851003038n, 1852457045n, 1920360618n, 779990819n, 1080011039n, 585017685n,
1093051731n, 249426030n, 967262243n, 623744062n, 280332881n, 1995600430n, 1751988435n,
317724737n, 1895035071n],
},
{
name: "testvec",
in: [894848333n, 1437655012n, 1200606629n, 1690012884n, 71131202n, 1749206695n, 1717947831n,
120589055n, 19776022n, 42382981n, 1831865506n, 724844064n, 171220207n, 1299207443n,
227047920n, 1783754913n],
out: [512585766n, 975869435n, 1921378527n, 1238606951n, 899635794n, 132650430n, 1426417547n,
1734425242n, 57415409n, 67173027n, 1535042492n, 1318033394n, 1070659233n, 17258943n,
856719028n, 1500534995n],
},
];
export function conformanceKats() {
let passed = 0;
for (const kat of CONFORMANCE_KATS) {
const out = permute(kat.in.slice());
if (out.every((v, i) => mod(v) === mod(kat.out[i]))) passed++;
}
return { passed, total: CONFORMANCE_KATS.length };
}
// ---- shared cross-language vector set ----
function inputStates() {
const states = [
new Array(WIDTH).fill(0n),
Array.from({ length: WIDTH }, (_, i) => BigInt(i)),
new Array(WIDTH).fill(P - 1n),
Array.from({ length: WIDTH }, (_, i) => (BigInt(i) * 2654435761n) % P),
[11n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n, 1n],
];
// BigInt LCG (1103515245*x overflows JS Number's 2^53; BigInt keeps it exact and matching Py/Java)
let x = 123456789n;
for (let k = 0; k < 8; k++) {
const st = [];
for (let i = 0; i < WIDTH; i++) {
x = (1103515245n * x + 12345n) % 2147483648n;
st.push(x % P);
}
states.push(st);
}
return states;
}
const nlist = (a) => a.map((v) => Number(v));
export function sharedVectors() {
const rcExtFlat = [];
for (let r = 0; r < ROUNDS_F; r++) for (let i = 0; i < WIDTH; i++) rcExtFlat.push(Number(RC_EXTERNAL[r][i]));
const states = inputStates();
return {
constants: {
P: Number(P),
rInv: Number(R_INV),
width: WIDTH,
sboxDegree: Number(SBOX_DEGREE),
roundsF: ROUNDS_F,
roundsP: ROUNDS_P,
internalDiagM1: nlist(INTERNAL_DIAG_M1_16),
m4: [2, 3, 1, 1, 1, 2, 3, 1, 1, 1, 2, 3, 3, 1, 1, 2],
rcExternalFlat: rcExtFlat,
rcInternal: nlist(RC_INTERNAL),
},
permute: states.map((st) => {
const out = permute(st);
const back = permuteInverse(out);
const rt = back.every((v, i) => mod(v) === mod(st[i]));
return { in: nlist(st), out: nlist(out), roundtrip: rt };
}),
conformanceKats: CONFORMANCE_KATS.map((k) => {
const out = permute(k.in.slice());
return { name: k.name, in: nlist(k.in), out: nlist(out), match: out.every((v, i) => mod(v) === mod(k.out[i])) };
}),
};
}
import { pathToFileURL } from "node:url";
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.stdout.write(JSON.stringify(sharedVectors()));
}