aere-research/kat/MlKemAcvpKat.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

105 lines
4.3 KiB
Java

package org.hyperledger.besu.evm.precompile;
// AERE ML-KEM-768 precompile (0x0AE6) KAT harness.
//
// Drives the REAL MLKEM768PrecompiledContract.computePrecompile over NIST ACVP
// ML-KEM-encapDecap-FIPS203 encapsulation vectors. Each vectors-file line is:
// tcId|ek_hex|m_hex|expected_c_hex|expected_k_hex
// The harness assembles the precompile input (ek || m), runs the precompile, and
// asserts the 1120-byte output equals (expected_c || expected_k) byte-for-byte.
// It also re-runs each vector to confirm the encapsulation is deterministic.
//
// Writes a JSON results file (path in argv[1]) mirroring nethermind-kat-results.
import org.apache.tuweni.bytes.Bytes;
import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator;
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class MlKemAcvpKat {
static byte[] hex(String s) {
s = s.trim();
if (s.startsWith("0x")) s = s.substring(2);
if (s.isEmpty()) return new byte[0];
byte[] b = new byte[s.length() / 2];
for (int i = 0; i < b.length; i++)
b[i] = (byte) Integer.parseInt(s.substring(2 * i, 2 * i + 2), 16);
return b;
}
static String hex(byte[] b) {
StringBuilder s = new StringBuilder();
for (byte x : b) s.append(String.format("%02x", x));
return s.toString();
}
public static void main(String[] a) throws Exception {
String vectorsPath = a.length > 0 ? a[0] : "mlkem768_acvp.txt";
String outPath = a.length > 1 ? a[1] : "kat-results-mlkem.json";
MLKEM768PrecompiledContract pc = new MLKEM768PrecompiledContract(new CancunGasCalculator());
int passed = 0, failed = 0, total = 0;
StringBuilder results = new StringBuilder();
results.append("{\n \"precompile\": \"ML-KEM-768 (FIPS 203) deterministic encapsulate\",\n");
results.append(" \"address\": \"0x0000000000000000000000000000000000000ae6\",\n");
results.append(" \"source\": \"NIST ACVP ML-KEM-encapDecap-FIPS203 (encapsulation AFT)\",\n");
results.append(" \"results\": [\n");
List<String> lines = Files.readAllLines(Paths.get(vectorsPath));
boolean first = true;
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
String[] p = line.split("\\|");
if (p.length != 5) continue;
total++;
String tcId = p[0];
byte[] ek = hex(p[1]);
byte[] m = hex(p[2]);
byte[] wantC = hex(p[3]);
byte[] wantK = hex(p[4]);
byte[] input = new byte[ek.length + m.length];
System.arraycopy(ek, 0, input, 0, ek.length);
System.arraycopy(m, 0, input, ek.length, m.length);
Bytes out1 = pc.computePrecompile(Bytes.wrap(input), null).output();
Bytes out2 = pc.computePrecompile(Bytes.wrap(input), null).output(); // determinism check
byte[] want = new byte[wantC.length + wantK.length];
System.arraycopy(wantC, 0, want, 0, wantC.length);
System.arraycopy(wantK, 0, want, wantC.length, wantK.length);
boolean match = out1 != null && Arrays.equals(out1.toArrayUnsafe(), want);
boolean deterministic = out1 != null && out2 != null
&& Arrays.equals(out1.toArrayUnsafe(), out2.toArrayUnsafe());
boolean ok = match && deterministic;
if (ok) passed++; else failed++;
if (!first) results.append(",\n");
first = false;
results.append(" {\"tcId\": \"").append(tcId).append("\", \"ekLen\": ").append(ek.length)
.append(", \"ctLen\": ").append(out1 == null ? 0 : out1.size())
.append(", \"expectedMatch\": ").append(match)
.append(", \"deterministic\": ").append(deterministic)
.append(", \"status\": \"").append(ok ? "PASS" : "FAIL").append("\"}");
System.out.println("MLKEM768 tc" + tcId + " match=" + match + " deterministic=" + deterministic
+ " -> " + (ok ? "PASS" : "FAIL"));
}
results.append("\n ],\n \"passed\": ").append(passed).append(",\n \"failed\": ")
.append(failed).append(",\n \"total\": ").append(total).append("\n}\n");
Files.write(Paths.get(outPath), results.toString().getBytes());
System.out.println("MLKEM768 KAT: passed=" + passed + " failed=" + failed + " total=" + total);
if (failed != 0 || total == 0) {
System.out.println("MLKEM768_KAT_FAILED");
System.exit(3);
}
System.out.println("MLKEM768_KAT_OK");
}
}