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

160 lines
6.7 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("AereInferNet", () => {
let provider, regulator, outsider;
let net;
const MODEL_ID = ethers.keccak256(ethers.toUtf8Bytes("claude-4.7-sonnet-20251022"));
const MODEL_DIGEST = ethers.keccak256(ethers.toUtf8Bytes("model-weights-blob"));
const ATT_DIGEST = ethers.keccak256(ethers.toUtf8Bytes("sgx-attestation-quote"));
beforeEach(async () => {
[provider, regulator, outsider] = await ethers.getSigners();
const F = await ethers.getContractFactory("AereInferNet");
net = await F.deploy();
await net.waitForDeployment();
});
function buildLeaf(req, resp, ts, deployer) {
return ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(
["bytes32", "bytes32", "uint64", "bytes32"], [req, resp, ts, deployer]
));
}
// tiny sort-pair Merkle: builds a 4-leaf tree and returns root + a proof for leaf 0
function tinyTree(leaves) {
const hashPair = (a, b) => {
const [lo, hi] = BigInt(a) <= BigInt(b) ? [a, b] : [b, a];
return ethers.keccak256(ethers.concat([lo, hi]));
};
while (leaves.length > 1) {
const next = [];
for (let i = 0; i < leaves.length; i += 2) {
if (i + 1 < leaves.length) next.push(hashPair(leaves[i], leaves[i + 1]));
else next.push(leaves[i]);
}
leaves = next;
}
return leaves[0];
}
function buildTreeWithProof(allLeaves, idx) {
// returns { root, proof } for the leaf at idx in a sorted-pair tree.
const hashPair = (a, b) => {
const [lo, hi] = BigInt(a) <= BigInt(b) ? [a, b] : [b, a];
return ethers.keccak256(ethers.concat([lo, hi]));
};
let layer = allLeaves.slice();
let proof = [];
let cur = idx;
while (layer.length > 1) {
const sibling = cur ^ 1;
if (sibling < layer.length) proof.push(layer[sibling]);
const next = [];
for (let i = 0; i < layer.length; i += 2) {
if (i + 1 < layer.length) next.push(hashPair(layer[i], layer[i + 1]));
else next.push(layer[i]);
}
layer = next;
cur = Math.floor(cur / 2);
}
return { root: layer[0], proof };
}
it("registers a model + reads it back", async () => {
await expect(
net.connect(provider).registerModel(
MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "claude-4.7-sonnet-20251022", "ipfs://attestation"
)
).to.emit(net, "ModelRegistered");
const m = await net.modelOf(provider.address, MODEL_ID);
expect(m.exists).to.equal(true);
expect(m.modelDigest).to.equal(MODEL_DIGEST);
expect(m.risk).to.equal(2);
});
it("rejects duplicate model registration + malformed inputs", async () => {
await net.connect(provider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "n", "u");
await expect(
net.connect(provider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "n", "u")
).to.be.revertedWithCustomError(net, "ModelExists");
await expect(
net.connect(outsider).registerModel(MODEL_ID, ethers.ZeroHash, ATT_DIGEST, 2, "n", "u")
).to.be.revertedWithCustomError(net, "ZeroDigest");
await expect(
net.connect(outsider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "", "u")
).to.be.revertedWithCustomError(net, "EmptyName");
});
it("commits an epoch only when model is registered", async () => {
const root = ethers.keccak256(ethers.toUtf8Bytes("root-x"));
const today = BigInt(Math.floor((await ethers.provider.getBlock("latest")).timestamp / 86400));
await expect(
net.connect(provider).commitEpoch(MODEL_ID, today, root, 100, "ipfs://meta")
).to.be.revertedWithCustomError(net, "ModelUnknown");
await net.connect(provider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "n", "u");
await expect(
net.connect(provider).commitEpoch(MODEL_ID, today, root, 100, "ipfs://meta")
).to.emit(net, "EpochCommitted").withArgs(provider.address, MODEL_ID, today, root, 100, "ipfs://meta");
const e = await net.epochOf(provider.address, MODEL_ID, today);
expect(e.root).to.equal(root);
expect(e.inferenceCount).to.equal(100n);
expect(await net.modelEpochCount(provider.address, MODEL_ID)).to.equal(1n);
});
it("rejects re-committing the same epoch + zero root + out-of-range epoch (audit fix HIGH #26)", async () => {
const root = ethers.keccak256(ethers.toUtf8Bytes("root"));
const today = BigInt(Math.floor((await ethers.provider.getBlock("latest")).timestamp / 86400));
await net.connect(provider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "n", "u");
// Old epoch (>1 day in past) rejected.
await expect(
net.connect(provider).commitEpoch(MODEL_ID, 1n, root, 1, "")
).to.be.revertedWithCustomError(net, "EpochOutOfRange");
// Future epoch rejected.
await expect(
net.connect(provider).commitEpoch(MODEL_ID, today + 5n, root, 1, "")
).to.be.revertedWithCustomError(net, "EpochOutOfRange");
await expect(
net.connect(provider).commitEpoch(MODEL_ID, today, ethers.ZeroHash, 1, "")
).to.be.revertedWithCustomError(net, "ZeroDigest");
await net.connect(provider).commitEpoch(MODEL_ID, today, root, 1, "");
await expect(
net.connect(provider).commitEpoch(MODEL_ID, today, root, 1, "")
).to.be.revertedWithCustomError(net, "EpochExists");
});
it("verifyInference proves inclusion against committed root", async () => {
await net.connect(provider).registerModel(MODEL_ID, MODEL_DIGEST, ATT_DIGEST, 2, "n", "u");
const leaves = [];
const targets = [];
for (let i = 0; i < 4; i++) {
const req = ethers.keccak256(ethers.toUtf8Bytes(`req-${i}`));
const resp = ethers.keccak256(ethers.toUtf8Bytes(`resp-${i}`));
const ts = BigInt(1717880000000 + i * 1000);
const dep = ethers.keccak256(ethers.toUtf8Bytes(`deployer-${i}`));
leaves.push(buildLeaf(req, resp, ts, dep));
targets.push({ req, resp, ts, dep });
}
const idx = 2;
const { root, proof } = buildTreeWithProof(leaves, idx);
const today = BigInt(Math.floor((await ethers.provider.getBlock("latest")).timestamp / 86400));
await net.connect(provider).commitEpoch(MODEL_ID, today, root, 4, "");
const t = targets[idx];
expect(await net.verifyInference(provider.address, MODEL_ID, today, t.req, t.resp, t.ts, t.dep, proof))
.to.equal(true);
// Wrong leaf → false.
expect(await net.verifyInference(
provider.address, MODEL_ID, today,
ethers.keccak256(ethers.toUtf8Bytes("not-req")), t.resp, t.ts, t.dep, proof
)).to.equal(false);
// Unknown epoch → false (no revert).
expect(await net.verifyInference(provider.address, MODEL_ID, 999999n, t.req, t.resp, t.ts, t.dep, proof))
.to.equal(false);
});
});