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.
100 lines
4.8 KiB
JavaScript
100 lines
4.8 KiB
JavaScript
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
describe("AereFaucetV2 — anonymous claim", () => {
|
|
let owner, signer, recipient, outsider;
|
|
let f;
|
|
|
|
beforeEach(async () => {
|
|
[owner, signer, recipient, outsider] = await ethers.getSigners();
|
|
const F = await ethers.getContractFactory("AereFaucetV2");
|
|
f = await F.deploy(signer.address);
|
|
await f.waitForDeployment();
|
|
// Seed 10 AERE.
|
|
await owner.sendTransaction({ to: await f.getAddress(), value: ethers.parseEther("10") });
|
|
});
|
|
|
|
async function ticket(commitment, recipientAddr, validUntil) {
|
|
const inner = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["string", "uint256", "address", "bytes32", "address", "uint64"],
|
|
["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commitment, recipientAddr, validUntil]
|
|
);
|
|
const innerHash = ethers.keccak256(inner);
|
|
return signer.signMessage(ethers.getBytes(innerHash));
|
|
}
|
|
|
|
it("regular claim works with cooldown", async () => {
|
|
const before = await ethers.provider.getBalance(outsider.address);
|
|
await f.connect(outsider).claim();
|
|
const after = await ethers.provider.getBalance(outsider.address);
|
|
expect(after).to.be.gt(before); // roughly + dripAmount - gas
|
|
await expect(f.connect(outsider).claim()).to.be.revertedWithCustomError(f, "InCooldown");
|
|
});
|
|
|
|
it("anonymous claim with valid ticket sends drip to recipient", async () => {
|
|
const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-1"));
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
|
|
const sig = await ticket(commit, recipient.address, validUntil);
|
|
|
|
const before = await ethers.provider.getBalance(recipient.address);
|
|
await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
|
|
const after = await ethers.provider.getBalance(recipient.address);
|
|
expect(after - before).to.equal(ethers.parseEther("0.05"));
|
|
expect(await f.spentCommitments(commit)).to.equal(true);
|
|
});
|
|
|
|
it("re-using a commitment reverts", async () => {
|
|
const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-2"));
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
|
|
const sig = await ticket(commit, recipient.address, validUntil);
|
|
await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
|
|
await expect(
|
|
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
|
|
).to.be.revertedWithCustomError(f, "CommitmentSpent");
|
|
});
|
|
|
|
it("expired ticket rejected", async () => {
|
|
const commit = ethers.keccak256(ethers.toUtf8Bytes("expired"));
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 5);
|
|
const sig = await ticket(commit, recipient.address, validUntil);
|
|
await ethers.provider.send("evm_increaseTime", [10]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
await expect(
|
|
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
|
|
).to.be.revertedWithCustomError(f, "TicketExpired");
|
|
});
|
|
|
|
it("ticket signed by wrong key rejected", async () => {
|
|
const commit = ethers.keccak256(ethers.toUtf8Bytes("wrong-key"));
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
|
|
// signed by `outsider` (not the SIGNER)
|
|
const inner = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
["string", "uint256", "address", "bytes32", "address", "uint64"],
|
|
["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commit, recipient.address, validUntil]
|
|
);
|
|
const innerHash = ethers.keccak256(inner);
|
|
const badSig = await outsider.signMessage(ethers.getBytes(innerHash));
|
|
await expect(
|
|
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, badSig)
|
|
).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
|
|
});
|
|
|
|
it("recipient bound in signed message — cannot redirect", async () => {
|
|
const commit = ethers.keccak256(ethers.toUtf8Bytes("redirect"));
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
|
|
const sig = await ticket(commit, recipient.address, validUntil);
|
|
// Try claiming for a different recipient with the same ticket.
|
|
await expect(
|
|
f.connect(outsider).claimAnonymous(commit, outsider.address, validUntil, sig)
|
|
).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
|
|
});
|
|
|
|
it("zero recipient + zero commitment rejected", async () => {
|
|
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
|
|
const sig = await ticket(ethers.ZeroHash, recipient.address, validUntil);
|
|
await expect(
|
|
f.connect(outsider).claimAnonymous(ethers.ZeroHash, recipient.address, validUntil, sig)
|
|
).to.be.revertedWithCustomError(f, "BadParams");
|
|
});
|
|
});
|