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.
341 lines
12 KiB
JavaScript
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()));
|
|
}
|