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