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.
513 lines
25 KiB
Java
513 lines
25 KiB
Java
// Standalone (no-Besu-classpath) reference for the Mixed Matrix Commitment Scheme (MMCS) that
|
|
// Plonky3/SP1 use over BabyBear, component (c) of the PQ STARK-verify precompile 0x0AE8, plus a
|
|
// self-test. It is a verbatim mirror of the Sp1StarkVerifierPrecompiledContract.Mmcs logic (same
|
|
// PaddingFreeSponge hasher, TruncatedPermutation compressor, FieldMerkleTree build/open/verify over
|
|
// the CONFIRMED Poseidon2-BabyBear permutation), compilable and runnable without the Besu EVM jars so
|
|
// the MMCS can be exercised offline and cross-checked against the Python and Node references.
|
|
//
|
|
// HONEST SCOPE (CONFIRMED 2026-07-19). This is component (c) of the six-component STARK port
|
|
// (docs/AERE-STARK-VERIFIER-PORT-SPEC.md section 4). The construction is the exact SP1 inner config
|
|
// (PaddingFreeSponge<Perm,16,8,8> hasher, TruncatedPermutation<Perm,2,8,16> compressor,
|
|
// FieldMerkleTreeMmcs<...,8> so DIGEST_ELEMS=8), verbatim from p3-merkle-tree's own mmcs.rs tests, and
|
|
// the root/opening/verify outputs reproduce real known-answer vectors emitted by executing the pinned
|
|
// p3-merkle-tree / p3-symmetric / p3-commit 0.4.3-succinct crates (checksums matched). It verifies
|
|
// NOTHING about a real proof and the top-level 0x0AE8 precompile stays fail-closed. See
|
|
// mmcs_babybear_reference.py / spec-mmcs-babybear.md for the full source citation.
|
|
//
|
|
// Usage:
|
|
// javac -d out MmcsBabyBearSelfTest.java
|
|
// java -cp out MmcsBabyBearSelfTest # run the self-test (prints PASS/FAIL counts)
|
|
// java -cp out MmcsBabyBearSelfTest --emit # emit the shared cross-language vector set (JSON)
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public final class MmcsBabyBearSelfTest {
|
|
|
|
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 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 ----
|
|
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 mul(final long a, final long b) { return (reduce(a) * reduce(b)) % P; }
|
|
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])));
|
|
}
|
|
|
|
// The CONFIRMED Poseidon2-BabyBear width-16 permutation (component (b)).
|
|
static long[] permute(final long[] state16) {
|
|
if (state16.length != WIDTH) throw new IllegalArgumentException("state must have 16 elements");
|
|
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 primitives ----
|
|
// PaddingFreeSponge<Perm, 16, 8, 8>: overwrite-mode, padding-free sponge.
|
|
static long[] hashIter(final long[] elems) {
|
|
final long[] state = new long[WIDTH]; // all-zero
|
|
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;
|
|
}
|
|
// TruncatedPermutation<Perm, 2, 8, 16>: permute(left||right) truncated to 8 lanes.
|
|
static long[] compress2to1(final long[] left8, final long[] right8) {
|
|
if (left8.length != DIGEST_ELEMS || right8.length != DIGEST_ELEMS) throw new IllegalArgumentException("digest length 8");
|
|
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;
|
|
}
|
|
|
|
// ---- helpers ----
|
|
static int log2Ceil(final int n) {
|
|
if (n <= 1) return 0;
|
|
return 32 - Integer.numberOfLeadingZeros(n - 1);
|
|
}
|
|
static int nextPow2(final int n) {
|
|
if (n <= 1) return 1;
|
|
return Integer.highestOneBit(n - 1) << 1;
|
|
}
|
|
|
|
static final long[] DEFAULT_DIGEST = new long[DIGEST_ELEMS];
|
|
|
|
// A row-major matrix.
|
|
static final class Matrix {
|
|
final long[][] rows;
|
|
final int height;
|
|
final int width;
|
|
Matrix(final long[][] r) {
|
|
rows = r;
|
|
height = r.length;
|
|
width = r.length > 0 ? r[0].length : 0;
|
|
}
|
|
long[] row(final int i) { return rows[i].clone(); }
|
|
}
|
|
|
|
static Matrix makeMatrix(final int height, final int width, final int base) {
|
|
final long[][] rows = new long[height][width];
|
|
for (int i = 0; i < height; i++) for (int j = 0; j < width; j++) rows[i][j] = base + (long) i * width + j;
|
|
return new Matrix(rows);
|
|
}
|
|
|
|
// ---- tree build (FieldMerkleTree::new) ----
|
|
static final class Tree {
|
|
final long[][][] digestLayers; // [layer][node][8]
|
|
final int logMaxHeight;
|
|
final Matrix[] matrices;
|
|
Tree(final long[][][] d, final int l, final Matrix[] m) { digestLayers = d; logMaxHeight = l; matrices = m; }
|
|
}
|
|
|
|
static Tree buildTree(final Matrix[] matrices) {
|
|
if (matrices.length == 0) throw new IllegalArgumentException("no matrices");
|
|
// height property check
|
|
final int[] hs = new int[matrices.length];
|
|
for (int i = 0; i < matrices.length; i++) hs[i] = matrices[i].height;
|
|
final int[] sortedH = hs.clone();
|
|
java.util.Arrays.sort(sortedH);
|
|
for (int k = 1; k < sortedH.length; k++) {
|
|
if (sortedH[k - 1] != sortedH[k] && nextPow2(sortedH[k - 1]) == nextPow2(sortedH[k])) {
|
|
throw new IllegalStateException("heights rounding to same power of two must be equal");
|
|
}
|
|
}
|
|
// stable sort tallest-first
|
|
final Integer[] order = new Integer[matrices.length];
|
|
for (int i = 0; i < order.length; i++) order[i] = i;
|
|
java.util.Arrays.sort(order, (a, b) -> {
|
|
if (matrices[b].height != matrices[a].height) return matrices[b].height - matrices[a].height;
|
|
return a - b;
|
|
});
|
|
final Matrix[] ordered = new Matrix[matrices.length];
|
|
for (int i = 0; i < order.length; i++) ordered[i] = matrices[order[i]];
|
|
|
|
final int maxHeight = ordered[0].height;
|
|
final int logMaxHeight = log2Ceil(maxHeight);
|
|
final int maxHeightPadded = nextPow2(maxHeight);
|
|
|
|
int idx = 0;
|
|
final List<Matrix> tallest = new ArrayList<>();
|
|
while (idx < ordered.length && ordered[idx].height == maxHeight) tallest.add(ordered[idx++]);
|
|
|
|
final List<long[][]> layers = new ArrayList<>();
|
|
final long[][] layer0 = new long[maxHeightPadded][];
|
|
for (int i = 0; i < maxHeightPadded; i++) layer0[i] = DEFAULT_DIGEST.clone();
|
|
for (int i = 0; i < maxHeight; i++) {
|
|
layer0[i] = hashIter(concatRows(tallest, i));
|
|
}
|
|
layers.add(layer0);
|
|
|
|
while (layers.get(layers.size() - 1).length > 1) {
|
|
final long[][] prev = layers.get(layers.size() - 1);
|
|
final int nextLenPadded = prev.length / 2;
|
|
final List<Matrix> inject = new ArrayList<>();
|
|
while (idx < ordered.length && nextPow2(ordered[idx].height) == nextLenPadded) inject.add(ordered[idx++]);
|
|
layers.add(compressAndInject(prev, inject, nextLenPadded));
|
|
}
|
|
final long[][][] dl = layers.toArray(new long[0][][]);
|
|
return new Tree(dl, logMaxHeight, matrices);
|
|
}
|
|
|
|
static long[] concatRows(final List<Matrix> mats, final int rowIdx) {
|
|
int total = 0;
|
|
for (final Matrix m : mats) total += m.width;
|
|
final long[] out = new long[total];
|
|
int p = 0;
|
|
for (final Matrix m : mats) {
|
|
final long[] row = m.row(rowIdx);
|
|
System.arraycopy(row, 0, out, p, row.length);
|
|
p += row.length;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static long[][] compressAndInject(final long[][] prev, final List<Matrix> inject, final int nextLenPadded) {
|
|
final long[][] out = new long[nextLenPadded][];
|
|
for (int i = 0; i < nextLenPadded; i++) out[i] = DEFAULT_DIGEST.clone();
|
|
if (inject.isEmpty()) {
|
|
for (int i = 0; i < nextLenPadded; i++) out[i] = compress2to1(prev[2 * i], prev[2 * i + 1]);
|
|
return out;
|
|
}
|
|
final int injectHeight = inject.get(0).height;
|
|
for (int i = 0; i < injectHeight; i++) {
|
|
final long[] digest = compress2to1(prev[2 * i], prev[2 * i + 1]);
|
|
final long[] rowsDigest = hashIter(concatRows(inject, i));
|
|
out[i] = compress2to1(digest, rowsDigest);
|
|
}
|
|
for (int i = injectHeight; i < nextLenPadded; i++) {
|
|
final long[] digest = compress2to1(prev[2 * i], prev[2 * i + 1]);
|
|
out[i] = compress2to1(digest, DEFAULT_DIGEST.clone());
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static long[] commitRoot(final Tree t) { return t.digestLayers[t.digestLayers.length - 1][0]; }
|
|
|
|
static long[][] openings(final int index, final Tree t) {
|
|
final long[][] op = new long[t.matrices.length][];
|
|
for (int m = 0; m < t.matrices.length; m++) {
|
|
final int bitsReduced = t.logMaxHeight - log2Ceil(t.matrices[m].height);
|
|
op[m] = t.matrices[m].row(index >> bitsReduced);
|
|
}
|
|
return op;
|
|
}
|
|
static long[][] proof(final int index, final Tree t) {
|
|
final long[][] pf = new long[t.logMaxHeight][];
|
|
for (int i = 0; i < t.logMaxHeight; i++) pf[i] = t.digestLayers[i][(index >> i) ^ 1];
|
|
return pf;
|
|
}
|
|
|
|
// ---- verify_batch ----
|
|
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) -> {
|
|
if (dims[b][1] != dims[a][1]) return dims[b][1] - dims[a][1];
|
|
return 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) {
|
|
final long[] nxt = hashIter(concatOpened(order, heights, ptr, currHeightPadded, opened));
|
|
root = compress2to1(root, nxt);
|
|
}
|
|
}
|
|
return eqDigest(root, commit);
|
|
}
|
|
|
|
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) {
|
|
final long[] row = opened[order[ptr[0]]];
|
|
for (final long v : row) 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 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;
|
|
}
|
|
|
|
// ---- CONFIRMED conformance vectors (from the pinned library) ----
|
|
static final long[] PERM_ZEROS = {1787823396L, 953829438L, 89382455L, 347481625L, 1754527224L, 916217775L,
|
|
1056029082L, 410644796L, 1169123478L, 1854704276L, 1195829987L, 1485264906L, 1824644035L, 1948268315L,
|
|
847945433L, 190591038L};
|
|
|
|
// Each case: name; matspec (list of [height,width,base]); index; root; openings; proof.
|
|
static final class Case {
|
|
final String name;
|
|
final int[][] matspec;
|
|
final int index;
|
|
final long[] root;
|
|
final long[][] openings;
|
|
final long[][] proof;
|
|
Case(String n, int[][] ms, int idx, long[] r, long[][] o, long[][] p) {
|
|
name = n; matspec = ms; index = idx; root = r; openings = o; proof = p;
|
|
}
|
|
Matrix[] matrices() {
|
|
final Matrix[] out = new Matrix[matspec.length];
|
|
for (int i = 0; i < matspec.length; i++) out[i] = makeMatrix(matspec[i][0], matspec[i][1], matspec[i][2]);
|
|
return out;
|
|
}
|
|
int[][] dims() {
|
|
final int[][] d = new int[matspec.length][2];
|
|
for (int i = 0; i < matspec.length; i++) { d[i][0] = matspec[i][1]; d[i][1] = matspec[i][0]; }
|
|
return d;
|
|
}
|
|
}
|
|
|
|
static final Case[] CASES = {
|
|
new Case("single_8x2", new int[][]{{8, 2, 10}}, 3,
|
|
new long[]{35761595L, 1133593632L, 733114748L, 517674920L, 1449914397L, 74512820L, 381712048L, 469819303L},
|
|
new long[][]{{16, 17}},
|
|
new long[][]{{1513856679L, 1890540197L, 1949407553L, 586338782L, 197781917L, 573541314L, 1955108120L, 661229895L},
|
|
{57846071L, 657849236L, 262378030L, 1091294242L, 669362455L, 676261940L, 190131986L, 664894526L},
|
|
{1143223726L, 1793027126L, 1353127284L, 491971160L, 1959272008L, 1195367436L, 417822678L, 1372117039L}}),
|
|
new Case("single_6x2", new int[][]{{6, 2, 100}}, 5,
|
|
new long[]{1043564500L, 1812685593L, 1597353357L, 1392680266L, 1355213241L, 1159759876L, 1586103175L, 799081422L},
|
|
new long[][]{{110, 111}},
|
|
new long[][]{{1089158652L, 427625262L, 1157475631L, 1692145862L, 1671348918L, 1986544368L, 497315490L, 1960470114L},
|
|
{1787823396L, 953829438L, 89382455L, 347481625L, 1754527224L, 916217775L, 1056029082L, 410644796L},
|
|
{747170349L, 1608481817L, 1569266992L, 1154307224L, 561605764L, 1907985030L, 1454474361L, 357054770L}}),
|
|
new Case("single_8x1", new int[][]{{8, 1, 200}}, 6,
|
|
new long[]{447902873L, 1264273264L, 1781377203L, 392525377L, 451230220L, 285479002L, 124104889L, 737840045L},
|
|
new long[][]{{206}},
|
|
new long[][]{{854476755L, 1853946983L, 1409001429L, 1897109439L, 1880752521L, 1036730533L, 1817549359L, 907866104L},
|
|
{757904997L, 571589503L, 673816220L, 1066413580L, 473597103L, 1271204909L, 363424036L, 1450652756L},
|
|
{402779725L, 764084359L, 1406275333L, 1158469515L, 32391761L, 236523006L, 1039588073L, 264072612L}}),
|
|
new Case("mixed_8x2_4x3", new int[][]{{8, 2, 10}, {4, 3, 300}}, 5,
|
|
new long[]{902263792L, 353615665L, 93922356L, 1251424848L, 837594048L, 2005066510L, 431363100L, 287722769L},
|
|
new long[][]{{20, 21}, {306, 307, 308}},
|
|
new long[][]{{1732750591L, 1876733121L, 1364724824L, 1847015379L, 1792368333L, 483325461L, 1174939365L, 1939380322L},
|
|
{932176417L, 320077372L, 1748871293L, 625438914L, 179521004L, 655947905L, 828588297L, 31952751L},
|
|
{930401557L, 471785873L, 904189917L, 1626475238L, 550043796L, 1590748862L, 306781755L, 496390863L}}),
|
|
new Case("mixed_8x1_4x2_2x2", new int[][]{{8, 1, 1}, {4, 2, 50}, {2, 2, 400}}, 6,
|
|
new long[]{439675894L, 37405611L, 255560432L, 531897590L, 349476430L, 1463998586L, 540993751L, 1307259928L},
|
|
new long[][]{{7}, {56, 57}, {402, 403}},
|
|
new long[][]{{723290459L, 412642417L, 484634914L, 785252854L, 182396116L, 1183763863L, 630430401L, 175545021L},
|
|
{1257356383L, 1204259532L, 1915921675L, 53621155L, 1015367194L, 742423503L, 1998129904L, 1554700909L},
|
|
{13474675L, 1966185163L, 1255599565L, 471961853L, 1823737756L, 1607265064L, 1964440250L, 1219296485L}}),
|
|
new Case("mixed_5x2_3x1", new int[][]{{5, 2, 20}, {3, 1, 70}}, 4,
|
|
new long[]{700351145L, 394944774L, 166238365L, 1683786139L, 1659356855L, 1460803726L, 573770743L, 1196572690L},
|
|
new long[][]{{28, 29}, {72}},
|
|
new long[][]{{0, 0, 0, 0, 0, 0, 0, 0},
|
|
{1714214715L, 1781831455L, 644202942L, 667527548L, 1395787195L, 671219385L, 1433934871L, 1883643874L},
|
|
{1521468259L, 1627990232L, 1145489794L, 839124709L, 507242897L, 777414579L, 429233007L, 1147797058L}}),
|
|
};
|
|
|
|
// ================================= self-test =================================
|
|
static int pass = 0;
|
|
static int fail = 0;
|
|
static void check(final String name, final boolean cond) {
|
|
if (cond) pass++;
|
|
else { fail++; System.out.println("FAIL " + name); }
|
|
}
|
|
|
|
static boolean eq2d(final long[][] a, final long[][] b) {
|
|
if (a.length != b.length) return false;
|
|
for (int i = 0; i < a.length; i++) if (!eqDigest(a[i], b[i])) return false;
|
|
return true;
|
|
}
|
|
|
|
static void selfTest() {
|
|
// perm sanity
|
|
check("perm.zeros", eqDigest(permute(new long[WIDTH]), PERM_ZEROS));
|
|
|
|
// primitive KATs
|
|
check("hash_item_5", eqDigest(hashIter(new long[]{5}), new long[]{881553380L, 703286570L, 452412164L, 1683859154L, 394218790L, 501691982L, 498441819L, 937656841L}));
|
|
check("hash_slice_2", eqDigest(hashIter(new long[]{1, 2}), new long[]{1843359319L, 912981492L, 1448073574L, 280410802L, 1934669154L, 1885461061L, 224340142L, 529679527L}));
|
|
check("hash_slice_3", eqDigest(hashIter(new long[]{1, 2, 3}), new long[]{1831345102L, 1426305082L, 956789587L, 1706209078L, 311264389L, 552910277L, 9044084L, 1828165221L}));
|
|
check("hash_slice_8", eqDigest(hashIter(new long[]{1, 2, 3, 4, 5, 6, 7, 8}), new long[]{8999572L, 1033765830L, 347083905L, 1304769627L, 1321299677L, 1106238442L, 1523849989L, 954594225L}));
|
|
check("hash_slice_9", eqDigest(hashIter(new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9}), new long[]{1134257664L, 40304233L, 1823880005L, 1134792540L, 180685702L, 1633847360L, 1460964786L, 872499523L}));
|
|
check("compress_h2_h3", eqDigest(compress2to1(hashIter(new long[]{1, 2}), hashIter(new long[]{1, 2, 3})), new long[]{1968576159L, 1450511489L, 1750728079L, 899535959L, 1052761632L, 829425096L, 282044891L, 1592617075L}));
|
|
|
|
for (final Case c : CASES) {
|
|
final Matrix[] mats = c.matrices();
|
|
final Tree t = buildTree(mats);
|
|
final long[] root = commitRoot(t);
|
|
final long[][] op = openings(c.index, t);
|
|
final long[][] pf = proof(c.index, t);
|
|
check("case." + c.name + ".root", eqDigest(root, c.root));
|
|
check("case." + c.name + ".openings", eq2d(op, c.openings));
|
|
check("case." + c.name + ".proof", eq2d(pf, c.proof));
|
|
// verify accepts the emitted opening
|
|
check("case." + c.name + ".verify", verifyBatch(c.root, c.dims(), c.index, c.openings, c.proof));
|
|
// verify rejects a tampered proof
|
|
final long[][] tampered = new long[c.proof.length][];
|
|
for (int i = 0; i < c.proof.length; i++) tampered[i] = c.proof[i].clone();
|
|
tampered[0][0] = add(tampered[0][0], 1);
|
|
check("case." + c.name + ".tamper", !verifyBatch(c.root, c.dims(), c.index, c.openings, tampered));
|
|
// self-consistency: open EVERY valid leaf index and confirm the recomputed root matches. Valid
|
|
// indices are [0, tallest actual height); padded rows above it are never opened (open_batch
|
|
// would index past the tallest matrix, exactly as in Plonky3 where LDE domains are powers of two).
|
|
int maxHeightActual = 0;
|
|
for (final Matrix m : mats) maxHeightActual = Math.max(maxHeightActual, m.height);
|
|
for (int q = 0; q < maxHeightActual; q++) {
|
|
final long[][] qop = openings(q, t);
|
|
final long[][] qpf = proof(q, t);
|
|
check("case." + c.name + ".self." + q, verifyBatch(root, c.dims(), q, qop, qpf));
|
|
}
|
|
}
|
|
|
|
System.out.println("MmcsBabyBearSelfTest PASS=" + pass + " FAIL=" + fail);
|
|
if (fail != 0) { System.out.println("MMCS_BABYBEAR_SELFTEST_FAILED"); System.exit(1); }
|
|
System.out.println("MMCS_BABYBEAR_SELFTEST_OK");
|
|
}
|
|
|
|
// ================================= shared-vector emit (JSON) =================================
|
|
static String jArr(final long[] a) {
|
|
final StringBuilder sb = new StringBuilder("[");
|
|
for (int i = 0; i < a.length; i++) { if (i > 0) sb.append(','); sb.append(a[i]); }
|
|
return sb.append(']').toString();
|
|
}
|
|
static String jArr2(final long[][] a) {
|
|
final StringBuilder sb = new StringBuilder("[");
|
|
for (int i = 0; i < a.length; i++) { if (i > 0) sb.append(','); sb.append(jArr(a[i])); }
|
|
return sb.append(']').toString();
|
|
}
|
|
|
|
static void emit() {
|
|
final StringBuilder sb = new StringBuilder();
|
|
sb.append('{');
|
|
sb.append("\"constants\":{\"P\":").append(P).append(",\"width\":").append(WIDTH)
|
|
.append(",\"rate\":").append(RATE).append(",\"out\":").append(OUT)
|
|
.append(",\"digestElems\":").append(DIGEST_ELEMS).append('}');
|
|
sb.append(",\"permZeros\":").append(jArr(permute(new long[WIDTH])));
|
|
sb.append(",\"prim\":{")
|
|
.append("\"hash_item_5\":").append(jArr(hashIter(new long[]{5})))
|
|
.append(",\"hash_slice_2\":").append(jArr(hashIter(new long[]{1, 2})))
|
|
.append(",\"hash_slice_3\":").append(jArr(hashIter(new long[]{1, 2, 3})))
|
|
.append(",\"hash_slice_8\":").append(jArr(hashIter(new long[]{1, 2, 3, 4, 5, 6, 7, 8})))
|
|
.append(",\"hash_slice_9\":").append(jArr(hashIter(new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9})))
|
|
.append(",\"compress_h2_h3\":").append(jArr(compress2to1(hashIter(new long[]{1, 2}), hashIter(new long[]{1, 2, 3}))))
|
|
.append('}');
|
|
sb.append(",\"cases\":[");
|
|
for (int k = 0; k < CASES.length; k++) {
|
|
final Case c = CASES[k];
|
|
if (k > 0) sb.append(',');
|
|
final Matrix[] mats = c.matrices();
|
|
final Tree t = buildTree(mats);
|
|
final long[] root = commitRoot(t);
|
|
final long[][] op = openings(c.index, t);
|
|
final long[][] pf = proof(c.index, t);
|
|
sb.append("{\"name\":\"").append(c.name).append('"')
|
|
.append(",\"index\":").append(c.index)
|
|
.append(",\"root\":").append(jArr(root))
|
|
.append(",\"openings\":").append(jArr2(op))
|
|
.append(",\"proof\":").append(jArr2(pf))
|
|
.append(",\"rootMatch\":").append(eqDigest(root, c.root))
|
|
.append(",\"openingsMatch\":").append(eq2d(op, c.openings))
|
|
.append(",\"proofMatch\":").append(eq2d(pf, c.proof))
|
|
.append(",\"verifyOk\":").append(verifyBatch(c.root, c.dims(), c.index, c.openings, c.proof))
|
|
.append('}');
|
|
}
|
|
sb.append("]}");
|
|
System.out.print(sb);
|
|
}
|
|
|
|
public static void main(final String[] args) {
|
|
if (args.length > 0 && args[0].equals("--emit")) emit();
|
|
else selfTest();
|
|
}
|
|
}
|