const { expect } = require("chai"); const { ethers, network } = require("hardhat"); // --------------------------------------------------------------------------- // AereCryptoRegistry — access control, seeding, routed verification, lifecycle // policy, successor resolution, and seeded invariant fuzz. // // Hardhat has no PQC precompile, so we install MockPQCPrecompile at 0x0AE1..0x0AE4 // (hardhat_setCode), exactly as the AerePQCAttestation test does. The mock parses the // input at the precompiles' SPEC offsets and accepts iff commitment == keccak256(pk || // message). The registry builds the SAME wire format, so a positive verify() through the // registry proves it encoded the input at the correct offsets. The REAL encoding is // separately proven against the LIVE mainnet precompiles via eth_call (AerePQCAttestation // suite / scripts). SHAKE256 (scheme 5) is a HASH entry the registry refuses to route. // --------------------------------------------------------------------------- const PRECOMPILE = { 1: ethers.getAddress("0x0000000000000000000000000000000000000ae1"), // Falcon-512 2: ethers.getAddress("0x0000000000000000000000000000000000000ae2"), // Falcon-1024 3: ethers.getAddress("0x0000000000000000000000000000000000000ae3"), // ML-DSA-44 4: ethers.getAddress("0x0000000000000000000000000000000000000ae4"), // SLH-DSA-128s }; // Scheme metadata identical to the live seeded rows; the seeded algorithmId equals the // scheme number (id 1 = Falcon-512, ... id 4 = SLH-DSA-128s, id 5 = SHAKE256). const META = { 1: { name: "Falcon-512", pkLen: 897, pkHeader: 0x09, esigHeader: 0x29, sigLen: 0, falcon: true, gas: 120000 }, 2: { name: "Falcon-1024", pkLen: 1793, pkHeader: 0x0a, esigHeader: 0x2a, sigLen: 0, falcon: true, gas: 145000 }, 3: { name: "ML-DSA-44", pkLen: 1312, esigHeader: 0x00, sigLen: 2420, falcon: false, gas: 351000 }, 4: { name: "SLH-DSA-128s", pkLen: 32, esigHeader: 0x00, sigLen: 7856, falcon: false, gas: 400000 }, }; const Status = { UNKNOWN: 0, ACTIVE: 1, DEPRECATED: 2, REVOKED: 3 }; // Deterministic seeded RNG (keccak hash-chain), so the fuzz is fully reproducible. function makeRng(seedText) { let seed = ethers.keccak256(ethers.toUtf8Bytes(seedText)); function bytes(n) { const out = new Uint8Array(n); let i = 0; while (i < n) { seed = ethers.keccak256(seed); const chunk = ethers.getBytes(seed); for (let j = 0; j < chunk.length && i < n; j++, i++) out[i] = chunk[j]; } return out; } function int(n) { const b = bytes(4); return (((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0) % n; } return { bytes, int }; } function makePubKey(scheme, rng) { const m = META[scheme]; const raw = rng.bytes(m.pkLen); if (m.falcon) raw[0] = m.pkHeader; // realistic Falcon header (not required by the mock) return ethers.hexlify(raw); } // commitment the mock expects for a genuine signature over `message` by `pubKey`. function commitmentFor(pubKey, message) { return ethers.keccak256(ethers.concat([pubKey, message])); } // Build the signature envelope with a chosen commitment (genuine or forged). function envelope(scheme, commitment, rng) { const m = META[scheme]; if (m.falcon) { const nonce = rng.bytes(40); const esig = ethers.concat([new Uint8Array([m.esigHeader]), commitment]); // 33 bytes return ethers.hexlify(ethers.concat([nonce, esig])); } else { const sig = new Uint8Array(m.sigLen); sig.set(ethers.getBytes(commitment), 0); return ethers.hexlify(sig); } } // A genuine signature envelope binding to `message` (a bytes32) under `pubKey`. function genuine(scheme, pubKey, message, rng) { return envelope(scheme, commitmentFor(pubKey, message), rng); } // Flip one byte inside the commitment region -> a genuine forgery (mock rejects it). function tamperCommitment(scheme, sigHex) { const b = ethers.getBytes(sigHex); const idx = META[scheme].falcon ? 41 : 0; // Falcon: esig[1] at 40+1; fixed: sig[0] b[idx] ^= 0x01; return ethers.hexlify(b); } describe("AereCryptoRegistry", function () { let reg, owner, other, mockCode; before(async function () { [owner, other] = await ethers.getSigners(); // Deploy one mock per scheme; capture its runtime bytecode (immutable scheme baked in). mockCode = {}; const F = await ethers.getContractFactory("MockPQCPrecompile"); for (const scheme of [1, 2, 3, 4]) { const mock = await F.deploy(scheme); await mock.waitForDeployment(); mockCode[scheme] = await ethers.provider.getCode(await mock.getAddress()); } }); async function installMocks() { for (const scheme of [1, 2, 3, 4]) { await network.provider.send("hardhat_setCode", [PRECOMPILE[scheme], mockCode[scheme]]); } } beforeEach(async function () { await installMocks(); const R = await ethers.getContractFactory("AereCryptoRegistry"); reg = await R.deploy(); await reg.waitForDeployment(); }); // Send addAlgorithm, parse the event, return the new id. async function addAlgo(args, signer = owner) { const tx = await reg.connect(signer).addAlgorithm(...args); const rc = await tx.wait(); const log = rc.logs.map((l) => reg.interface.parseLog(l)).find((p) => p && p.name === "AlgorithmAdded"); return Number(log.args.id); } // Convenience args for a Falcon-512-style row pointing at the scheme-1 mock. function falcon512Args(name, gas = 120000) { return [name, 1, PRECOMPILE[1], 897, 0, 0x29, gas, true]; } // ----------------------------------------------------------------------- // Access control // ----------------------------------------------------------------------- describe("access control (Ownable)", function () { const OWNABLE_REVERT = "Ownable: caller is not the owner"; it("sets the deployer as owner", async function () { expect(await reg.owner()).to.equal(owner.address); }); it("reverts for a non-owner on addAlgorithm / setStatus / setSuccessor / seedLiveSchemes", async function () { await expect(reg.connect(other).addAlgorithm(...falcon512Args("X"))).to.be.revertedWith(OWNABLE_REVERT); await expect(reg.connect(other).setStatus(1, Status.REVOKED)).to.be.revertedWith(OWNABLE_REVERT); await expect(reg.connect(other).setSuccessor(1, 2)).to.be.revertedWith(OWNABLE_REVERT); await expect(reg.connect(other).seedLiveSchemes()).to.be.revertedWith(OWNABLE_REVERT); }); it("lets the owner add an algorithm", async function () { const id = await addAlgo(falcon512Args("Falcon-512")); expect(id).to.equal(1); expect(await reg.algorithmCount()).to.equal(1n); }); }); // ----------------------------------------------------------------------- // addAlgorithm validation // ----------------------------------------------------------------------- describe("addAlgorithm validation", function () { it("rejects an empty or over-long name", async function () { await expect(reg.addAlgorithm("", 1, PRECOMPILE[1], 897, 0, 0x29, 1, true)).to.be.revertedWithCustomError( reg, "InvalidAlgorithmParams" ); const long = "n".repeat(65); await expect(reg.addAlgorithm(long, 1, PRECOMPILE[1], 897, 0, 0x29, 1, true)).to.be.revertedWithCustomError( reg, "InvalidAlgorithmParams" ); }); it("rejects a zero verifier", async function () { await expect(reg.addAlgorithm("X", 1, ethers.ZeroAddress, 897, 0, 0x29, 1, true)).to.be.revertedWithCustomError( reg, "InvalidAlgorithmParams" ); }); it("rejects a signature scheme with pubKeyLen 0", async function () { await expect(reg.addAlgorithm("X", 1, PRECOMPILE[1], 0, 0, 0x29, 1, true)).to.be.revertedWithCustomError( reg, "InvalidAlgorithmParams" ); }); it("rejects a fixed-concatenation signature scheme with sigLen 0", async function () { await expect(reg.addAlgorithm("X", 3, PRECOMPILE[3], 1312, 0, 0x00, 1, true)).to.be.revertedWithCustomError( reg, "InvalidAlgorithmParams" ); }); it("allows a hash-only entry with pubKeyLen 0 and sigLen 0", async function () { const id = await addAlgo(["SHAKE256", 5, PRECOMPILE[1], 0, 0, 0x00, 30000, false]); const a = await reg.getAlgorithm(id); expect(a.isSignature).to.equal(false); }); }); // ----------------------------------------------------------------------- // Seeding // ----------------------------------------------------------------------- describe("seedLiveSchemes", function () { it("registers exactly the five live schemes with correct fields", async function () { await reg.seedLiveSchemes(); expect(await reg.algorithmCount()).to.equal(5n); expect(await reg.seeded()).to.equal(true); const expected = [ { id: 1, name: "Falcon-512", scheme: 1, verifier: PRECOMPILE[1], pk: 897, sig: 0, esig: 0x29, sign: true, gas: 120000 }, { id: 2, name: "Falcon-1024", scheme: 2, verifier: PRECOMPILE[2], pk: 1793, sig: 0, esig: 0x2a, sign: true, gas: 145000 }, { id: 3, name: "ML-DSA-44", scheme: 3, verifier: PRECOMPILE[3], pk: 1312, sig: 2420, esig: 0x00, sign: true, gas: 351000 }, { id: 4, name: "SLH-DSA-128s", scheme: 4, verifier: PRECOMPILE[4], pk: 32, sig: 7856, esig: 0x00, sign: true, gas: 400000 }, { id: 5, name: "SHAKE256", scheme: 5, verifier: ethers.getAddress("0x0000000000000000000000000000000000000ae5"), pk: 0, sig: 0, esig: 0x00, sign: false, gas: 30000 }, ]; for (const e of expected) { const a = await reg.getAlgorithm(e.id); expect(a.name).to.equal(e.name); expect(Number(a.scheme)).to.equal(e.scheme); expect(a.verifier).to.equal(e.verifier); expect(Number(a.pubKeyLen)).to.equal(e.pk); expect(Number(a.sigLen)).to.equal(e.sig); expect(Number(a.esigHeader)).to.equal(e.esig); expect(Number(a.status)).to.equal(Status.ACTIVE); expect(Number(a.gasEstimate)).to.equal(e.gas); expect(Number(a.successorId)).to.equal(0); expect(a.isSignature).to.equal(e.sign); expect(Number(a.addedBlock)).to.be.greaterThan(0); } }); it("is one-shot: a second seed reverts AlreadySeeded", async function () { await reg.seedLiveSchemes(); await expect(reg.seedLiveSchemes()).to.be.revertedWithCustomError(reg, "AlreadySeeded"); }); it("precompileFor(scheme) returns the ACTIVE verifier and reverts for an unknown scheme", async function () { await reg.seedLiveSchemes(); expect(await reg.precompileFor(1)).to.equal(PRECOMPILE[1]); expect(await reg.precompileFor(4)).to.equal(PRECOMPILE[4]); await expect(reg.precompileFor(9)).to.be.revertedWithCustomError(reg, "UnknownScheme"); }); }); // ----------------------------------------------------------------------- // Routed verification (happy path per scheme) // ----------------------------------------------------------------------- describe("verify (routed happy path)", function () { beforeEach(async function () { await reg.seedLiveSchemes(); }); for (const scheme of [1, 2, 3, 4]) { it(`routes ${META[scheme].name} and returns true for genuine, false for tampered`, async function () { const rng = makeRng("verify-" + scheme); const pk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("msg-" + scheme)); const good = genuine(scheme, pk, messageHash, rng); expect(await reg.verify(scheme, pk, messageHash, good)).to.equal(true); const bad = tamperCommitment(scheme, good); expect(await reg.verify(scheme, pk, messageHash, bad)).to.equal(false); }); } it("returns false when the message differs from the signed one", async function () { const rng = makeRng("wrong-msg"); const scheme = 2; const pk = makePubKey(scheme, rng); const signed = ethers.keccak256(ethers.toUtf8Bytes("signed")); const other = ethers.keccak256(ethers.toUtf8Bytes("other")); const sig = genuine(scheme, pk, signed, rng); expect(await reg.verify(scheme, pk, other, sig)).to.equal(false); }); it("returns false for a signature by a different key", async function () { const rng = makeRng("wrong-key"); const scheme = 3; const pk = makePubKey(scheme, rng); const otherPk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const sigOther = genuine(scheme, otherPk, messageHash, rng); expect(await reg.verify(scheme, pk, messageHash, sigOther)).to.equal(false); }); it("returns false for a wrong-length public key or signature (fail-closed, no revert)", async function () { const rng = makeRng("bad-len"); const scheme = 3; // ML-DSA-44: pk 1312, sig 2420 const pk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const good = genuine(scheme, pk, messageHash, rng); // short pk expect(await reg.verify(scheme, "0x" + "00".repeat(1311), messageHash, good)).to.equal(false); // short signature expect(await reg.verify(scheme, pk, messageHash, "0x" + "00".repeat(2419))).to.equal(false); // Falcon with only a nonce (no esig) const f = makePubKey(1, rng); expect(await reg.verify(1, f, messageHash, "0x" + "00".repeat(40))).to.equal(false); }); it("returns false for a hash-only entry (SHAKE256, isSignature=false)", async function () { const rng = makeRng("hash"); const anyPk = makePubKey(1, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const anySig = genuine(1, anyPk, messageHash, rng); expect(await reg.verify(5, anyPk, messageHash, anySig)).to.equal(false); }); it("returns false for an unknown algorithmId (no revert)", async function () { const rng = makeRng("unknown"); const pk = makePubKey(1, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(1, pk, messageHash, rng); expect(await reg.verify(0, pk, messageHash, sig)).to.equal(false); expect(await reg.verify(999, pk, messageHash, sig)).to.equal(false); }); it("treats an empty verifier return (pre-fork / wiped) as invalid", async function () { const rng = makeRng("empty"); const scheme = 2; const pk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(scheme, pk, messageHash, rng); expect(await reg.verify(scheme, pk, messageHash, sig)).to.equal(true); await network.provider.send("hardhat_setCode", [PRECOMPILE[scheme], "0x"]); expect(await reg.verify(scheme, pk, messageHash, sig)).to.equal(false); }); }); // ----------------------------------------------------------------------- // Explicit encoding check: registry input == the precompile SPEC layout. // ----------------------------------------------------------------------- describe("encoding matches the precompile spec", function () { beforeEach(async function () { await reg.seedLiveSchemes(); }); it("Falcon-1024: the same (pk,msg,sig) verified directly by the mock also verifies via the registry", async function () { const rng = makeRng("enc-falcon"); const scheme = 2; const m = META[scheme]; const pk = ethers.getBytes(makePubKey(scheme, rng)); const message = ethers.getBytes(ethers.keccak256(ethers.toUtf8Bytes("enc"))); const commitment = ethers.getBytes(commitmentFor(ethers.hexlify(pk), ethers.hexlify(message))); const nonce = rng.bytes(40); const esig = ethers.concat([new Uint8Array([m.esigHeader]), commitment]); const sigLen = ethers.getBytes(esig).length; const sm = ethers.concat([new Uint8Array([(sigLen >> 8) & 0xff, sigLen & 0xff]), nonce, message, esig]); const specInput = ethers.hexlify(ethers.concat([pk, sm])); // Direct precompile call with the hand-built spec input. const ret = await ethers.provider.call({ to: PRECOMPILE[scheme], data: specInput }); expect(BigInt(ret)).to.equal(1n); // The registry, given the envelope (nonce || esig), must build the identical input. const env = ethers.hexlify(ethers.concat([nonce, esig])); expect(await reg.verify(scheme, ethers.hexlify(pk), ethers.hexlify(message), env)).to.equal(true); }); it("ML-DSA-44: fixed concatenation pk||sig||msg matches", async function () { const rng = makeRng("enc-mldsa"); const scheme = 3; const m = META[scheme]; const pk = ethers.getBytes(makePubKey(scheme, rng)); const message = ethers.getBytes(ethers.keccak256(ethers.toUtf8Bytes("enc2"))); const commitment = ethers.getBytes(commitmentFor(ethers.hexlify(pk), ethers.hexlify(message))); const sig = new Uint8Array(m.sigLen); sig.set(commitment, 0); const specInput = ethers.hexlify(ethers.concat([pk, sig, message])); const ret = await ethers.provider.call({ to: PRECOMPILE[scheme], data: specInput }); expect(BigInt(ret)).to.equal(1n); expect(await reg.verify(scheme, ethers.hexlify(pk), ethers.hexlify(message), ethers.hexlify(sig))).to.equal(true); }); }); // ----------------------------------------------------------------------- // Lifecycle policy: UNKNOWN / REVOKED rejected; DEPRECATED still verifies // ----------------------------------------------------------------------- describe("lifecycle policy", function () { beforeEach(async function () { await reg.seedLiveSchemes(); }); it("DEPRECATED still verifies, but isActive=false and isUsable=false", async function () { const rng = makeRng("dep"); const scheme = 1; const pk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(scheme, pk, messageHash, rng); await reg.setStatus(1, Status.DEPRECATED); expect(Number(await reg.statusOf(1))).to.equal(Status.DEPRECATED); expect(await reg.isActive(1)).to.equal(false); expect(await reg.isUsable(1)).to.equal(false); expect(await reg.verify(1, pk, messageHash, sig)).to.equal(true); // legacy verify still works }); it("REVOKED never verifies", async function () { const rng = makeRng("rev"); const scheme = 1; const pk = makePubKey(scheme, rng); const messageHash = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(scheme, pk, messageHash, rng); expect(await reg.verify(1, pk, messageHash, sig)).to.equal(true); await reg.setStatus(1, Status.REVOKED); expect(await reg.verify(1, pk, messageHash, sig)).to.equal(false); expect(await reg.isActive(1)).to.equal(false); expect(await reg.isUsable(1)).to.equal(false); }); it("an ACTIVE hash-only entry is isActive but not isUsable", async function () { expect(await reg.isActive(5)).to.equal(true); // SHAKE256 seeded ACTIVE expect(await reg.isUsable(5)).to.equal(false); // but not a usable signature verifier }); it("setStatus cannot set UNKNOWN", async function () { await expect(reg.setStatus(1, Status.UNKNOWN)).to.be.revertedWithCustomError(reg, "InvalidStatusTarget"); }); it("REVOKED is terminal: no further status change", async function () { await reg.setStatus(1, Status.REVOKED); await expect(reg.setStatus(1, Status.ACTIVE)).to.be.revertedWithCustomError(reg, "AlgorithmRevoked"); await expect(reg.setStatus(1, Status.DEPRECATED)).to.be.revertedWithCustomError(reg, "AlgorithmRevoked"); }); it("a DEPRECATED row can be reactivated to ACTIVE", async function () { await reg.setStatus(1, Status.DEPRECATED); await reg.setStatus(1, Status.ACTIVE); expect(await reg.isActive(1)).to.equal(true); expect(await reg.isUsable(1)).to.equal(true); }); it("setStatus / setSuccessor revert on an unknown id", async function () { await expect(reg.setStatus(999, Status.ACTIVE)).to.be.revertedWithCustomError(reg, "UnknownAlgorithm"); await expect(reg.setSuccessor(999, 1)).to.be.revertedWithCustomError(reg, "UnknownAlgorithm"); await expect(reg.setSuccessor(1, 999)).to.be.revertedWithCustomError(reg, "UnknownAlgorithm"); }); it("statusOf/isActive/isUsable report UNKNOWN/false for a never-registered id", async function () { expect(Number(await reg.statusOf(999))).to.equal(Status.UNKNOWN); expect(await reg.isActive(999)).to.equal(false); expect(await reg.isUsable(999)).to.equal(false); }); }); // ----------------------------------------------------------------------- // Successor resolution // ----------------------------------------------------------------------- describe("resolveActive (successor chain)", function () { beforeEach(async function () { await reg.seedLiveSchemes(); }); it("returns the id itself when it is ACTIVE", async function () { expect(Number(await reg.resolveActive(1))).to.equal(1); }); it("follows a single successor from a DEPRECATED row to its ACTIVE successor", async function () { const v2 = await addAlgo(falcon512Args("Falcon-512-v2")); // id 6, ACTIVE await reg.setStatus(1, Status.DEPRECATED); await reg.setSuccessor(1, v2); expect(Number(await reg.resolveActive(1))).to.equal(v2); }); it("follows a multi-hop chain to the first ACTIVE row", async function () { const v2 = await addAlgo(falcon512Args("Falcon-512-v2")); // id 6 const v3 = await addAlgo(falcon512Args("Falcon-512-v3")); // id 7 await reg.setStatus(1, Status.DEPRECATED); await reg.setStatus(v2, Status.DEPRECATED); await reg.setSuccessor(1, v2); await reg.setSuccessor(v2, v3); expect(Number(await reg.resolveActive(1))).to.equal(v3); }); it("reverts NoActiveSuccessor when the chain dead-ends with no ACTIVE row", async function () { await reg.setStatus(2, Status.DEPRECATED); // no successor set await expect(reg.resolveActive(2)).to.be.revertedWithCustomError(reg, "NoActiveSuccessor"); }); it("reverts SuccessorCycle on a cyclic chain with no ACTIVE row", async function () { const x = await addAlgo(falcon512Args("X")); // id 6 const y = await addAlgo(falcon512Args("Y")); // id 7 await reg.setStatus(x, Status.DEPRECATED); await reg.setStatus(y, Status.DEPRECATED); await reg.setSuccessor(x, y); await reg.setSuccessor(y, x); await expect(reg.resolveActive(x)).to.be.revertedWithCustomError(reg, "SuccessorCycle"); }); it("reverts UnknownAlgorithm for a nonexistent id", async function () { await expect(reg.resolveActive(999)).to.be.revertedWithCustomError(reg, "UnknownAlgorithm"); }); it("setSuccessor rejects a self-successor", async function () { await expect(reg.setSuccessor(1, 1)).to.be.revertedWithCustomError(reg, "InvalidSuccessor"); }); }); // ----------------------------------------------------------------------- // Seeded invariant fuzz // ----------------------------------------------------------------------- describe("seeded invariant fuzz", function () { it("a REVOKED algorithm never verifies, across many rows and random statuses", async function () { const rng = makeRng("fuzz-revoked-v1"); const N = 16; const rows = []; for (let i = 0; i < N; i++) { const id = await addAlgo(falcon512Args("F-" + i)); const pk = makePubKey(1, rng); const messageHash = ethers.hexlify(rng.bytes(32)); const sig = genuine(1, pk, messageHash, rng); // random status: 0 ACTIVE, 1 DEPRECATED, 2 REVOKED const pick = rng.int(3); let status = Status.ACTIVE; if (pick === 1) { await reg.setStatus(id, Status.DEPRECATED); status = Status.DEPRECATED; } else if (pick === 2) { await reg.setStatus(id, Status.REVOKED); status = Status.REVOKED; } rows.push({ id, pk, messageHash, sig, status }); } for (const r of rows) { const expected = r.status !== Status.REVOKED; // ACTIVE or DEPRECATED verify; REVOKED never expect(await reg.verify(r.id, r.pk, r.messageHash, r.sig)).to.equal(expected); // A tampered signature is always false regardless of status. expect(await reg.verify(r.id, r.pk, r.messageHash, tamperCommitment(1, r.sig))).to.equal(false); // Revoked rows are never usable/active. if (r.status === Status.REVOKED) { expect(await reg.isUsable(r.id)).to.equal(false); expect(await reg.isActive(r.id)).to.equal(false); } } // Unknown ids are always false. expect(await reg.verify(N + 100, rows[0].pk, rows[0].messageHash, rows[0].sig)).to.equal(false); }); it("adding N algorithms keeps ids monotonic and counts consistent", async function () { const rng = makeRng("fuzz-monotonic-v1"); const N = 24; let prev = Number(await reg.algorithmCount()); for (let i = 0; i < N; i++) { const sig = rng.int(2) === 0; const args = sig ? falcon512Args("A-" + i) : ["H-" + i, 9, PRECOMPILE[1], 0, 0, 0x00, rng.int(1000000), false]; const id = await addAlgo(args); expect(id).to.equal(prev + 1); // monotonic, +1 each time expect(Number(await reg.algorithmCount())).to.equal(id); // count == last id prev = id; } }); it("resolveActive always terminates (returns an ACTIVE id or reverts) over random graphs", async function () { const rng = makeRng("fuzz-resolve-v1"); const M = 12; const ids = []; for (let i = 0; i < M; i++) ids.push(await addAlgo(falcon512Args("G-" + i))); // Randomly deprecate ~2/3 of them (leave the rest ACTIVE). const status = {}; for (const id of ids) { if (rng.int(3) !== 0) { await reg.setStatus(id, Status.DEPRECATED); status[id] = Status.DEPRECATED; } else { status[id] = Status.ACTIVE; } } // Random successor edges (may form cycles); skip self-edges. const succ = {}; for (const id of ids) { if (rng.int(2) === 0) { let t = ids[rng.int(M)]; if (t !== id) { await reg.setSuccessor(id, t); succ[id] = t; } } } // Off-chain reference walk: what resolveActive SHOULD do. function refResolve(start) { let cur = start; const seen = new Set(); while (true) { if (status[cur] === Status.ACTIVE) return cur; if (seen.has(cur)) return "cycle"; seen.add(cur); const n = succ[cur]; if (n === undefined) return "dead"; cur = n; } } for (const id of ids) { const ref = refResolve(id); if (typeof ref === "number") { const got = Number(await reg.resolveActive(id)); expect(got).to.equal(ref); expect(await reg.isActive(got)).to.equal(true); } else { // dead-end or cycle -> must revert (and thus terminate, not hang). let reverted = false; try { await reg.resolveActive(id); } catch (e) { reverted = true; } expect(reverted).to.equal(true); } } }); }); // ----------------------------------------------------------------------- // Gas measurement (informational) // ----------------------------------------------------------------------- describe("gas", function () { it("reports deploy / seed / verify / admin gas", async function () { const R = await ethers.getContractFactory("AereCryptoRegistry"); const fresh = await R.deploy(); const deployRc = await fresh.deploymentTransaction().wait(); const seedRc = await (await fresh.seedLiveSchemes()).wait(); const rng = makeRng("gas"); const pkF = makePubKey(2, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("gas")); const sigF = genuine(2, pkF, msg, rng); const pkM = makePubKey(3, rng); const sigM = genuine(3, pkM, msg, rng); const gVerifyFalcon = await fresh.verify.estimateGas(2, pkF, msg, sigF); const gVerifyMldsa = await fresh.verify.estimateGas(3, pkM, msg, sigM); const gAdd = await fresh.addAlgorithm.estimateGas(...falcon512Args("Falcon-512-v2")); const addRc = await (await fresh.addAlgorithm(...falcon512Args("Falcon-512-v2"))).wait(); const gStatus = await fresh.setStatus.estimateGas(6, Status.DEPRECATED); const gSucc = await fresh.setSuccessor.estimateGas(1, 6); console.log(" gas — deploy :", deployRc.gasUsed.toString()); console.log(" gas — seedLiveSchemes :", seedRc.gasUsed.toString()); console.log(" gas — verify (Falcon-1024, mock):", gVerifyFalcon.toString()); console.log(" gas — verify (ML-DSA-44, mock) :", gVerifyMldsa.toString()); console.log(" gas — addAlgorithm :", gAdd.toString(), "(actual", addRc.gasUsed.toString() + ")"); console.log(" gas — setStatus :", gStatus.toString()); console.log(" gas — setSuccessor :", gSucc.toString()); expect(deployRc.gasUsed).to.be.greaterThan(0n); }); }); });