aere-contracts/test/AerePQStarkVerifier.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

133 lines
6.6 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
// ---------------------------------------------------------------------------
// AerePQStarkVerifier - ISP1Verifier-compatible adapter for the native PQ STARK
// precompile at 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify, no BN254 wrap).
//
// HONEST SCOPE: the precompile at 0x0AE8 is a REFERENCE SKELETON that fail-closes
// (returns EMPTY for every input) and is NOT deployed on the hardhat EVM at all. So
// on this test EVM a staticcall to 0x0AE8 hits an empty account and returns empty ->
// the adapter must REVERT on verifyProof and return false on isValidProof for every
// input. That is exactly the safety property we want to prove: a consumer swapped to
// this adapter DENIES all proofs (never a false accept) until the precompile core is
// ported, externally audited, and founder-activated.
//
// This test also demonstrates the ONE-LINE consumer swap: it deploys the REAL
// AereEVMValidityV2 consumer with SP1_VERIFIER pointed at the PQ adapter (instead of
// the BN254 gateway) and shows the consumer fail-closes end-to-end.
// ---------------------------------------------------------------------------
const PQ_STARK_ADDR = ethers.getAddress("0x0000000000000000000000000000000000000ae8");
const SUCCESS_WORD = "0x" + "00".repeat(31) + "01";
describe("AerePQStarkVerifier (PQ STARK precompile adapter @ 0x0AE8)", function () {
let adapter;
beforeEach(async function () {
const F = await ethers.getContractFactory("AerePQStarkVerifier");
adapter = await F.deploy(1); // configId 1 = SP1 inner/shrink
await adapter.waitForDeployment();
});
it("targets precompile 0x0AE8 and reports its config/version", async function () {
expect(await adapter.CONFIG_ID()).to.equal(1);
expect(await adapter.VERSION()).to.equal("pq-stark-v1(reference-skeleton)");
// domain-separated tag, distinct from any BN254 SP1 VERIFIER_HASH
expect(await adapter.VERIFIER_HASH()).to.equal(
ethers.keccak256(ethers.toUtf8Bytes("AerePQStarkVerifier.v1"))
);
});
it("rejects configId outside {1,2}", async function () {
const F = await ethers.getContractFactory("AerePQStarkVerifier");
await expect(F.deploy(0)).to.be.reverted;
await expect(F.deploy(3)).to.be.reverted;
const two = await F.deploy(2);
await two.waitForDeployment();
expect(await two.CONFIG_ID()).to.equal(2);
});
it("FAIL-CLOSED: verifyProof reverts when the precompile is absent/empty (no false accept)", async function () {
const vkey = ethers.hexlify(ethers.randomBytes(32));
const publicValues = ethers.hexlify(ethers.randomBytes(96));
const proof = ethers.hexlify(ethers.randomBytes(512));
await expect(
adapter.verifyProof(vkey, publicValues, proof)
).to.be.revertedWithCustomError(adapter, "PQProofInvalid");
});
it("FAIL-CLOSED: verifyProof reverts even on empty public values / empty proof", async function () {
const vkey = ethers.ZeroHash;
await expect(
adapter.verifyProof(vkey, "0x", "0x")
).to.be.revertedWithCustomError(adapter, "PQProofInvalid");
});
it("FAIL-CLOSED: isValidProof returns false (never true) for arbitrary input", async function () {
const vkey = ethers.hexlify(ethers.randomBytes(32));
const publicValues = ethers.hexlify(ethers.randomBytes(288));
const proof = ethers.hexlify(ethers.randomBytes(1024));
expect(await adapter.isValidProof(vkey, publicValues, proof)).to.equal(false);
});
it("would ACCEPT only on the exact 32-byte success word (mocked precompile via setCode)", async function () {
// Prove the accept path is real: inject a tiny mock at 0x0AE8 that returns 0x..01,
// then the SAME adapter call succeeds. This shows the adapter is a faithful gate -
// it is the *precompile skeleton*, not the adapter, that currently denies everything.
// A contract that unconditionally returns the 32-byte success word:
// PUSH1 0x01 PUSH1 0x00 MSTORE (mem[0..32] = 0x..01)
// PUSH1 0x20 PUSH1 0x00 RETURN
const runtime = "0x600160005260206000f3";
await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, runtime]);
try {
const vkey = ethers.hexlify(ethers.randomBytes(32));
// staticcall to a mock returns success word -> verifyProof does NOT revert
await adapter.verifyProof.staticCall(vkey, "0x", "0x");
expect(await adapter.isValidProof(vkey, "0x", "0x")).to.equal(true);
} finally {
await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, "0x"]);
}
});
it("rejects a mock precompile that returns the WRONG word (not 0x..01)", async function () {
// mem[0..32] = 0x..02 then return 32 bytes
const runtime = "0x600260005260206000f3";
await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, runtime]);
try {
const vkey = ethers.hexlify(ethers.randomBytes(32));
expect(await adapter.isValidProof(vkey, "0x", "0x")).to.equal(false);
} finally {
await ethers.provider.send("hardhat_setCode", [PQ_STARK_ADDR, "0x"]);
}
});
// -------------------------------------------------------------------------
// ONE-LINE CONSUMER SWAP: AereEVMValidityV2 with SP1_VERIFIER = PQ adapter.
// -------------------------------------------------------------------------
describe("one-line swap into a real consumer (AereEVMValidityV2)", function () {
it("deploys AereEVMValidityV2 against the PQ adapter and fail-closes end-to-end", async function () {
const vkey = ethers.hexlify(ethers.randomBytes(32));
const V = await ethers.getContractFactory("AereEVMValidityV2");
// THE SWAP: first constructor arg is SP1_VERIFIER. Passing the PQ adapter instead
// of the BN254 gateway 0x9ca479... is the entire change. No consumer code edited.
const consumer = await V.deploy(await adapter.getAddress(), vkey);
await consumer.waitForDeployment();
expect(await consumer.SP1_VERIFIER()).to.equal(await adapter.getAddress());
expect(await consumer.PROGRAM_VKEY()).to.equal(vkey);
// 288-byte public values (9 x 32) the V2 consumer expects.
const abi = ethers.AbiCoder.defaultAbiCoder();
const publicValues = abi.encode(
["uint256", "uint256", "bytes32", "bytes32", "bytes32", "bytes32", "bytes32", "uint256", "uint256"],
[2800, 12345, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, ethers.ZeroHash, 0, 0]
);
const proof = ethers.hexlify(ethers.randomBytes(512));
// Precompile absent -> adapter reverts -> consumer.verify() reverts. Fail-closed.
await expect(consumer.verify(publicValues, proof)).to.be.reverted;
});
});
});