// Hardhat tests for AereVectorStore (Aere VSM, verifiable semantic memory). // // Covers exactly the acceptance cases: // - commit a store version (with a mock-Falcon-512-verified signature via 0x0AE1) // - commit a second version (append-only history preserved) // - a tampered Falcon signature is rejected fail-closed (nothing recorded) // - a retrieval attestation against a valid version records; a missing version reverts // - an inclusion proof for a committed vector verifies; a non-member fails // - AERE402 paid-query rail: paidQuery settles through the real facilitator, and a priced // store's attestRetrieval is gated on the payment receipt (unpaid reverts fail-closed) // - classical (owner-signed) store commits + non-owner rejection // - createStore fail-closed on a malformed Falcon key / inconsistent price config // // Falcon is MOCKED here: Hardhat has no PQC precompile, so we install MockPQCPrecompile // (scheme 1) at 0x0AE1 via hardhat_setCode. The mock parses the input at the precompile's // SPEC offsets and accepts iff commitment == keccak256(pk || message). The REAL Falcon-512 // encoding is separately proven against the LIVE mainnet precompile (0x0AE1) elsewhere in the // repo (AerePQCAttestation). The embedding model + similarity search + vector data are // off-chain [MEASURE]; this test exercises only the on-chain provenance / attestation / pay path. // // Run: npx hardhat test test/aere-vector-store.test.js const { expect } = require("chai"); const { ethers, network } = require("hardhat"); const ONE = 10n ** 18n; const FALCON512 = ethers.getAddress("0x0000000000000000000000000000000000000ae1"); // --------------------------------------------------------------------------- // Falcon-512 mock-signature helpers (mirror AerePQCAttestation.test.js, scheme 1). // --------------------------------------------------------------------------- const PK_LEN = 897; const PK_HEADER = 0x09; const ESIG_HEADER = 0x29; function randBytes(n, salt) { // deterministic keccak hash-chain so the test is fully reproducible const out = new Uint8Array(n); let seed = ethers.keccak256(ethers.toUtf8Bytes("vsm-seed-" + salt)); let i = 0; while (i < n) { seed = ethers.keccak256(seed); const chunk = ethers.getBytes(seed); for (let j = 0; j < chunk.length && i < n; j++, i++) out[i] = chunk[j]; } return out; } function makeFalconPubKey(salt) { const raw = randBytes(PK_LEN, salt); raw[0] = PK_HEADER; // Falcon-512 requires the header byte return ethers.hexlify(raw); } function commitmentFor(pubKey, message) { return ethers.keccak256(ethers.concat([pubKey, message])); } // Falcon envelope handed to commit(): nonce(40) || esig ; esig = 0x29 || commitment(32). function falconEnvelope(commitment, salt) { const nonce = randBytes(40, "nonce-" + salt); const esig = ethers.concat([new Uint8Array([ESIG_HEADER]), ethers.getBytes(commitment)]); return ethers.hexlify(ethers.concat([nonce, esig])); } function genuineFalconSig(pubKey, message, salt) { return falconEnvelope(commitmentFor(pubKey, message), salt); } function tamperFalconSig(sigHex) { const b = ethers.getBytes(sigHex); b[41] ^= 0x01; // flip a byte inside the commitment region -> genuine forgery return ethers.hexlify(b); } // --------------------------------------------------------------------------- // Minimal OpenZeppelin-compatible Merkle tree (sorted-pair keccak256). // --------------------------------------------------------------------------- function hashPair(a, b) { return BigInt(a) < BigInt(b) ? ethers.keccak256(ethers.concat([a, b])) : ethers.keccak256(ethers.concat([b, a])); } function buildTree(leaves) { const layers = [leaves.slice()]; while (layers[layers.length - 1].length > 1) { const prev = layers[layers.length - 1]; const next = []; for (let i = 0; i < prev.length; i += 2) { if (i + 1 < prev.length) next.push(hashPair(prev[i], prev[i + 1])); else next.push(prev[i]); // odd node: promote } layers.push(next); } return layers; } function treeRoot(layers) { return layers[layers.length - 1][0]; } function treeProof(layers, index) { const proof = []; let idx = index; for (let l = 0; l < layers.length - 1; l++) { const layer = layers[l]; const pairIndex = idx ^ 1; if (pairIndex < layer.length) proof.push(layer[pairIndex]); idx = Math.floor(idx / 2); } return proof; } function leafOf(label) { return ethers.keccak256(ethers.toUtf8Bytes(label)); } // --------------------------------------------------------------------------- // EIP-712 AERE402 payment-auth signer (mirror agentic.test.js). // --------------------------------------------------------------------------- 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("AereVectorStore (Aere VSM)", function () { let vsm, mockCode; let deployer, owner, provider, operator, signer, consumer; before(async function () { // Capture MockPQCPrecompile (scheme 1) runtime bytecode for hardhat_setCode installs. const F = await ethers.getContractFactory("MockPQCPrecompile"); const mock = await F.deploy(1); await mock.waitForDeployment(); mockCode = await ethers.provider.getCode(await mock.getAddress()); }); beforeEach(async function () { [deployer, owner, provider, operator, signer, consumer] = await ethers.getSigners(); await network.provider.send("hardhat_setCode", [FALCON512, mockCode]); // The AERE402 rail: AereAgent registry + facilitator + a simple sink, referenced (not // redeployed) by the vector store. Facilitator address is pre-computed so the registry // can pin it immutably. const sink = await (await ethers.getContractFactory("MockSinkSimple")).deploy(); await sink.waitForDeployment(); const nonce0 = await ethers.provider.getTransactionCount(deployer.address); const futureFac = ethers.getCreateAddress({ from: deployer.address, nonce: nonce0 + 1 }); const agent = await (await ethers.getContractFactory("AereAgent")).deploy(futureFac); 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(futureFac.toLowerCase()); vsm = await (await ethers.getContractFactory("AereVectorStore")).deploy(await fac.getAddress()); await vsm.waitForDeployment(); // stash rail handles on the suite for the payment test this.agent = agent; this.fac = fac; }); // ------------------------------------------------------------------------- // 1 + 2: PQC commit v1, then v2 (append-only history preserved) // ------------------------------------------------------------------------- it("commits a PQC store version 1, then version 2, preserving history append-only", async function () { const storeId = ethers.id("agent-memory://claude/user-42"); const pk = makeFalconPubKey("store-a"); await vsm.connect(owner).createStore(storeId, pk, ethers.ZeroAddress, 0, ethers.ZeroAddress); // vectorRoot v1 = Merkle root over 4 vector commitments const leavesV1 = ["v0", "v1", "v2", "v3"].map(leafOf); const treeV1 = buildTree(leavesV1); const rootV1 = treeRoot(treeV1); const uriV1 = "ipfs://QmIndexV1"; const chal1 = await vsm.commitChallenge(storeId, 1, rootV1, 4, uriV1); const sig1 = genuineFalconSig(pk, chal1, "s1"); await expect(vsm.connect(operator).commit(storeId, rootV1, 4, uriV1, sig1)) .to.emit(vsm, "Committed") .withArgs(storeId, 1, rootV1, 4, chal1, uriV1); expect(await vsm.versionCount(storeId)).to.equal(1n); let cur = await vsm.currentRoot(storeId); expect(cur.version).to.equal(1n); expect(cur.vectorRoot).to.equal(rootV1); expect(cur.vectorCount).to.equal(4n); // version 2 with a larger index (5 vectors) const leavesV2 = ["v0", "v1", "v2", "v3", "v4"].map(leafOf); const rootV2 = treeRoot(buildTree(leavesV2)); const uriV2 = "ipfs://QmIndexV2"; const chal2 = await vsm.commitChallenge(storeId, 2, rootV2, 5, uriV2); const sig2 = genuineFalconSig(pk, chal2, "s2"); await vsm.connect(operator).commit(storeId, rootV2, 5, uriV2, sig2); expect(await vsm.versionCount(storeId)).to.equal(2n); cur = await vsm.currentRoot(storeId); expect(cur.version).to.equal(2n); expect(cur.vectorRoot).to.equal(rootV2); // append-only: version 1 is still exactly as committed (not overwritten) const v1 = await vsm.getVersion(storeId, 1); expect(v1.vectorRoot).to.equal(rootV1); expect(v1.vectorCount).to.equal(4n); expect(v1.uri).to.equal(uriV1); const v2 = await vsm.getVersion(storeId, 2); expect(v2.vectorRoot).to.equal(rootV2); expect(await vsm.commitCount()).to.equal(2n); }); // ------------------------------------------------------------------------- // 3: tampered Falcon signature rejected fail-closed // ------------------------------------------------------------------------- it("rejects a tampered Falcon signature fail-closed (nothing recorded)", async function () { const storeId = ethers.id("agent-memory://claude/tamper"); const pk = makeFalconPubKey("store-t"); await vsm.connect(owner).createStore(storeId, pk, ethers.ZeroAddress, 0, ethers.ZeroAddress); const root = treeRoot(buildTree(["a", "b"].map(leafOf))); const uri = "ipfs://QmTamper"; const chal = await vsm.commitChallenge(storeId, 1, root, 2, uri); const bad = tamperFalconSig(genuineFalconSig(pk, chal, "st")); await expect(vsm.connect(operator).commit(storeId, root, 2, uri, bad)).to.be.revertedWithCustomError( vsm, "PQCVerificationFailed" ); // fail-closed: no version, no head, commitCount unchanged expect(await vsm.versionCount(storeId)).to.equal(0n); await expect(vsm.currentRoot(storeId)).to.be.revertedWithCustomError(vsm, "UnknownVersion"); expect(await vsm.commitCount()).to.equal(0n); // a signature for a DIFFERENT message (wrong version) is also rejected const chalWrong = await vsm.commitChallenge(storeId, 2, root, 2, uri); const sigWrongVersion = genuineFalconSig(pk, chalWrong, "sw"); await expect( vsm.connect(operator).commit(storeId, root, 2, uri, sigWrongVersion) ).to.be.revertedWithCustomError(vsm, "PQCVerificationFailed"); // a signature by a DIFFERENT key is rejected const otherPk = makeFalconPubKey("other"); const sigOtherKey = genuineFalconSig(otherPk, chal, "so"); await expect( vsm.connect(operator).commit(storeId, root, 2, uri, sigOtherKey) ).to.be.revertedWithCustomError(vsm, "PQCVerificationFailed"); // control: the genuine signature still works after all the rejections const good = genuineFalconSig(pk, chal, "sg"); await vsm.connect(operator).commit(storeId, root, 2, uri, good); expect(await vsm.versionCount(storeId)).to.equal(1n); }); it("treats an empty precompile return (pre-fork / stale node) as invalid", async function () { const storeId = ethers.id("agent-memory://claude/prefork"); const pk = makeFalconPubKey("store-pf"); await vsm.connect(owner).createStore(storeId, pk, ethers.ZeroAddress, 0, ethers.ZeroAddress); const root = treeRoot(buildTree(["x", "y"].map(leafOf))); const chal = await vsm.commitChallenge(storeId, 1, root, 2, "u"); const good = genuineFalconSig(pk, chal, "pf"); // wipe the precompile: an empty account returns success + empty returndata await network.provider.send("hardhat_setCode", [FALCON512, "0x"]); await expect(vsm.connect(operator).commit(storeId, root, 2, "u", good)).to.be.revertedWithCustomError( vsm, "PQCVerificationFailed" ); expect(await vsm.versionCount(storeId)).to.equal(0n); }); // ------------------------------------------------------------------------- // 4: retrieval attestation records against a valid version; missing version reverts // ------------------------------------------------------------------------- it("records a retrieval attestation against a committed version and reverts on a missing version", async function () { const storeId = ethers.id("agent-memory://claude/retrieval"); const pk = makeFalconPubKey("store-r"); await vsm.connect(owner).createStore(storeId, pk, ethers.ZeroAddress, 0, ethers.ZeroAddress); const leaves = ["r0", "r1", "r2", "r3"].map(leafOf); const root = treeRoot(buildTree(leaves)); const chal = await vsm.commitChallenge(storeId, 1, root, 4, "ipfs://QmR"); await vsm.connect(operator).commit(storeId, root, 4, "ipfs://QmR", genuineFalconSig(pk, chal, "r")); const queryHash = ethers.id("query: what did the user say about post-quantum?"); const resultRoot = treeRoot(buildTree(["r1", "r2"].map(leafOf))); // provider returned 2 results // valid version 1: records const tx = await vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, resultRoot, ethers.ZeroHash); const rc = await tx.wait(); const ev = rc.logs.map((l) => vsm.interface.parseLog(l)).find((p) => p && p.name === "RetrievalAttested"); const retrievalId = ev.args.retrievalId; expect(await vsm.retrievalCount(storeId)).to.equal(1n); const r = await vsm.getRetrieval(retrievalId); expect(r.exists).to.equal(true); expect(r.version).to.equal(1n); expect(r.provider).to.equal(provider.address); expect(r.queryHash).to.equal(queryHash); expect(r.resultRoot).to.equal(resultRoot); // missing version 2 (never committed): reverts fail-closed await expect( vsm.connect(provider).attestRetrieval(storeId, 2, queryHash, resultRoot, ethers.ZeroHash) ).to.be.revertedWithCustomError(vsm, "UnknownVersion"); // version 0 is also invalid await expect( vsm.connect(provider).attestRetrieval(storeId, 0, queryHash, resultRoot, ethers.ZeroHash) ).to.be.revertedWithCustomError(vsm, "UnknownVersion"); // still only one attestation recorded expect(await vsm.retrievalCount(storeId)).to.equal(1n); }); // ------------------------------------------------------------------------- // 5: inclusion proof for a committed vector verifies; non-member fails // ------------------------------------------------------------------------- it("verifies an inclusion proof for a committed vector and rejects a non-member", async function () { const storeId = ethers.id("agent-memory://claude/inclusion"); const pk = makeFalconPubKey("store-i"); await vsm.connect(owner).createStore(storeId, pk, ethers.ZeroAddress, 0, ethers.ZeroAddress); const labels = ["vec-alpha", "vec-beta", "vec-gamma", "vec-delta", "vec-epsilon"]; const leaves = labels.map(leafOf); const tree = buildTree(leaves); const root = treeRoot(tree); const chal = await vsm.commitChallenge(storeId, 1, root, leaves.length, "ipfs://QmInc"); await vsm.connect(operator).commit(storeId, root, leaves.length, "ipfs://QmInc", genuineFalconSig(pk, chal, "i")); // member: vec-gamma (index 2) proves against the committed vectorRoot const memberLeaf = leaves[2]; const memberProof = treeProof(tree, 2); expect(await vsm.verifyVectorInclusion(storeId, 1, memberLeaf, memberProof)).to.equal(true); // non-member: a leaf that was never in the tree fails with the same-shaped proof const nonMember = leafOf("vec-not-in-store"); expect(await vsm.verifyVectorInclusion(storeId, 1, nonMember, memberProof)).to.equal(false); // result-root inclusion: provider attests a result set, consumer proves a returned result is in it const queryHash = ethers.id("q: gamma?"); const resultLeaves = [leaves[2], leaves[4]]; // gamma + epsilon const resultTree = buildTree(resultLeaves); const resultRoot = treeRoot(resultTree); const tx = await vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, resultRoot, ethers.ZeroHash); const rc = await tx.wait(); const retrievalId = rc.logs .map((l) => vsm.interface.parseLog(l)) .find((p) => p && p.name === "RetrievalAttested").args.retrievalId; expect(await vsm.verifyResultInclusion(retrievalId, resultLeaves[0], treeProof(resultTree, 0))).to.equal(true); expect(await vsm.verifyResultInclusion(retrievalId, nonMember, treeProof(resultTree, 0))).to.equal(false); }); // ------------------------------------------------------------------------- // 6: AERE402 paid-query rail gates attestRetrieval on a priced store // ------------------------------------------------------------------------- it("meters a priced store on the AERE402 rail and gates attestRetrieval on payment", async function () { const agent = this.agent; const fac = this.fac; // A classical, PRICED store: owner signs commits; queries cost 1 token; proceeds go to provider. const token = await (await ethers.getContractFactory("MockERC20Lending")).deploy("USD Coin e", "USDC.e", 18); await token.waitForDeployment(); const storeId = ethers.id("agent-memory://claude/paid"); const price = 1n * ONE; await vsm .connect(owner) .createStore(storeId, "0x", await token.getAddress(), price, provider.address); // owner commits version 1 (classical auth: msg.sender == owner) const leaves = ["p0", "p1", "p2"].map(leafOf); const root = treeRoot(buildTree(leaves)); await vsm.connect(owner).commit(storeId, root, 3, "ipfs://QmPaid", "0x"); expect(await vsm.versionCount(storeId)).to.equal(1n); // register + fund an AERE402 paying agent const regTx = await agent .connect(operator) .registerAgent(signer.address, ethers.ZeroHash, ethers.ZeroHash, 10n * ONE, 0, 0, "ipfs://agent"); const agentId = (await regTx.wait()).logs.find((l) => l.fragment?.name === "AgentRegistered").args.agentId; await token.mint(operator.address, 100n * ONE); await token.connect(operator).approve(await agent.getAddress(), 100n * ONE); await agent.connect(operator).topUp(agentId, await token.getAddress(), 100n * ONE); const queryHash = ethers.id("query: recall my PQC preferences"); // unpaid retrieval on a priced store is rejected fail-closed await expect( vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, root, ethers.ZeroHash) ).to.be.revertedWithCustomError(vsm, "PaymentRequired"); // pay through the rail: the agent authorizes payment to the vector store (the payee) const amount = price; const rail_nonce = 1n; const deadline = (await ethers.provider.getBlock("latest")).timestamp + 3600; const resourceId = await vsm.queryResourceId(storeId, queryHash); const sig = await signAuth( signer, fac, agentId, await token.getAddress(), await vsm.getAddress(), amount, resourceId, rail_nonce, deadline ); const provBefore = await token.balanceOf(provider.address); const payTx = await vsm .connect(consumer) .paidQuery(storeId, queryHash, provider.address, agentId, amount, rail_nonce, deadline, sig); const payRc = await payTx.wait(); const receiptId = payRc.logs .map((l) => vsm.interface.parseLog(l)) .find((p) => p && p.name === "QueryPaid").args.receiptId; // fee (25 bps) went to the sink; the remainder was forwarded to the provider (recipient) const fee = (amount * 25n) / 10_000n; expect((await token.balanceOf(provider.address)) - provBefore).to.equal(amount - fee); expect(await fac.consumed(agentId, rail_nonce)).to.equal(true); // the store custodies nothing across the call expect(await token.balanceOf(await vsm.getAddress())).to.equal(0n); const receipt = await vsm.getReceipt(receiptId); expect(receipt.exists).to.equal(true); expect(receipt.consumed).to.equal(false); expect(receipt.storeId).to.equal(storeId); expect(receipt.queryHash).to.equal(queryHash); expect(receipt.amount).to.equal(amount); expect(receipt.provider).to.equal(provider.address); // L2: receipt is bound to the intended provider // L2 (LOW) fix: a THIRD PARTY (not the bound provider) cannot front-run and consume the receipt // with a junk resultRoot to grief the honest provider. Even the payer (consumer) is not the // bound provider, so it too is rejected; the receipt stays unconsumed and usable. const junkRoot = ethers.id("junk-result-root-from-griefer"); await expect( vsm.connect(operator).attestRetrieval(storeId, 1, queryHash, junkRoot, receiptId) ).to.be.revertedWithCustomError(vsm, "NotReceiptProvider"); await expect( vsm.connect(consumer).attestRetrieval(storeId, 1, queryHash, junkRoot, receiptId) ).to.be.revertedWithCustomError(vsm, "NotReceiptProvider"); expect((await vsm.getReceipt(receiptId)).consumed).to.equal(false); // now the retrieval attestation is allowed for the BOUND provider, and consumes the receipt one-shot const resultRoot = treeRoot(buildTree([leaves[0], leaves[1]])); await vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, resultRoot, receiptId); expect(await vsm.retrievalCount(storeId)).to.equal(1n); expect((await vsm.getReceipt(receiptId)).consumed).to.equal(true); // the same receipt cannot back a second retrieval await expect( vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, resultRoot, receiptId) ).to.be.revertedWithCustomError(vsm, "ReceiptConsumed"); // a FRESH receipt paid for query B cannot back a retrieval that claims query A (ReceiptMismatch) const queryB = ethers.id("query: something else"); const nonceB = 2n; const sigB = await signAuth( signer, fac, agentId, await token.getAddress(), await vsm.getAddress(), amount, await vsm.queryResourceId(storeId, queryB), nonceB, deadline ); const rcB = await (await vsm.connect(consumer).paidQuery(storeId, queryB, provider.address, agentId, amount, nonceB, deadline, sigB)).wait(); const receiptB = rcB.logs.map((l) => vsm.interface.parseLog(l)).find((p) => p && p.name === "QueryPaid").args.receiptId; await expect( vsm.connect(provider).attestRetrieval(storeId, 1, queryHash, resultRoot, receiptB) ).to.be.revertedWithCustomError(vsm, "ReceiptMismatch"); }); // ------------------------------------------------------------------------- // 7: classical store owner auth + non-owner rejection // ------------------------------------------------------------------------- it("classical store: only the owner may commit", async function () { const storeId = ethers.id("agent-memory://classical"); await vsm.connect(owner).createStore(storeId, "0x", ethers.ZeroAddress, 0, ethers.ZeroAddress); const root = treeRoot(buildTree(["c0", "c1"].map(leafOf))); // non-owner attempt rejected await expect(vsm.connect(provider).commit(storeId, root, 2, "u", "0x")).to.be.revertedWithCustomError( vsm, "NotOwner" ); // owner commit works (falconSig ignored for a classical store) await vsm.connect(owner).commit(storeId, root, 2, "u", "0x"); expect(await vsm.versionCount(storeId)).to.equal(1n); }); // ------------------------------------------------------------------------- // 8: createStore fail-closed on malformed config // ------------------------------------------------------------------------- it("createStore rejects a malformed Falcon key and inconsistent price config", async function () { const storeId = ethers.id("agent-memory://bad"); // wrong-length Falcon key await expect( vsm.connect(owner).createStore(storeId, "0x" + "09" + "00".repeat(800), ethers.ZeroAddress, 0, ethers.ZeroAddress) ).to.be.revertedWithCustomError(vsm, "InvalidPubKey"); // correct length but wrong header await expect( vsm.connect(owner).createStore(storeId, "0x" + "0a" + "00".repeat(896), ethers.ZeroAddress, 0, ethers.ZeroAddress) ).to.be.revertedWithCustomError(vsm, "InvalidPubKey"); // priced store without a token / recipient await expect( vsm.connect(owner).createStore(storeId, "0x", ethers.ZeroAddress, 1n * ONE, ethers.ZeroAddress) ).to.be.revertedWithCustomError(vsm, "InvalidConfig"); // free store that nonetheless sets a token await expect( vsm.connect(owner).createStore(storeId, "0x", owner.address, 0, ethers.ZeroAddress) ).to.be.revertedWithCustomError(vsm, "InvalidConfig"); // duplicate storeId rejected await vsm.connect(owner).createStore(storeId, "0x", ethers.ZeroAddress, 0, ethers.ZeroAddress); await expect( vsm.connect(owner).createStore(storeId, "0x", ethers.ZeroAddress, 0, ethers.ZeroAddress) ).to.be.revertedWithCustomError(vsm, "StoreExists"); }); });