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.
129 lines
6.1 KiB
JavaScript
129 lines
6.1 KiB
JavaScript
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
describe("AereShutterMempool", () => {
|
|
let foundation, keyper, user, outsider;
|
|
let m;
|
|
const REVEAL_DELAY = 2;
|
|
const KEYPER_SIZE = 13;
|
|
const KEYPER_THRESHOLD = 9;
|
|
const COMMITTEE_ROOT = ethers.keccak256(ethers.toUtf8Bytes("committee-v1"));
|
|
const AGG_PUBKEY = ethers.keccak256(ethers.toUtf8Bytes("agg-pubkey-v1"));
|
|
|
|
beforeEach(async () => {
|
|
[foundation, keyper, user, outsider] = await ethers.getSigners();
|
|
const F = await ethers.getContractFactory("AereShutterMempool");
|
|
m = await F.deploy(foundation.address, REVEAL_DELAY, KEYPER_SIZE, KEYPER_THRESHOLD);
|
|
await m.waitForDeployment();
|
|
});
|
|
|
|
async function mineBlocks(n) {
|
|
for (let i = 0; i < n; i++) await ethers.provider.send("evm_mine", []);
|
|
}
|
|
|
|
it("only Foundation opens epochs; cannot reopen the same epoch", async () => {
|
|
await expect(
|
|
m.connect(user).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY)
|
|
).to.be.revertedWithCustomError(m, "NotFoundation");
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
expect((await m.epochs(1n)).exists).to.equal(true);
|
|
await expect(
|
|
m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY)
|
|
).to.be.revertedWithCustomError(m, "EpochClosedAlready");
|
|
});
|
|
|
|
it("opening a new epoch closes the previous one", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
await m.connect(foundation).openEpoch(2n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
expect((await m.epochs(1n)).endBlock).to.be.gt(0n);
|
|
expect((await m.epochs(2n)).endBlock).to.equal(0n);
|
|
expect(await m.currentEpochId()).to.equal(2n);
|
|
});
|
|
|
|
it("commit requires open epoch; envelope is recorded", async () => {
|
|
const ch = ethers.keccak256(ethers.toUtf8Bytes("cipher-1"));
|
|
await expect(
|
|
m.connect(user).commit(ch, "ipfs://x")
|
|
).to.be.revertedWithCustomError(m, "EpochNotOpen");
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
const tx = await m.connect(user).commit(ch, "ipfs://x");
|
|
const rc = await tx.wait();
|
|
const ev = rc.logs.map(l => { try { return m.interface.parseLog(l); } catch { return null; } }).find(x => x?.name === 'Committed');
|
|
expect(ev).to.exist;
|
|
const env = await m.envelopeOf(ev.args.envelopeId);
|
|
expect(env.sender).to.equal(user.address);
|
|
expect(env.ciphertextHash).to.equal(ch);
|
|
expect(await m.envelopeCount(1n)).to.equal(1n);
|
|
});
|
|
|
|
it("releaseDecryptionKey rejected before REVEAL_DELAY; succeeds after", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
const kh = ethers.keccak256(ethers.toUtf8Bytes("key"));
|
|
await expect(
|
|
m.connect(foundation).releaseDecryptionKey(1n, kh, "ipfs://key")
|
|
).to.be.revertedWithCustomError(m, "RevealTooEarly");
|
|
await mineBlocks(REVEAL_DELAY);
|
|
await m.connect(foundation).releaseDecryptionKey(1n, kh, "ipfs://key");
|
|
expect(await m.isKeyReleased(1n)).to.equal(true);
|
|
});
|
|
|
|
it("markRevealed: Foundation-only, plaintext-bound, idempotent (audit fix HIGH #3)", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
const plaintext = ethers.toUtf8Bytes("hello-world");
|
|
const releasedKey = ethers.keccak256(ethers.toUtf8Bytes("k"));
|
|
const ch = ethers.keccak256(ethers.solidityPacked(["bytes", "bytes32"], [plaintext, releasedKey]));
|
|
const tx = await m.connect(user).commit(ch, "");
|
|
const rc = await tx.wait();
|
|
const ev = rc.logs.map(l => { try { return m.interface.parseLog(l); } catch { return null; } }).find(x => x?.name === 'Committed');
|
|
const id = ev.args.envelopeId;
|
|
|
|
// Non-Foundation can't markRevealed.
|
|
await expect(m.connect(outsider).markRevealed(id, plaintext)).to.be.revertedWithCustomError(m, "NotFoundation");
|
|
await mineBlocks(REVEAL_DELAY);
|
|
// Key not yet released.
|
|
await expect(m.connect(foundation).markRevealed(id, plaintext)).to.be.revertedWithCustomError(m, "KeyNotReleased");
|
|
await m.connect(foundation).releaseDecryptionKey(1n, releasedKey, "");
|
|
// Plaintext-mismatch rejected.
|
|
await expect(m.connect(foundation).markRevealed(id, ethers.toUtf8Bytes("WRONG"))).to.be.revertedWithCustomError(m, "PlaintextMismatch");
|
|
// Correct plaintext succeeds.
|
|
await m.connect(foundation).markRevealed(id, plaintext);
|
|
expect(await m.isRevealed(id)).to.equal(true);
|
|
// Idempotent.
|
|
await expect(m.connect(foundation).markRevealed(id, plaintext)).to.be.revertedWithCustomError(m, "AlreadyRevealed");
|
|
});
|
|
|
|
it("unknown envelope reveal reverts", async () => {
|
|
const fake = ethers.keccak256(ethers.toUtf8Bytes("nonexistent"));
|
|
await expect(
|
|
m.connect(foundation).markRevealed(fake, "0x")
|
|
).to.be.revertedWithCustomError(m, "EnvelopeUnknown");
|
|
});
|
|
|
|
it("commit refuses to overwrite an existing envelope (audit fix HIGH #4)", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
const ch = ethers.keccak256(ethers.toUtf8Bytes("c1"));
|
|
await m.connect(user).commit(ch, "");
|
|
await expect(m.connect(user).commit(ch, "")).to.be.revertedWithCustomError(m, "EnvelopeAlreadyCommitted");
|
|
});
|
|
|
|
it("releaseDecryptionKey one-shot per epoch (audit fix HIGH #5)", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
await mineBlocks(REVEAL_DELAY);
|
|
const k = ethers.keccak256(ethers.toUtf8Bytes("k"));
|
|
await m.connect(foundation).releaseDecryptionKey(1n, k, "");
|
|
await expect(m.connect(foundation).releaseDecryptionKey(1n, k, "")).to.be.revertedWithCustomError(m, "KeyAlreadyReleased");
|
|
});
|
|
|
|
it("constructor requires revealDelayBlocks >= 1 (audit fix MED #31)", async () => {
|
|
const F = await ethers.getContractFactory("AereShutterMempool");
|
|
await expect(F.deploy(foundation.address, 0, KEYPER_SIZE, KEYPER_THRESHOLD)).to.be.revertedWith("reveal-delay-zero");
|
|
});
|
|
|
|
it("zero ciphertext hash rejected", async () => {
|
|
await m.connect(foundation).openEpoch(1n, COMMITTEE_ROOT, AGG_PUBKEY);
|
|
await expect(
|
|
m.connect(user).commit(ethers.ZeroHash, "")
|
|
).to.be.revertedWithCustomError(m, "EmptyCommitment");
|
|
});
|
|
});
|