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.
164 lines
8.4 KiB
JavaScript
164 lines
8.4 KiB
JavaScript
const { expect } = require("chai");
|
|
const { ethers } = require("hardhat");
|
|
|
|
describe("AereAttestationGateway", () => {
|
|
let foundation, bafin, finma, subject1, outsider;
|
|
let gw;
|
|
const SCHEMA_MICA = ethers.keccak256(ethers.toUtf8Bytes("mica-casp-v1"));
|
|
const SCHEMA_AIACT = ethers.keccak256(ethers.toUtf8Bytes("ai-act-annex3-v1"));
|
|
const SPEC_HASH_MICA = ethers.keccak256(ethers.toUtf8Bytes("mica-spec-bytes"));
|
|
const SPEC_HASH_AI = ethers.keccak256(ethers.toUtf8Bytes("ai-act-spec-bytes"));
|
|
const SUBJECT_A = ethers.keccak256(ethers.toUtf8Bytes("subject:exchange-A"));
|
|
const PAYLOAD_HASH = ethers.keccak256(ethers.toUtf8Bytes("payload-bytes"));
|
|
|
|
beforeEach(async () => {
|
|
[foundation, bafin, finma, subject1, outsider] = await ethers.getSigners();
|
|
const F = await ethers.getContractFactory("AereAttestationGateway");
|
|
gw = await F.deploy(foundation.address);
|
|
await gw.waitForDeployment();
|
|
});
|
|
|
|
async function nowTs() {
|
|
return (await ethers.provider.getBlock("latest")).timestamp;
|
|
}
|
|
|
|
it("only Foundation publishes schemas; cannot republish", async () => {
|
|
await expect(
|
|
gw.connect(outsider).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "mica-casp-v1", "1.0.0", "ipfs://")
|
|
).to.be.revertedWithCustomError(gw, "NotFoundation");
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "mica-casp-v1", "1.0.0", "ipfs://");
|
|
expect((await gw.schemas(SCHEMA_MICA)).exists).to.equal(true);
|
|
await expect(
|
|
gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "x", "y", "z")
|
|
).to.be.revertedWithCustomError(gw, "SchemaExists");
|
|
await expect(
|
|
gw.connect(foundation).publishSchema(SCHEMA_AIACT, ethers.ZeroHash, "n", "v", "u")
|
|
).to.be.revertedWithCustomError(gw, "EmptySpec");
|
|
});
|
|
|
|
it("rejects attest from non-authorised attestor + unknown schema", async () => {
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, "")
|
|
).to.be.revertedWithCustomError(gw, "SchemaUnknown");
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, "")
|
|
).to.be.revertedWithCustomError(gw, "NotAuthorisedAttestor");
|
|
});
|
|
|
|
it("authorise + attest emits + records + latestFor + subjectCurrentlyAttested", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const t = await nowTs();
|
|
await expect(
|
|
gw.connect(bafin).attest(
|
|
SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, t + 3600, "ipfs://x"
|
|
)
|
|
).to.emit(gw, "Attested");
|
|
const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA);
|
|
const a = await gw.attestationOf(id);
|
|
expect(a.attestor).to.equal(bafin.address);
|
|
expect(a.payloadHash).to.equal(PAYLOAD_HASH);
|
|
expect(await gw.isValidNow(id)).to.equal(true);
|
|
expect(await gw.subjectCurrentlyAttested(SUBJECT_A, SCHEMA_MICA)).to.equal(true);
|
|
});
|
|
|
|
it("validity window — past validUntil is invalid", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const t = await nowTs();
|
|
await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, t + 10, "");
|
|
await ethers.provider.send("evm_increaseTime", [20]);
|
|
await ethers.provider.send("evm_mine", []);
|
|
const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA);
|
|
expect(await gw.isValidNow(id)).to.equal(false);
|
|
});
|
|
|
|
it("bad validity window (validUntil < validFrom) rejected", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const t = await nowTs();
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t + 100, t, "")
|
|
).to.be.revertedWithCustomError(gw, "BadValidityWindow");
|
|
});
|
|
|
|
it("revocation: only issuer; immediate invalidity; cannot revoke twice", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const t = await nowTs();
|
|
await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, "");
|
|
const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA);
|
|
|
|
await expect(gw.connect(outsider).revoke(id)).to.be.revertedWithCustomError(gw, "NotIssuer");
|
|
await gw.connect(bafin).revoke(id);
|
|
expect(await gw.isValidNow(id)).to.equal(false);
|
|
await expect(gw.connect(bafin).revoke(id)).to.be.revertedWithCustomError(gw, "AlreadyRevoked");
|
|
});
|
|
|
|
it("inheritance: child invalid if parent revoked OR expired", async () => {
|
|
// KYB schema by Foundation; child = best-ex schema by BAFIN.
|
|
const SCHEMA_KYB = ethers.keccak256(ethers.toUtf8Bytes("foundation-kyb-v1"));
|
|
await gw.connect(foundation).publishSchema(SCHEMA_KYB, SPEC_HASH_MICA, "kyb", "1", "u");
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_KYB, foundation.address, true);
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
|
|
const t = await nowTs();
|
|
await gw.connect(foundation).attest(SCHEMA_KYB, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, "");
|
|
const parent = await gw.latestFor(SUBJECT_A, SCHEMA_KYB);
|
|
|
|
await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, parent, PAYLOAD_HASH, t, 0, "");
|
|
const child = await gw.latestFor(SUBJECT_A, SCHEMA_MICA);
|
|
|
|
expect(await gw.isValidNow(child)).to.equal(true);
|
|
|
|
// Revoke parent → child fails.
|
|
await gw.connect(foundation).revoke(parent);
|
|
expect(await gw.isValidNow(child)).to.equal(false);
|
|
});
|
|
|
|
it("inheritance: bad parent rejected at attest time", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const fakeParent = ethers.keccak256(ethers.toUtf8Bytes("nonexistent"));
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, fakeParent, PAYLOAD_HASH, 0, 0, "")
|
|
).to.be.revertedWithCustomError(gw, "ParentInvalid");
|
|
});
|
|
|
|
it("inheritance: parent must match subject (audit fix HIGH #23)", async () => {
|
|
const SCHEMA_KYB = ethers.keccak256(ethers.toUtf8Bytes("kyb"));
|
|
await gw.connect(foundation).publishSchema(SCHEMA_KYB, SPEC_HASH_MICA, "kyb", "1", "u");
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_KYB, foundation.address, true);
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const SUBJECT_B = ethers.keccak256(ethers.toUtf8Bytes("subject:exchange-B"));
|
|
// KYB issued for SUBJECT_B (not SUBJECT_A).
|
|
await gw.connect(foundation).attest(SCHEMA_KYB, SUBJECT_B, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, "");
|
|
const parent = await gw.latestFor(SUBJECT_B, SCHEMA_KYB);
|
|
// Trying to chain that parent for SUBJECT_A → rejected.
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, parent, PAYLOAD_HASH, 0, 0, "")
|
|
).to.be.revertedWithCustomError(gw, "ParentSubjectMismatch");
|
|
});
|
|
|
|
it("toggling attestor off does not retro-invalidate past attestations", async () => {
|
|
await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u");
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true);
|
|
const t = await nowTs();
|
|
await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, "");
|
|
const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA);
|
|
|
|
// De-authorise BAFIN going forward.
|
|
await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, false);
|
|
// Past attestation is still valid (only revocation by issuer kills it).
|
|
expect(await gw.isValidNow(id)).to.equal(true);
|
|
|
|
// But BAFIN can no longer issue new attestations under this schema.
|
|
await expect(
|
|
gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, "")
|
|
).to.be.revertedWithCustomError(gw, "NotAuthorisedAttestor");
|
|
});
|
|
});
|