// Independent (second-language) reference for the BabyBear field F_p and its degree-4 extension // F_{p^4}, the FOUNDATION layer (component (a)) of the PQ STARK-verify precompile 0x0AE8. Mirrors // babybear_field_reference.py and the Java BabyBearFieldSelfTest. Its ONLY purpose is // cross-language agreement: three independent implementations of the same public field definition // should produce byte-identical output on a shared vector set. 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 2. // // Run standalone to emit the shared vector set as JSON on stdout: // node babybear_field_reference.mjs // // Uses BigInt throughout so the extension Fermat inverse exponent (p^4 - 2, ~124 bits) is exact. // JSON is emitted with Number values (every field element fits in a JS double, < 2^31). export const P = 2013265921n; // 2^31 - 2^27 + 1 = 15 * 2^27 + 1 export const GENERATOR = 31n; // [VERIFY] Plonky3 generator; order proven = P-1 in the harness export const TWO_ADICITY = 27n; export const W = 11n; // [VERIFY] Plonky3 BabyBear quartic non-residue; irreducibility proven // ---- base field F_p ---- const mod = (a) => ((a % P) + P) % P; export const add = (a, b) => mod(a + b); export const sub = (a, b) => mod(a - b); export const neg = (a) => mod(-a); export const mul = (a, b) => mod(a * b); export function powF(base, e) { if (e < 0n) throw new Error("use inv for negative exponents"); let b = mod(base); let acc = 1n; while (e > 0n) { if (e & 1n) acc = mod(acc * b); b = mod(b * b); e >>= 1n; } return acc; } export function inv(a) { const r = mod(a); if (r === 0n) return 0n; // inv(0) := 0, matches the Java precompile convention return powF(r, P - 2n); } export function twoAdicGenerator(bits) { if (bits < 0n || bits > TWO_ADICITY) throw new Error("bits out of range [0,27]"); return powF(GENERATOR, (P - 1n) >> bits); } // ---- degree-4 extension F_{p^4} = F_p[x]/(x^4 - W), elements as [c0,c1,c2,c3] BigInt ---- export const extFromBase = (a) => [mod(a), 0n, 0n, 0n]; export const extAdd = (a, b) => a.map((_, i) => mod(a[i] + b[i])); export const extSub = (a, b) => a.map((_, i) => mod(a[i] - b[i])); export const extNeg = (a) => a.map((v) => mod(-v)); export function extMul(a, b) { const t = [0n, 0n, 0n, 0n, 0n, 0n, 0n]; for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { t[i + j] = mod(t[i + j] + a[i] * b[j]); } } return [mod(t[0] + W * t[4]), mod(t[1] + W * t[5]), mod(t[2] + W * t[6]), mod(t[3])]; } export function extPow(a, e) { if (e < 0n) throw new Error("use extInv for negative exponents"); let base = a.map((v) => mod(v)); let acc = [1n, 0n, 0n, 0n]; while (e > 0n) { if (e & 1n) acc = extMul(acc, base); base = extMul(base, base); e >>= 1n; } return acc; } // Frobenius pi(a) = a^p; Frobenius^4 == id, fixes the base field. Generic power = independent ref. export const extFrobenius = (a) => extPow(a, P); export function extInv(a) { if (a.every((v) => mod(v) === 0n)) return [0n, 0n, 0n, 0n]; return extPow(a, P ** 4n - 2n); } // ---- shared cross-language vector set (must match the Python schema) ---- const BASE_UNARY = [0n, 1n, 2n, 31n, 1000000n, 123456789n, P - 1n]; const BASE_BINARY = [ [2n, 3n], [P - 1n, 1n], [1000000n, 999n], [123456789n, 987654321n], [0n, 5n], ]; const EXT_UNARY = [ [1n, 0n, 0n, 0n], [0n, 1n, 0n, 0n], [2n, 3n, 5n, 7n], [P - 1n, P - 1n, P - 1n, P - 1n], [11n, 0n, 0n, 0n], [123n, 456n, 789n, 1011n], ]; const EXT_BINARY = [ [[1n, 2n, 3n, 4n], [5n, 6n, 7n, 8n]], [[0n, 1n, 0n, 0n], [0n, 1n, 0n, 0n]], [[2n, 3n, 5n, 7n], [11n, 0n, 0n, 0n]], ]; const POW_E = 12345n; const n = (x) => Number(x); // every element < 2^31, safe as a JS Number const arr = (a) => a.map(n); export function sharedVectors() { return { constants: { P: n(P), generator: n(GENERATOR), twoAdicity: n(TWO_ADICITY), twoAdicGen27: n(twoAdicGenerator(27n)), W: n(W), powE: n(POW_E), }, base_unary: BASE_UNARY.map((a) => ({ a: n(a), neg: n(neg(a)), inv: n(inv(a)), pow: n(powF(a, POW_E)), })), base_binary: BASE_BINARY.map(([a, b]) => ({ a: n(a), b: n(b), add: n(add(a, b)), sub: n(sub(a, b)), mul: n(mul(a, b)), })), ext_unary: EXT_UNARY.map((a) => ({ a: arr(a), neg: arr(extNeg(a)), inv: arr(extInv(a)), frob: arr(extFrobenius(a)), })), ext_binary: EXT_BINARY.map(([a, b]) => ({ a: arr(a), b: arr(b), add: arr(extAdd(a, b)), sub: arr(extSub(a, b)), mul: arr(extMul(a, b)), })), }; } import { pathToFileURL } from "node:url"; if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { process.stdout.write(JSON.stringify(sharedVectors())); }