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.
144 lines
5.8 KiB
JavaScript
144 lines
5.8 KiB
JavaScript
// Independent (second-language) reference for the FRI VERIFIER (component (d) of the PQ STARK-verify
|
|
// precompile 0x0AE8). Mirrors fri_verify_reference.py and the standalone Java FriVerifySelfTest. It
|
|
// verifies real FRI proofs emitted by the pinned Plonky3 p3-fri 0.4.3-succinct prover (the ground-truth
|
|
// extractor, pq-stark/fri-extractor), and it verifies NOTHING trustless: the transcript that derives
|
|
// the betas/query-indices is component (f) and remains un-ported, so the top-level 0x0AE8 stays
|
|
// fail-closed. See docs/AERE-STARK-VERIFIER-PORT-SPEC.md section 5 and spec-fri-babybear.md.
|
|
//
|
|
// It reuses the CONFIRMED sub-components: F_{p^4} arithmetic (component (a),
|
|
// babybear_field_reference.mjs) and the FieldMerkleTreeMmcs verify_batch (component (c),
|
|
// mmcs_babybear_reference.mjs, whose leaf hash/compress is the CONFIRMED Poseidon2). BigInt throughout.
|
|
//
|
|
// node fri_verify_reference.mjs # emits the shared cross-language vector set as JSON
|
|
|
|
import { readFileSync } from "fs";
|
|
import { fileURLToPath } from "url";
|
|
import { dirname, join } from "path";
|
|
import {
|
|
P, twoAdicGenerator, powF, extFromBase, extAdd, extSub, extMul, extInv,
|
|
} from "./babybear_field_reference.mjs";
|
|
import { permute as p2permute, verifyBatch } from "./mmcs_babybear_reference.mjs";
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const GROUND_TRUTH = join(HERE, "fri_ground_truth.json");
|
|
|
|
const B = (x) => BigInt(x);
|
|
const asBig = (a) => a.map(B);
|
|
const asNum = (a) => a.map(Number);
|
|
|
|
function reverseBitsLen(x, bitLen) {
|
|
let r = 0;
|
|
for (let i = 0; i < bitLen; i++) r = (r << 1) | ((x >> i) & 1);
|
|
return r;
|
|
}
|
|
|
|
// verify one query: reproduce p3-fri verifier::verify_query. Returns folded eval (BigInt[4]) or null
|
|
// if a commit-phase MMCS opening fails. betas/index/roFull are supplied inputs (transcript = comp (f)).
|
|
function verifyQuery(logBlowup, logMaxHeight, commits, betas, index, roFull, layers) {
|
|
let folded = [0n, 0n, 0n, 0n];
|
|
const g = twoAdicGenerator(B(logMaxHeight));
|
|
let x = extFromBase(powF(g, B(reverseBitsLen(index, logMaxHeight))));
|
|
const gen1 = extFromBase(twoAdicGenerator(1n)); // order-2 root = -1, embedded
|
|
let idx = index;
|
|
const numLayers = logMaxHeight - logBlowup;
|
|
for (let layer = 0; layer < numLayers; layer++) {
|
|
const lfh = logMaxHeight - 1 - layer;
|
|
folded = extAdd(folded, roFull[lfh + 1]);
|
|
|
|
const isib = idx ^ 1;
|
|
const ipair = idx >> 1;
|
|
|
|
const evals = [folded.slice(), folded.slice()];
|
|
evals[isib % 2] = asBig(layers[layer].sibling_value);
|
|
|
|
const row = evals[0].concat(evals[1]); // 8 base coords (ExtensionMmcs flatten)
|
|
const height = 1 << lfh;
|
|
if (!verifyBatch(commits[layer], [[8, height]], ipair, [row], layers[layer].opening_proof)) {
|
|
return null; // commit-phase MMCS opening failed -> reject
|
|
}
|
|
|
|
const xs = isib % 2 === 1 ? [x, extMul(x, gen1)] : [extMul(x, gen1), x];
|
|
const beta = asBig(betas[layer]);
|
|
const num = extMul(extSub(beta, xs[0]), extSub(evals[1], evals[0]));
|
|
const den = extSub(xs[1], xs[0]);
|
|
folded = extAdd(evals[0], extMul(num, extInv(den)));
|
|
|
|
idx = ipair;
|
|
x = extMul(x, x);
|
|
}
|
|
return folded;
|
|
}
|
|
|
|
function buildRoFull(logMaxHeight, roTop) {
|
|
const ro = [];
|
|
for (let i = 0; i < logMaxHeight + 2; i++) ro.push([0n, 0n, 0n, 0n]);
|
|
ro[logMaxHeight] = asBig(roTop);
|
|
return ro;
|
|
}
|
|
|
|
const eqArr = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
|
|
function verifyCase(cas, tamper) {
|
|
const logBlowup = cas.log_blowup;
|
|
const logMaxHeight = cas.log_max_height;
|
|
const commits = cas.commit_phase_commits;
|
|
let finalPoly = asBig(cas.final_poly);
|
|
let betas = cas.betas.map((b) => b.slice());
|
|
|
|
if (tamper === "final") finalPoly = [(finalPoly[0] + 1n) % P, finalPoly[1], finalPoly[2], finalPoly[3]];
|
|
if (tamper === "beta") { betas = betas.map((b) => b.slice()); betas[0] = betas[0].slice(); betas[0][0] = Number((B(betas[0][0]) + 1n) % P); }
|
|
|
|
for (const q of cas.queries) {
|
|
const layers = q.layers.map((s) => ({
|
|
sibling_value: s.sibling_value.slice(),
|
|
opening_proof: s.opening_proof.map((d) => d.slice()),
|
|
}));
|
|
if (tamper === "sibling") layers[0].sibling_value[0] = Number((B(layers[0].sibling_value[0]) + 1n) % P);
|
|
if (tamper === "proof") layers[0].opening_proof[0][0] = Number((B(layers[0].opening_proof[0][0]) + 1n) % P);
|
|
|
|
const roFull = buildRoFull(logMaxHeight, q.ro_top);
|
|
const folded = verifyQuery(logBlowup, logMaxHeight, commits, betas, q.index, roFull, layers);
|
|
if (folded === null) return false; // MMCS opening failed
|
|
if (!eqArr(folded, finalPoly)) return false; // FinalPolyMismatch
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function caseFolded(cas) {
|
|
const commits = cas.commit_phase_commits;
|
|
const betas = cas.betas;
|
|
const out = [];
|
|
for (const q of cas.queries) {
|
|
const roFull = buildRoFull(cas.log_max_height, q.ro_top);
|
|
const folded = verifyQuery(cas.log_blowup, cas.log_max_height, commits, betas, q.index, roFull, q.layers);
|
|
out.push(asNum(folded));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function sharedVectors() {
|
|
const gt = JSON.parse(readFileSync(GROUND_TRUTH, "utf8"));
|
|
const cases = gt.cases.map((cas) => ({
|
|
name: cas.name,
|
|
logBlowup: cas.log_blowup,
|
|
logMaxHeight: cas.log_max_height,
|
|
numQueries: cas.num_queries,
|
|
folded: caseFolded(cas),
|
|
finalPoly: cas.final_poly.slice(),
|
|
accept: verifyCase(cas, null),
|
|
rejectSibling: !verifyCase(cas, "sibling"),
|
|
rejectProof: !verifyCase(cas, "proof"),
|
|
rejectBeta: !verifyCase(cas, "beta"),
|
|
rejectFinal: !verifyCase(cas, "final"),
|
|
}));
|
|
return {
|
|
permZeros: asNum(p2permute(Array(16).fill(0n))),
|
|
twoAdicGenerators: Array.from({ length: 28 }, (_, b) => Number(twoAdicGenerator(B(b)))),
|
|
cases,
|
|
};
|
|
}
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
process.stdout.write(JSON.stringify(sharedVectors()));
|
|
}
|