aere-research/pq-stark/Poseidon2BabyBearSelfTest.java
Aere Network 4a0b48588c Initial public release
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.
2026-07-20 01:02:30 +03:00

469 lines
18 KiB
Java

// Standalone (no-Besu-classpath) copy of the Poseidon2-over-BabyBear width-16 permutation used by the
// PQ STARK-verify precompile 0x0AE8, plus a self-test. It is a verbatim mirror of the
// Sp1StarkVerifierPrecompiledContract.Poseidon2Bb logic (same P, same M4, same internal diagonal, same
// CONFIRMED round constants), compilable and runnable without the Besu EVM jars so the permutation
// can be exercised offline and cross-checked against the Python and Node references.
//
// HONEST SCOPE (CONFIRMED 2026-07-19). This is component (b) of the six-component STARK port
// (docs/AERE-STARK-VERIFIER-PORT-SPEC.md section 3). The 141 round constants, internal diagonal, M4,
// round counts (ROUNDS_F=8, ROUNDS_P=13), x^7 S-box, and layer order all match p3-baby-bear /
// p3-poseidon2 0.4.3-succinct (the exact crates pinned in aerenew Cargo.lock; lockfile checksums
// matched), and a real known-answer test PASSES (conformanceKats: zeros/iota/testvec emitted by
// executing the pinned crates). MONTGOMERY SUBTLETY (resolved): the internal layer as a canonical map
// is R^{-1} * (J + diag(D)), R^{-1} = 943718400; a prior pass omitted the R^{-1} (textbook
// state[i]*D[i] + sum) which is self-consistent but disagrees with the prover. It verifies NOTHING
// about a real proof and the top-level 0x0AE8 precompile stays fail-closed. See
// poseidon2_babybear_reference.py for the full source citation.
//
// Usage:
// javac -d out Poseidon2BabyBearSelfTest.java
// java -cp out Poseidon2BabyBearSelfTest # run the self-test (prints PASS/FAIL counts)
// java -cp out Poseidon2BabyBearSelfTest --emit # emit the shared cross-language vector set (JSON)
import java.math.BigInteger;
import java.util.Random;
public final class Poseidon2BabyBearSelfTest {
static final long P = 2013265921L; // BabyBear 2^31 - 2^27 + 1
// Montgomery inverse factor: p3-baby-bear stores field elements in Montgomery form with R = 2^32; its
// internal diffusion layer, as a canonical linear map, carries a factor of R^{-1} = (2^32)^{-1} mod p.
static final long R_INV = 943718400L; // = (2^32)^{-1} mod p, verified against the pinned library's matrix
static final int WIDTH = 16;
static final int SBOX_DEGREE = 7; // x^7: smallest d>1 with gcd(d, p-1)=1. CONFIRMED.
static final int ROUNDS_F = 8; // external (full) rounds. CONFIRMED (round_numbers).
static final int ROUNDS_P = 13; // internal (partial) rounds. CONFIRMED (round_numbers).
static final long[][] M4 = {
{2, 3, 1, 1},
{1, 2, 3, 1},
{1, 1, 2, 3},
{3, 1, 1, 2},
};
// diag(M_I - I) = D; internal layer M_I = R^{-1} * (J + diag(D)). CONFIRMED against p3-baby-bear
// 0.4.3-succinct POSEIDON2_INTERNAL_MATRIX_DIAG_16_BABYBEAR_MONTY (canonical form).
static final long[] INTERNAL_DIAG_M1_16 = {
P - 2, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 32768
};
// ---- 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 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; // both < 2^31, product < 2^62
}
static long pow(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 sboxMono(final long x) {
final long x2 = mul(x, x);
final long x4 = mul(x2, x2);
return mul(x4, mul(x2, x)); // x^7
}
// 7^{-1} mod (p-1): exists because gcd(7, p-1) = 1. Used only by the inverse permutation.
static final long SBOX_INV_E =
BigInteger.valueOf(SBOX_DEGREE).modInverse(BigInteger.valueOf(P - 1)).longValueExact();
static long sboxMonoInv(final long y) {
return pow(y, SBOX_INV_E);
}
// ---- CONFIRMED round constants (p3-baby-bear/p3-poseidon2 0.4.3-succinct via seed_from_u64(1)) ----
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,
};
// ---- linear layers ----
static long[] m4Apply(final long[] t) {
final long[] out = new long[4];
for (int row = 0; row < 4; row++) {
out[row] = reduce(M4[row][0] * t[0] + M4[row][1] * t[1] + M4[row][2] * t[2] + M4[row][3] * t[3]);
}
return out;
}
static long[] externalLayer(final long[] state) {
final long[][] blocks = new long[4][];
for (int b = 0; b < 4; b++) {
blocks[b] = m4Apply(new long[] {state[4 * b], state[4 * b + 1], state[4 * b + 2], state[4 * b + 3]});
}
final long[] colSum = new long[4];
for (int j = 0; j < 4; j++) {
colSum[j] = reduce(blocks[0][j] + blocks[1][j] + blocks[2][j] + blocks[3][j]);
}
final long[] out = new long[WIDTH];
for (int b = 0; b < 4; b++) for (int j = 0; j < 4; j++) out[4 * b + j] = add(blocks[b][j], colSum[j]);
return out;
}
static long[] internalLayer(final long[] state) {
// p3-baby-bear DiffusionMatrixBabyBear as a canonical map: M_I = R^{-1} * (J + diag(D)),
// i.e. out[i] = R_INV * (sum + D[i]*state[i]). The R_INV factor is the Montgomery-form artifact.
long s = 0;
for (final long v : state) s = add(s, v);
final long[] out = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) out[i] = mul(R_INV, add(s, mul(state[i], INTERNAL_DIAG_M1_16[i])));
return out;
}
// ---- explicit matrices (for invertibility / inverse round-trip only) ----
static long[][] externalMatrix() {
final long[][] m = new long[WIDTH][WIDTH];
for (int bi = 0; bi < 4; bi++) {
for (int bj = 0; bj < 4; bj++) {
final long coef = bi == bj ? 2 : 1;
for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) m[4 * bi + r][4 * bj + c] = mul(coef, M4[r][c]);
}
}
return m;
}
static long[][] internalMatrix() {
// M_I = R^{-1} * (J + diag(D)): off-diagonal = R_INV, diagonal = R_INV*(1 + D[i]).
final long[][] m = new long[WIDTH][WIDTH];
for (int i = 0; i < WIDTH; i++) for (int j = 0; j < WIDTH; j++) m[i][j] = R_INV;
for (int i = 0; i < WIDTH; i++) m[i][i] = mul(R_INV, add(1, INTERNAL_DIAG_M1_16[i]));
return m;
}
static long[] matVec(final long[][] m, final long[] v) {
final long[] out = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) {
long acc = 0;
for (int j = 0; j < WIDTH; j++) acc = add(acc, mul(m[i][j], v[j]));
out[i] = acc;
}
return out;
}
static long[][] matInverse(final long[][] m) {
final int n = WIDTH;
final long[][] a = new long[n][2 * n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) a[i][j] = reduce(m[i][j]);
a[i][n + i] = 1;
}
for (int col = 0; col < n; col++) {
int piv = -1;
for (int r = col; r < n; r++) if (a[r][col] != 0) { piv = r; break; }
if (piv < 0) throw new IllegalStateException("singular matrix");
final long[] tmp = a[col]; a[col] = a[piv]; a[piv] = tmp;
final long invP = pow(a[col][col], P - 2);
for (int k = 0; k < 2 * n; k++) a[col][k] = mul(a[col][k], invP);
for (int r = 0; r < n; r++) {
if (r != col && a[r][col] != 0) {
final long f = a[r][col];
for (int k = 0; k < 2 * n; k++) a[r][k] = sub(a[r][k], mul(f, a[col][k]));
}
}
}
final long[][] inv = new long[n][n];
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) inv[i][j] = a[i][n + j];
return inv;
}
// ---- the permutation ----
static long[] permute(final long[] state) {
if (state.length != WIDTH) throw new IllegalArgumentException("state must have WIDTH elements");
long[] s = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) s[i] = reduce(state[i]);
s = 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] = sboxMono(s[i]);
s = externalLayer(s);
}
for (int r = 0; r < ROUNDS_P; r++) {
s[0] = add(s[0], RC_INTERNAL[r]);
s[0] = sboxMono(s[0]);
s = 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] = sboxMono(s[i]);
s = externalLayer(s);
}
return s;
}
static long[] permuteInverse(final long[] state) {
long[] s = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) s[i] = reduce(state[i]);
final long[][] extInv = matInverse(externalMatrix());
final long[][] intInv = matInverse(internalMatrix());
final int half = ROUNDS_F / 2;
for (int r = ROUNDS_F - 1; r >= half; r--) {
s = matVec(extInv, s);
for (int i = 0; i < WIDTH; i++) s[i] = sboxMonoInv(s[i]);
for (int i = 0; i < WIDTH; i++) s[i] = sub(s[i], RC_EXTERNAL[r][i]);
}
for (int r = ROUNDS_P - 1; r >= 0; r--) {
s = matVec(intInv, s);
s[0] = sboxMonoInv(s[0]);
s[0] = sub(s[0], RC_INTERNAL[r]);
}
for (int r = half - 1; r >= 0; r--) {
s = matVec(extInv, s);
for (int i = 0; i < WIDTH; i++) s[i] = sboxMonoInv(s[i]);
for (int i = 0; i < WIDTH; i++) s[i] = sub(s[i], RC_EXTERNAL[r][i]);
}
s = matVec(extInv, s);
return s;
}
// ---- CONFIRMED conformance KATs (from the pinned p3-baby-bear/p3-poseidon2 0.4.3-succinct crates) ----
static final long[][] KAT_IN = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
{894848333L, 1437655012L, 1200606629L, 1690012884L, 71131202L, 1749206695L, 1717947831L,
120589055L, 19776022L, 42382981L, 1831865506L, 724844064L, 171220207L, 1299207443L,
227047920L, 1783754913L},
};
static final long[][] KAT_OUT = {
{1787823396L, 953829438L, 89382455L, 347481625L, 1754527224L, 916217775L, 1056029082L,
410644796L, 1169123478L, 1854704276L, 1195829987L, 1485264906L, 1824644035L, 1948268315L,
847945433L, 190591038L},
{157639285L, 1851003038L, 1852457045L, 1920360618L, 779990819L, 1080011039L, 585017685L,
1093051731L, 249426030L, 967262243L, 623744062L, 280332881L, 1995600430L, 1751988435L,
317724737L, 1895035071L},
{512585766L, 975869435L, 1921378527L, 1238606951L, 899635794L, 132650430L, 1426417547L,
1734425242L, 57415409L, 67173027L, 1535042492L, 1318033394L, 1070659233L, 17258943L,
856719028L, 1500534995L},
};
// ================================= 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 long gcd(long a, long b) {
while (b != 0) { final long t = a % b; a = b; b = t; }
return a;
}
static boolean eq(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 void selfTest() {
// S-box degree: 7 is the smallest d>1 with gcd(d, p-1)=1 (p-1 = 2^27*3*5).
check("sbox.deg.min", SBOX_DEGREE == 7);
for (int d = 2; d <= 6; d++) check("sbox.deg.notcoprime.d" + d, gcd(d, P - 1) != 1);
check("sbox.deg7.coprime", gcd(7, P - 1) == 1);
final Random rnd = new Random(0x0AE8);
// x^7 is a bijection: inverse round-trips
for (int it = 0; it < 5000; it++) {
final long x = Math.floorMod(rnd.nextLong(), P);
check("sbox.bijection", sboxMonoInv(sboxMono(x)) == x);
}
// linear layers equal their explicit matrices; matrices are invertible
final long[][] ext = externalMatrix();
final long[][] inte = internalMatrix();
final long[][] extInv = matInverse(ext);
final long[][] intInv = matInverse(inte);
for (int it = 0; it < 2000; it++) {
final long[] v = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) v[i] = Math.floorMod(rnd.nextLong(), P);
check("ext.layer.eq.matrix", eq(externalLayer(v), matVec(ext, v)));
check("int.layer.eq.matrix", eq(internalLayer(v), matVec(inte, v)));
check("ext.inv.roundtrip", eq(matVec(extInv, matVec(ext, v)), v));
check("int.inv.roundtrip", eq(matVec(intInv, matVec(inte, v)), v));
}
// permutation: determinism + inverse round-trip (genuine bijection)
for (final long[] st : inputStates()) {
check("permute.deterministic", eq(permute(st), permute(st)));
check("permute.bijection", eq(permuteInverse(permute(st)), st));
}
for (int it = 0; it < 500; it++) {
final long[] st = new long[WIDTH];
for (int i = 0; i < WIDTH; i++) st[i] = Math.floorMod(rnd.nextLong(), P);
check("permute.rand.bijection", eq(permuteInverse(permute(st)), st));
}
// CONFORMANCE: reproduce the pinned-library known-answer vectors exactly.
for (int k = 0; k < KAT_IN.length; k++) {
check("conformance.kat" + k, eq(permute(KAT_IN[k]), KAT_OUT[k]));
}
System.out.println("Poseidon2BabyBearSelfTest PASS=" + pass + " FAIL=" + fail);
if (fail != 0) {
System.out.println("POSEIDON2_BABYBEAR_SELFTEST_FAILED");
System.exit(1);
}
System.out.println("POSEIDON2_BABYBEAR_SELFTEST_OK");
}
// ================================= shared-vector emit (JSON) =================================
static long[][] inputStates() {
final long[][] fixed = new long[5][WIDTH];
for (int i = 0; i < WIDTH; i++) {
fixed[0][i] = 0;
fixed[1][i] = i;
fixed[2][i] = P - 1;
fixed[3][i] = ((long) i * 2654435761L) % P;
fixed[4][i] = 0;
}
fixed[4][0] = 11;
fixed[4][15] = 1;
final long[][] states = new long[5 + 8][WIDTH];
for (int i = 0; i < 5; i++) states[i] = fixed[i];
long x = 123456789L;
for (int k = 0; k < 8; k++) {
for (int i = 0; i < WIDTH; i++) {
x = (1103515245L * x + 12345L) % 2147483648L;
states[5 + k][i] = x % P;
}
}
return states;
}
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 void emit() {
final StringBuilder sb = new StringBuilder();
sb.append('{');
// constants
final long[] rcExtFlat = new long[ROUNDS_F * WIDTH];
for (int r = 0; r < ROUNDS_F; r++) for (int i = 0; i < WIDTH; i++) rcExtFlat[r * WIDTH + i] = RC_EXTERNAL[r][i];
final long[] m4flat = new long[16];
for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) m4flat[r * 4 + c] = M4[r][c];
sb.append("\"constants\":{")
.append("\"P\":").append(P)
.append(",\"rInv\":").append(R_INV)
.append(",\"width\":").append(WIDTH)
.append(",\"sboxDegree\":").append(SBOX_DEGREE)
.append(",\"roundsF\":").append(ROUNDS_F)
.append(",\"roundsP\":").append(ROUNDS_P)
.append(",\"internalDiagM1\":").append(jArr(INTERNAL_DIAG_M1_16))
.append(",\"m4\":").append(jArr(m4flat))
.append(",\"rcExternalFlat\":").append(jArr(rcExtFlat))
.append(",\"rcInternal\":").append(jArr(RC_INTERNAL))
.append('}');
sb.append(",\"permute\":[");
final long[][] states = inputStates();
for (int k = 0; k < states.length; k++) {
if (k > 0) sb.append(',');
final long[] out = permute(states[k]);
final boolean rt = eq(permuteInverse(out), states[k]);
sb.append("{\"in\":").append(jArr(states[k]))
.append(",\"out\":").append(jArr(out))
.append(",\"roundtrip\":").append(rt ? "true" : "false")
.append('}');
}
sb.append(']');
sb.append(",\"conformanceKats\":[");
final String[] katNames = {"zeros", "iota", "testvec"};
for (int k = 0; k < KAT_IN.length; k++) {
if (k > 0) sb.append(',');
final long[] out = permute(KAT_IN[k]);
sb.append("{\"name\":\"").append(katNames[k]).append('"')
.append(",\"in\":").append(jArr(KAT_IN[k]))
.append(",\"out\":").append(jArr(out))
.append(",\"match\":").append(eq(out, KAT_OUT[k]) ? "true" : "false")
.append('}');
}
sb.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();
}
}