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.
160 lines
6.7 KiB
JavaScript
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);
|
|
});
|
|
});
|