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