aere-contracts/test/shutter-mempool.test.js
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (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
Initial public release
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.
2026-07-20 01:02:37 +03:00

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");
});
});