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
98 lines
7.1 KiB
Java
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);
|
|
}
|
|
}
|