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.
116 lines
5.5 KiB
JavaScript
116 lines
5.5 KiB
JavaScript
// AUDIT VERIFICATION — Finding 13
|
|
// AERE402Facilitator replay key is consumed[agentId][nonce] (GLOBAL per agent,
|
|
// not per payee). Two providers following the documented "monotonic nonce per
|
|
// provider-agent pair" convention collide, and a valid settlement reverts.
|
|
// Uses the SAME real deploy stack as test/agentic.test.js. No sources modified.
|
|
|
|
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
const ONE = 10n ** 18n;
|
|
|
|
async function deployStack() {
|
|
const [deployer, operator, signer, payeeA, payeeB] = await ethers.getSigners();
|
|
|
|
const WAERE = await (await ethers.getContractFactory("contracts/WAERE.sol:WAERE")).deploy();
|
|
await WAERE.waitForDeployment();
|
|
|
|
const nonce = await ethers.provider.getTransactionCount(deployer.address);
|
|
const futureSaere = ethers.getCreateAddress({ from: deployer.address, nonce: nonce + 2 });
|
|
await WAERE.connect(deployer).deposit({ value: 1000n });
|
|
await WAERE.connect(deployer).approve(futureSaere, 1000n);
|
|
const sAERE = await (await ethers.getContractFactory("sAERE")).deploy(await WAERE.getAddress());
|
|
await sAERE.waitForDeployment();
|
|
|
|
const burnVault = await (await ethers.getContractFactory("AereFeeBurnVault")).deploy();
|
|
await burnVault.waitForDeployment();
|
|
const fakeRouter = await (await ethers.getContractFactory("WAERE")).deploy();
|
|
await fakeRouter.waitForDeployment();
|
|
|
|
const sink = await (await ethers.getContractFactory("AereSink")).deploy(
|
|
await WAERE.getAddress(),
|
|
await burnVault.getAddress(),
|
|
await sAERE.getAddress(),
|
|
await fakeRouter.getAddress(),
|
|
1500, 4000, 4500, 200, ethers.ZeroAddress
|
|
);
|
|
await sink.waitForDeployment();
|
|
|
|
const nonce2 = await ethers.provider.getTransactionCount(deployer.address);
|
|
const futureFacilitator = ethers.getCreateAddress({ from: deployer.address, nonce: nonce2 + 1 });
|
|
|
|
const agent = await (await ethers.getContractFactory("AereAgent")).deploy(futureFacilitator);
|
|
await agent.waitForDeployment();
|
|
|
|
const fac = await (await ethers.getContractFactory("AERE402Facilitator")).deploy(
|
|
await agent.getAddress(),
|
|
await sink.getAddress()
|
|
);
|
|
await fac.waitForDeployment();
|
|
expect((await fac.getAddress()).toLowerCase()).to.equal(futureFacilitator.toLowerCase());
|
|
|
|
return { deployer, operator, signer, payeeA, payeeB, WAERE, sink, agent, fac };
|
|
}
|
|
|
|
async function signAuth(signerWallet, fac, agentId, token, payee, amount, resourceId, nonce, deadline) {
|
|
const domain = {
|
|
name: "AERE402Facilitator",
|
|
version: "1",
|
|
chainId: (await ethers.provider.getNetwork()).chainId,
|
|
verifyingContract: await fac.getAddress(),
|
|
};
|
|
const types = {
|
|
PaymentAuth: [
|
|
{ name: "agentId", type: "uint256" },
|
|
{ name: "token", type: "address" },
|
|
{ name: "payee", type: "address" },
|
|
{ name: "amount", type: "uint256" },
|
|
{ name: "resourceId", type: "bytes32" },
|
|
{ name: "nonce", type: "uint256" },
|
|
{ name: "deadline", type: "uint256" },
|
|
],
|
|
};
|
|
const value = { agentId, token, payee, amount, resourceId, nonce, deadline };
|
|
return signerWallet.signTypedData(domain, types, value);
|
|
}
|
|
|
|
describe("AUDIT VERIFY — F13 AERE402Facilitator per-agent (not per-payee) nonce collision", function () {
|
|
it("CONFIRMED: two providers using the same monotonic nonce collide; provider B's valid settlement reverts", async () => {
|
|
const { operator, signer, payeeA, payeeB, WAERE, agent, fac } = await deployStack();
|
|
|
|
const tx = await agent.connect(operator).registerAgent(
|
|
signer.address, ethers.ZeroHash, ethers.ZeroHash, 10n * ONE, 0, 0, ""
|
|
);
|
|
const agentId = (await tx.wait()).logs.find(l => l.fragment?.name === "AgentRegistered").args.agentId;
|
|
|
|
// Fund the agent generously so balance is NOT the limiting factor.
|
|
await WAERE.connect(operator).deposit({ value: 100n * ONE });
|
|
await WAERE.connect(operator).approve(await agent.getAddress(), 100n * ONE);
|
|
await agent.connect(operator).topUp(agentId, await WAERE.getAddress(), 100n * ONE);
|
|
|
|
const amount = 2n * ONE;
|
|
const deadline = (await ethers.provider.getBlock("latest")).timestamp + 3600;
|
|
const token = await WAERE.getAddress();
|
|
|
|
// Provider A: agentId, nonce=1, payee = payeeA. (A's first invoice.)
|
|
const sigA = await signAuth(signer, fac, agentId, token, payeeA.address, amount, ethers.id("providerA:/gpu"), 1n, deadline);
|
|
await fac.connect(payeeA).settle(agentId, token, payeeA.address, amount, ethers.id("providerA:/gpu"), 1n, deadline, sigA);
|
|
expect(await fac.consumed(agentId, 1n)).to.equal(true);
|
|
|
|
// Provider B: a DIFFERENT, fully valid payment to payeeB, but with B's own
|
|
// monotonic nonce=1 (per the documented per-provider convention). The agent
|
|
// signed it and has ample balance — yet it reverts because the nonce key is
|
|
// global per agent, so A already consumed (agentId, 1).
|
|
const sigB = await signAuth(signer, fac, agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 1n, deadline);
|
|
await expect(
|
|
fac.connect(payeeB).settle(agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 1n, deadline, sigB)
|
|
).to.be.revertedWithCustomError(fac, "NonceAlreadyConsumed");
|
|
|
|
// Control: provider B succeeds only by picking a non-colliding nonce (proving
|
|
// it was the shared nonce namespace, not signature/balance, that blocked it).
|
|
const sigB2 = await signAuth(signer, fac, agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 2n, deadline);
|
|
await fac.connect(payeeB).settle(agentId, token, payeeB.address, amount, ethers.id("providerB:/api"), 2n, deadline, sigB2);
|
|
expect(await fac.consumed(agentId, 2n)).to.equal(true);
|
|
});
|
|
});
|