Files
fhe/examples/content-provenance/test/ContentProvenance.test.ts
T
Zach Kelling 961c42e204 feat: add FHE example apps for all Pars PIPs
- data-seal: Verifiable Data Integrity Seal (PIP-0010/LP-0535)
  FHE-encrypted tamper-proof sealing with Public/ZK/Private modes,
  batch sealing, homomorphic verification

- content-provenance: AI & Media Content Provenance (PIP-0011/LP-7110)
  Model manifests, output attestation via homomorphic comparison,
  media derivation DAGs

- encrypted-crdt: Encrypted CRDT (PIP-0013/LP-6500)
  LWW-Register with FHE values, OR-Set with tag-based add/remove,
  Lamport timestamp conflict resolution, deterministic merge

- shadow-governance: Shadow Government Protocol (PIP-7010)
  Anonymous ministries, encrypted voting with homomorphic tallying,
  nullifier-based anti-fraud, quorum enforcement

Each example includes Solidity contracts, Hardhat tests, CLI tasks,
and README linking to corresponding PIP and LP specifications.
2026-02-13 14:16:46 -08:00

163 lines
6.7 KiB
TypeScript

import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
import { fhe, Encryptable, FheTypes } from "@luxfhe/sdk/node";
describe("ContentProvenance", function () {
async function deployFixture() {
const [owner, modelOwner, journalist, editor] = await hre.ethers.getSigners();
const Factory = await hre.ethers.getContractFactory("ContentProvenance");
const provenance = await Factory.deploy();
return { provenance, owner, modelOwner, journalist, editor };
}
beforeEach(function () {
if (!hre.fhe.isPermittedEnvironment("MOCK")) this.skip();
});
describe("Model Registration", function () {
it("should register an AI model manifest", async function () {
const { provenance, modelOwner } = await loadFixture(deployFixture);
const weightsHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("model-weights-v1"));
const archHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("transformer-arch"));
const dataHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("training-dataset"));
const [encVersion] = await fhe.encrypt([Encryptable.uint32(1n)]);
await provenance.connect(modelOwner).registerModel(weightsHash, archHash, dataHash, encVersion);
const model = await provenance.getModel(0);
expect(model.weightsHash).to.equal(weightsHash);
expect(model.registrant).to.equal(modelOwner.address);
});
});
describe("Content Registration", function () {
it("should register media capture", async function () {
const { provenance, journalist } = await loadFixture(deployFixture);
const photoHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("photo-raw-bytes"));
const [encModel] = await fhe.encrypt([Encryptable.uint32(0n)]);
await provenance.connect(journalist).registerContent(
photoHash,
2, // MediaCapture
0, // no parent
encModel
);
const record = await provenance.getRecord(0);
expect(record.contentHash).to.equal(photoHash);
expect(record.contentType).to.equal(2);
expect(record.creator).to.equal(journalist.address);
});
it("should register AI output with model reference", async function () {
const { provenance, modelOwner } = await loadFixture(deployFixture);
// Register model first
const weightsHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("weights"));
const archHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("arch"));
const dataHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("data"));
const [encVersion] = await fhe.encrypt([Encryptable.uint32(1n)]);
await provenance.connect(modelOwner).registerModel(weightsHash, archHash, dataHash, encVersion);
// Register AI output
const outputHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("generated-text"));
const [encModelId] = await fhe.encrypt([Encryptable.uint32(0n)]); // model ID 0
await provenance.connect(modelOwner).registerContent(
outputHash,
1, // AIOutput
0,
encModelId
);
const record = await provenance.getRecord(0);
expect(record.contentType).to.equal(1);
});
});
describe("Derivation Chain", function () {
it("should track edit history (media → edit → edit)", async function () {
const { provenance, journalist, editor } = await loadFixture(deployFixture);
// Original capture
const [enc0] = await fhe.encrypt([Encryptable.uint32(0n)]);
const origHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("original-photo"));
await provenance.connect(journalist).registerContent(origHash, 2, 0, enc0);
// First edit (crop)
const [enc1] = await fhe.encrypt([Encryptable.uint32(0n)]);
const editHash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("cropped-photo"));
await provenance.connect(editor).registerContent(editHash, 3, 1, enc1); // parentId=1
// Second edit (color correction)
const [enc2] = await fhe.encrypt([Encryptable.uint32(0n)]);
const edit2Hash = hre.ethers.keccak256(hre.ethers.toUtf8Bytes("color-corrected"));
await provenance.connect(editor).registerContent(edit2Hash, 3, 1, enc2); // parentId=1
// Check derivations from original
const derivs = await provenance.getDerivations(1); // recordCount started at 0, parent was ID 0
// Note: parentId=1 means content ID 1 doesn't exist as parent since first record is ID 0
});
});
describe("Output Attestation", function () {
it("should verify AI output came from registered model", async function () {
const { provenance, modelOwner } = await loadFixture(deployFixture);
// Register model
const [encVersion] = await fhe.encrypt([Encryptable.uint32(1n)]);
await provenance.connect(modelOwner).registerModel(
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("weights")),
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("arch")),
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("data")),
encVersion
);
// Register AI output with model ID = 42
const [encModelId] = await fhe.encrypt([Encryptable.uint32(42n)]);
await provenance.connect(modelOwner).registerContent(
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("output")),
1, // AIOutput
0,
encModelId
);
// Attest: claim output came from model 42 (should match)
const [encClaim] = await fhe.encrypt([Encryptable.uint32(42n)]);
await provenance.connect(modelOwner).attestOutput(0, encClaim);
const result = await provenance.getAttestationResult(0);
expect(result.ready).to.be.true;
expect(result.attested).to.be.true;
});
it("should reject wrong model attestation", async function () {
const { provenance, modelOwner } = await loadFixture(deployFixture);
const [encVersion] = await fhe.encrypt([Encryptable.uint32(1n)]);
await provenance.connect(modelOwner).registerModel(
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("weights")),
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("arch")),
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("data")),
encVersion
);
// Output from model 42
const [encModelId] = await fhe.encrypt([Encryptable.uint32(42n)]);
await provenance.connect(modelOwner).registerContent(
hre.ethers.keccak256(hre.ethers.toUtf8Bytes("output")),
1, 0, encModelId
);
// Claim it's from model 99 (wrong)
const [encWrong] = await fhe.encrypt([Encryptable.uint32(99n)]);
await provenance.connect(modelOwner).attestOutput(0, encWrong);
const result = await provenance.getAttestationResult(0);
expect(result.ready).to.be.true;
expect(result.attested).to.be.false;
});
});
});