aere-contracts/test/evidence/ExercitiuEchivoc.java
Aere Network 6038f079af
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
PqEquivocationEvidence: on-chain equivocation evidence for post-quantum consensus seals (AIP-22 section 6), with its local tests and the drill signer; measured on testnet 28001
2026-09-24 01:38:15 +03:00

98 lines
7.1 KiB
Java

// AIP-22 stage 6, the DRILL for the live run on testnet 28001 (2026-09-23). It builds a registry-v3 epoch of THREE rows whose Falcon-512
// keys are generated here, in memory, for this run only (drill keys: never written to disk, never printed; the epoch names chain 28099,
// the rehearsal chain, so no record of the drill can be read as one about a validator of 28001 or 2800), and has ONE row seal two
// conflicting PREPARE messages and two conflicting COMMIT messages, with the same code the clients use (PqAnchor.*Message,
// FalconSealScheme through BouncyCastle, PqRegistryV3 row pre-image, Merkle tree and registry hash). Everything it prints is public:
// row pre-images, Merkle paths, registry hash, the messages and the seals, plus a positive and a negative control of each seal under the
// same verifier the clients use. The contract on 28001 then checks the same seals with the live precompile.
// javac -cp "<besu>/lib/*" ExercitiuEchivoc.java && java -cp ".:<besu>/lib/*" ExercitiuEchivoc > exercitiu.json
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.pqc.crypto.falcon.FalconKeyGenerationParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconKeyPairGenerator;
import org.bouncycastle.pqc.crypto.falcon.FalconParameters;
import org.bouncycastle.pqc.crypto.falcon.FalconPrivateKeyParameters;
import org.hyperledger.besu.consensus.common.bft.FalconSealScheme;
import org.hyperledger.besu.consensus.common.bft.PqAnchor;
import org.hyperledger.besu.consensus.common.bft.PqRegistryV3;
import org.hyperledger.besu.consensus.common.bft.SealSchemes;
public final class ExercitiuEchivoc {
static final long CHAIN = 28099L;
static final long BIND = 1000L;
static final int COUNT = 3;
static final int SIGNER = 1;
static final long HEIGHT = 2000L;
static final int ROUND = 2;
static String hx(byte[] b) { return Bytes.wrap(b).toHexString(); }
public static void main(String[] a) {
final SecureRandom rng = new SecureRandom();
final FalconSealScheme falcon = (FalconSealScheme) SealSchemes.FALCON_512;
final List<FalconPrivateKeyParameters> sks = new ArrayList<>();
final List<byte[]> std = new ArrayList<>();
final List<byte[]> pre = new ArrayList<>();
final List<Bytes32> leaves = new ArrayList<>();
for (int i = 0; i < COUNT; i++) {
final FalconKeyPairGenerator g = new FalconKeyPairGenerator();
g.init(new FalconKeyGenerationParameters(rng, FalconParameters.falcon_512));
final FalconPrivateKeyParameters sk = (FalconPrivateKeyParameters) g.generateKeyPair().getPrivate();
final byte[] h = sk.getPublicKey();
final byte[] s = new byte[PqRegistryV3.FALCON_512_PK_LENGTH];
s[0] = PqRegistryV3.FALCON_512_PK_HEADER;
System.arraycopy(h, 0, s, 1, h.length);
final byte[] id = PqRegistryV3.idOf(s);
final byte[] pop = falcon.signWithParams(sk, PqRegistryV3.popMessage(CHAIN, BIND, COUNT, i, id, SealSchemes.FALCON_512.wireId(), s).toArrayUnsafe()).orElseThrow();
final TreeMap<Integer, PqRegistryV3.Key> keys = new TreeMap<>();
keys.put(SealSchemes.FALCON_512.wireId() & 0xff, new PqRegistryV3.Key(SealSchemes.FALCON_512, s, pop));
final PqRegistryV3.Row row = new PqRegistryV3.Row(i, id, keys, null, Map.of(), null, null);
final byte[] p = PqRegistryV3.rowPreimage(CHAIN, BIND, COUNT, row);
sks.add(sk); std.add(s); pre.add(p); leaves.add(PqRegistryV3.rowHash(CHAIN, BIND, COUNT, row));
}
final Bytes32 root = PqRegistryV3.merkleRoot(leaves);
final Bytes32 rh = PqRegistryV3.registryHash(CHAIN, BIND, COUNT, root);
final Bytes32 dA = Bytes32.random(rng), dB = Bytes32.random(rng), hA = Bytes32.random(rng), hB = Bytes32.random(rng);
final Bytes32 pA = PqAnchor.prepareMessage(CHAIN, HEIGHT, ROUND, dA), pB = PqAnchor.prepareMessage(CHAIN, HEIGHT, ROUND, dB);
final Bytes32 cA = PqAnchor.commitMessage(CHAIN, HEIGHT, hA), cB = PqAnchor.commitMessage(CHAIN, HEIGHT, hB);
final FalconPrivateKeyParameters sk = sks.get(SIGNER);
final byte[] spA = falcon.signWithParams(sk, pA.toArrayUnsafe()).orElseThrow(), spB = falcon.signWithParams(sk, pB.toArrayUnsafe()).orElseThrow();
final byte[] scA = falcon.signWithParams(sk, cA.toArrayUnsafe()).orElseThrow(), scB = falcon.signWithParams(sk, cB.toArrayUnsafe()).orElseThrow();
final byte[] reg = sks.get(SIGNER).getPublicKey();
final byte[] other = sks.get((SIGNER + 1) % COUNT).getPublicKey();
// controls under the clients' own verifier: each seal verifies under its row's key and NOT under another row's, nor over the other message
final boolean ok = falcon.verifyRaw(reg, pA.toArrayUnsafe(), spA) && falcon.verifyRaw(reg, pB.toArrayUnsafe(), spB)
&& falcon.verifyRaw(reg, cA.toArrayUnsafe(), scA) && falcon.verifyRaw(reg, cB.toArrayUnsafe(), scB);
final boolean neg = !falcon.verifyRaw(other, pA.toArrayUnsafe(), spA) && !falcon.verifyRaw(reg, pB.toArrayUnsafe(), spA);
final StringBuilder o = new StringBuilder();
o.append("{\n \"what\": \"AIP-22 stage 6 drill: chain 28099 epoch of drill keys generated in memory for this run\",\n");
o.append(" \"chainId\": ").append(CHAIN).append(", \"bindHeight\": ").append(BIND).append(", \"count\": ").append(COUNT).append(",\n");
o.append(" \"root\": \"").append(root.toHexString()).append("\", \"registryHash\": \"").append(rh.toHexString()).append("\",\n");
o.append(" \"rows\": [");
for (int i = 0; i < COUNT; i++) o.append(i > 0 ? ", " : "").append('"').append(hx(pre.get(i))).append('"');
o.append("],\n \"proofs\": [");
for (int i = 0; i < COUNT; i++) {
o.append(i > 0 ? ", " : "").append('[');
final List<Bytes32> pr = PqRegistryV3.merkleProof(leaves, i);
for (int j = 0; j < pr.size(); j++) o.append(j > 0 ? ", " : "").append('"').append(pr.get(j).toHexString()).append('"');
o.append(']');
}
o.append("],\n \"signer\": ").append(SIGNER).append(", \"height\": ").append(HEIGHT).append(", \"round\": ").append(ROUND).append(",\n");
o.append(" \"prepare\": {\"digestA\": \"").append(dA.toHexString()).append("\", \"digestB\": \"").append(dB.toHexString())
.append("\", \"messageA\": \"").append(pA.toHexString()).append("\", \"messageB\": \"").append(pB.toHexString())
.append("\", \"sealA\": \"").append(hx(spA)).append("\", \"sealB\": \"").append(hx(spB)).append("\"},\n");
o.append(" \"commit\": {\"hashA\": \"").append(hA.toHexString()).append("\", \"hashB\": \"").append(hB.toHexString())
.append("\", \"messageA\": \"").append(cA.toHexString()).append("\", \"messageB\": \"").append(cB.toHexString())
.append("\", \"sealA\": \"").append(hx(scA)).append("\", \"sealB\": \"").append(hx(scB)).append("\"},\n");
o.append(" \"otherRowSeal\": \"").append(hx(falcon.signWithParams(sks.get((SIGNER + 1) % COUNT), pB.toArrayUnsafe()).orElseThrow())).append("\",\n");
o.append(" \"controlsUnderClientVerifier\": {\"positive\": ").append(ok).append(", \"negative\": ").append(neg).append("}\n}\n");
System.out.print(o);
if (!ok || !neg) System.exit(1);
}
}