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.
99 lines
4.2 KiB
JavaScript
99 lines
4.2 KiB
JavaScript
// AereDACommittee — unit tests (pure ECDSA M-of-N, runs fully local).
|
|
|
|
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
describe("AereDACommittee", function () {
|
|
let dac, admin, m1, m2, m3, outsider;
|
|
let members; // sorted ascending by address
|
|
|
|
const rollupId = ethers.id("rollup:demo");
|
|
const epoch = 7n;
|
|
const dataCommitment = ethers.id("batch-data-commitment");
|
|
|
|
beforeEach(async function () {
|
|
[admin, m1, m2, m3, outsider] = await ethers.getSigners();
|
|
// sort committee members ascending (attest requires ascending recovered addr)
|
|
members = [m1, m2, m3].sort((a, b) =>
|
|
a.address.toLowerCase() < b.address.toLowerCase() ? -1 : 1
|
|
);
|
|
const F = await ethers.getContractFactory("AereDACommittee");
|
|
dac = await F.deploy(admin.address, members.map((m) => m.address), 2);
|
|
await dac.waitForDeployment();
|
|
});
|
|
|
|
async function sign(wallet, rId = rollupId, ep = epoch, dc = dataCommitment) {
|
|
// members sign the raw inner hash via personal_sign; the contract wraps with
|
|
// the EIP-191 prefix in attestationDigest.
|
|
const inner = ethers.keccak256(
|
|
ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["string", "uint256", "address", "bytes32", "uint256", "bytes32"],
|
|
["AereDACommittee:attest", 31337n, await dac.getAddress(), rId, ep, dc]
|
|
)
|
|
);
|
|
return wallet.signMessage(ethers.getBytes(inner));
|
|
}
|
|
|
|
it("constructs with the committee and threshold", async function () {
|
|
expect(await dac.memberCount()).to.equal(3n);
|
|
expect(await dac.threshold()).to.equal(2n);
|
|
expect(await dac.isMember(members[0].address)).to.equal(true);
|
|
expect(await dac.isMember(outsider.address)).to.equal(false);
|
|
expect((await dac.getMembers()).length).to.equal(3);
|
|
});
|
|
|
|
it("records availability with M distinct member signatures", async function () {
|
|
const sigs = [await sign(members[0]), await sign(members[1])]; // ascending
|
|
const tx = await dac.attest(rollupId, epoch, dataCommitment, sigs);
|
|
await tx.wait();
|
|
expect(await dac.isAvailable(rollupId, epoch)).to.equal(true);
|
|
const [available, dc, , n] = await dac.getAttestation(rollupId, epoch);
|
|
expect(available).to.equal(true);
|
|
expect(dc).to.equal(dataCommitment);
|
|
expect(n).to.equal(2n);
|
|
});
|
|
|
|
it("rejects M-1 signatures", async function () {
|
|
const sigs = [await sign(members[0])];
|
|
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs))
|
|
.to.be.revertedWithCustomError(dac, "ThresholdNotMet")
|
|
.withArgs(1, 2);
|
|
expect(await dac.isAvailable(rollupId, epoch)).to.equal(false);
|
|
});
|
|
|
|
it("rejects a non-member signature", async function () {
|
|
const sigs = [await sign(members[0]), await sign(outsider)];
|
|
// ordering may or may not place outsider second; either way it must revert.
|
|
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs)).to.be.reverted;
|
|
expect(await dac.isAvailable(rollupId, epoch)).to.equal(false);
|
|
});
|
|
|
|
it("rejects duplicate / unsorted signatures", async function () {
|
|
const dup = await sign(members[0]);
|
|
await expect(dac.attest(rollupId, epoch, dataCommitment, [dup, dup]))
|
|
.to.be.revertedWithCustomError(dac, "UnsortedOrDuplicate");
|
|
});
|
|
|
|
it("rejects re-recording the same epoch", async function () {
|
|
const sigs = [await sign(members[0]), await sign(members[1])];
|
|
await dac.attest(rollupId, epoch, dataCommitment, sigs);
|
|
await expect(dac.attest(rollupId, epoch, dataCommitment, sigs))
|
|
.to.be.revertedWithCustomError(dac, "AlreadyRecorded");
|
|
});
|
|
|
|
it("admin governance cannot forge availability, and freeze locks it", async function () {
|
|
// no admin function can set availability; only attest can.
|
|
await dac.connect(admin).freeze();
|
|
expect(await dac.frozen()).to.equal(true);
|
|
await expect(dac.connect(admin).addMember(outsider.address)).to.be.revertedWithCustomError(dac, "IsFrozen");
|
|
// attest still works post-freeze
|
|
const sigs = [await sign(members[0]), await sign(members[1])];
|
|
await dac.attest(rollupId, epoch, dataCommitment, sigs);
|
|
expect(await dac.isAvailable(rollupId, epoch)).to.equal(true);
|
|
});
|
|
|
|
it("only admin can manage the committee", async function () {
|
|
await expect(dac.connect(outsider).setThreshold(1)).to.be.revertedWithCustomError(dac, "NotAdmin");
|
|
});
|
|
});
|