const { expect } = require("chai"); const { ethers, network } = require("hardhat"); // --------------------------------------------------------------------------- // AereHybridAuthorizer — a crypto-agility CONSUMER over AereCryptoRegistry. // // The authorizer never pins a scheme: every check calls registry.resolveActive(id) to // follow the successor chain to the live ACTIVE row, then verifies through the registry's // routed verifier. So a Foundation-governed scheme swap on the REGISTRY (deprecate old -> // setSuccessor -> new ACTIVE) migrates this consumer with NO redeploy. // // Like the registry test, Hardhat has no PQC precompile, so we install MockPQCPrecompile // at 0x0AE1..0x0AE4 (hardhat_setCode). 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 authorize through the registry proves correct encoding. // The REAL encoding is separately proven against the LIVE mainnet precompiles via eth_call // (AerePQCAttestation suite / conformance runner). Mock-vs-live caveat: these tests prove // the ROUTING/POLICY/AGILITY logic, not the cryptography of the native precompiles. // --------------------------------------------------------------------------- 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 }; 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) — identical to AereCryptoRegistry.test.js. 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; return ethers.hexlify(raw); } function commitmentFor(pubKey, message) { return ethers.keccak256(ethers.concat([pubKey, message])); } 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); } } function genuine(scheme, pubKey, message, rng) { return envelope(scheme, commitmentFor(pubKey, message), rng); } function tamperCommitment(scheme, sigHex) { const b = ethers.getBytes(sigHex); const idx = META[scheme].falcon ? 41 : 0; b[idx] ^= 0x01; return ethers.hexlify(b); } // Convenience: registry.addAlgorithm args for a scheme's live shape, pointing at its mock. function algoArgs(scheme, name) { const m = META[scheme]; return [name, scheme, PRECOMPILE[scheme], m.pkLen, m.sigLen, m.esigHeader, m.gas, true]; } describe("AereHybridAuthorizer", function () { let reg, auth, owner, other, mockCode; before(async function () { [owner, other] = await ethers.getSigners(); 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]]); } } // Add an algorithm row to the registry, return its id. async function addAlgo(scheme, name, signer = owner) { const tx = await reg.connect(signer).addAlgorithm(...algoArgs(scheme, name)); 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); } async function deployAuthorizer(defaultId) { const A = await ethers.getContractFactory("AereHybridAuthorizer"); const a = await A.deploy(await reg.getAddress(), defaultId); await a.waitForDeployment(); return a; } beforeEach(async function () { await installMocks(); const R = await ethers.getContractFactory("AereCryptoRegistry"); reg = await R.deploy(); await reg.waitForDeployment(); await reg.seedLiveSchemes(); // ids 1..5 = Falcon-512/1024, ML-DSA-44, SLH-DSA-128s, SHAKE256 auth = await deployAuthorizer(1); // default = Falcon-512 }); // ----------------------------------------------------------------------- // Construction / wiring // ----------------------------------------------------------------------- describe("construction", function () { it("wires the registry, sets the deployer as owner and a usable default", async function () { expect(await auth.registry()).to.equal(await reg.getAddress()); expect(await auth.owner()).to.equal(owner.address); expect(Number(await auth.defaultAlgorithmId())).to.equal(1); }); it("reverts ZeroRegistry for a zero registry", async function () { const A = await ethers.getContractFactory("AereHybridAuthorizer"); await expect(A.deploy(ethers.ZeroAddress, 1)).to.be.revertedWithCustomError(auth, "ZeroRegistry"); }); it("reverts UnusableAlgorithm for a hash-only default (SHAKE256 id 5)", async function () { const A = await ethers.getContractFactory("AereHybridAuthorizer"); await expect(A.deploy(await reg.getAddress(), 5)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); }); it("reverts UnusableAlgorithm for a nonexistent default id", async function () { const A = await ethers.getContractFactory("AereHybridAuthorizer"); await expect(A.deploy(await reg.getAddress(), 999)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); }); it("reverts UnusableAlgorithm when the default id is REVOKED with no successor", async function () { await reg.setStatus(2, Status.REVOKED); // Falcon-1024 revoked, no successor const A = await ethers.getContractFactory("AereHybridAuthorizer"); await expect(A.deploy(await reg.getAddress(), 2)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); }); }); // ----------------------------------------------------------------------- // checkAuthorized (fail-closed routed happy path) // ----------------------------------------------------------------------- describe("checkAuthorized (routed, fail-closed)", function () { for (const scheme of [1, 2, 3, 4]) { it(`authorizes a genuine ${META[scheme].name} signature and rejects a tampered one`, async function () { const rng = makeRng("auth-" + scheme); const pk = makePubKey(scheme, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("msg-" + scheme)); const good = genuine(scheme, pk, msg, rng); // requested id == scheme id (seeded 1:1) expect(await auth.checkAuthorized(scheme, pk, msg, good)).to.equal(true); expect(await auth.checkAuthorized(scheme, pk, msg, tamperCommitment(scheme, good))).to.equal(false); }); } it("returns false (no revert) for an unknown requested id", async function () { const rng = makeRng("unknown"); const pk = makePubKey(1, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(1, pk, msg, rng); expect(await auth.checkAuthorized(0, pk, msg, sig)).to.equal(false); expect(await auth.checkAuthorized(999, pk, msg, sig)).to.equal(false); }); it("returns false when the message differs from the signed one", async function () { const rng = makeRng("wrong-msg"); const pk = makePubKey(1, rng); const signed = ethers.keccak256(ethers.toUtf8Bytes("signed")); const otherMsg = ethers.keccak256(ethers.toUtf8Bytes("other")); const sig = genuine(1, pk, signed, rng); expect(await auth.checkAuthorized(1, pk, otherMsg, sig)).to.equal(false); }); it("routes to a hash-only entry as false (SHAKE256 id 5 is not usable)", async function () { const rng = makeRng("hash"); const pk = makePubKey(1, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(1, pk, msg, rng); // resolveActive(5) returns 5 (ACTIVE) but registry.verify refuses a hash-only row. expect(await auth.checkAuthorized(5, pk, msg, sig)).to.equal(false); }); }); // ----------------------------------------------------------------------- // Per-account preference with a safe default // ----------------------------------------------------------------------- describe("per-account preference", function () { it("uses the app default for an account with no preference", async function () { expect(Number(await auth.algorithmFor(other.address))).to.equal(1); expect(Number(await auth.preferredAlgorithmOf(other.address))).to.equal(0); expect(Number(await auth.resolvedAlgorithmFor(other.address))).to.equal(1); }); it("lets an account pick a usable scheme and route checks under it", async function () { await auth.connect(other).setPreferredAlgorithm(3); // ML-DSA-44 expect(Number(await auth.algorithmFor(other.address))).to.equal(3); const rng = makeRng("pref-mldsa"); const pk = makePubKey(3, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("p")); const sig = genuine(3, pk, msg, rng); expect(await auth.checkAuthorizedFor(other.address, pk, msg, sig)).to.equal(true); // A Falcon-512 signature (the default scheme) is NOT accepted for this account. const fpk = makePubKey(1, rng); const fsig = genuine(1, fpk, msg, rng); expect(await auth.checkAuthorizedFor(other.address, fpk, msg, fsig)).to.equal(false); }); it("clears a preference with id 0 and falls back to the safe default", async function () { await auth.connect(other).setPreferredAlgorithm(3); await auth.connect(other).setPreferredAlgorithm(0); expect(Number(await auth.preferredAlgorithmOf(other.address))).to.equal(0); expect(Number(await auth.algorithmFor(other.address))).to.equal(1); }); it("rejects a preference that is unusable (hash-only / unknown / revoked-no-successor)", async function () { await expect(auth.connect(other).setPreferredAlgorithm(5)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); await expect(auth.connect(other).setPreferredAlgorithm(999)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); await reg.setStatus(4, Status.REVOKED); await expect(auth.connect(other).setPreferredAlgorithm(4)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); }); it("setPreferredAlgorithm is permissionless (any account, not owner-gated)", async function () { await expect(auth.connect(other).setPreferredAlgorithm(2)).to.not.be.reverted; expect(Number(await auth.algorithmFor(other.address))).to.equal(2); }); }); // ----------------------------------------------------------------------- // THE HEADLINE: a successor swap transparently changes the accepted scheme, // with NO redeploy of the authorizer and the SAME requested id. // ----------------------------------------------------------------------- describe("zero-redeploy scheme swap (deprecate -> successor -> new)", function () { it("the same requested id starts accepting the NEW scheme and rejecting the OLD one", async function () { const rng = makeRng("swap"); const msg = ethers.keccak256(ethers.toUtf8Bytes("swap-msg")); // Old world: id 1 == Falcon-512. const fpk = makePubKey(1, rng); const fsig = genuine(1, fpk, msg, rng); // New world signature: an ML-DSA-44 key/sig. const mpk = makePubKey(3, rng); const msig = genuine(3, mpk, msg, rng); // BEFORE the swap: id 1 accepts Falcon-512, rejects the ML-DSA-44 signature. expect(await auth.checkAuthorized(1, fpk, msg, fsig)).to.equal(true); expect(await auth.checkAuthorized(1, mpk, msg, msig)).to.equal(false); // Foundation-governed swap on the REGISTRY (no authorizer redeploy): // 1. register the successor (an ML-DSA-44-shaped row), ACTIVE // 2. point id 1 -> successor // 3. deprecate id 1 const newId = await addAlgo(3, "ML-DSA-44-succ"); // id 6, routes to the scheme-3 mock await reg.setSuccessor(1, newId); await reg.setStatus(1, Status.DEPRECATED); // AFTER the swap: SAME requested id 1 now routes to the successor. expect(Number(await auth.resolvedAlgorithmFor(owner.address))).to.equal(newId); // The old Falcon-512 signature no longer authorizes; the new ML-DSA-44 one does. expect(await auth.checkAuthorized(1, fpk, msg, fsig)).to.equal(false); expect(await auth.checkAuthorized(1, mpk, msg, msig)).to.equal(true); }); it("a multi-hop successor chain resolves to the final ACTIVE scheme", async function () { const rng = makeRng("multi-hop"); const msg = ethers.keccak256(ethers.toUtf8Bytes("m")); const id6 = await addAlgo(2, "F1024-v2"); // ACTIVE const id7 = await addAlgo(4, "SLH-v3"); // ACTIVE await reg.setSuccessor(1, id6); await reg.setSuccessor(id6, id7); await reg.setStatus(1, Status.DEPRECATED); await reg.setStatus(id6, Status.DEPRECATED); expect(Number(await auth.resolvedAlgorithmFor(owner.address))).to.equal(id7); // A genuine SLH-DSA-128s (scheme 4) signature authorizes under requested id 1. const pk = makePubKey(4, rng); const sig = genuine(4, pk, msg, rng); expect(await auth.checkAuthorized(1, pk, msg, sig)).to.equal(true); }); }); // ----------------------------------------------------------------------- // A REVOKED scheme is NEVER authorized // ----------------------------------------------------------------------- describe("REVOKED is never authorized", function () { it("a revoked id with no successor authorizes nothing (fail-closed view, revert on authorize)", async function () { const rng = makeRng("revoked"); const pk = makePubKey(1, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("m")); const sig = genuine(1, pk, msg, rng); expect(await auth.checkAuthorized(1, pk, msg, sig)).to.equal(true); await reg.setStatus(1, Status.REVOKED); // no successor // View fail-closes to false (resolveActive reverts NoActiveSuccessor, caught). expect(await auth.checkAuthorized(1, pk, msg, sig)).to.equal(false); // State-changing authorize surfaces the registry revert (NoActiveSuccessor). await expect(auth.authorize(1, pk, msg, sig)).to.be.revertedWithCustomError(reg, "NoActiveSuccessor"); }); it("a revoked id is skipped for its ACTIVE successor; a signature under the revoked scheme still fails", async function () { const rng = makeRng("revoked-succ"); const msg = ethers.keccak256(ethers.toUtf8Bytes("m")); const fpk = makePubKey(1, rng); // revoked-scheme (Falcon-512) key const fsig = genuine(1, fpk, msg, rng); const mpk = makePubKey(3, rng); // successor-scheme (ML-DSA-44) key const msig = genuine(3, mpk, msg, rng); const succ = await addAlgo(3, "ML-DSA-succ"); // ACTIVE await reg.setSuccessor(1, succ); await reg.setStatus(1, Status.REVOKED); // The revoked scheme's own signature can NEVER authorize (routes to successor). expect(await auth.checkAuthorized(1, fpk, msg, fsig)).to.equal(false); // Only a signature valid under the ACTIVE successor authorizes. expect(await auth.checkAuthorized(1, mpk, msg, msig)).to.equal(true); expect(Number(await auth.resolvedAlgorithmFor(owner.address))).to.equal(succ); }); }); // ----------------------------------------------------------------------- // Owner governance: setDefaultAlgorithm / migrate — only owner // ----------------------------------------------------------------------- describe("owner governance (only-owner)", function () { const OWNABLE_REVERT = "Ownable: caller is not the owner"; it("reverts for a non-owner on setDefaultAlgorithm / migrate", async function () { await expect(auth.connect(other).setDefaultAlgorithm(2)).to.be.revertedWith(OWNABLE_REVERT); await expect(auth.connect(other).migrate(1)).to.be.revertedWith(OWNABLE_REVERT); }); it("owner can retarget the default to another usable scheme", async function () { await expect(auth.setDefaultAlgorithm(3)).to.emit(auth, "DefaultAlgorithmSet").withArgs(3); expect(Number(await auth.defaultAlgorithmId())).to.equal(3); }); it("setDefaultAlgorithm rejects an unusable target (hash-only / unknown)", async function () { await expect(auth.setDefaultAlgorithm(5)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); await expect(auth.setDefaultAlgorithm(999)).to.be.revertedWithCustomError(auth, "UnusableAlgorithm"); }); it("migrate crystallizes the default onto the ACTIVE successor after a swap", async function () { const newId = await addAlgo(2, "F1024-succ"); await reg.setSuccessor(1, newId); await reg.setStatus(1, Status.DEPRECATED); await expect(auth.migrate(1)).to.emit(auth, "DefaultMigrated").withArgs(1, newId); expect(Number(await auth.defaultAlgorithmId())).to.equal(newId); }); it("migrate is a no-op (no event, default unchanged) when the id is still ACTIVE", async function () { await expect(auth.migrate(1)).to.not.emit(auth, "DefaultMigrated"); expect(Number(await auth.defaultAlgorithmId())).to.equal(1); }); it("migrate reverts NotCurrentDefault when oldId != the current default", async function () { await expect(auth.migrate(2)).to.be.revertedWithCustomError(auth, "NotCurrentDefault").withArgs(2, 1); }); it("migrate reverts (from resolveActive) if the default dead-ends with no ACTIVE successor", async function () { // Make the default (1) DEPRECATED with no successor -> resolveActive reverts. await reg.setStatus(1, Status.DEPRECATED); await expect(auth.migrate(1)).to.be.revertedWithCustomError(reg, "NoActiveSuccessor"); }); }); // ----------------------------------------------------------------------- // State-changing authorize: ledger + events // ----------------------------------------------------------------------- describe("authorize (state-changing, recorded)", function () { it("records a successful authorization and emits Authorized", async function () { const rng = makeRng("authorize"); const pk = makePubKey(1, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("do-it")); const sig = genuine(1, pk, msg, rng); expect(await auth.isProofAuthorized(pk, msg)).to.equal(false); await expect(auth.authorize(1, pk, msg, sig)) .to.emit(auth, "Authorized") .withArgs(owner.address, 1, 1, msg); expect(Number(await auth.authorizationCount())).to.equal(1); expect(await auth.isProofAuthorized(pk, msg)).to.equal(true); expect(Number(await auth.lastResolvedId())).to.equal(1); expect(await auth.lastMessageHash()).to.equal(msg); }); it("reverts AuthorizationFailed on a tampered signature (verifier returns false)", async function () { const rng = makeRng("authorize-bad"); const pk = makePubKey(1, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("nope")); const bad = tamperCommitment(1, genuine(1, pk, msg, rng)); await expect(auth.authorize(1, pk, msg, bad)) .to.be.revertedWithCustomError(auth, "AuthorizationFailed") .withArgs(1, 1); expect(Number(await auth.authorizationCount())).to.equal(0); }); it("authorizeWithPreferred uses the caller's effective scheme and records the resolved id", async function () { await auth.connect(other).setPreferredAlgorithm(4); // SLH-DSA-128s const rng = makeRng("authorize-pref"); const pk = makePubKey(4, rng); const msg = ethers.keccak256(ethers.toUtf8Bytes("pref-do")); const sig = genuine(4, pk, msg, rng); await expect(auth.connect(other).authorizeWithPreferred(pk, msg, sig)) .to.emit(auth, "Authorized") .withArgs(other.address, 4, 4, msg); expect(Number(await auth.lastResolvedId())).to.equal(4); }); it("authorize routes through a live successor swap without a redeploy", async function () { const rng = makeRng("authorize-swap"); const msg = ethers.keccak256(ethers.toUtf8Bytes("swap-do")); const newId = await addAlgo(3, "ML-DSA-do"); await reg.setSuccessor(1, newId); await reg.setStatus(1, Status.DEPRECATED); const pk = makePubKey(3, rng); const sig = genuine(3, pk, msg, rng); await expect(auth.authorize(1, pk, msg, sig)) .to.emit(auth, "Authorized") .withArgs(owner.address, 1, newId, msg); // requestedId 1, resolvedId = successor expect(Number(await auth.lastResolvedId())).to.equal(newId); }); }); // ----------------------------------------------------------------------- // Seeded invariant fuzz // ----------------------------------------------------------------------- describe("seeded invariant fuzz", function () { it("a REVOKED scheme is NEVER authorized, across many rows and random statuses", async function () { const rng = makeRng("fuzz-authz-revoked-v1"); const N = 16; const rows = []; for (let i = 0; i < N; i++) { const scheme = 1 + rng.int(4); // 1..4 const id = await addAlgo(scheme, "R-" + i); const pk = makePubKey(scheme, rng); const msg = ethers.hexlify(rng.bytes(32)); const sig = genuine(scheme, pk, msg, rng); const pick = rng.int(3); // 0 ACTIVE, 1 DEPRECATED, 2 REVOKED 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, scheme, pk, msg, sig, status }); } for (const r of rows) { // No successor is ever set on these rows. So: // ACTIVE -> authorizes a genuine sig (true) // DEPRECATED / REVOKED (no ACTIVE successor) -> resolveActive reverts -> false const expected = r.status === Status.ACTIVE; expect(await auth.checkAuthorized(r.id, r.pk, r.msg, r.sig)).to.equal(expected); // A tampered signature is always false. expect(await auth.checkAuthorized(r.id, r.pk, r.msg, tamperCommitment(r.scheme, r.sig))).to.equal(false); // A REVOKED row can never be authorized, and authorize() must revert. if (r.status === Status.REVOKED) { let reverted = false; try { await auth.authorize.staticCall(r.id, r.pk, r.msg, r.sig); } catch (e) { reverted = true; } expect(reverted).to.equal(true); } } }); it("checkAuthorized always terminates (never hangs) over random successor graphs", async function () { const rng = makeRng("fuzz-authz-resolve-v1"); const M = 10; const ids = []; for (let i = 0; i < M; i++) ids.push(await addAlgo(1, "G-" + i)); 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; } } const succ = {}; for (const id of ids) { if (rng.int(2) === 0) { const t = ids[rng.int(M)]; if (t !== id) { await reg.setSuccessor(id, t); succ[id] = t; } } } // Off-chain reference walk mirroring resolveActive. 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 rng2 = makeRng("g-" + id); const pk = makePubKey(1, rng2); const msg = ethers.hexlify(rng2.bytes(32)); const sig = genuine(1, pk, msg, rng2); const ref = refResolve(id); const got = await auth.checkAuthorized(id, pk, msg, sig); if (typeof ref === "number") { // Resolves to an ACTIVE Falcon-512 row -> the genuine Falcon-512 sig authorizes. expect(got).to.equal(true); } else { // Dead-end or cycle -> fail-closed false (terminated, did not hang). expect(got).to.equal(false); } } }); }); // ----------------------------------------------------------------------- // Gas (informational) // ----------------------------------------------------------------------- describe("gas", function () { it("reports deploy / authorize / admin gas", async function () { const A = await ethers.getContractFactory("AereHybridAuthorizer"); const fresh = await A.deploy(await reg.getAddress(), 1); const deployRc = await fresh.deploymentTransaction().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 gCheckFalcon = await fresh.checkAuthorized.estimateGas(2, pkF, msg, sigF); const gCheckMldsa = await fresh.checkAuthorized.estimateGas(3, pkM, msg, sigM); const gAuth = await fresh.authorize.estimateGas(2, pkF, msg, sigF); const authRc = await (await fresh.authorize(2, pkF, msg, sigF)).wait(); const gSetDefault = await fresh.setDefaultAlgorithm.estimateGas(3); const gSetPref = await fresh.connect(other).setPreferredAlgorithm.estimateGas(3); console.log(" gas — deploy :", deployRc.gasUsed.toString()); console.log(" gas — checkAuthorized(F1024):", gCheckFalcon.toString()); console.log(" gas — checkAuthorized(MLDSA):", gCheckMldsa.toString()); console.log(" gas — authorize (F1024) :", gAuth.toString(), "(actual", authRc.gasUsed.toString() + ")"); console.log(" gas — setDefaultAlgorithm :", gSetDefault.toString()); console.log(" gas — setPreferredAlgorithm :", gSetPref.toString()); expect(deployRc.gasUsed).to.be.greaterThan(0n); }); }); });