488 lines
70 KiB
JavaScript
488 lines
70 KiB
JavaScript
#!/usr/bin/env node
|
|
// genereaza-vectori.mjs: the conformance vectors, DERIVED from the producers and from evidence already recorded, never typed by hand.
|
|
//
|
|
// node genereaza-vectori.mjs rewrites vectori/ from: the producers run on fixed inputs (the AIP-23 kinds), the evidence
|
|
// recorded on testnet 28001 beside the products (a receipt, five market timelines, a notarized
|
|
// dataset commitment), and surse-proaspete.json (what needs a key, randomness or the chain)
|
|
// node genereaza-vectori.mjs --verifica regenerates in memory and compares with vectori/ byte for byte: LA ZI (0) or DIFERIT (1)
|
|
// node genereaza-vectori.mjs --proaspat completes surse-proaspete.json with what is MISSING from it (what is there stays, so the
|
|
// vectors derived from it stay): keys made and discarded here sign a tree head (ML-DSA-65)
|
|
// and its classical impostor (Ed25519), a random envelope nobody notarized, a small dataset
|
|
// (random salts), and the transaction of the recorded receipt READ from testnet 28001 (two
|
|
// eth_ calls; nothing is sent). With --tot everything is made anew
|
|
//
|
|
// A vector is { artifact, input, expected: { verdict, failed, levels } }. An INVALID vector names its `base` (a vector that is not
|
|
// INVALID) and its `mutation`: ONE operation (set, remove, add at one JSON path) applied to the base input, so that it differs from the
|
|
// base in exactly one place and must be refused for exactly the rules in `expected.failed`. Where the rule sits under a hash (an amount
|
|
// under the instruction hash, an order under an index root) one place is not reachable: the mutation is then made at the PRODUCER
|
|
// (`mutation.producer`), the commitments above it recomputed, and `mutation.changed` lists every path that moved.
|
|
// `exercises` are the rules an implementation must be able to judge to be scored on the vector; `requires: ['rpc-28001']` marks the
|
|
// vectors that need the testnet endpoint (run only with --online).
|
|
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import crypto from 'node:crypto'; import { fileURLToPath } from 'node:url';
|
|
import { buildProof, KINDS } from '../proof-kinds/proof-kinds.mjs';
|
|
import { buildProofOfAi } from '../proof-of-ai/proof-of-ai.mjs';
|
|
import { definePolicy, checkAction, decisionEnvelope } from '../agents/agent-policy.mjs';
|
|
import { capDeArbore, dovadaIncludere, dovadaConsistenta } from '../verify-layer/jurnal-arbore.mjs';
|
|
import { hashIntrare, semneazaCap } from '../verify-layer/sidecar.mjs';
|
|
import { merkleTree } from '../verify-layer/arbore-merkle.mjs';
|
|
import * as RISC from '../payments/risc.mjs';
|
|
import * as BAZA from '../verifiable-data/baza.mjs';
|
|
import { newAgentIdentity, openLedger, agentIdFromKey, sessionId, canonical as canonicLedger } from '../agents/agent-ledger.mjs';
|
|
import { construieste as lantAI } from '../ai-trust/lant-de-incredere.mjs';
|
|
import { hartaPublica, LEVELS } from './reguli.mjs';
|
|
|
|
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
|
const DIR = path.join(AICI, 'vectori'); const PROASPETE = path.join(AICI, 'surse-proaspete.json');
|
|
const DOV = { chitanta: new URL('../payments/dovezi/chitanta-28001-2026-10-02T1716Z.json', import.meta.url), piata: new URL('../market-settlement/dovezi/piata-28001-2026-10-02T1652Z.json', import.meta.url),
|
|
date: new URL('../verifiable-data/dovezi/notarizare-28001-2026-10-02T0820Z.json', import.meta.url),
|
|
lantAI: new URL('../ai-trust/dovezi/lant-28001-2026-10-02T0519Z.json', import.meta.url) };
|
|
const citeste = (u) => JSON.parse(fs.readFileSync(fileURLToPath(u), 'utf8'));
|
|
const clona = (x) => JSON.parse(JSON.stringify(x));
|
|
const sha256 = (b) => '0x' + crypto.createHash('sha256').update(b).digest('hex');
|
|
const HARTA = hartaPublica();
|
|
const RPC_28001 = process.env.AERE_CONFORMANCE_RPC_28001 || 'https://testnet-rpc.aere.network';
|
|
|
|
// ---------- one operation at one path ----------
|
|
export function aplica(input, op) {
|
|
const c = clona(input); let o = c; const p = op.path;
|
|
for (const k of p.slice(0, -1)) { if (o == null || !(k in Object(o))) throw new Error('mutation path not in the base: ' + p.join('.')); o = o[k]; }
|
|
const k = p[p.length - 1];
|
|
if (op.op === 'set') { if (!(k in Object(o))) throw new Error('set: nothing at ' + p.join('.')); o[k] = clona(op.value); }
|
|
else if (op.op === 'add') { if (k in Object(o)) throw new Error('add: something already at ' + p.join('.')); o[k] = clona(op.value); }
|
|
else if (op.op === 'remove') { if (!(k in Object(o))) throw new Error('remove: nothing at ' + p.join('.')); if (Array.isArray(o)) o.splice(k, 1); else delete o[k]; }
|
|
else throw new Error('unknown op ' + op.op);
|
|
return c;
|
|
}
|
|
const nivele = (failed, extra = {}) => {
|
|
const n = Object.fromEntries(LEVELS.map((l) => [l, 'NOT_MEASURED'])); n.integrity = 'PASSED'; Object.assign(n, extra);
|
|
for (const r of failed) { if (!HARTA[r]) throw new Error('a vector expects an unknown rule: ' + r); n[HARTA[r].level] = 'FAILED'; }
|
|
return n;
|
|
};
|
|
|
|
const V = []; // the vectors, in order
|
|
function valid(id, artifact, title, source, input, { exercises, requires = [], verdict = 'VALID', levels = {}, base = null, mutation = null }) {
|
|
V.push({ v: 1, kind: 'aere-conformance-vector', id, artifact, title, source, requires, exercises: [...exercises].sort(), base, mutation, input, expected: { verdict, failed: [], levels: nivele([], levels) } });
|
|
return input;
|
|
}
|
|
function invalid(id, title, bazaId, op, failed, { levels = {}, exercises = null, requires = null, producer = null } = {}) {
|
|
const b = V.find((x) => x.id === bazaId); if (!b) throw new Error('no base ' + bazaId); if (b.expected.verdict === 'INVALID') throw new Error('the base of ' + id + ' is itself INVALID');
|
|
const input = producer ? producer.input : aplica(b.input, op);
|
|
const mutation = producer ? { producer: producer.note, changed: producer.changed } : op;
|
|
V.push({ v: 1, kind: 'aere-conformance-vector', id, artifact: b.artifact, title, source: 'derived from ' + bazaId, requires: requires || b.requires, exercises: (exercises || b.exercises).slice().sort(), base: bazaId, mutation, input,
|
|
expected: { verdict: 'INVALID', failed: [...failed].sort(), levels: nivele(failed, { ...b.expected.levels, ...levels }) } });
|
|
}
|
|
|
|
// ---------- the fresh sources: a key, randomness, the chain ----------
|
|
async function proaspat(tot) {
|
|
// what is already in surse-proaspete.json STAYS (the vectors derived from it stay byte for byte); only what is missing is made.
|
|
// --tot remakes everything (other keys, other salts: every vector derived from the fresh sources changes).
|
|
const P = !tot && fs.existsSync(PROASPETE) ? JSON.parse(fs.readFileSync(PROASPETE, 'utf8')) : { v: 1, kind: 'aere-conformance-fresh-sources', at: new Date().toISOString() };
|
|
const AT = new Date().toISOString(); const facute = []; const INT = intrariKinds();
|
|
if (!P.signedHead) { // a signed tree head: three envelopes in a hash-chained log, the head signed with an ML-DSA-65 key that exists only in this call
|
|
let prev = '0x' + '00'.repeat(32);
|
|
const jurnal = ['data', 'execution', 'time'].map((k, seq) => { const proof = buildProof(k, INT[k]); const hash = hashIntrare(seq, prev, proof); const e = { seq, prev, proof, hash }; prev = hash; return e; });
|
|
const { privateKey } = crypto.generateKeyPairSync('ml-dsa-65');
|
|
P.signedHead = capDeArbore(jurnal, { host: 'conformance', createdAt: AT, signKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }) }); facute.push('signedHead');
|
|
}
|
|
if (!P.classicalHead) { // the same head "signed" by a signer that used a CLASSICAL key and declared the post-quantum scheme (the key exists only in this call)
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519'); const h = clona(P.signedHead);
|
|
h.signature = { scheme: 'ml-dsa-65', publicKey: publicKey.export({ type: 'spki', format: 'der' }).toString('base64'), signature: crypto.sign(null, Buffer.from(JSON.stringify(h.statement), 'utf8'), privateKey).toString('base64') };
|
|
P.classicalHead = h; facute.push('classicalHead');
|
|
}
|
|
if (!P.fresh) { P.fresh = buildProof('data', { name: 'conformance-fresh', content: crypto.randomBytes(32).toString('hex'), createdAt: AT }); facute.push('fresh'); } // an envelope nobody notarized
|
|
if (!P.receiptTx) { // the transaction of the recorded receipt, read from the chain
|
|
const ins = citeste(DOV.chitanta).instruction;
|
|
const rpc = async (method, params) => { const r = await fetch(RPC_28001, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), signal: AbortSignal.timeout(20000) }); if (!r.ok) throw new Error(method + ': HTTP ' + r.status); const j = await r.json(); if (j.error || !j.result) throw new Error(method + ': ' + JSON.stringify(j.error || 'no result')); return j.result; };
|
|
const t = await rpc('eth_getTransactionByHash', [ins.txHash]); const rc = await rpc('eth_getTransactionReceipt', [ins.txHash]);
|
|
P.receiptTx = { chainId: 28001, readAt: AT, tx: { from: t.from, to: t.to, value: t.value, blockHash: t.blockHash }, txReceipt: { status: rc.status, blockHash: rc.blockHash } }; facute.push('receiptTx');
|
|
}
|
|
if (!P.dataset) { // a small dataset: 12 records committed as version 1, 4 more as version 2
|
|
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-conf-set-'));
|
|
try {
|
|
const TARI = ['RO', 'DE', 'FR', 'ES']; const rec = (n, de = 0) => Array.from({ length: n }, (_, j) => { const k = de + j; return { id: 'r' + k, country: TARI[k % 4], age: 20 + ((k * 7) % 40), email: 'u' + k + '@example.test', note: 'undisclosed-' + k }; });
|
|
const db = BAZA.creeaza(path.join(T, 'set'), { name: 'conformance', indexes: ['country', 'age'] });
|
|
BAZA.adauga(db, rec(12)); BAZA.angajeaza(db, { createdAt: '2026-10-03T00:00:00.000Z' }); BAZA.adauga(db, rec(4, 12)); BAZA.angajeaza(db, { createdAt: '2026-10-03T00:01:00.000Z' });
|
|
const c1 = BAZA.versiune(db, 1), c2 = BAZA.versiune(db, 2);
|
|
const cerute = []; const seed = 'conformance-sample'; const k = 4;
|
|
await BAZA.esantioneaza({ url: 'vector://served', commitment: c2, k, seed, ia: async (u) => { cerute.push(Number(u.split('/').pop())); return null; } });
|
|
P.dataset = { older: c1, newer: c2, record: BAZA.dovadaInregistrare(db, 2, 5, { fields: ['country', 'age'] }), answer: BAZA.interogheaza(db, 2, { field: 'age', from: 30, to: 45, fields: ['country'] }),
|
|
consistency: BAZA.dovadaConsistenta(db, 1, 2), sample: { k, seed, served: Object.fromEntries(cerute.map((i) => [String(i), BAZA.dovadaInregistrare(db, 2, i, { fields: ['country'] })])) } };
|
|
facute.push('dataset');
|
|
} finally { fs.rmSync(T, { recursive: true, force: true }); }
|
|
}
|
|
for (const [nume, face] of Object.entries(PROASPETE_IN_PLUS)) if (!P[nume]) { P[nume] = await face(P, AT); facute.push(nume); }
|
|
fs.writeFileSync(PROASPETE, JSON.stringify(P, null, 1) + '\n');
|
|
console.log(path.basename(PROASPETE) + ': ' + (facute.length ? 'made now: ' + facute.join(', ') : 'nothing was missing') + '; kept: ' + Object.keys(P).filter((k) => !['v', 'kind', 'at'].includes(k) && !facute.includes(k)).join(', '));
|
|
}
|
|
/** more fresh sources, each made once: name -> async (P, AT) => value. The sections of the vectors below add theirs here. */
|
|
/** the audit log of the vectors: six envelopes of the fixed inputs in a hash chain (no key, no randomness: remade the same every time) */
|
|
function jurnalAudit(strica = null) {
|
|
const INT = intrariKinds(); let prev = '0x' + '00'.repeat(32);
|
|
return ['data', 'execution', 'time', 'identity', 'location', 'device'].map((k, seq) => { const proof = buildProof(k, INT[k]); if (strica) strica(seq, proof); const hash = hashIntrare(seq, prev, proof); const e = { seq, prev, proof, hash }; prev = hash; return e; });
|
|
}
|
|
/** the attested head of the first `count` entries, in the form the sidecar's attest-head writes */
|
|
function capAudit(log, count) {
|
|
const statement = { v: 1, kind: 'aere-audit-head', host: 'conformance', count, head: count ? log[count - 1].hash : '0x' + '00'.repeat(32), createdAt: '2026-10-03T00:00:00Z' };
|
|
return { v: 1, kind: 'aere-audit-head-attestation', statement, statementHash: sha256(Buffer.from(JSON.stringify(statement), 'utf8')) };
|
|
}
|
|
const PROASPETE_IN_PLUS = {
|
|
// the ledger of ONE agent (its ML-DSA-65 key exists only in this call): four entries written through the ledger; the same ledger
|
|
// with its fourth entry written by an agent that signs what the ledger would not write (an empty approvals list, a false decision,
|
|
// a revocation entry without a revocation); and a ledger whose "agent identity" is a classical key
|
|
agentLedger: async () => {
|
|
const t0 = Math.floor(Date.now() / 1000) - 3600; const sha = (s) => crypto.createHash('sha256').update(Buffer.from(s, 'utf8')).digest('hex');
|
|
const identity = newAgentIdentity(); const { policy, policyHash } = definePolicy({ agentId: identity.agentId, spend: { amount: '100', windowSeconds: 3600 }, recipients: ['0xbbbb'], tools: ['retrieval'] });
|
|
const L = openLedger({ identity, policy, policyHash, now: () => t0 });
|
|
L.record({ kind: 'tool', tool: 'retrieval' }); L.record({ kind: 'payment', to: '0xbbbb', amount: '40' }); L.record({ kind: 'payment', to: '0xbbbb', amount: '70' });
|
|
const trei = L.export(); L.record({ kind: 'tool', tool: 'retrieval' }); const base = L.export();
|
|
const intrare = (key, prev, seq, body) => { const b = { ...body, seq }; const signature = crypto.sign(null, Buffer.from(`${seq}|${prev}|${canonicLedger(b)}`, 'utf8'), key).toString('base64'); return { seq, prev, body: b, signature, hash: sha(`${seq}|${prev}|${canonicLedger(b)}|${signature}`) }; };
|
|
const antet = { v: 2, agentId: identity.agentId, policyHash, session: trei.session }; const prev3 = trei.entries[2].hash;
|
|
const cu = (body) => ({ ...trei, entries: [...trei.entries, intrare(identity.privateKey, prev3, 3, { ...antet, at: t0, ...body })] });
|
|
const unealta = { kind: 'tool', tool: 'retrieval', at: t0 };
|
|
const cl = crypto.generateKeyPairSync('ed25519'); const idCl = agentIdFromKey(cl.publicKey); const pc = definePolicy({ agentId: idCl, tools: ['retrieval'] }); const sesCl = sessionId(idCl, pc.policyHash);
|
|
const clasic = { version: base.version, agentId: idCl, publicKeyPem: cl.publicKey.export({ type: 'spki', format: 'pem' }), policyHash: pc.policyHash, session: sesCl,
|
|
entries: [intrare(cl.privateKey, '0'.repeat(64), 0, { v: 2, agentId: idCl, policyHash: pc.policyHash, session: sesCl, at: t0, action: unealta, decision: { allowed: true, reason: 'tool allowed' }, provenance: null })] };
|
|
return { t0, policy, ledger: base,
|
|
approvalsEmpty: cu({ action: unealta, decision: { allowed: true, reason: 'tool allowed' }, provenance: null, approvals: [] }),
|
|
falseDecision: cu({ action: { kind: 'payment', to: '0xbbbb', amount: '10', at: t0 }, decision: { allowed: false, reason: 'refused by the agent' }, provenance: null }),
|
|
revocationWithout: cu({ action: { kind: 'revocation', revokedAt: t0, at: t0 }, decision: { allowed: false, reason: 'revocation' }, provenance: null }),
|
|
classical: { ledger: clasic, policy: pc.policy } };
|
|
},
|
|
// the head of the audit log signed by its operator (ML-DSA-65), by a classical key that declares ml-dsa-65, and the public key of
|
|
// another operator; the private keys exist only in this call
|
|
auditHead: async () => {
|
|
const cap = capAudit(jurnalAudit(), 4); const op = crypto.generateKeyPairSync('ml-dsa-65'), alt = crypto.generateKeyPairSync('ml-dsa-65'), cl = crypto.generateKeyPairSync('ed25519');
|
|
const pem = (k) => k.export({ type: 'spki', format: 'pem' });
|
|
const clasic = { ...cap, signature: { scheme: 'ml-dsa-65', publicKey: cl.publicKey.export({ type: 'spki', format: 'der' }).toString('base64'), signature: crypto.sign(null, Buffer.from(JSON.stringify(cap.statement), 'utf8'), cl.privateKey).toString('base64') } };
|
|
return { signed: semneazaCap(cap, op.privateKey.export({ type: 'pkcs8', format: 'pem' })), signerPem: pem(op.publicKey), otherSignerPem: pem(alt.publicKey), classical: clasic };
|
|
},
|
|
// three trust chains of ONE agent (its ML-DSA-65 key exists only in this call), without model attestation and runtime record:
|
|
// an action the policy allows; the same action under a policy that does not list the tool (the ledger records the refusal);
|
|
// an output dated a long time before its ledger entry
|
|
aiTrust: async () => {
|
|
const t1 = Math.floor(Date.now() / 1000) - 3600, iso = (t) => new Date(t * 1000).toISOString(); const identity = newAgentIdentity();
|
|
const lant = (tools, outAt) => { const { policy, policyHash } = definePolicy({ agentId: identity.agentId, tools }); const ledger = openLedger({ identity, policy, policyHash, now: () => t1 });
|
|
const proofOfAi = buildProofOfAi({ model: { name: 'example-model', version: '1.0' }, prompt: 'summarise document X', input: 'document X', output: 'a summary of document X', tools: ['model:example-model'], agentId: identity.agentId, requester: 'tester', createdAt: iso(outAt) });
|
|
ledger.record({ kind: 'tool', tool: 'model:example-model', args: { promptHash: proofOfAi.statement.promptHash } }, { provenance: proofOfAi, at: t1 });
|
|
return lantAI({ ledger: ledger.export(), policy: { policy, policyHash }, proofOfAi, createdAt: iso(t1) }); };
|
|
return { allowed: lant(['model:example-model'], t1), refused: lant(['model:another-model'], t1), lateEntry: lant(['model:example-model'], t1 - 7200) };
|
|
},
|
|
};
|
|
|
|
// ---------- fixed inputs of the AIP-23 kinds (the inputs of the published AIP-23 kind vectors) ----------
|
|
function intrariKinds() {
|
|
const AT = '2026-09-26T00:00:00Z'; const H = (s) => sha256(Buffer.from(s, 'utf8'));
|
|
return { data: { name: 'report.csv', content: 'a,b,c', createdAt: AT }, execution: { program: 'aere-node', input: 'in', output: 'out', exitCode: 0, createdAt: AT },
|
|
identity: { subjectId: 'agent-1', publicKey: 'PK', method: 'ml-dsa-65', createdAt: AT }, compliance: { subject: 'org-x', policy: 'pq-ready', result: 'pass', evidence: 'EV', createdAt: AT },
|
|
runtime: { host: 'h1', artifactContent: 'BIN', attested: H('BIN'), matches: true, createdAt: AT }, location: { subject: 'srv-eu', region: 'eu-central', evidence: 'EV', createdAt: AT },
|
|
device: { deviceId: 'dev-9', attestation: 'ATT', publicKey: 'PK', posture: 'secure-boot', createdAt: AT }, payment: { from: '0xa', to: '0xb', amount: '100', asset: 'AERE', tx: 'TX', createdAt: AT },
|
|
ownership: { owner: '0xowner', assetId: 'nft-7', asset: 'ASSET', createdAt: AT }, time: { subject: 'SUBJ', source: 'aere-anchor', at: AT, createdAt: AT },
|
|
block: { blockHash: '0x' + 'ab'.repeat(32), stateRoot: '0x' + 'cd'.repeat(32), anchorHeight: 20255488, certificateDigest: '0x' + 'ef'.repeat(32), createdAt: AT },
|
|
authorization: { grantor: 'org-x', grantee: 'agent-7', scope: 'payments:send<=100', policy: 'POLICY', expiresAt: '2026-12-31T00:00:00Z', createdAt: AT },
|
|
settlement: { chainId: 2800, txHash: '0x' + '12'.repeat(32), blockHash: '0x' + '34'.repeat(32), from: '0xa', to: '0xb', amount: '100', asset: 'AERE', instruction: 'INSTR', createdAt: AT },
|
|
provenance: { subject: 'ARTIFACT', parents: ['SRC-1', 'SRC-2'], process: 'BUILD', actor: 'build-bot', createdAt: AT } };
|
|
}
|
|
/** Every AIP-23 kind, from its producer, on the fixed inputs. */
|
|
export function plicuriKinds() {
|
|
const AT = '2026-09-26T00:00:00Z'; const INT = intrariKinds(); const out = [];
|
|
for (const k of KINDS) { if (!INT[k]) throw new Error('proof kind ' + k + ' has no fixed input here: a new kind needs its vectors'); out.push([k, buildProof(k, INT[k])]); }
|
|
out.push(['ai', buildProofOfAi({ model: { name: 'example-model', version: '1.0' }, prompt: 'summarize', output: 'summary', tools: ['search'], agentId: 'agent-7', createdAt: AT })]);
|
|
const { policy, policyHash } = definePolicy({ agentId: 'agent-7', spend: { amount: '100', windowSeconds: 3600, asset: 'AERE' }, tools: ['search'], recipients: null });
|
|
const action = { kind: 'payment', to: '0xb', amount: '50', at: 1 };
|
|
out.push(['agent-decision', decisionEnvelope({ policyHash, action, decision: checkAction(policy, action, []), createdAt: AT })]);
|
|
return out;
|
|
}
|
|
|
|
// ---------- the vectors ----------
|
|
function construieste() {
|
|
if (!fs.existsSync(PROASPETE)) throw new Error('surse-proaspete.json is missing: run with --proaspat once (it reads testnet 28001)');
|
|
const P = JSON.parse(fs.readFileSync(PROASPETE, 'utf8'));
|
|
const E = ['envelope.form', 'envelope.integrity'];
|
|
|
|
// (A) AIP-23 envelopes: every kind VALID; its statementHash cut short (form); one statement field changed (integrity)
|
|
for (const [k, env] of plicuriKinds()) {
|
|
valid(`envelope/${k}.valid`, 'envelope', `the ${k} envelope from its producer`, 'producer on fixed inputs', { envelope: env }, { exercises: E });
|
|
invalid(`envelope/${k}.form`, `${k}: the statementHash is 63 hex digits`, `envelope/${k}.valid`, { op: 'set', path: ['envelope', 'statementHash'], value: env.statementHash.slice(0, -1) }, ['envelope.form']);
|
|
const camp = Object.keys(env.statement).find((c) => c !== 'kind' && typeof env.statement[c] === 'string'); if (!camp) throw new Error('kind ' + k + ' has no string field to change');
|
|
invalid(`envelope/${k}.integrity`, `${k}: statement.${camp} changed after hashing`, `envelope/${k}.valid`, { op: 'set', path: ['envelope', 'statement', camp], value: env.statement[camp] + '0' }, ['envelope.integrity']);
|
|
}
|
|
// files that are not envelopes at all: one base (the data envelope), the whole file replaced
|
|
{ const b = 'envelope/data.valid'; const env = V.find((x) => x.id === b).input.envelope;
|
|
const nuPlic = (nume, titlu, v) => invalid('envelope/data.' + nume, titlu, b, { op: 'set', path: ['envelope'], value: v }, ['envelope.form']);
|
|
nuPlic('form-null', 'the file holds the JSON value null', null); nuPlic('form-number', 'the file holds a number', 7); nuPlic('form-string', 'the file holds a string', 'x');
|
|
nuPlic('form-true', 'the file holds true', true); nuPlic('form-list', 'the file holds a list with the envelope inside', [env]);
|
|
const lista = '[]';
|
|
invalid('envelope/data.form-statement-list', 'the statement is a list, with the hash of that list', b, null, ['envelope.form'], { producer: { note: 'a producer that made the statement a list and hashed it', input: { envelope: { ...clona(env), statement: [], statementHash: sha256(Buffer.from(lista, 'utf8')) } }, changed: [['envelope', 'statement'], ['envelope', 'statementHash']] } });
|
|
invalid('envelope/data.form-not-json', 'the file is not JSON (the envelope cut short)', b, null, ['envelope.form'], { producer: { note: 'the file holds text that is not JSON: the input carries it as `raw`, the text of the file, in place of `envelope`', input: { raw: JSON.stringify(env).slice(0, 60) }, changed: [['envelope'], ['raw']] } }); }
|
|
// a signed envelope: the tree head of an audit log, ML-DSA-65
|
|
const b64 = P.signedHead.signature.signature;
|
|
valid('envelope/signed-head.valid', 'envelope', 'a tree head signed with ML-DSA-65', 'producer (verify-layer tree head), key made and discarded at generation', { envelope: P.signedHead }, { exercises: [...E, 'envelope.signature'], levels: { authenticity: 'PASSED' } });
|
|
invalid('envelope/signed-head.signature', 'the signature has one character changed', 'envelope/signed-head.valid', { op: 'set', path: ['envelope', 'signature', 'signature'], value: (b64[0] === 'A' ? 'B' : 'A') + b64.slice(1) }, ['envelope.signature']);
|
|
invalid('envelope/signed-head.signature-classical-key', 'the signature declares ml-dsa-65 and was made with an Ed25519 key: a classical signature is not a post-quantum one because it says so', 'envelope/signed-head.valid', null, ['envelope.signature'],
|
|
{ producer: { note: 'the signer used a classical key (Ed25519, made and discarded at generation) and declared the scheme ml-dsa-65', input: { envelope: P.classicalHead }, changed: [['envelope', 'signature', 'publicKey'], ['envelope', 'signature', 'signature']] } });
|
|
invalid('envelope/signed-head.signature-not-object', 'the signature field is a string', 'envelope/signed-head.valid', { op: 'set', path: ['envelope', 'signature'], value: 'signed' }, ['envelope.signature']);
|
|
invalid('envelope/signed-head.signature-no-key', 'the signature carries no public key', 'envelope/signed-head.valid', { op: 'remove', path: ['envelope', 'signature', 'publicKey'] }, ['envelope.signature']);
|
|
// notarization: a recorded envelope on 28001 (online), the same judged offline (the claim is not checked), a fresh one with a false claim
|
|
const D = citeste(DOV.date); const F = [...E, 'envelope.finality']; const R = ['rpc-28001'];
|
|
valid('envelope/notarized-28001.valid', 'envelope', 'a dataset commitment notarized on testnet 28001, read back under a verified post-quantum anchor', 'recorded (verifiable-data, 2026-10-02)', { envelope: D.envelope, chain: 28001 }, { exercises: F, requires: R, levels: { anchoring: 'PASSED', finality: 'PASSED' } });
|
|
valid('envelope/notarized-claim.partial', 'envelope', 'the same envelope judged offline: the notarization it claims is not checked, so it is not VALID', 'recorded (verifiable-data, 2026-10-02)', { envelope: D.envelope, chain: 28001 }, { exercises: E, verdict: 'PARTIAL' });
|
|
valid('envelope/fresh-28001.valid', 'envelope', 'an envelope nobody notarized, claiming nothing', 'producer, random content', { envelope: P.fresh, chain: 28001 }, { exercises: F, requires: R });
|
|
invalid('envelope/fresh-28001.finality', 'the same envelope claims a notarization the chain does not hold', 'envelope/fresh-28001.valid', { op: 'add', path: ['envelope', 'notarization'], value: { chainId: 28001 } }, ['envelope.finality']);
|
|
|
|
// (B) a payment under a declared policy: the recorded payment P1, then one input changed per rule
|
|
const C = citeste(DOV.chitanta); const RK = Object.keys(HARTA).filter((r) => r.startsWith('risk.'));
|
|
valid('payment-risk/company-p1.valid', 'payment-risk', 'the recorded payment to a company, allowed by its policy', 'recorded (payments, 2026-10-02)', { policy: C.policy, inputs: C.inputs }, { exercises: RK });
|
|
const ref = C.policy.refusedCounterparties[0].address; const acum = C.inputs.now;
|
|
const risc = (nume, titlu, op, r) => invalid('payment-risk/company-p1.' + nume, titlu, 'payment-risk/company-p1.valid', op, [r]);
|
|
risc('form', 'the payer is not an address', { op: 'set', path: ['inputs', 'payer'], value: '0x1234' }, 'risk.form');
|
|
risc('form-amount', 'the amount is zero', { op: 'set', path: ['inputs', 'amount'], value: '0' }, 'risk.form');
|
|
risc('form-time', 'no time given', { op: 'remove', path: ['inputs', 'now'] }, 'risk.form');
|
|
risc('form-history', 'a history entry the engine cannot count (it is refused, not left out of the window)', { op: 'set', path: ['inputs', 'history'], value: [{ at: 'yesterday', amountUsd: '0.100000' }] }, 'risk.form');
|
|
risc('asset', 'an asset the policy does not allow', { op: 'set', path: ['inputs', 'asset'], value: 'other-token' }, 'risk.asset');
|
|
risc('counterparty', 'the payee is a refused counterparty of the policy', { op: 'set', path: ['inputs', 'payee'], value: ref }, 'risk.counterparty');
|
|
risc('rate', 'no USD rate declared, and the policy requires one', { op: 'remove', path: ['inputs', 'rate'] }, 'risk.rate');
|
|
risc('rate-number', 'the USD rate given as a JSON number instead of a decimal string (refused, not rounded)', { op: 'set', path: ['inputs', 'rate', 'usdPerUnit'], value: 0.5 }, 'risk.rate');
|
|
risc('limit', 'an amount above the per-payment limit', { op: 'set', path: ['inputs', 'amount'], value: '2400000000000000000' }, 'risk.limit');
|
|
risc('window', 'three payments already in the window (the fourth is one too many)', { op: 'set', path: ['inputs', 'history'], value: [1, 2, 3].map((s) => ({ at: acum - 10 * s, amountUsd: '0.100000' })) }, 'risk.window');
|
|
|
|
// (C) the receipt of that payment: as the issuer built it; with its notarization claim (offline: PARTIAL; online: VALID)
|
|
const CK = [...Object.keys(HARTA).filter((r) => r.startsWith('receipt.')), ...E]; const claim = C.envelope.notarization; const env0 = clona(C.envelope); delete env0.notarization;
|
|
const rb = 'payment-receipt/company-p1.valid'; const tx = P.receiptTx;
|
|
valid(rb, 'payment-receipt', 'the recorded receipt as its issuer built it, with the policy, the inputs judged and the transaction read from the chain', 'recorded (payments, 2026-10-02); transaction read from 28001 at ' + tx.readAt, { receipt: { instruction: C.instruction, envelope: env0 }, policy: C.policy, inputs: C.inputs, tx: tx.tx, txReceipt: tx.txReceipt, chain: 28001 }, { exercises: CK });
|
|
const chit = (nume, titlu, op, r, o) => invalid('payment-receipt/company-p1.' + nume, titlu, rb, op, r, o);
|
|
chit('binding', 'the purpose on the instruction changed after the envelope committed to it', { op: 'set', path: ['receipt', 'instruction', 'purpose'], value: C.instruction.purpose + ' (edited)' }, ['receipt.binding']);
|
|
chit('policy', 'a looser policy shown in place of the one under the hash (so the verdict does not reproduce either)', { op: 'set', path: ['policy', 'perPaymentMaxUsd'], value: '1000.000000' }, ['receipt.policy', 'receipt.risk']);
|
|
chit('risk', 'other inputs shown than the ones the issuer judged', { op: 'set', path: ['inputs', 'now'], value: C.inputs.now + 1 }, ['receipt.risk']);
|
|
chit('chain', 'the transaction on chain pays someone else', { op: 'set', path: ['tx', 'to'], value: C.policy.refusedCounterparties[0].address }, ['receipt.chain']);
|
|
{ // the amount in USD sits under the instruction hash: the issuer writes another amount and commits to it
|
|
const r = { instruction: clona(C.instruction), envelope: clona(env0) }; r.instruction.amountUsd = '0.300000';
|
|
r.envelope.statement.instructionHash = sha256(Buffer.from(RISC.canonical(r.instruction), 'utf8')); r.envelope.statementHash = sha256(Buffer.from(JSON.stringify(r.envelope.statement), 'utf8'));
|
|
const b = V.find((x) => x.id === rb);
|
|
chit('amount', 'the issuer wrote a USD amount that is not the amount at the declared rate, and committed to it', null, ['receipt.amount'], { producer: { note: 'instruction.amountUsd changed by the issuer; instructionHash and statementHash recomputed with the published formulas', input: { ...clona(b.input), receipt: r },
|
|
changed: [['receipt', 'instruction', 'amountUsd'], ['receipt', 'envelope', 'statement', 'instructionHash'], ['receipt', 'envelope', 'statementHash']] } });
|
|
}
|
|
{ // the risk verdict sits under the instruction hash: the issuer attaches a verdict made for ANOTHER payment and commits to it
|
|
const b = V.find((x) => x.id === rb); const reangajeaza = (r) => { r.envelope.statement.instructionHash = sha256(Buffer.from(RISC.canonical(r.instruction), 'utf8')); r.envelope.statementHash = sha256(Buffer.from(JSON.stringify(r.envelope.statement), 'utf8')); return r; };
|
|
const cuVerdict = (inputs) => { const v = RISC.judeca(C.policy, inputs); if (v.decision !== 'ALLOW') throw new Error('the reused verdict must be an ALLOW'); const r = { instruction: clona(C.instruction), envelope: clona(env0) };
|
|
r.instruction.risk = { ...r.instruction.risk, policyHash: v.policyHash, inputsHash: v.inputsHash, verdictHash: v.verdictHash, decision: v.decision, unmeasured: v.unmeasured }; return { ...clona(b.input), receipt: reangajeaza(r), inputs }; };
|
|
const H = [['receipt', 'instruction', 'risk', 'inputsHash'], ['receipt', 'instruction', 'risk', 'verdictHash'], ['receipt', 'envelope', 'statement', 'instructionHash'], ['receipt', 'envelope', 'statementHash']];
|
|
chit('risk-other-payment', 'the issuer attached an ALLOW verdict that was made for a payment to someone else', null, ['receipt.risk'], { producer: { note: 'the risk verdict was computed for another payee; its hashes are on the instruction; instructionHash and statementHash recomputed', input: cuVerdict({ ...clona(C.inputs), payee: '0x' + '11'.repeat(20) }), changed: [['inputs', 'payee'], ...H] } });
|
|
chit('risk-other-rate', 'the issuer attached an ALLOW verdict that was made at another USD rate than the one on the receipt', null, ['receipt.risk'], { producer: { note: 'the risk verdict was computed at another rate; its hashes are on the instruction; instructionHash and statementHash recomputed', input: cuVerdict({ ...clona(C.inputs), rate: { ...C.inputs.rate, usdPerUnit: '0.25' } }), changed: [['inputs', 'rate', 'usdPerUnit'], ...H] } }); }
|
|
const cuClaim = { op: 'add', path: ['receipt', 'envelope', 'notarization'], value: claim };
|
|
{ const b = V.find((x) => x.id === rb); const inp = aplica(b.input, cuClaim);
|
|
valid('payment-receipt/company-p1-claim.partial', 'payment-receipt', 'the notarized receipt judged offline: its notarization claim is not checked', 'recorded (payments, 2026-10-02)', inp, { exercises: CK, verdict: 'PARTIAL', base: rb, mutation: cuClaim });
|
|
valid('payment-receipt/company-p1-28001.valid', 'payment-receipt', 'the notarized receipt judged with the chain: integrity, anchoring and finality in one verdict', 'recorded (payments, 2026-10-02)', inp, { exercises: [...CK, 'envelope.finality'], requires: R, levels: { anchoring: 'PASSED', finality: 'PASSED' }, base: rb, mutation: cuClaim }); }
|
|
|
|
// (D) market timelines recorded on 28001. The public task (`spec`) stays in the input only where the replay rule is exercised:
|
|
// each replay recomputes the task and costs seconds.
|
|
const M = citeste(DOV.piata).scenarii; const MK = Object.keys(HARTA).filter((r) => r.startsWith('market.'));
|
|
const tl = (s, cuSpec) => { const d = clona(M[s].date); if (!cuSpec) delete d.spec; if (d.envelope === null) delete d.envelope; return d; };
|
|
const mv = (id, s, titlu, o = {}) => valid('market-timeline/' + id, 'market-timeline', titlu, 'recorded (market-settlement, 2026-10-02), scenario ' + s, tl(s, o.spec), { exercises: o.exercises || MK, verdict: o.verdict, requires: o.requires, levels: o.levels });
|
|
mv('s2-slashed.valid', 'S2', 'a dispute the challenger won: the provider slashed');
|
|
mv('s3-paid-after-dispute.valid', 'S3', 'a dispute the provider won');
|
|
mv('s3-paid-after-dispute-replayed.valid', 'S3', 'the same timeline with the public task: the judge recomputes it and agrees with the arbiter', { spec: true });
|
|
mv('s4-refunded-expired.valid', 'S4', 'claimed and never delivered: the escrow reclaimed after the deadline');
|
|
mv('s5-refunded-cancelled.valid', 'S5', 'posted and cancelled: the escrow returned');
|
|
mv('s1-paid-claim.partial', 'S1', 'accepted and paid; its execution envelope claims a notarization that an offline run does not check', { exercises: [...MK, ...E], verdict: 'PARTIAL' });
|
|
mv('s1-paid-28001.valid', 'S1', 'the same timeline judged with the chain: the execution envelope is under a verified post-quantum anchor', { exercises: [...MK, ...F], requires: R, levels: { anchoring: 'PASSED', finality: 'PASSED' } });
|
|
const ev = (d, n) => d.events.findIndex((e) => e.name === n); const S = (s) => V.find((x) => x.id === 'market-timeline/' + s).input;
|
|
const piata = (nume, titlu, baza, op, r) => invalid('market-timeline/' + nume, titlu, 'market-timeline/' + baza, op, r);
|
|
{ const d = S('s2-slashed.valid'); const j = d.job;
|
|
piata('s2.posting', 'the JobPosted event names another requester than the job', 's2-slashed.valid', { op: 'set', path: ['events', ev(d, 'JobPosted'), 'args', 'requester'], value: j.provider }, ['market.posting']);
|
|
piata('s2.window', 'the dispute was raised after the challenge window closed', 's2-slashed.valid', { op: 'set', path: ['events', ev(d, 'JobDisputed'), 'timestamp'], value: j.submittedAt + j.challengeWindow + 5 }, ['market.window']);
|
|
piata('s2.deadline-submitted', 'the result was submitted after the deadline', 's2-slashed.valid', { op: 'set', path: ['events', ev(d, 'ResultSubmitted'), 'timestamp'], value: j.deadline + 1 }, ['market.deadline']);
|
|
piata('s2.arbiter', 'the dispute was resolved by an address that is not the arbiter', 's2-slashed.valid', { op: 'set', path: ['events', ev(d, 'DisputeResolved'), 'from'], value: j.requester }, ['market.arbiter']);
|
|
piata('s2.payout', 'the slashed bond is paid to someone who is not the challenger', 's2-slashed.valid', { op: 'set', path: ['events', ev(d, 'JobSlashed'), 'args', 'challenger'], value: j.provider }, ['market.payout']);
|
|
piata('s2.result', 'the result bytes shown do not hash to the commitment on chain', 's2-slashed.valid', { op: 'set', path: ['result'], value: d.result.slice(0, -2) + (d.result.endsWith('00') ? '01' : '00') }, ['market.result']); }
|
|
{ const d = S('s3-paid-after-dispute.valid');
|
|
piata('s3.payout', 'the payout names someone who is not the provider', 's3-paid-after-dispute.valid', { op: 'set', path: ['events', ev(d, 'JobPaid'), 'args', 'provider'], value: d.job.requester }, ['market.payout']);
|
|
piata('s3.arbiter-no-dispute', 'the dispute withheld from the timeline: a resolution with no dispute, and an event the job state implies is missing', 's3-paid-after-dispute.valid', { op: 'remove', path: ['events', ev(d, 'JobDisputed')] }, ['market.arbiter', 'market.complete']);
|
|
piata('s3.replay', 'another task shown than the one the job committed to: the recomputation contradicts the arbiter', 's3-paid-after-dispute-replayed.valid', { op: 'set', path: ['spec', 'seed'], value: '0x' + 'ab'.repeat(32) }, ['market.replay']); }
|
|
{ const d = S('s4-refunded-expired.valid');
|
|
piata('s4.deadline', 'the escrow was reclaimed as expired before the deadline', 's4-refunded-expired.valid', { op: 'set', path: ['events', ev(d, 'JobRefunded'), 'timestamp'], value: d.job.deadline - 1 }, ['market.deadline']);
|
|
piata('s4.complete-claim', 'the claim withheld from the timeline (the job state names a provider)', 's4-refunded-expired.valid', { op: 'remove', path: ['events', ev(d, 'JobClaimed')] }, ['market.complete']);
|
|
piata('s4.payout', 'the refund names someone who is not the requester', 's4-refunded-expired.valid', { op: 'set', path: ['events', ev(d, 'JobRefunded'), 'args', 'requester'], value: d.job.provider }, ['market.payout']); }
|
|
{ const d = S('s5-refunded-cancelled.valid');
|
|
piata('s5.form', 'an event whose time is not a whole number of seconds', 's5-refunded-cancelled.valid', { op: 'set', path: ['events', ev(d, 'JobPosted'), 'timestamp'], value: d.events[ev(d, 'JobPosted')].timestamp + 0.5 }, ['market.form']); }
|
|
{ const d = S('s1-paid-claim.partial'); const j = d.job;
|
|
piata('s1.window', 'a third party finalized inside the challenge window', 's1-paid-claim.partial', { op: 'set', path: ['events', ev(d, 'JobPaid'), 'timestamp'], value: j.submittedAt + 1 }, ['market.window']);
|
|
piata('s1.complete-submission', 'the submission withheld from the timeline (the job state has a submission time)', 's1-paid-claim.partial', { op: 'remove', path: ['events', ev(d, 'ResultSubmitted')] }, ['market.complete']);
|
|
piata('s1.deadline', 'the job was claimed after its deadline', 's1-paid-claim.partial', { op: 'set', path: ['events', ev(d, 'JobClaimed'), 'timestamp'], value: j.deadline + 1 }, ['market.deadline']);
|
|
piata('s1.proof-late', 'the execution envelope was notarized only after the challenge window closed', 's1-paid-claim.partial', { op: 'set', path: ['envelope', 'notarization', 'firstSeen'], value: j.submittedAt + j.challengeWindow + 1 }, ['market.proof']);
|
|
invalid('market-timeline/s1-28001.proof-claim-early', 'the envelope claims it was notarized earlier than the chain, read under the anchor, says', 'market-timeline/s1-paid-28001.valid', { op: 'set', path: ['envelope', 'notarization', 'firstSeen'], value: d.envelope.notarization.firstSeen - 600 }, ['market.proof']);
|
|
piata('s1.proof-other-result', 'the execution envelope names another result (and so no longer hashes to its statementHash)', 's1-paid-claim.partial', { op: 'set', path: ['envelope', 'statement', 'outputHash'], value: '0x' + 'cc'.repeat(32) }, ['envelope.integrity', 'market.proof']); }
|
|
|
|
// (F) the trust chain of an AI result (ai-trust): the bundle recorded on 28001 with its witness, and three made by one fresh agent
|
|
{ const AK = Object.keys(HARTA).filter((r) => r.startsWith('aitrust.')); const B = citeste(DOV.lantAI).bundle; const A = 'ai-trust-bundle'; const src = 'recorded (ai-trust, 2026-10-02)';
|
|
const fara = AK.filter((r) => !['aitrust.witness-head', 'aitrust.provenance-finality'].includes(r)); const L = { authenticity: 'PASSED' };
|
|
const bo = 'ai-trust/recorded.partial', bl = 'ai-trust/recorded-28001.valid';
|
|
valid(bo, A, 'the recorded trust chain judged offline: every link holds, the notarized heads it brings are not read', src, { bundle: B, chain: 28001 }, { exercises: fara, verdict: 'PARTIAL', levels: L });
|
|
valid(bl, A, 'the same trust chain with the chain: the ledger head is under a verified post-quantum anchor', src, { bundle: B, chain: 28001 }, { exercises: AK, requires: R, levels: { ...L, anchoring: 'PASSED', finality: 'PASSED' } });
|
|
const lv = (nume, titlu, op, r, o) => invalid('ai-trust/recorded.' + nume, titlu, bo, op, r, o); const altHex = (h) => h.slice(0, -1) + (h.endsWith('0') ? '1' : '0');
|
|
const rehash = (e) => { e.statementHash = sha256(Buffer.from(JSON.stringify(e.statement), 'utf8')); return e; }; const cu = (f) => { const b = clona(B); f(b); return { bundle: b, chain: 28001 }; };
|
|
lv('form', 'the bundle declares another format', { op: 'set', path: ['bundle', 'format'], value: 'another-format' }, ['aitrust.form'], { levels: { authenticity: 'NOT_MEASURED' } });
|
|
lv('identity-mismatch', 'the bundle names another agent than the key derives', { op: 'set', path: ['bundle', 'identity', 'agentId'], value: 'aere-agent:' + '11'.repeat(20) }, ['aitrust.identity-mismatch']);
|
|
lv('identity-key', 'the public key of the identity is not a key', { op: 'set', path: ['bundle', 'identity', 'publicKeyPem'], value: 'not a key' }, ['aitrust.identity-key']);
|
|
lv('version-attestation', 'the model attestation changed after it was hashed', { op: 'set', path: ['bundle', 'version', 'attestation', 'statement', 'artifacts', 0, 'sha256'], value: '0x' + 'ef'.repeat(32) }, ['aitrust.version-attestation']);
|
|
lv('version-artifact-mismatch', 'the attestation covers another artifact than the weights the output names', null, ['aitrust.version-artifact-mismatch'], { producer: { note: 'the attester attested another artifact digest (statement rehashed)', input: cu((b) => { b.version.attestation.statement.artifacts[0].sha256 = '0x' + 'ab'.repeat(32); rehash(b.version.attestation); }), changed: [['bundle', 'version', 'attestation', 'statement', 'artifacts', 0, 'sha256'], ['bundle', 'version', 'attestation', 'statementHash']] } });
|
|
lv('version-label-mismatch', 'the attestation names another model version than the output', null, ['aitrust.version-label-mismatch'], { producer: { note: 'the attester attested another version label (statement rehashed)', input: cu((b) => { b.version.attestation.statement.subject.version = String(b.version.attestation.statement.subject.version) + '-other'; rehash(b.version.attestation); }), changed: [['bundle', 'version', 'attestation', 'statement', 'subject', 'version'], ['bundle', 'version', 'attestation', 'statementHash']] } });
|
|
lv('runtime-record', 'the runtime record is an envelope of another kind', { op: 'set', path: ['bundle', 'runtime', 'record', 'kind'], value: 'aere-proof-of-data-attestation' }, ['aitrust.runtime-record']);
|
|
lv('runtime-artifact-mismatch', 'the host declared another artifact running', null, ['aitrust.runtime-artifact-mismatch'], { producer: { note: 'the host recorded another artifact digest (statement rehashed)', input: cu((b) => { b.runtime.record.statement.artifactSha256 = '0x' + 'cd'.repeat(32); rehash(b.runtime.record); }), changed: [['bundle', 'runtime', 'record', 'statement', 'artifactSha256'], ['bundle', 'runtime', 'record', 'statementHash']] } });
|
|
lv('runtime-after-output', 'the runtime record is dated an hour after the output', null, ['aitrust.runtime-after-output'], { producer: { note: 'the host dated its record after the output (statement rehashed)', input: cu((b) => { b.runtime.record.statement.createdAt = new Date(Date.parse(b.provenance.proofOfAi.statement.createdAt) + 3600000).toISOString(); rehash(b.runtime.record); }), changed: [['bundle', 'runtime', 'record', 'statement', 'createdAt'], ['bundle', 'runtime', 'record', 'statementHash']] } });
|
|
lv('audit-log-broken', 'one entry hash of the audit log changed', { op: 'set', path: ['bundle', 'runtime', 'auditLog', 0, 'hash'], value: altHex(B.runtime.auditLog[0].hash) }, ['aitrust.audit-log-broken']);
|
|
lv('runtime-not-in-audit-log', 'an audit log that does not hold the runtime record', { op: 'set', path: ['bundle', 'runtime', 'auditLog'], value: [] }, ['aitrust.runtime-not-in-audit-log']);
|
|
lv('audit-head', 'the attested head of the audit log changed after it was hashed', { op: 'set', path: ['bundle', 'runtime', 'head', 'statementHash'], value: altHex(B.runtime.head.statementHash) }, ['aitrust.audit-head']);
|
|
lv('policy-hash', 'the bundle pins another policy hash than the policy it shows', { op: 'set', path: ['bundle', 'permissions', 'policyHash'], value: altHex(B.permissions.policyHash) }, ['aitrust.policy-hash']);
|
|
lv('policy', 'the policy shown is not a policy', { op: 'set', path: ['bundle', 'permissions', 'policy'], value: 'no policy' }, ['aitrust.policy']);
|
|
lv('ledger-invalid', 'a ledger entry changed after it was signed', { op: 'set', path: ['bundle', 'permissions', 'ledger', 'entries', 0, 'body', 'action', 'args'], value: { promptHash: '0x' + '00'.repeat(32) } }, ['aitrust.ledger-invalid']);
|
|
lv('output-not-in-ledger', 'another output than the one the ledger entry links', null, ['aitrust.output-not-in-ledger'], { producer: { note: 'the producer of the output wrote another output digest (statement rehashed); the ledger still links the first', input: cu((b) => { b.provenance.proofOfAi.statement.outputHash = sha256(Buffer.from('another output', 'utf8')); rehash(b.provenance.proofOfAi); }), changed: [['bundle', 'provenance', 'proofOfAi', 'statement', 'outputHash'], ['bundle', 'provenance', 'proofOfAi', 'statementHash']] } });
|
|
lv('provenance-envelope', 'the Proof of AI envelope is of another kind (and so is no longer what the ledger links)', { op: 'set', path: ['bundle', 'provenance', 'proofOfAi', 'kind'], value: 'aere-proof-of-data-attestation' }, ['aitrust.output-not-in-ledger', 'aitrust.provenance-envelope']);
|
|
invalid('ai-trust/recorded-28001.witness-head', 'a notarized head that is not a head of this ledger', bl, { op: 'set', path: ['bundle', 'witness', 'heads', 0, 'statementHash'], value: altHex(B.witness.heads[0].statementHash) }, ['aitrust.witness-head'], { levels: { anchoring: 'FAILED', finality: 'NOT_MEASURED' } });
|
|
invalid('ai-trust/recorded-28001.provenance-finality', 'the Proof of AI envelope claims a notarization the chain does not hold (and so is no longer what the ledger links)', bl, { op: 'add', path: ['bundle', 'provenance', 'proofOfAi', 'notarization'], value: { chainId: 28001 } }, ['aitrust.output-not-in-ledger', 'aitrust.provenance-finality'], { levels: { anchoring: 'FAILED', finality: 'PASSED' } });
|
|
// one fresh agent, three chains: allowed (no witness at all), refused by the policy, dated apart
|
|
const T = P.aiTrust; const fb = 'ai-trust/fresh-agent.partial'; const fsrc = 'producers (agents ledger, Proof of AI), one agent key made and discarded at generation';
|
|
valid(fb, A, 'a trust chain with no witness: the links hold, and nobody but the agent fixed a time', fsrc, { bundle: T.allowed }, { exercises: fara, verdict: 'PARTIAL', levels: L });
|
|
invalid('ai-trust/fresh-agent.action-refused', 'the same action under a policy that does not allow the tool: the ledger entry of the output is a refusal', fb, null, ['aitrust.action-refused'], { producer: { note: 'the owner\'s policy lists another tool; the agent recorded the action and the ledger holds the refusal', input: { bundle: T.refused }, changed: [['bundle', 'permissions']] } });
|
|
invalid('ai-trust/fresh-agent.ledger-time-mismatch', 'the output is dated two hours before its ledger entry', fb, null, ['aitrust.ledger-time-mismatch'], { producer: { note: 'the producer dated the output two hours before the agent recorded it', input: { bundle: T.lateEntry }, changed: [['bundle', 'permissions'], ['bundle', 'provenance']] } }); }
|
|
// (G) an audit log (verify-layer): the chain, an attested head, a signed head; inclusion and consistency against tree heads
|
|
{ const LK = Object.keys(HARTA).filter((r) => r.startsWith('auditlog.')), TK = Object.keys(HARTA).filter((r) => r.startsWith('audittree.')); const sig = ['auditlog.head-unsigned', 'auditlog.head-scheme', 'auditlog.head-key', 'auditlog.head-signature', 'auditlog.head-signer'];
|
|
const lant = LK.filter((r) => !r.includes('.head-')), cuCap = LK.filter((r) => !sig.includes(r)); const log = jurnalAudit(), patru = log.slice(0, 4), cap4 = capAudit(log, 4); const AH = P.auditHead;
|
|
const altHex = (h) => h.slice(0, -1) + (h.endsWith('0') ? '1' : '0'); const src = 'producers (verify-layer log and tree) on the fixed inputs';
|
|
const NA = { integrity: 'NOT_MEASURED', authenticity: 'FAILED' };
|
|
valid('audit-log/six-entries.valid', 'audit-log', 'a log of six entries, its hash chain intact', src, { log }, { exercises: lant });
|
|
const lg = (nume, titlu, op, r) => invalid('audit-log/six-entries.' + nume, titlu, 'audit-log/six-entries.valid', op, [r]);
|
|
lg('entry', 'an entry of the log is not a log entry', { op: 'set', path: ['log', 1], value: 'not an entry' }, 'auditlog.entry');
|
|
lg('seq', 'an entry carries another sequence number', { op: 'set', path: ['log', 1, 'seq'], value: 5 }, 'auditlog.seq');
|
|
lg('chain', 'an entry does not link the one before it', { op: 'set', path: ['log', 2, 'prev'], value: altHex(log[2].prev) }, 'auditlog.chain');
|
|
lg('hash', 'the hash of the last entry is not the hash of its content', { op: 'set', path: ['log', 5, 'hash'], value: altHex(log[5].hash) }, 'auditlog.hash');
|
|
const bh = 'audit-log/head-at-four.valid';
|
|
valid(bh, 'audit-log', 'four entries and the attested head that covers them', src, { log: patru, head: cap4 }, { exercises: cuCap });
|
|
const hd = (nume, titlu, op, r, o) => invalid('audit-log/head-at-four.' + nume, titlu, bh, op, [r], o);
|
|
hd('head-form', 'the head is an attestation of another kind', { op: 'set', path: ['head', 'kind'], value: 'aere-proof-of-data-attestation' }, 'auditlog.head-form');
|
|
hd('head-hash', 'the head changed after it was hashed', { op: 'set', path: ['head', 'statementHash'], value: altHex(cap4.statementHash) }, 'auditlog.head-hash');
|
|
hd('head-short', 'the last attested entry removed from the log', { op: 'remove', path: ['log', 3] }, 'auditlog.head-short');
|
|
{ const rescris = jurnalAudit((seq, proof) => { if (seq === 1) { proof.statement.createdAt = '2026-09-27T00:00:00Z'; proof.statementHash = sha256(Buffer.from(JSON.stringify(proof.statement), 'utf8')); } }).slice(0, 4);
|
|
hd('head-rewritten', 'the host rewrote an entry before the attested point and chained the log again', null, 'auditlog.head-rewritten', { producer: { note: 'entry 1 rewritten (another envelope), every later entry re-chained; the head is the one attested before', input: { log: rescris, head: cap4 }, changed: [['log', 1], ['log', 2], ['log', 3]] } }); }
|
|
const bs = 'audit-log/signed-head.valid'; const b64 = AH.signed.signature.signature;
|
|
valid(bs, 'audit-log', 'the same log against its head signed by the operator (ML-DSA-65), with the signer expected', src + '; operator key made and discarded at generation', { log: patru, head: AH.signed, signer: AH.signerPem }, { exercises: LK, levels: { authenticity: 'PASSED' } });
|
|
const sg = (nume, titlu, op, r, o = {}) => invalid('audit-log/signed-head.' + nume, titlu, bs, op, [r], { levels: NA, ...o });
|
|
sg('head-unsigned', 'the signature taken off a head whose signer is required', { op: 'remove', path: ['head', 'signature'] }, 'auditlog.head-unsigned');
|
|
sg('head-scheme', 'the head signature declares another scheme', { op: 'set', path: ['head', 'signature', 'scheme'], value: 'ed25519' }, 'auditlog.head-scheme');
|
|
sg('head-scheme-classical-key', 'the head is signed with an Ed25519 key that declares ml-dsa-65', null, 'auditlog.head-scheme', { producer: { note: 'the signer used a classical key (made and discarded at generation) and declared the scheme ml-dsa-65', input: { log: patru, head: AH.classical, signer: AH.signerPem }, changed: [['head', 'signature', 'publicKey'], ['head', 'signature', 'signature']] } });
|
|
sg('head-key', 'the public key of the head signature is not a key', { op: 'set', path: ['head', 'signature', 'publicKey'], value: 'AAAA' }, 'auditlog.head-key');
|
|
sg('head-signature', 'the head signature has one character changed', { op: 'set', path: ['head', 'signature', 'signature'], value: (b64[0] === 'A' ? 'B' : 'A') + b64.slice(1) }, 'auditlog.head-signature');
|
|
sg('head-signer', 'the head is signed, by another key than the signer expected', { op: 'set', path: ['signer'], value: AH.otherSignerPem }, 'auditlog.head-signer');
|
|
// the tree: inclusion of entry 2 under the head of six, consistency from the head of four to the head of six
|
|
const t6 = capDeArbore(log, { host: 'conformance', createdAt: '2026-10-03T00:00:00Z' }), t4 = capDeArbore(patru, { host: 'conformance', createdAt: '2026-10-03T00:00:00Z' }); const inc = dovadaIncludere(log, 2);
|
|
const bi = 'audit-inclusion/entry-2.valid'; const INC = TK.filter((r) => !r.includes('consistency'));
|
|
valid(bi, 'audit-inclusion', 'entry 2 proven in the tree of six entries, against its head', src, { proof: inc, head: t6 }, { exercises: INC });
|
|
const ic = (nume, titlu, op, r, o) => invalid('audit-inclusion/entry-2.' + nume, titlu, bi, op, [r], o);
|
|
ic('proof-form', 'the proof declares another kind', { op: 'set', path: ['proof', 'kind'], value: 'another-proof' }, 'audittree.proof-form');
|
|
ic('inclusion-index', 'the proof is for another index than the entry shown', { op: 'set', path: ['proof', 'index'], value: 3 }, 'audittree.inclusion-index');
|
|
ic('inclusion-entry', 'the entry shown changed after it was hashed', { op: 'set', path: ['proof', 'entry', 'prev'], value: altHex(inc.entry.prev) }, 'audittree.inclusion-entry');
|
|
ic('inclusion-leaf', 'the leaf is not the leaf of the entry shown', { op: 'set', path: ['proof', 'leafHash'], value: altHex(inc.leafHash) }, 'audittree.inclusion-leaf');
|
|
ic('inclusion-path', 'one hash of the path changed', { op: 'set', path: ['proof', 'path', 0], value: altHex(inc.path[0]) }, 'audittree.inclusion-path');
|
|
ic('inclusion-head', 'the head given is the head of another tree', { op: 'set', path: ['head'], value: t4 }, 'audittree.inclusion-head');
|
|
ic('head-form', 'the tree head is an attestation of another kind', { op: 'set', path: ['head', 'kind'], value: 'aere-audit-head-attestation' }, 'audittree.head-form');
|
|
ic('head-hash', 'the tree head changed after it was hashed', { op: 'set', path: ['head', 'statementHash'], value: altHex(t6.statementHash) }, 'audittree.head-hash');
|
|
{ const stricat = jurnalAudit((seq, proof) => { if (seq === 2) proof.statementHash = altHex(proof.statementHash); });
|
|
ic('inclusion-envelope', 'the host logged an envelope that does not hash to its statementHash: the entry is in the tree, and it is not an envelope', null, 'audittree.inclusion-envelope', { producer: { note: 'the host wrote a broken envelope into entry 2 and chained the log over it', input: { proof: dovadaIncludere(stricat, 2), head: capDeArbore(stricat, { host: 'conformance', createdAt: '2026-10-03T00:00:00Z' }) }, changed: [['proof'], ['head']] } }); }
|
|
const cons = dovadaConsistenta(log, 4); const bc = 'audit-consistency/four-to-six.valid';
|
|
valid(bc, 'audit-consistency', 'the tree of six entries extends the tree of four', src, { proof: cons, older: t4, newer: t6 }, { exercises: TK.filter((r) => !r.includes('inclusion')) });
|
|
const co = (nume, titlu, op, r) => invalid('audit-consistency/four-to-six.' + nume, titlu, bc, op, [r]);
|
|
co('consistency-start', 'the proof starts at another root than the old head', { op: 'set', path: ['proof', 'firstRoot'], value: altHex(cons.firstRoot) }, 'audittree.consistency-start');
|
|
co('consistency-end', 'the proof ends at another root than the new head', { op: 'set', path: ['proof', 'secondRoot'], value: altHex(cons.secondRoot) }, 'audittree.consistency-end');
|
|
co('consistency', 'one hash of the consistency path changed', { op: 'set', path: ['proof', 'path', 0], value: altHex(cons.path[0]) }, 'audittree.consistency'); }
|
|
// (H) an agent's signed ledger (agents): four entries of one fresh agent, verified as a stranger would
|
|
{ const GK = Object.keys(HARTA).filter((r) => r.startsWith('agents.')); const G = P.agentLedger; const bg = 'agent-ledger/four-entries.valid'; const L0 = G.ledger;
|
|
const altHex = (h) => h.slice(0, -1) + (h.endsWith('0') ? '1' : '0'); const NA = { integrity: 'NOT_MEASURED', authenticity: 'FAILED' }, NI = { authenticity: 'NOT_MEASURED' };
|
|
valid(bg, 'agent-ledger', 'the ledger of an agent: a tool call, a payment, a payment refused above the limit, a tool call', 'producer (agents ledger), one agent key made and discarded at generation', { ledger: L0, policy: G.policy }, { exercises: GK, levels: { authenticity: 'PASSED' } });
|
|
const ag = (nume, titlu, op, r, o = {}) => invalid('agent-ledger/four-entries.' + nume, titlu, bg, op, [r], { levels: ['agents.key', 'agents.identity', 'agents.signature'].includes(r) ? NA : NI, ...o });
|
|
ag('ledger-form', 'the entries are not a list', { op: 'set', path: ['ledger', 'entries'], value: 'none' }, 'agents.ledger-form');
|
|
ag('policy', 'the verifier pins another policy hash than the policy given', { op: 'add', path: ['policyHash'], value: '0x' + 'ab'.repeat(32) }, 'agents.policy');
|
|
ag('other-policy', 'the ledger names another policy', { op: 'set', path: ['ledger', 'policyHash'], value: altHex(L0.policyHash) }, 'agents.other-policy');
|
|
ag('session', 'the session is not the one derived from the agent and the policy', { op: 'set', path: ['ledger', 'session'], value: altHex(L0.session) }, 'agents.session');
|
|
ag('key', 'the public key is not a key', { op: 'set', path: ['ledger', 'publicKeyPem'], value: 'not a key' }, 'agents.key');
|
|
ag('key-classical', 'a ledger whose agent identity is an Ed25519 key: the id is derived from it and every entry is signed by it', null, 'agents.key', { producer: { note: 'the agent\'s identity key is a classical key (made and discarded at generation)', input: { ledger: G.classical.ledger, policy: G.classical.policy }, changed: [['ledger'], ['policy']] } });
|
|
{ const x = 'aere-agent:' + '11'.repeat(20); ag('identity', 'the ledger names an agent id that its key does not derive (with the session of that id)', null, 'agents.identity', { producer: { note: 'the ledger claims another agent id, and the session derived from it', input: { ledger: { ...clona(L0), agentId: x, session: sessionId(x, L0.policyHash) }, policy: G.policy }, changed: [['ledger', 'agentId'], ['ledger', 'session']] } }); }
|
|
ag('anchor', 'an anchor that names no entry of this ledger', { op: 'add', path: ['anchors'], value: [{ seq: 99, hash: L0.entries[0].hash, at: G.t0 }] }, 'agents.anchor');
|
|
ag('seq', 'an entry carries another sequence number', { op: 'set', path: ['ledger', 'entries', 1, 'seq'], value: 5 }, 'agents.seq');
|
|
ag('chain', 'an entry does not link the one before it', { op: 'set', path: ['ledger', 'entries', 1, 'prev'], value: altHex(L0.entries[1].prev) }, 'agents.chain');
|
|
ag('body', 'the body of an entry names another session than its ledger', { op: 'set', path: ['ledger', 'entries', 1, 'body', 'session'], value: altHex(L0.session) }, 'agents.body');
|
|
ag('signature', 'the signature of an entry has one character changed', { op: 'set', path: ['ledger', 'entries', 1, 'signature'], value: (L0.entries[1].signature[0] === 'A' ? 'B' : 'A') + L0.entries[1].signature.slice(1) }, 'agents.signature');
|
|
ag('hash', 'the hash of the last entry does not reproduce', { op: 'set', path: ['ledger', 'entries', 3, 'hash'], value: altHex(L0.entries[3].hash) }, 'agents.hash');
|
|
ag('time', 'the verifier\'s clock is before the entries: they are from its future', { op: 'add', path: ['maxTime'], value: G.t0 - 1000 }, 'agents.time');
|
|
const al4 = (nume, titlu, l, r, nota) => ag(nume, titlu, null, r, { producer: { note: nota, input: { ledger: l, policy: G.policy }, changed: [['ledger', 'entries', 3]] } });
|
|
al4('approvals', 'the fourth entry carries an empty approvals list', G.approvalsEmpty, 'agents.approvals', 'the agent signed an entry with an approvals field that is an empty list');
|
|
al4('decision', 'the fourth entry writes a refusal for a payment the policy allows', G.falseDecision, 'agents.decision', 'the agent signed an entry whose decision is not the one the policy gives');
|
|
al4('revocation', 'the fourth entry is a revocation entry that carries no revocation', G.revocationWithout, 'agents.revocation', 'the agent signed a revocation entry without the owner\'s revocation'); }
|
|
// (E) a dataset: a record, a range answer, a consistency proof, an availability sample
|
|
const DS = P.dataset; const REC = ['dataset.form', 'dataset.commitment', 'dataset.field', 'dataset.inclusion']; const src = 'producer (verifiable-data), random salts, generated ' + P.at;
|
|
valid('dataset-record/r5.valid', 'dataset-record', 'one record against the commitment of version 2, two fields disclosed', src, { record: DS.record, commitment: DS.newer }, { exercises: REC });
|
|
const inr = (nume, titlu, op, r) => invalid('dataset-record/r5.' + nume, titlu, 'dataset-record/r5.valid', op, [r]);
|
|
const altHex = (h) => h.slice(0, -1) + (h.endsWith('0') ? '1' : '0');
|
|
inr('form', 'the field commitments are not an object', { op: 'set', path: ['record', 'c'], value: 'x' }, 'dataset.form');
|
|
inr('commitment', 'the record names another commitment', { op: 'set', path: ['record', 'commitmentHash'], value: altHex(DS.record.commitmentHash) }, 'dataset.commitment');
|
|
inr('field', 'a disclosed value changed after commitment', { op: 'set', path: ['record', 'disclosed', 'age', 'value'], value: DS.record.disclosed.age.value + 1 }, 'dataset.field');
|
|
inr('field-no-salt', 'a disclosed field shown without its salt', { op: 'remove', path: ['record', 'disclosed', 'age', 'salt'] }, 'dataset.field');
|
|
inr('inclusion', 'one hash of the log proof changed', { op: 'set', path: ['record', 'logProof', 'path', 0], value: altHex(DS.record.logProof.path[0]) }, 'dataset.inclusion');
|
|
const ANS = [...REC, 'dataset.index', 'dataset.range', 'dataset.order', 'dataset.completeness'];
|
|
if (DS.answer.rows.length < 4) throw new Error('the fresh dataset answer has under four rows; regenerate with --proaspat');
|
|
valid('dataset-answer/age-30-45.valid', 'dataset-answer', 'a range query on an indexed field: every row, and the two neighbours that prove nothing is missing', src, { answer: DS.answer, commitment: DS.newer }, { exercises: ANS });
|
|
const rasp = (nume, titlu, op, r) => invalid('dataset-answer/age-30-45.' + nume, titlu, 'dataset-answer/age-30-45.valid', op, [r]);
|
|
rasp('commitment', 'an answer that names another version than the commitment it is checked against', { op: 'set', path: ['answer', 'version'], value: DS.answer.version - 1 }, 'dataset.commitment');
|
|
rasp('index-size', 'the answer states another index size than the commitment', { op: 'set', path: ['answer', 'indexSize'], value: DS.answer.indexSize + 1 }, 'dataset.commitment');
|
|
rasp('index-undisclosed', 'a row that does not disclose the queried field', { op: 'remove', path: ['answer', 'rows', 1, 'disclosed', 'age'] }, 'dataset.index');
|
|
rasp('index', 'one hash of a row\'s index proof changed', { op: 'set', path: ['answer', 'rows', 1, 'indexProof', 'path', 0], value: altHex(DS.answer.rows[1].indexProof.path[0]) }, 'dataset.index');
|
|
rasp('range', 'the answer claims a narrower range than the rows it returns', { op: 'set', path: ['answer', 'to'], value: DS.answer.rows[0].disclosed.age.value }, 'dataset.range');
|
|
rasp('completeness', 'a row dropped from the middle of the run', { op: 'remove', path: ['answer', 'rows', 1] }, 'dataset.completeness');
|
|
{ // the two forgeries only the SERVER of an answer can make: a matching row withheld and shown as the neighbour that closes the run
|
|
const b = V.find((x) => x.id === 'dataset-answer/age-30-45.valid'); const vecin = (r) => ({ pos: r.pos, value: r.disclosed.age.value, i: r.i, recordLeaf: BAZA.frunzaInregistrare(r.id, r.c), indexProof: r.indexProof });
|
|
const dupa = clona(b.input); const ultima = dupa.answer.rows.pop(); dupa.answer.after = vecin(ultima);
|
|
invalid('dataset-answer/age-30-45.completeness-last', 'the last matching row withheld and shown as the neighbour after the run (its value is still in range)', 'dataset-answer/age-30-45.valid', null, ['dataset.completeness'], { producer: { note: 'the server dropped the last row and presented it as the neighbour after', input: dupa, changed: [['answer', 'rows', dupa.answer.rows.length], ['answer', 'after']] } });
|
|
const inainte = clona(b.input); const prima = inainte.answer.rows.shift(); inainte.answer.before = vecin(prima);
|
|
invalid('dataset-answer/age-30-45.completeness-first', 'the first matching row withheld and shown as the neighbour before the run', 'dataset-answer/age-30-45.valid', null, ['dataset.completeness'], { producer: { note: 'the server dropped the first row and presented it as the neighbour before', input: inainte, changed: [['answer', 'rows'], ['answer', 'before']] } }); }
|
|
{ // order sits under the index root: two publishers of the same two records, one builds the index sorted, the other not
|
|
const recs = [{ id: 'x', k: 'b', salt: '00'.repeat(16) }, { id: 'y', k: 'a', salt: '11'.repeat(16) }].map((r) => { const c = { k: BAZA.angajamentCamp('k', r.k, r.salt) }; return { ...r, c, leaf: BAZA.frunzaInregistrare(r.id, c) }; });
|
|
const log = merkleTree(recs.map((r) => r.leaf)); const dov = (p) => ({ index: p.index, treeSize: p.treeSize, path: p.path });
|
|
const publica = (ordine, nume) => { const idx = merkleTree(ordine.map((i) => BAZA.frunzaIndex('k', recs[i].k, i, recs[i].leaf)));
|
|
const c = { v: 1, format: BAZA.FORMAT, name: nume, version: 1, size: 2, logRoot: log.root(), createdAt: '2026-10-03T00:00:00.000Z', indexes: { k: { root: idx.root(), size: 2 } }, previous: null };
|
|
const rand = (pos) => { const i = ordine[pos]; return { pos, i, id: recs[i].id, c: recs[i].c, disclosed: { k: { value: recs[i].k, salt: recs[i].salt } }, logProof: dov(log.inclusionProof(i, 2)), indexProof: dov(idx.inclusionProof(pos, 2)) }; };
|
|
return { answer: { v: 1, kind: BAZA.KIND_RASPUNS, name: nume, version: 1, commitmentHash: BAZA.hashAngajament(c), field: 'k', from: 'a', to: 'b', indexSize: 2, rows: [rand(0), rand(1)], before: null, after: null }, commitment: c }; };
|
|
valid('dataset-answer/two-records.valid', 'dataset-answer', 'two records, the index built in sorted order', 'producer formulas (verifiable-data), fixed salts', publica([1, 0], 'two-records'), { exercises: ANS });
|
|
invalid('dataset-answer/two-records.order', 'the same two records from a publisher whose index is not sorted: every proof holds, the order does not', 'dataset-answer/two-records.valid', null, ['dataset.order'],
|
|
{ producer: { note: 'the publisher built the index in record order instead of sorted order; the index root, the commitment hash and the rows follow from that', input: publica([0, 1], 'two-records'), changed: [['commitment', 'indexes', 'k', 'root'], ['answer', 'commitmentHash'], ['answer', 'rows']] } }); }
|
|
valid('dataset-consistency/v1-v2.valid', 'dataset-consistency', 'version 1 is a prefix of version 2', src, { proof: DS.consistency, older: DS.older, newer: DS.newer }, { exercises: ['dataset.commitment', 'dataset.consistency'] });
|
|
invalid('dataset-consistency/v1-v2.commitment', 'the proof names another newer commitment', 'dataset-consistency/v1-v2.valid', { op: 'set', path: ['proof', 'to', 'commitmentHash'], value: altHex(DS.consistency.to.commitmentHash) }, ['dataset.commitment']);
|
|
invalid('dataset-consistency/v1-v2.consistency', 'one hash of the consistency path changed', 'dataset-consistency/v1-v2.valid', { op: 'set', path: ['proof', 'proof', 'path', 0], value: altHex(DS.consistency.proof.path[0]) }, ['dataset.consistency']);
|
|
valid('dataset-sample/four-of-sixteen.valid', 'dataset-sample', 'four positions drawn from a seed, each served and verified', src, { commitment: DS.newer, k: DS.sample.k, seed: DS.sample.seed, served: DS.sample.served }, { exercises: [...REC, 'dataset.sample'] });
|
|
invalid('dataset-sample/four-of-sixteen.sample', 'the publisher does not serve one of the sampled records', 'dataset-sample/four-of-sixteen.valid', { op: 'remove', path: ['served', Object.keys(DS.sample.served)[0]] }, ['dataset.sample']);
|
|
return V;
|
|
}
|
|
|
|
const text = (v) => JSON.stringify(v, null, 1) + '\n';
|
|
function fisiere(vectori) {
|
|
const out = new Map(); const reguli = {};
|
|
for (const v of vectori) { if (out.has(v.id + '.json')) throw new Error('two vectors with the id ' + v.id); out.set(v.id + '.json', text(v)); for (const r of v.expected.failed) (reguli[r] || (reguli[r] = [])).push(v.id); }
|
|
const artifacte = {}; for (const v of vectori) artifacte[v.artifact] = (artifacte[v.artifact] || 0) + 1;
|
|
const index = { v: 1, kind: 'aere-conformance-vector-index', count: vectori.length, artifacts: artifacte, offline: vectori.filter((v) => !v.requires.length).length, online: vectori.filter((v) => v.requires.length).length,
|
|
rules: Object.fromEntries(Object.keys(reguli).sort().map((r) => [r, reguli[r]])), vectors: vectori.map((v) => ({ id: v.id, artifact: v.artifact, verdict: v.expected.verdict, requires: v.requires, sha256: sha256(Buffer.from(out.get(v.id + '.json'), 'utf8')) })) };
|
|
out.set('INDEX.json', text(index)); return out;
|
|
}
|
|
const peDisc = (d, pre = '') => (fs.existsSync(d) ? fs.readdirSync(d, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? peDisc(path.join(d, e.name), pre + e.name + '/') : [pre + e.name])) : []);
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
const a = process.argv.slice(2);
|
|
if (a.includes('--proaspat')) await proaspat(a.includes('--tot'));
|
|
const out = fisiere(construieste());
|
|
if (a.includes('--verifica')) {
|
|
let dif = 0; for (const [f, t] of out) { const p = path.join(DIR, f); if (!fs.existsSync(p) || fs.readFileSync(p, 'utf8').replace(/\r\n/g, '\n') !== t) { dif++; console.log('DIFERIT: ' + f); } }
|
|
const straine = peDisc(DIR).filter((f) => !out.has(f)); if (straine.length) { dif += straine.length; console.log('STRAINE: ' + straine.join(', ')); }
|
|
console.log(dif ? `DIFERIT (${dif})` : `LA ZI (${out.size - 1} vectors + INDEX.json)`); process.exitCode = dif ? 1 : 0;
|
|
} else {
|
|
for (const f of peDisc(DIR)) if (!out.has(f)) fs.rmSync(path.join(DIR, f));
|
|
for (const [f, t] of out) { const p = path.join(DIR, f); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, t); }
|
|
const idx = JSON.parse(out.get('INDEX.json')); console.log(`written ${idx.count} vectors (${idx.offline} offline, ${idx.online} need testnet 28001): ` + Object.entries(idx.artifacts).map(([k, n]) => k + ' ' + n).join(', '));
|
|
}
|
|
}
|