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.
488 lines
23 KiB
Java
488 lines
23 KiB
Java
// Standalone (no-Besu-classpath) reference + self-test for the FRI VERIFIER, component (d) of the PQ
|
|
// STARK-verify precompile 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify). Mirrors
|
|
// fri_verify_reference.py and fri_verify_reference.mjs. 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).
|
|
//
|
|
// HONEST SCOPE: this implements the FOLD + OPENING relations (verify_query), taking the transcript
|
|
// derived betas / query indices / reduced openings as INPUTS. The transcript BINDING that derives them
|
|
// in-circuit is component (f) (the duplex-sponge challenger) and remains un-ported, so even with this
|
|
// passing KAT the top-level 0x0AE8 stays FAIL-CLOSED. The Poseidon2 permutation (component (b)) and the
|
|
// FieldMerkleTreeMmcs verify_batch (component (c)) below are copied verbatim from the CONFIRMED
|
|
// MmcsBabyBearSelfTest; the F_{p^4} arithmetic (component (a)) matches the precompile's BabyBearExt4.
|
|
//
|
|
// javac -d out FriVerifySelfTest.java
|
|
// java -cp out FriVerifySelfTest [ground_truth.json] # run the conformance self-test
|
|
// java -cp out FriVerifySelfTest --emit [ground_truth.json] # emit the shared vector set (JSON)
|
|
|
|
import java.math.BigInteger;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
|
|
public final class FriVerifySelfTest {
|
|
|
|
static final long P = 2013265921L; // BabyBear 2^31 - 2^27 + 1
|
|
static final long R_INV = 943718400L; // (2^32)^{-1} mod p (Poseidon2 internal-layer Montgomery factor)
|
|
static final long GENERATOR = 31L; // BabyBear multiplicative generator (order p-1)
|
|
static final long EXT_W = 11L; // F_{p^4} = F_p[x]/(x^4 - 11) non-residue
|
|
static final int WIDTH = 16;
|
|
static final int RATE = 8;
|
|
static final int OUT = 8;
|
|
static final int DIGEST_ELEMS = 8;
|
|
|
|
static final long[][] M4 = {{2, 3, 1, 1}, {1, 2, 3, 1}, {1, 1, 2, 3}, {3, 1, 1, 2}};
|
|
static final long[] INTERNAL_DIAG_M1_16 = {
|
|
P - 2, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 32768
|
|
};
|
|
static final long[][] RC_EXTERNAL = {
|
|
{1321363468L, 285374923L, 858595076L, 131742120L, 550898981L, 109281027L, 1548327248L, 299186948L,
|
|
1198120888L, 1302311359L, 568137078L, 1484856917L, 1301979945L, 725688886L, 941758026L, 323341913L},
|
|
{1049323172L, 822409348L, 1406080127L, 1279024384L, 214862539L, 904628921L, 1320747287L, 11578228L,
|
|
1036373712L, 1474430466L, 1430509860L, 111174484L, 1124450171L, 85382027L, 679880882L, 243277213L},
|
|
{1338495990L, 1523013347L, 1841068573L, 578194469L, 47683837L, 1790441672L, 1628061601L, 1716216090L,
|
|
1635810049L, 1115145248L, 1117524270L, 678640014L, 1962751651L, 1367401392L, 11688709L, 1950824358L},
|
|
{528649031L, 1937116923L, 1460949223L, 1193074357L, 1221801411L, 1183923117L, 433505619L, 1928933309L,
|
|
505759755L, 285671663L, 1047265910L, 909281502L, 1258966486L, 864761693L, 307024510L, 504858517L},
|
|
{1467478033L, 1754565867L, 432187324L, 1452390672L, 881974300L, 550050336L, 1447309270L, 939419487L,
|
|
1783112406L, 1166910332L, 107514714L, 580516863L, 2003318760L, 854475946L, 934896823L, 994783668L},
|
|
{1841107561L, 438269126L, 1550523825L, 913322122L, 600932628L, 583000098L, 1262690949L, 105797869L,
|
|
277542016L, 170491952L, 365854467L, 1479645308L, 1457660602L, 1635879552L, 499155053L, 741227047L},
|
|
{651389942L, 464828001L, 89696107L, 360044673L, 230330371L, 1773129416L, 1380150763L, 745014723L,
|
|
793475694L, 1361274828L, 1443741698L, 51616650L, 731414218L, 1087554954L, 1273943885L, 311581717L},
|
|
{702702762L, 1473247301L, 132108357L, 1348260424L, 476775430L, 1438949459L, 2434448L, 1349232398L,
|
|
1954471898L, 1762138591L, 1271221795L, 1593266476L, 864488771L, 139147729L, 1053373910L, 422842363L},
|
|
};
|
|
static final long[] RC_INTERNAL = {
|
|
402771160L, 320708227L, 1122772462L, 100431997L, 202594011L, 1226485372L, 1088619034L, 64118538L,
|
|
109828860L, 724723599L, 1662837151L, 797753907L, 1075635743L,
|
|
};
|
|
static final int ROUNDS_F = 8;
|
|
static final int ROUNDS_P = 13;
|
|
|
|
// ================= base field F_p =================
|
|
static long reduce(final long a) { long m = a % P; if (m < 0) m += P; return m; }
|
|
static long add(final long a, final long b) { long s = a + b; if (s >= P) s -= P; return s; }
|
|
static long sub(final long a, final long b) { long s = a - b; if (s < 0) s += P; return s; }
|
|
static long mul(final long a, final long b) { return (reduce(a) * reduce(b)) % P; }
|
|
static long powBase(final long base, long e) {
|
|
long b = reduce(base); long acc = 1L;
|
|
while (e > 0) { if ((e & 1L) == 1L) acc = mul(acc, b); b = mul(b, b); e >>= 1; }
|
|
return acc;
|
|
}
|
|
static long twoAdicGenerator(final int bits) { return powBase(GENERATOR, (P - 1) >> bits); }
|
|
|
|
// ================= F_{p^4} = F_p[x]/(x^4 - 11), elements long[4] = [c0,c1,c2,c3] =================
|
|
static long[] extFromBase(final long a) { return new long[]{reduce(a), 0, 0, 0}; }
|
|
static long[] extAdd(final long[] a, final long[] b) {
|
|
return new long[]{add(a[0], b[0]), add(a[1], b[1]), add(a[2], b[2]), add(a[3], b[3])};
|
|
}
|
|
static long[] extSub(final long[] a, final long[] b) {
|
|
return new long[]{sub(a[0], b[0]), sub(a[1], b[1]), sub(a[2], b[2]), sub(a[3], b[3])};
|
|
}
|
|
static long[] extMul(final long[] a, final long[] b) {
|
|
final long[] t = new long[7];
|
|
for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) t[i + j] = add(t[i + j], mul(a[i], b[j]));
|
|
return new long[]{add(t[0], mul(EXT_W, t[4])), add(t[1], mul(EXT_W, t[5])), add(t[2], mul(EXT_W, t[6])), t[3]};
|
|
}
|
|
static long[] extPow(final long[] a, final BigInteger e) {
|
|
long[] base = a.clone(); long[] acc = new long[]{1, 0, 0, 0}; BigInteger ee = e;
|
|
while (ee.signum() > 0) { if (ee.testBit(0)) acc = extMul(acc, base); base = extMul(base, base); ee = ee.shiftRight(1); }
|
|
return acc;
|
|
}
|
|
static long[] extInv(final long[] a) {
|
|
if (a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0) return new long[]{0, 0, 0, 0};
|
|
final BigInteger p = BigInteger.valueOf(P);
|
|
return extPow(a, p.pow(4).subtract(BigInteger.TWO));
|
|
}
|
|
static boolean extEq(final long[] a, final long[] b) {
|
|
for (int i = 0; i < 4; i++) if (reduce(a[i]) != reduce(b[i])) return false;
|
|
return true;
|
|
}
|
|
|
|
// ================= Poseidon2 (component (b), CONFIRMED) =================
|
|
static long sbox(final long x) { final long x2 = mul(x, x); final long x4 = mul(x2, x2); return mul(x4, mul(x2, x)); }
|
|
static long[] m4Apply(final long a, final long b, final long c, final long d) {
|
|
final long[] out = new long[4];
|
|
for (int row = 0; row < 4; row++) out[row] = reduce(M4[row][0] * a + M4[row][1] * b + M4[row][2] * c + M4[row][3] * d);
|
|
return out;
|
|
}
|
|
static void externalLayer(final long[] s) {
|
|
final long[][] blk = new long[4][];
|
|
for (int b = 0; b < 4; b++) blk[b] = m4Apply(s[4 * b], s[4 * b + 1], s[4 * b + 2], s[4 * b + 3]);
|
|
final long[] colSum = new long[4];
|
|
for (int j = 0; j < 4; j++) colSum[j] = reduce(blk[0][j] + blk[1][j] + blk[2][j] + blk[3][j]);
|
|
for (int b = 0; b < 4; b++) for (int j = 0; j < 4; j++) s[4 * b + j] = add(blk[b][j], colSum[j]);
|
|
}
|
|
static void internalLayer(final long[] s) {
|
|
long sum = 0;
|
|
for (final long v : s) sum = add(sum, v);
|
|
for (int i = 0; i < WIDTH; i++) s[i] = mul(R_INV, add(sum, mul(s[i], INTERNAL_DIAG_M1_16[i])));
|
|
}
|
|
static long[] permute(final long[] state16) {
|
|
final long[] s = new long[WIDTH];
|
|
for (int i = 0; i < WIDTH; i++) s[i] = reduce(state16[i]);
|
|
externalLayer(s);
|
|
final int half = ROUNDS_F / 2;
|
|
for (int r = 0; r < half; r++) {
|
|
for (int i = 0; i < WIDTH; i++) s[i] = add(s[i], RC_EXTERNAL[r][i]);
|
|
for (int i = 0; i < WIDTH; i++) s[i] = sbox(s[i]);
|
|
externalLayer(s);
|
|
}
|
|
for (int r = 0; r < ROUNDS_P; r++) { s[0] = add(s[0], RC_INTERNAL[r]); s[0] = sbox(s[0]); internalLayer(s); }
|
|
for (int r = half; r < ROUNDS_F; r++) {
|
|
for (int i = 0; i < WIDTH; i++) s[i] = add(s[i], RC_EXTERNAL[r][i]);
|
|
for (int i = 0; i < WIDTH; i++) s[i] = sbox(s[i]);
|
|
externalLayer(s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// ================= MMCS verify_batch (component (c), CONFIRMED) =================
|
|
static long[] hashIter(final long[] elems) {
|
|
final long[] state = new long[WIDTH];
|
|
int i = 0;
|
|
while (i < elems.length) {
|
|
final int end = Math.min(i + RATE, elems.length);
|
|
for (int j = i; j < end; j++) state[j - i] = reduce(elems[j]);
|
|
final long[] permuted = permute(state);
|
|
System.arraycopy(permuted, 0, state, 0, WIDTH);
|
|
i += RATE;
|
|
}
|
|
final long[] out = new long[OUT];
|
|
System.arraycopy(state, 0, out, 0, OUT);
|
|
return out;
|
|
}
|
|
static long[] compress2to1(final long[] left8, final long[] right8) {
|
|
final long[] pre = new long[WIDTH];
|
|
for (int i = 0; i < DIGEST_ELEMS; i++) pre[i] = reduce(left8[i]);
|
|
for (int i = 0; i < DIGEST_ELEMS; i++) pre[DIGEST_ELEMS + i] = reduce(right8[i]);
|
|
final long[] post = permute(pre);
|
|
final long[] out = new long[DIGEST_ELEMS];
|
|
System.arraycopy(post, 0, out, 0, DIGEST_ELEMS);
|
|
return out;
|
|
}
|
|
static int nextPow2(final int n) { if (n <= 1) return 1; return Integer.highestOneBit(n - 1) << 1; }
|
|
static boolean eqDigest(final long[] a, final long[] b) {
|
|
if (a.length != b.length) return false;
|
|
for (int i = 0; i < a.length; i++) if (reduce(a[i]) != reduce(b[i])) return false;
|
|
return true;
|
|
}
|
|
static long[] concatOpened(final Integer[] order, final int[] heights, final int[] ptr, final int padded, final long[][] opened) {
|
|
final List<Long> buf = new ArrayList<>();
|
|
while (ptr[0] < heights.length && nextPow2(heights[ptr[0]]) == padded) {
|
|
for (final long v : opened[order[ptr[0]]]) buf.add(v);
|
|
ptr[0]++;
|
|
}
|
|
final long[] out = new long[buf.size()];
|
|
for (int i = 0; i < out.length; i++) out[i] = buf.get(i);
|
|
return out;
|
|
}
|
|
static boolean verifyBatch(final long[] commit, final int[][] dims, final int index, final long[][] opened, final long[][] pf) {
|
|
final int n = dims.length;
|
|
if (n == 0) return false;
|
|
final Integer[] order = new Integer[n];
|
|
for (int i = 0; i < n; i++) order[i] = i;
|
|
java.util.Arrays.sort(order, (a, b) -> dims[b][1] != dims[a][1] ? dims[b][1] - dims[a][1] : a - b);
|
|
final int[] heights = new int[n];
|
|
for (int i = 0; i < n; i++) heights[i] = dims[order[i]][1];
|
|
final int[] ptr = {0};
|
|
int currHeightPadded = nextPow2(heights[0]);
|
|
long[] root = hashIter(concatOpened(order, heights, ptr, currHeightPadded, opened));
|
|
int idx = index;
|
|
for (final long[] sibling : pf) {
|
|
final long[] left; final long[] right;
|
|
if ((idx & 1) == 0) { left = root; right = sibling; } else { left = sibling; right = root; }
|
|
root = compress2to1(left, right);
|
|
idx >>= 1;
|
|
currHeightPadded >>= 1;
|
|
if (ptr[0] < n && nextPow2(heights[ptr[0]]) == currHeightPadded) {
|
|
root = compress2to1(root, hashIter(concatOpened(order, heights, ptr, currHeightPadded, opened)));
|
|
}
|
|
}
|
|
return eqDigest(root, commit);
|
|
}
|
|
|
|
// ================= FRI verify_query (component (d)) =================
|
|
static int reverseBitsLen(final int x, final int bitLen) {
|
|
int r = 0;
|
|
for (int i = 0; i < bitLen; i++) r = (r << 1) | ((x >> i) & 1);
|
|
return r;
|
|
}
|
|
|
|
static final class Layer { long[] sibling; long[][] openingProof; }
|
|
|
|
/** Reproduce p3-fri verifier::verify_query for one query. Returns folded eval (F_{p^4}) or null if a
|
|
* commit-phase MMCS opening fails. */
|
|
static long[] verifyQuery(final int logBlowup, final int logMaxHeight, final long[][] commits,
|
|
final long[][] betas, final int index, final long[][] roFull, final Layer[] layers) {
|
|
long[] folded = new long[]{0, 0, 0, 0};
|
|
final long g = twoAdicGenerator(logMaxHeight);
|
|
long[] x = extFromBase(powBase(g, reverseBitsLen(index, logMaxHeight)));
|
|
final long[] gen1 = extFromBase(twoAdicGenerator(1)); // order-2 root = -1, embedded
|
|
int idx = index;
|
|
final int numLayers = logMaxHeight - logBlowup;
|
|
for (int layer = 0; layer < numLayers; layer++) {
|
|
final int lfh = logMaxHeight - 1 - layer;
|
|
folded = extAdd(folded, roFull[lfh + 1]);
|
|
|
|
final int isib = idx ^ 1;
|
|
final int ipair = idx >> 1;
|
|
|
|
final long[][] evals = {folded.clone(), folded.clone()};
|
|
evals[isib % 2] = layers[layer].sibling.clone();
|
|
|
|
final long[] row = new long[8];
|
|
System.arraycopy(evals[0], 0, row, 0, 4);
|
|
System.arraycopy(evals[1], 0, row, 4, 4);
|
|
final int height = 1 << lfh;
|
|
if (!verifyBatch(commits[layer], new int[][]{{8, height}}, ipair, new long[][]{row}, layers[layer].openingProof)) {
|
|
return null;
|
|
}
|
|
|
|
final long[] xSib = extMul(x, gen1);
|
|
final long[][] xs = (isib % 2 == 1) ? new long[][]{x, xSib} : new long[][]{xSib, x};
|
|
final long[] beta = betas[layer];
|
|
final long[] num = extMul(extSub(beta, xs[0]), extSub(evals[1], evals[0]));
|
|
final long[] den = extSub(xs[1], xs[0]);
|
|
folded = extAdd(evals[0], extMul(num, extInv(den)));
|
|
|
|
idx = ipair;
|
|
x = extMul(x, x);
|
|
}
|
|
return folded;
|
|
}
|
|
|
|
// ================= case model + verify (accept / reject) =================
|
|
static final class Query { int index; long[] roTop; Layer[] layers; }
|
|
static final class FriCase {
|
|
String name; int logBlowup, logMaxHeight, numQueries;
|
|
long[][] commits; long[][] betas; long[] finalPoly; Query[] queries;
|
|
}
|
|
|
|
static long[][] buildRoFull(final int logMaxHeight, final long[] roTop) {
|
|
final long[][] ro = new long[logMaxHeight + 2][4];
|
|
ro[logMaxHeight] = roTop.clone();
|
|
return ro;
|
|
}
|
|
|
|
static boolean verifyCase(final FriCase c, final String tamper) {
|
|
long[] finalPoly = c.finalPoly.clone();
|
|
long[][] betas = new long[c.betas.length][];
|
|
for (int i = 0; i < betas.length; i++) betas[i] = c.betas[i].clone();
|
|
if ("final".equals(tamper)) finalPoly[0] = (finalPoly[0] + 1) % P;
|
|
if ("beta".equals(tamper)) betas[0][0] = (betas[0][0] + 1) % P;
|
|
|
|
for (final Query q : c.queries) {
|
|
final Layer[] layers = new Layer[q.layers.length];
|
|
for (int i = 0; i < layers.length; i++) {
|
|
layers[i] = new Layer();
|
|
layers[i].sibling = q.layers[i].sibling.clone();
|
|
layers[i].openingProof = new long[q.layers[i].openingProof.length][];
|
|
for (int j = 0; j < layers[i].openingProof.length; j++) layers[i].openingProof[j] = q.layers[i].openingProof[j].clone();
|
|
}
|
|
if ("sibling".equals(tamper)) layers[0].sibling[0] = (layers[0].sibling[0] + 1) % P;
|
|
if ("proof".equals(tamper)) layers[0].openingProof[0][0] = (layers[0].openingProof[0][0] + 1) % P;
|
|
|
|
final long[][] roFull = buildRoFull(c.logMaxHeight, q.roTop);
|
|
final long[] folded = verifyQuery(c.logBlowup, c.logMaxHeight, c.commits, betas, q.index, roFull, layers);
|
|
if (folded == null) return false;
|
|
if (!extEq(folded, finalPoly)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ================= minimal JSON parser (numbers/strings/arrays/objects/bools) =================
|
|
static final class JP {
|
|
final String s; int i;
|
|
JP(final String s) { this.s = s; }
|
|
void ws() { while (i < s.length() && Character.isWhitespace(s.charAt(i))) i++; }
|
|
Object val() {
|
|
ws(); final char c = s.charAt(i);
|
|
if (c == '{') return obj();
|
|
if (c == '[') return arr();
|
|
if (c == '"') return str();
|
|
if (c == 't') { i += 4; return Boolean.TRUE; }
|
|
if (c == 'f') { i += 5; return Boolean.FALSE; }
|
|
if (c == 'n') { i += 4; return null; }
|
|
return num();
|
|
}
|
|
Map<String, Object> obj() {
|
|
final Map<String, Object> m = new HashMap<>(); i++; ws();
|
|
if (s.charAt(i) == '}') { i++; return m; }
|
|
while (true) {
|
|
ws(); final String k = str(); ws(); i++; // ':'
|
|
m.put(k, val()); ws();
|
|
if (s.charAt(i) == ',') { i++; continue; }
|
|
i++; break; // '}'
|
|
}
|
|
return m;
|
|
}
|
|
List<Object> arr() {
|
|
final List<Object> a = new ArrayList<>(); i++; ws();
|
|
if (s.charAt(i) == ']') { i++; return a; }
|
|
while (true) {
|
|
a.add(val()); ws();
|
|
if (s.charAt(i) == ',') { i++; continue; }
|
|
i++; break; // ']'
|
|
}
|
|
return a;
|
|
}
|
|
String str() {
|
|
final StringBuilder b = new StringBuilder(); i++; // opening quote
|
|
while (s.charAt(i) != '"') { if (s.charAt(i) == '\\') i++; b.append(s.charAt(i++)); }
|
|
i++; return b.toString();
|
|
}
|
|
Long num() {
|
|
final int start = i;
|
|
while (i < s.length() && "+-0123456789.eE".indexOf(s.charAt(i)) >= 0) i++;
|
|
return Long.parseLong(s.substring(start, i));
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static long[] la(final Object o) {
|
|
final List<Object> l = (List<Object>) o;
|
|
final long[] out = new long[l.size()];
|
|
for (int i = 0; i < out.length; i++) out[i] = (Long) l.get(i);
|
|
return out;
|
|
}
|
|
@SuppressWarnings("unchecked")
|
|
static long[][] laa(final Object o) {
|
|
final List<Object> l = (List<Object>) o;
|
|
final long[][] out = new long[l.size()][];
|
|
for (int i = 0; i < out.length; i++) out[i] = la(l.get(i));
|
|
return out;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static FriCase[] parseGroundTruth(final String json, final long[][] permOut, final long[][] gensOut) {
|
|
final JP jp = new JP(json);
|
|
final Map<String, Object> root = (Map<String, Object>) jp.val();
|
|
permOut[0] = la(root.get("perm_zeros"));
|
|
gensOut[0] = la(root.get("two_adic_generators"));
|
|
final List<Object> cases = (List<Object>) root.get("cases");
|
|
final FriCase[] out = new FriCase[cases.size()];
|
|
for (int ci = 0; ci < out.length; ci++) {
|
|
final Map<String, Object> cm = (Map<String, Object>) cases.get(ci);
|
|
final FriCase c = new FriCase();
|
|
c.name = (String) cm.get("name");
|
|
c.logBlowup = (int) (long) (Long) cm.get("log_blowup");
|
|
c.logMaxHeight = (int) (long) (Long) cm.get("log_max_height");
|
|
c.numQueries = (int) (long) (Long) cm.get("num_queries");
|
|
c.commits = laa(cm.get("commit_phase_commits"));
|
|
c.betas = laa(cm.get("betas"));
|
|
c.finalPoly = la(cm.get("final_poly"));
|
|
final List<Object> qs = (List<Object>) cm.get("queries");
|
|
c.queries = new Query[qs.size()];
|
|
for (int qi = 0; qi < c.queries.length; qi++) {
|
|
final Map<String, Object> qm = (Map<String, Object>) qs.get(qi);
|
|
final Query q = new Query();
|
|
q.index = (int) (long) (Long) qm.get("index");
|
|
q.roTop = la(qm.get("ro_top"));
|
|
final List<Object> ls = (List<Object>) qm.get("layers");
|
|
q.layers = new Layer[ls.size()];
|
|
for (int li = 0; li < q.layers.length; li++) {
|
|
final Map<String, Object> lm = (Map<String, Object>) ls.get(li);
|
|
final Layer L = new Layer();
|
|
L.sibling = la(lm.get("sibling_value"));
|
|
L.openingProof = laa(lm.get("opening_proof"));
|
|
q.layers[li] = L;
|
|
}
|
|
c.queries[qi] = q;
|
|
}
|
|
out[ci] = c;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ================= JSON emit (shared cross-language vector set) =================
|
|
static String ja(final long[] v) {
|
|
final StringBuilder b = new StringBuilder("[");
|
|
for (int i = 0; i < v.length; i++) { if (i > 0) b.append(','); b.append(reduce(v[i])); }
|
|
return b.append(']').toString();
|
|
}
|
|
static String jaa(final long[][] v) {
|
|
final StringBuilder b = new StringBuilder("[");
|
|
for (int i = 0; i < v.length; i++) { if (i > 0) b.append(','); b.append(ja(v[i])); }
|
|
return b.append(']').toString();
|
|
}
|
|
|
|
static void emit(final String path) throws Exception {
|
|
final long[][] permOut = new long[1][];
|
|
final long[][] gensOut = new long[1][];
|
|
final FriCase[] cases = parseGroundTruth(new String(Files.readAllBytes(Paths.get(path))), permOut, gensOut);
|
|
|
|
final long[] permZeros = permute(new long[16]);
|
|
final long[] gens = new long[28];
|
|
for (int b = 0; b < 28; b++) gens[b] = twoAdicGenerator(b);
|
|
|
|
final StringBuilder sb = new StringBuilder();
|
|
sb.append("{\"permZeros\":").append(ja(permZeros));
|
|
sb.append(",\"twoAdicGenerators\":").append(ja(gens));
|
|
sb.append(",\"cases\":[");
|
|
for (int ci = 0; ci < cases.length; ci++) {
|
|
final FriCase c = cases[ci];
|
|
if (ci > 0) sb.append(',');
|
|
final long[][] folded = new long[c.queries.length][];
|
|
for (int qi = 0; qi < c.queries.length; qi++) {
|
|
final long[][] roFull = buildRoFull(c.logMaxHeight, c.queries[qi].roTop);
|
|
folded[qi] = verifyQuery(c.logBlowup, c.logMaxHeight, c.commits, c.betas, c.queries[qi].index, roFull, c.queries[qi].layers);
|
|
}
|
|
sb.append("{\"name\":\"").append(c.name).append("\"");
|
|
sb.append(",\"logBlowup\":").append(c.logBlowup);
|
|
sb.append(",\"logMaxHeight\":").append(c.logMaxHeight);
|
|
sb.append(",\"numQueries\":").append(c.numQueries);
|
|
sb.append(",\"folded\":").append(jaa(folded));
|
|
sb.append(",\"finalPoly\":").append(ja(c.finalPoly));
|
|
sb.append(",\"accept\":").append(verifyCase(c, null));
|
|
sb.append(",\"rejectSibling\":").append(!verifyCase(c, "sibling"));
|
|
sb.append(",\"rejectProof\":").append(!verifyCase(c, "proof"));
|
|
sb.append(",\"rejectBeta\":").append(!verifyCase(c, "beta"));
|
|
sb.append(",\"rejectFinal\":").append(!verifyCase(c, "final"));
|
|
sb.append("}");
|
|
}
|
|
sb.append("]}");
|
|
System.out.print(sb);
|
|
}
|
|
|
|
// ================= self-test (conformance) =================
|
|
static int pass = 0, fail = 0;
|
|
static void check(final String name, final boolean cond) { if (cond) pass++; else { fail++; System.out.println("FAIL " + name); } }
|
|
|
|
static void selfTest(final String path) throws Exception {
|
|
final long[][] permOut = new long[1][];
|
|
final long[][] gensOut = new long[1][];
|
|
final FriCase[] cases = parseGroundTruth(new String(Files.readAllBytes(Paths.get(path))), permOut, gensOut);
|
|
|
|
// sanity: perm + generators match the confirmed sub-components
|
|
check("sanity.perm_zeros", java.util.Arrays.equals(permute(new long[16]), permOut[0]));
|
|
for (int b = 0; b < 28; b++) check("sanity.gen." + b, twoAdicGenerator(b) == gensOut[0][b]);
|
|
|
|
for (final FriCase c : cases) {
|
|
check("accept." + c.name, verifyCase(c, null));
|
|
check("reject.sibling." + c.name, !verifyCase(c, "sibling"));
|
|
check("reject.proof." + c.name, !verifyCase(c, "proof"));
|
|
check("reject.beta." + c.name, !verifyCase(c, "beta"));
|
|
check("reject.final." + c.name, !verifyCase(c, "final"));
|
|
}
|
|
|
|
System.out.println("FriVerifySelfTest PASS=" + pass + " FAIL=" + fail);
|
|
if (fail != 0) { System.out.println("FRI_VERIFY_SELFTEST_FAILED"); System.exit(1); }
|
|
System.out.println("FRI_VERIFY_SELFTEST_OK");
|
|
}
|
|
|
|
public static void main(final String[] args) throws Exception {
|
|
boolean emit = false;
|
|
String path = "fri_ground_truth.json";
|
|
for (final String a : args) {
|
|
if (a.equals("--emit")) emit = true;
|
|
else if (!a.startsWith("--")) path = a;
|
|
}
|
|
if (emit) emit(path);
|
|
else selfTest(path);
|
|
}
|
|
}
|