aere-research/pq-stark/MmcsBabyBearSelfTest.java
Aere Network 6cb0140fae Republished from a clean root: the compiled artifact is gone from history, and the local line of work joins the sanitized public line
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.
2026-08-15 13:52:14 +03:00

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