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.
440 lines
20 KiB
JavaScript
440 lines
20 KiB
JavaScript
const { expect } = require("chai");
|
|
const { ethers, network } = require("hardhat");
|
|
const kat = require("./falcon512_kat0.json");
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// AerePQCMessageVerifier: on-chain PQC message verification for cross-chain
|
|
// bridging / oracle attestations, on the LIVE mainnet precompiles.
|
|
//
|
|
// Two precompile backends are installed at the precompile addresses via
|
|
// hardhat_setCode (the Hardhat EVM has no PQC precompile):
|
|
//
|
|
// * Falcon512PrecompileShim at 0x0AE1 - delegates to AereFalcon512Verifier, the
|
|
// KAT-proven pure-Solidity Falcon-512 verifier. Used for the GENUINE accept +
|
|
// tamper-reject over the OFFICIAL NIST Falcon-512 KAT vector, so a positive here
|
|
// is a real Falcon-512 signature verified end to end through the contract's real
|
|
// precompile-calling path (input = pk || sm, raw 32-byte return).
|
|
//
|
|
// * MockPQCPrecompile at 0x0AE1 / 0x0AE3 - a faithful mock (parses the precompiles'
|
|
// SPEC offsets, accepts iff commitment == keccak256(pk || message)). Used for the
|
|
// signer-bound / domain-separation / rotation / consumer tests, where genuine
|
|
// signatures must be minted for arbitrary domain-separated digests that no offline
|
|
// signer is available to sign. The real wire encoding is proven by the KAT path
|
|
// above and (separately) against the live mainnet precompile via eth_call.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const AE1 = ethers.getAddress("0x0000000000000000000000000000000000000ae1"); // Falcon-512
|
|
const AE3 = ethers.getAddress("0x0000000000000000000000000000000000000ae3"); // ML-DSA-44
|
|
|
|
const SCHEME_FALCON512 = 1;
|
|
const SCHEME_MLDSA44 = 3;
|
|
|
|
const MESSAGE_DOMAIN = ethers.keccak256(ethers.toUtf8Bytes("AerePQCMessageVerifier.v1.message"));
|
|
const ACTION_DOMAIN = ethers.keccak256(ethers.toUtf8Bytes("AerePQCGatedAction.v1.action"));
|
|
|
|
// Deterministic seeded RNG (keccak hash-chain) so the tests are fully reproducible.
|
|
function makeRng(seedText) {
|
|
let seed = ethers.keccak256(ethers.toUtf8Bytes(seedText));
|
|
return {
|
|
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 falconPubKey(rng) {
|
|
const raw = rng.bytes(897);
|
|
raw[0] = 0x09; // Falcon-512 header
|
|
return ethers.hexlify(raw);
|
|
}
|
|
|
|
function mldsaPubKey(rng) {
|
|
return ethers.hexlify(rng.bytes(1312));
|
|
}
|
|
|
|
// The commitment the mock treats as a genuine signature by `pubKey` over `message`.
|
|
function commitmentFor(pubKey, message) {
|
|
return ethers.keccak256(ethers.concat([pubKey, message]));
|
|
}
|
|
|
|
// Genuine Falcon-512 envelope for the mock: nonce(40) || esig ; esig = 0x29 || commitment(32).
|
|
function falconMockEnvelope(pubKey, message, rng) {
|
|
const commitment = commitmentFor(pubKey, message);
|
|
const nonce = rng.bytes(40);
|
|
const esig = ethers.concat([new Uint8Array([0x29]), commitment]);
|
|
return ethers.hexlify(ethers.concat([nonce, esig]));
|
|
}
|
|
|
|
// Genuine ML-DSA-44 envelope for the mock: 2420-byte sig with commitment in the first 32 bytes.
|
|
function mldsaMockEnvelope(pubKey, message) {
|
|
const sig = new Uint8Array(2420);
|
|
sig.set(ethers.getBytes(commitmentFor(pubKey, message)), 0);
|
|
return ethers.hexlify(sig);
|
|
}
|
|
|
|
// Reproduce signerMessageDigest for an ARBITRARY chainId (to test cross-chain replay).
|
|
function foreignSignerDigest(chainId, mvAddr, signerId, epoch, domainTag, payload) {
|
|
const enc = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["bytes32", "uint256", "address", "uint256", "uint32", "bytes32", "bytes"],
|
|
[MESSAGE_DOMAIN, chainId, mvAddr, signerId, epoch, domainTag, payload]
|
|
);
|
|
return ethers.keccak256(enc);
|
|
}
|
|
|
|
async function setCodeFrom(factoryName, addr, ...ctorArgs) {
|
|
const F = await ethers.getContractFactory(factoryName);
|
|
const c = await F.deploy(...ctorArgs);
|
|
await c.waitForDeployment();
|
|
const code = await ethers.provider.getCode(await c.getAddress());
|
|
await network.provider.send("hardhat_setCode", [addr, code]);
|
|
}
|
|
|
|
async function deployVerifier() {
|
|
const F = await ethers.getContractFactory("AerePQCMessageVerifier");
|
|
const mv = await F.deploy();
|
|
await mv.waitForDeployment();
|
|
return mv;
|
|
}
|
|
|
|
async function registerSigner(mv, scheme, pubKey) {
|
|
const tx = await mv.registerSigner(scheme, pubKey);
|
|
const rc = await tx.wait();
|
|
const log = rc.logs.map((l) => mv.interface.parseLog(l)).find((p) => p && p.name === "SignerRegistered");
|
|
return Number(log.args.signerId);
|
|
}
|
|
|
|
describe("AerePQCMessageVerifier", function () {
|
|
// =========================================================================
|
|
// 1. Signer registry: register / rotate / revoke / transfer
|
|
// =========================================================================
|
|
describe("signer registry", function () {
|
|
let mv;
|
|
beforeEach(async function () {
|
|
mv = await deployVerifier();
|
|
});
|
|
|
|
it("registers well-formed keys with sequential ids, epoch 0, ACTIVE", async function () {
|
|
const rng = makeRng("reg");
|
|
const [a] = await ethers.getSigners();
|
|
|
|
const id0 = await registerSigner(mv, SCHEME_FALCON512, falconPubKey(rng));
|
|
const id1 = await registerSigner(mv, SCHEME_MLDSA44, mldsaPubKey(rng));
|
|
expect(id0).to.equal(0);
|
|
expect(id1).to.equal(1);
|
|
expect(await mv.signerCount()).to.equal(2n);
|
|
|
|
const s0 = await mv.getSigner(0);
|
|
expect(s0.owner).to.equal(a.address);
|
|
expect(Number(s0.scheme)).to.equal(SCHEME_FALCON512);
|
|
expect(Number(s0.status)).to.equal(1); // ACTIVE
|
|
expect(Number(s0.epoch)).to.equal(0);
|
|
expect(await mv.isActiveSigner(0)).to.equal(true);
|
|
expect(Number(await mv.epochOf(1))).to.equal(0);
|
|
});
|
|
|
|
it("rejects an unknown scheme and malformed keys", async function () {
|
|
await expect(mv.registerSigner(5, "0x" + "00".repeat(897))).to.be.revertedWithCustomError(mv, "InvalidScheme");
|
|
// Falcon-512 wrong length
|
|
await expect(mv.registerSigner(1, "0x09" + "00".repeat(895))).to.be.revertedWithCustomError(mv, "InvalidPubKey");
|
|
// Falcon-512 correct length, wrong header
|
|
await expect(mv.registerSigner(1, "0x0a" + "00".repeat(896))).to.be.revertedWithCustomError(mv, "InvalidPubKey");
|
|
// ML-DSA-44 wrong length
|
|
await expect(mv.registerSigner(3, "0x" + "00".repeat(1311))).to.be.revertedWithCustomError(mv, "InvalidPubKey");
|
|
});
|
|
|
|
it("rotates in place: owner-only, epoch advances, key/scheme update", async function () {
|
|
const rng = makeRng("rot");
|
|
const [owner, other] = await ethers.getSigners();
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, falconPubKey(rng));
|
|
|
|
// A non-owner cannot rotate.
|
|
await expect(
|
|
mv.connect(other).rotateSigner(id, SCHEME_FALCON512, falconPubKey(rng))
|
|
).to.be.revertedWithCustomError(mv, "NotSignerOwner");
|
|
|
|
const newKey = mldsaPubKey(rng); // rotate to a different scheme too
|
|
await expect(mv.connect(owner).rotateSigner(id, SCHEME_MLDSA44, newKey)).to.emit(mv, "SignerRotated");
|
|
const s = await mv.getSigner(id);
|
|
expect(Number(s.epoch)).to.equal(1);
|
|
expect(Number(s.scheme)).to.equal(SCHEME_MLDSA44);
|
|
expect(s.pubKey).to.equal(newKey);
|
|
});
|
|
|
|
it("revokes (owner-only, terminal) and blocks rotation afterwards", async function () {
|
|
const rng = makeRng("rev");
|
|
const [owner, other] = await ethers.getSigners();
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, falconPubKey(rng));
|
|
|
|
await expect(mv.connect(other).revokeSigner(id)).to.be.revertedWithCustomError(mv, "NotSignerOwner");
|
|
await mv.connect(owner).revokeSigner(id);
|
|
expect(await mv.isActiveSigner(id)).to.equal(false);
|
|
expect(Number(await mv.statusOf(id))).to.equal(2); // REVOKED
|
|
|
|
await expect(mv.connect(owner).rotateSigner(id, SCHEME_FALCON512, falconPubKey(rng)))
|
|
.to.be.revertedWithCustomError(mv, "SignerNotActive");
|
|
});
|
|
|
|
it("transfers signer ownership", async function () {
|
|
const rng = makeRng("xfer");
|
|
const [owner, next] = await ethers.getSigners();
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, falconPubKey(rng));
|
|
|
|
await expect(mv.connect(owner).transferSignerOwner(id, ethers.ZeroAddress))
|
|
.to.be.revertedWithCustomError(mv, "ZeroAddress");
|
|
await mv.connect(owner).transferSignerOwner(id, next.address);
|
|
expect((await mv.getSigner(id)).owner).to.equal(next.address);
|
|
// Old owner can no longer rotate; new owner can.
|
|
await expect(mv.connect(owner).rotateSigner(id, SCHEME_FALCON512, falconPubKey(rng)))
|
|
.to.be.revertedWithCustomError(mv, "NotSignerOwner");
|
|
await expect(mv.connect(next).rotateSigner(id, SCHEME_FALCON512, falconPubKey(rng))).to.emit(mv, "SignerRotated");
|
|
});
|
|
|
|
it("reverts on unknown signerId views", async function () {
|
|
await expect(mv.getSigner(0)).to.be.revertedWithCustomError(mv, "UnknownSigner");
|
|
expect(Number(await mv.statusOf(0))).to.equal(0); // STATUS_NONE, does not revert
|
|
expect(await mv.isActiveSigner(0)).to.equal(false);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// 2. GENUINE Falcon-512 accept via the KAT-proven verifier path (real crypto)
|
|
// =========================================================================
|
|
describe("genuine Falcon-512 accept (KAT-proven verifier path)", function () {
|
|
let mv;
|
|
|
|
before(async function () {
|
|
// Deploy the real KAT-proven verifier, then install a shim over it at 0x0AE1.
|
|
const V = await ethers.getContractFactory("AereFalcon512Verifier");
|
|
const verifier = await V.deploy();
|
|
await verifier.waitForDeployment();
|
|
await setCodeFrom("Falcon512PrecompileShim", AE1, await verifier.getAddress());
|
|
});
|
|
|
|
beforeEach(async function () {
|
|
mv = await deployVerifier();
|
|
});
|
|
|
|
// Envelope handed to verifyMessage: nonce(40) || esig ; esig = 0x29 || compsig.
|
|
function katEnvelope() {
|
|
return ethers.hexlify(ethers.concat([kat.nonce, new Uint8Array([0x29]), kat.compsig]));
|
|
}
|
|
|
|
it("verifyMessage ACCEPTS the official NIST Falcon-512 KAT signature", async function () {
|
|
const ok = await mv.verifyMessage(SCHEME_FALCON512, kat.pk, kat.msg, katEnvelope());
|
|
expect(ok).to.equal(true);
|
|
});
|
|
|
|
it("REJECTS a tampered signature (flip one byte of the compressed sig)", async function () {
|
|
const env = ethers.getBytes(katEnvelope());
|
|
env[100] ^= 0x01; // deep inside the compressed signature
|
|
const ok = await mv.verifyMessage(SCHEME_FALCON512, kat.pk, kat.msg, ethers.hexlify(env));
|
|
expect(ok).to.equal(false);
|
|
});
|
|
|
|
it("REJECTS a tampered message (flip one byte of the message)", async function () {
|
|
const msg = ethers.getBytes(kat.msg);
|
|
msg[0] ^= 0x01;
|
|
const ok = await mv.verifyMessage(SCHEME_FALCON512, kat.pk, ethers.hexlify(msg), katEnvelope());
|
|
expect(ok).to.equal(false);
|
|
});
|
|
|
|
it("REJECTS the genuine signature under the WRONG public key", async function () {
|
|
const ok = await mv.verifyMessage(SCHEME_FALCON512, kat.pk1, kat.msg, katEnvelope());
|
|
expect(ok).to.equal(false);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// 3. Domain separation, signer-bound verify, rotation, consumer gate (mock)
|
|
// =========================================================================
|
|
describe("signer-bound verification, domain separation, rotation, consumer", function () {
|
|
let mv, falconMockCode, mldsaMockCode;
|
|
|
|
before(async function () {
|
|
const M = await ethers.getContractFactory("MockPQCPrecompile");
|
|
const f = await M.deploy(1);
|
|
await f.waitForDeployment();
|
|
falconMockCode = await ethers.provider.getCode(await f.getAddress());
|
|
const d = await M.deploy(3);
|
|
await d.waitForDeployment();
|
|
mldsaMockCode = await ethers.provider.getCode(await d.getAddress());
|
|
});
|
|
|
|
beforeEach(async function () {
|
|
await network.provider.send("hardhat_setCode", [AE1, falconMockCode]);
|
|
await network.provider.send("hardhat_setCode", [AE3, mldsaMockCode]);
|
|
mv = await deployVerifier();
|
|
});
|
|
|
|
it("verifyBySigner ACCEPTS a genuine signer signature (Falcon-512 and ML-DSA-44)", async function () {
|
|
const rng = makeRng("bysigner");
|
|
const domain = ethers.keccak256(ethers.toUtf8Bytes("bridge-route-7"));
|
|
const payload = ethers.toUtf8Bytes("cross-chain-packet-A");
|
|
|
|
// Falcon-512 signer
|
|
const fpk = falconPubKey(rng);
|
|
const fid = await registerSigner(mv, SCHEME_FALCON512, fpk);
|
|
const fdigest = await mv.signerMessageDigest(fid, domain, payload);
|
|
const fsig = falconMockEnvelope(fpk, fdigest, rng);
|
|
expect(await mv.verifyBySigner(fid, domain, payload, fsig)).to.equal(true);
|
|
|
|
// ML-DSA-44 signer
|
|
const dpk = mldsaPubKey(rng);
|
|
const did = await registerSigner(mv, SCHEME_MLDSA44, dpk);
|
|
const ddigest = await mv.signerMessageDigest(did, domain, payload);
|
|
const dsig = mldsaMockEnvelope(dpk, ddigest);
|
|
expect(await mv.verifyBySigner(did, domain, payload, dsig)).to.equal(true);
|
|
});
|
|
|
|
it("verifyBySigner returns false for unknown / revoked signer", async function () {
|
|
const rng = makeRng("revoked");
|
|
const domain = ethers.keccak256(ethers.toUtf8Bytes("d"));
|
|
const payload = ethers.toUtf8Bytes("p");
|
|
expect(await mv.verifyBySigner(99, domain, payload, "0x" + "00".repeat(73))).to.equal(false);
|
|
|
|
const pk = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk);
|
|
const digest = await mv.signerMessageDigest(id, domain, payload);
|
|
const sig = falconMockEnvelope(pk, digest, rng);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig)).to.equal(true);
|
|
await mv.revokeSigner(id);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig)).to.equal(false);
|
|
});
|
|
|
|
it("DOMAIN SEPARATION: a signature for one domain tag is invalid under another", async function () {
|
|
const rng = makeRng("domainsep");
|
|
const domainA = ethers.keccak256(ethers.toUtf8Bytes("oracle-feed-ETHUSD"));
|
|
const domainB = ethers.keccak256(ethers.toUtf8Bytes("oracle-feed-BTCUSD"));
|
|
const payload = ethers.toUtf8Bytes("price=123456");
|
|
|
|
const pk = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk);
|
|
const digestA = await mv.signerMessageDigest(id, domainA, payload);
|
|
const sigA = falconMockEnvelope(pk, digestA, rng);
|
|
|
|
expect(await mv.verifyBySigner(id, domainA, payload, sigA)).to.equal(true);
|
|
// Same key, same payload, DIFFERENT domain -> different digest -> rejected.
|
|
expect(await mv.verifyBySigner(id, domainB, payload, sigA)).to.equal(false);
|
|
});
|
|
|
|
it("CROSS-CHAIN SEPARATION: a signature bound to a foreign chainId is invalid here", async function () {
|
|
const rng = makeRng("crosschain");
|
|
const domain = ethers.keccak256(ethers.toUtf8Bytes("bridge"));
|
|
const payload = ethers.toUtf8Bytes("withdraw-1000");
|
|
const pk = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk);
|
|
const mvAddr = await mv.getAddress();
|
|
|
|
const localChainId = (await ethers.provider.getNetwork()).chainId; // 31337
|
|
const foreignChainId = 1n; // Ethereum mainnet
|
|
|
|
// A signature genuine for the SAME (domain, payload) but bound to chainId=1.
|
|
const foreignDigest = foreignSignerDigest(foreignChainId, mvAddr, id, 0, domain, payload);
|
|
const foreignSig = falconMockEnvelope(pk, foreignDigest, rng);
|
|
// On THIS chain (31337) the digest differs -> the foreign-bound signature is rejected.
|
|
expect(await mv.verifyBySigner(id, domain, payload, foreignSig)).to.equal(false);
|
|
|
|
// Sanity: the same envelope built for the local chainId verifies.
|
|
const localDigest = foreignSignerDigest(localChainId, mvAddr, id, 0, domain, payload);
|
|
expect(localDigest).to.equal(await mv.signerMessageDigest(id, domain, payload));
|
|
const localSig = falconMockEnvelope(pk, localDigest, rng);
|
|
expect(await mv.verifyBySigner(id, domain, payload, localSig)).to.equal(true);
|
|
});
|
|
|
|
it("KEY ROTATION: a pre-rotation signature is rejected; a post-rotation one is accepted", async function () {
|
|
const rng = makeRng("rotate-verify");
|
|
const domain = ethers.keccak256(ethers.toUtf8Bytes("route"));
|
|
const payload = ethers.toUtf8Bytes("msg");
|
|
|
|
const pk0 = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk0);
|
|
|
|
// Genuine under epoch 0 / key0.
|
|
const digest0 = await mv.signerMessageDigest(id, domain, payload);
|
|
const sig0 = falconMockEnvelope(pk0, digest0, rng);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig0)).to.equal(true);
|
|
|
|
// Rotate to key1 (epoch 1). The old signature must no longer verify: the digest now binds
|
|
// epoch 1 AND the current key is key1, so both the message and the key have changed.
|
|
const pk1 = falconPubKey(rng);
|
|
await mv.rotateSigner(id, SCHEME_FALCON512, pk1);
|
|
expect(Number(await mv.epochOf(id))).to.equal(1);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig0)).to.equal(false);
|
|
|
|
// A fresh signature under key1 / epoch 1 verifies.
|
|
const digest1 = await mv.signerMessageDigest(id, domain, payload);
|
|
expect(digest1).to.not.equal(digest0);
|
|
const sig1 = falconMockEnvelope(pk1, digest1, rng);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig1)).to.equal(true);
|
|
});
|
|
|
|
it("CONSUMER GATE: executes once on a valid PQC signature, rejects tampered and replays", async function () {
|
|
const rng = makeRng("consumer");
|
|
const G = await ethers.getContractFactory("AerePQCGatedAction");
|
|
const gate = await G.deploy(await mv.getAddress());
|
|
await gate.waitForDeployment();
|
|
|
|
const pk = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk);
|
|
const payload = ethers.toUtf8Bytes("bridge-deliver:0xdeadbeef");
|
|
|
|
// Sign the consumer's ACTION_DOMAIN + payload digest.
|
|
const digest = await mv.signerMessageDigest(id, ACTION_DOMAIN, payload);
|
|
const sig = falconMockEnvelope(pk, digest, rng);
|
|
|
|
// Tampered signature -> gate reverts, nothing recorded.
|
|
const bad = ethers.getBytes(sig);
|
|
bad[41] ^= 0x01; // flip inside the commitment
|
|
await expect(gate.executeIfAttested(id, payload, ethers.hexlify(bad)))
|
|
.to.be.revertedWithCustomError(gate, "NotAttested");
|
|
expect(await gate.actionCount()).to.equal(0n);
|
|
|
|
// Genuine signature -> action executes exactly once.
|
|
await expect(gate.executeIfAttested(id, payload, sig)).to.emit(gate, "ActionExecuted");
|
|
expect(await gate.actionCount()).to.equal(1n);
|
|
const actionId = await gate.actionId(id, payload);
|
|
expect(await gate.consumed(actionId)).to.equal(true);
|
|
|
|
// Replay of the same (signer, payload) -> rejected.
|
|
await expect(gate.executeIfAttested(id, payload, sig))
|
|
.to.be.revertedWithCustomError(gate, "AlreadyConsumed");
|
|
expect(await gate.actionCount()).to.equal(1n);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// 4. Fail-closed: empty/stale precompile return is INVALID
|
|
// =========================================================================
|
|
describe("fail-closed", function () {
|
|
it("treats an empty precompile return (pre-fork / stale) as invalid", async function () {
|
|
const rng = makeRng("failclosed");
|
|
// Install the faithful Falcon mock, register + build a genuine sig, then wipe the precompile.
|
|
const M = await ethers.getContractFactory("MockPQCPrecompile");
|
|
const f = await M.deploy(1);
|
|
await f.waitForDeployment();
|
|
await network.provider.send("hardhat_setCode", [AE1, await ethers.provider.getCode(await f.getAddress())]);
|
|
|
|
const mv = await deployVerifier();
|
|
const domain = ethers.keccak256(ethers.toUtf8Bytes("d"));
|
|
const payload = ethers.toUtf8Bytes("p");
|
|
const pk = falconPubKey(rng);
|
|
const id = await registerSigner(mv, SCHEME_FALCON512, pk);
|
|
const digest = await mv.signerMessageDigest(id, domain, payload);
|
|
const sig = falconMockEnvelope(pk, digest, rng);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig)).to.equal(true);
|
|
|
|
// An address with no precompile returns success + empty returndata -> must be INVALID.
|
|
await network.provider.send("hardhat_setCode", [AE1, "0x"]);
|
|
expect(await mv.verifyBySigner(id, domain, payload, sig)).to.equal(false);
|
|
expect(await mv.verifyMessage(SCHEME_FALCON512, pk, digest, sig)).to.equal(false);
|
|
});
|
|
});
|
|
});
|