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

156 lines
4.8 KiB
JavaScript

// 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()));
}