@attested-intelligence/aga-verify 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/aga-verify.mjs +163 -0
- package/example-bundle.json +284 -0
- package/package.json +45 -0
- package/verify.ts +240 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# AGA Independent Verifier (`@attested-intelligence/aga-verify`)
|
|
2
|
+
|
|
3
|
+
Standalone verification of AGA **Evidence Bundles** using only standard
|
|
4
|
+
cryptographic primitives. **This verifier imports zero modules from the
|
|
5
|
+
AGA codebase** — it depends only on `@noble/ed25519` and `@noble/hashes`.
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
AGA claims that Evidence Bundles provide tamper-evident, offline-verifiable
|
|
10
|
+
proof of governance enforcement. This tool proves that claim is checkable by
|
|
11
|
+
**anyone**, with no trust in AGA's own code: it re-implements the complete
|
|
12
|
+
verification from scratch and runs against a bundle you provide.
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# verify any AGA evidence bundle
|
|
18
|
+
npx @attested-intelligence/aga-verify <bundle.json>
|
|
19
|
+
|
|
20
|
+
# or smoke-test against the bundled canonical example
|
|
21
|
+
npx @attested-intelligence/aga-verify example-bundle.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Exit code is `0` on `VERIFIED`, `1` on `FAILED` — usable directly in CI.
|
|
25
|
+
|
|
26
|
+
`example-bundle.json` is a real, signed evidence bundle produced by the
|
|
27
|
+
reference implementation (the AI-agent governance scenario); it is shipped so
|
|
28
|
+
a third party can confirm the verifier end-to-end in one command.
|
|
29
|
+
|
|
30
|
+
## What it verifies
|
|
31
|
+
|
|
32
|
+
1. **Artifact signature** — Ed25519 over RFC 8785 canonical JSON.
|
|
33
|
+
2. **Receipt signatures** — Ed25519 for each enforcement receipt.
|
|
34
|
+
3. **Merkle inclusion proofs** — structural-metadata leaf hashes vs. the checkpoint root.
|
|
35
|
+
4. **Checkpoint anchor** — optional; requires network access (skipped offline).
|
|
36
|
+
|
|
37
|
+
Steps 1–3 are fully offline. Step 4 is optional.
|
|
38
|
+
|
|
39
|
+
## Independence guarantee
|
|
40
|
+
|
|
41
|
+
The published package's only runtime dependencies are `@noble/ed25519` and
|
|
42
|
+
`@noble/hashes`. It contains no AGA code and makes no network calls for steps
|
|
43
|
+
1–3. You can confirm this from the dependency tree (`npm ls`) and by reading
|
|
44
|
+
the single source file, `verify.ts`.
|
|
45
|
+
|
|
46
|
+
## From source
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
npm test # vitest, including the zero-AGA-imports assertion
|
|
51
|
+
npm run build # bundles verify.ts -> dist/aga-verify.mjs (esbuild)
|
|
52
|
+
node dist/aga-verify.mjs example-bundle.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
Attested Intelligence Holdings LLC. Implements the AGA four-step offline verification process.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// verify.ts
|
|
4
|
+
import * as ed from "@noble/ed25519";
|
|
5
|
+
import { sha512 } from "@noble/hashes/sha512";
|
|
6
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
7
|
+
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
|
|
8
|
+
ed.etc.sha512Sync = (...m) => {
|
|
9
|
+
const total = m.reduce((n, a) => n + a.length, 0);
|
|
10
|
+
const buf = new Uint8Array(total);
|
|
11
|
+
let off = 0;
|
|
12
|
+
for (const a of m) {
|
|
13
|
+
buf.set(a, off);
|
|
14
|
+
off += a.length;
|
|
15
|
+
}
|
|
16
|
+
return sha512(buf);
|
|
17
|
+
};
|
|
18
|
+
var enc = new TextEncoder();
|
|
19
|
+
function deepSortKeys(obj) {
|
|
20
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
|
|
21
|
+
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
22
|
+
if (obj instanceof Uint8Array) return obj;
|
|
23
|
+
const sorted = {};
|
|
24
|
+
for (const key of Object.keys(obj).sort()) {
|
|
25
|
+
sorted[key] = deepSortKeys(obj[key]);
|
|
26
|
+
}
|
|
27
|
+
return sorted;
|
|
28
|
+
}
|
|
29
|
+
function canonicalize(obj) {
|
|
30
|
+
return JSON.stringify(deepSortKeys(obj));
|
|
31
|
+
}
|
|
32
|
+
function sha256Hex(data) {
|
|
33
|
+
return bytesToHex(sha256(enc.encode(data)));
|
|
34
|
+
}
|
|
35
|
+
function verifyEd25519(sigBase64, message, publicKeyHex) {
|
|
36
|
+
try {
|
|
37
|
+
const sig = new Uint8Array(Buffer.from(sigBase64, "base64"));
|
|
38
|
+
const pk = hexToBytes(publicKeyHex);
|
|
39
|
+
return ed.verify(sig, enc.encode(message), pk);
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function merkleParentHash(left, right) {
|
|
45
|
+
return sha256Hex(left + right);
|
|
46
|
+
}
|
|
47
|
+
function verifyArtifactSignature(artifact) {
|
|
48
|
+
const { signature, ...unsigned } = artifact;
|
|
49
|
+
const canonical = canonicalize(unsigned);
|
|
50
|
+
return verifyEd25519(signature, canonical, artifact.issuer_identifier);
|
|
51
|
+
}
|
|
52
|
+
function verifyReceiptSignatures(receipts, portalPublicKey) {
|
|
53
|
+
return receipts.map((receipt) => {
|
|
54
|
+
const { portal_signature, ...unsigned } = receipt;
|
|
55
|
+
const canonical = canonicalize(unsigned);
|
|
56
|
+
return verifyEd25519(portal_signature, canonical, portalPublicKey);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function verifyMerkleProofs(proofs, checkpointRoot) {
|
|
60
|
+
return proofs.map((proof) => {
|
|
61
|
+
let hash = proof.leafHash;
|
|
62
|
+
for (const sibling of proof.siblings) {
|
|
63
|
+
hash = sibling.position === "left" ? merkleParentHash(sibling.hash, hash) : merkleParentHash(hash, sibling.hash);
|
|
64
|
+
}
|
|
65
|
+
return hash === checkpointRoot;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function verifyCheckpointAnchor(_checkpoint) {
|
|
69
|
+
return "SKIPPED";
|
|
70
|
+
}
|
|
71
|
+
function validateBundleShape(b) {
|
|
72
|
+
if (b === null || typeof b !== "object" || Array.isArray(b)) return "not a JSON object";
|
|
73
|
+
if (typeof b.artifact !== "object" || b.artifact === null) return 'missing "artifact" object';
|
|
74
|
+
if (typeof b.artifact.signature !== "string") return 'missing "artifact.signature"';
|
|
75
|
+
if (typeof b.artifact.issuer_identifier !== "string") return 'missing "artifact.issuer_identifier"';
|
|
76
|
+
if (!Array.isArray(b.receipts)) return 'missing "receipts" array';
|
|
77
|
+
if (typeof b.public_key !== "string") return 'missing "public_key"';
|
|
78
|
+
if (!Array.isArray(b.merkle_proofs)) return 'missing "merkle_proofs" array';
|
|
79
|
+
if (typeof b.checkpoint_reference !== "object" || b.checkpoint_reference === null || typeof b.checkpoint_reference.merkle_root !== "string") {
|
|
80
|
+
return 'missing "checkpoint_reference.merkle_root"';
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function verifyEvidenceBundle(bundleJson) {
|
|
85
|
+
const errors = [];
|
|
86
|
+
let bundle;
|
|
87
|
+
try {
|
|
88
|
+
bundle = JSON.parse(bundleJson);
|
|
89
|
+
} catch {
|
|
90
|
+
return {
|
|
91
|
+
step1_artifact_sig: false,
|
|
92
|
+
step2_receipt_sigs: false,
|
|
93
|
+
step3_merkle_proofs: false,
|
|
94
|
+
step4_anchor: "SKIPPED",
|
|
95
|
+
overall: false,
|
|
96
|
+
errors: ["Failed to parse bundle JSON"],
|
|
97
|
+
details: { receipt_results: [], proof_results: [] }
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const shapeError = validateBundleShape(bundle);
|
|
101
|
+
if (shapeError) {
|
|
102
|
+
return {
|
|
103
|
+
step1_artifact_sig: false,
|
|
104
|
+
step2_receipt_sigs: false,
|
|
105
|
+
step3_merkle_proofs: false,
|
|
106
|
+
step4_anchor: "SKIPPED",
|
|
107
|
+
overall: false,
|
|
108
|
+
errors: [`unrecognized evidence-bundle format: ${shapeError}`],
|
|
109
|
+
details: { receipt_results: [], proof_results: [] }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const step1 = verifyArtifactSignature(bundle.artifact);
|
|
113
|
+
if (!step1) errors.push("Artifact signature verification failed");
|
|
114
|
+
const receiptResults = verifyReceiptSignatures(bundle.receipts, bundle.public_key);
|
|
115
|
+
const step2 = receiptResults.every((r) => r);
|
|
116
|
+
receiptResults.forEach((r, i) => {
|
|
117
|
+
if (!r) errors.push(`Receipt ${bundle.receipts[i].receipt_id} signature failed`);
|
|
118
|
+
});
|
|
119
|
+
const proofResults = verifyMerkleProofs(bundle.merkle_proofs, bundle.checkpoint_reference.merkle_root);
|
|
120
|
+
const step3 = proofResults.length === 0 ? true : proofResults.every((r) => r);
|
|
121
|
+
proofResults.forEach((r, i) => {
|
|
122
|
+
if (!r) errors.push(`Merkle proof ${i} failed`);
|
|
123
|
+
});
|
|
124
|
+
const step4 = verifyCheckpointAnchor(bundle.checkpoint_reference);
|
|
125
|
+
return {
|
|
126
|
+
step1_artifact_sig: step1,
|
|
127
|
+
step2_receipt_sigs: step2,
|
|
128
|
+
step3_merkle_proofs: step3,
|
|
129
|
+
step4_anchor: step4,
|
|
130
|
+
overall: step1 && step2 && step3,
|
|
131
|
+
errors,
|
|
132
|
+
details: { receipt_results: receiptResults, proof_results: proofResults }
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (typeof process !== "undefined" && process.argv[1]?.includes("verify")) {
|
|
136
|
+
const { readFileSync } = await import("node:fs");
|
|
137
|
+
const bundlePath = process.argv[2];
|
|
138
|
+
if (!bundlePath) {
|
|
139
|
+
console.error("Usage: npx tsx verify.ts <bundle.json>");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
const bundleJson = readFileSync(bundlePath, "utf-8");
|
|
143
|
+
const result = verifyEvidenceBundle(bundleJson);
|
|
144
|
+
console.log("\nAGA Independent Verifier\n");
|
|
145
|
+
console.log(`Step 1 - Artifact signature: ${result.step1_artifact_sig ? "PASS" : "FAIL"}`);
|
|
146
|
+
console.log(`Step 2 - Receipt signatures: ${result.step2_receipt_sigs ? "PASS" : "FAIL"} (${result.details.receipt_results.filter((r) => r).length}/${result.details.receipt_results.length})`);
|
|
147
|
+
console.log(`Step 3 - Merkle inclusion proofs: ${result.step3_merkle_proofs ? "PASS" : "FAIL"} (${result.details.proof_results.filter((r) => r).length}/${result.details.proof_results.length})`);
|
|
148
|
+
console.log(`Step 4 - Checkpoint anchor: ${result.step4_anchor}`);
|
|
149
|
+
console.log(`
|
|
150
|
+
OVERALL: ${result.overall ? "VERIFIED" : "FAILED"}`);
|
|
151
|
+
if (result.errors.length) {
|
|
152
|
+
console.log("\nErrors:");
|
|
153
|
+
result.errors.forEach((e) => console.log(` - ${e}`));
|
|
154
|
+
}
|
|
155
|
+
process.exit(result.overall ? 0 : 1);
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
verifyArtifactSignature,
|
|
159
|
+
verifyCheckpointAnchor,
|
|
160
|
+
verifyEvidenceBundle,
|
|
161
|
+
verifyMerkleProofs,
|
|
162
|
+
verifyReceiptSignatures
|
|
163
|
+
};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
{
|
|
2
|
+
"artifact": {
|
|
3
|
+
"schema_version": "1.0.0",
|
|
4
|
+
"protocol_version": "1.0.0",
|
|
5
|
+
"subject_identifier": {
|
|
6
|
+
"bytes_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3",
|
|
7
|
+
"metadata_hash": "88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63"
|
|
8
|
+
},
|
|
9
|
+
"policy_reference": "c5090e8677afc35133da645aa2aecdcd36cd67184dfcfe031f1c55d5d1294020",
|
|
10
|
+
"policy_version": 4,
|
|
11
|
+
"sealed_hash": "1dd8ed34d6cd1c1c5cec647d26cc4c3676d7796a729376bb3fcf26b2a6edba33",
|
|
12
|
+
"seal_salt": "910887666ac8dc2b7db5695fd0186b4e",
|
|
13
|
+
"issued_timestamp": "2026-06-02T08:30:36.164Z",
|
|
14
|
+
"effective_timestamp": "2026-06-02T08:30:36.164Z",
|
|
15
|
+
"expiration_timestamp": null,
|
|
16
|
+
"issuer_identifier": "ac92dd48402bf3255177df55198c449dc61e36c97a3ed62e81462e72f741b311",
|
|
17
|
+
"enforcement_parameters": {
|
|
18
|
+
"measurement_cadence_ms": 200,
|
|
19
|
+
"ttl_seconds": 3600,
|
|
20
|
+
"enforcement_triggers": [
|
|
21
|
+
"QUARANTINE",
|
|
22
|
+
"TERMINATE",
|
|
23
|
+
"SAFE_STATE"
|
|
24
|
+
],
|
|
25
|
+
"re_attestation_required": true,
|
|
26
|
+
"measurement_types": [
|
|
27
|
+
"EXECUTABLE_IMAGE",
|
|
28
|
+
"LOADED_MODULES",
|
|
29
|
+
"CONFIG_MANIFEST"
|
|
30
|
+
],
|
|
31
|
+
"behavioral_baseline": {
|
|
32
|
+
"permitted_tools": [
|
|
33
|
+
"web_search",
|
|
34
|
+
"code_execute",
|
|
35
|
+
"file_read",
|
|
36
|
+
"summarize"
|
|
37
|
+
],
|
|
38
|
+
"forbidden_sequences": [
|
|
39
|
+
[
|
|
40
|
+
"file_read",
|
|
41
|
+
"web_search",
|
|
42
|
+
"code_execute"
|
|
43
|
+
]
|
|
44
|
+
],
|
|
45
|
+
"rate_limits": {
|
|
46
|
+
"web_search": 20,
|
|
47
|
+
"code_execute": 10,
|
|
48
|
+
"file_read": 30,
|
|
49
|
+
"summarize": 50
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"disclosure_policy": {
|
|
54
|
+
"claims_taxonomy": [
|
|
55
|
+
{
|
|
56
|
+
"claim_id": "agent.model_weights_hash",
|
|
57
|
+
"sensitivity": "S4_CRITICAL",
|
|
58
|
+
"substitutes": [
|
|
59
|
+
"agent.model_family",
|
|
60
|
+
"agent.model_generation"
|
|
61
|
+
],
|
|
62
|
+
"inference_risks": [],
|
|
63
|
+
"permitted_modes": []
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"claim_id": "agent.model_family",
|
|
67
|
+
"sensitivity": "S2_MODERATE",
|
|
68
|
+
"substitutes": [
|
|
69
|
+
"agent.model_generation"
|
|
70
|
+
],
|
|
71
|
+
"inference_risks": [],
|
|
72
|
+
"permitted_modes": [
|
|
73
|
+
"REVEAL_MIN",
|
|
74
|
+
"REVEAL_FULL"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"claim_id": "agent.model_generation",
|
|
79
|
+
"sensitivity": "S1_LOW",
|
|
80
|
+
"substitutes": [],
|
|
81
|
+
"inference_risks": [],
|
|
82
|
+
"permitted_modes": [
|
|
83
|
+
"PROOF_ONLY",
|
|
84
|
+
"REVEAL_MIN",
|
|
85
|
+
"REVEAL_FULL"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"substitution_rules": []
|
|
90
|
+
},
|
|
91
|
+
"evidence_commitments": [
|
|
92
|
+
{
|
|
93
|
+
"commitment": "3a7cafa939fa3732b4d36875b8909b467a2d063ce01c617b1f491983f9b8de71",
|
|
94
|
+
"salt": "eb5050aaff095529b52e03f920f32736",
|
|
95
|
+
"label": "model_card"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"commitment": "8f6a61abea33aca30d67af6fee22780ac2b689058882f70f8723c9f37d8da8bd",
|
|
99
|
+
"salt": "edba40897cd4d45923f00891c11d7746",
|
|
100
|
+
"label": "scope_approval"
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"signature": "X9IEtvUlU8AUixd/ZTbWqUGFhfqEbeQKYZy3/nzKhbj43uYS678X8BV+7hrd26Ts1m2hbUa+JHPAqzgj+z5YDg=="
|
|
104
|
+
},
|
|
105
|
+
"receipts": [
|
|
106
|
+
{
|
|
107
|
+
"receipt_id": "c77294b7-567f-4019-bc82-1db8f8b41a36",
|
|
108
|
+
"subject_identifier": {
|
|
109
|
+
"bytes_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3",
|
|
110
|
+
"metadata_hash": "88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63"
|
|
111
|
+
},
|
|
112
|
+
"artifact_reference": "c801497e4020ea3913e94541c2c869fac9422735686a6d3c4b75c4298145b237",
|
|
113
|
+
"current_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
114
|
+
"sealed_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
115
|
+
"drift_detected": false,
|
|
116
|
+
"drift_description": null,
|
|
117
|
+
"enforcement_action": null,
|
|
118
|
+
"measurement_type": "EXECUTABLE_IMAGE",
|
|
119
|
+
"timestamp": "2026-06-02T08:30:36.178Z",
|
|
120
|
+
"sequence_number": 0,
|
|
121
|
+
"previous_leaf_hash": "dea5872156dd67a5cf43b9124ce0e8d0d33090a4186a98f573c55666ea172739",
|
|
122
|
+
"portal_signature": "TaIz2Tb0TCE34lKD0UKdTqpQzThourYzGZHhpVPajy6C2bBA3oJsbE9BpZhx3utm62O1erKYOlT/WWUAVCGjDg=="
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"receipt_id": "a5900acb-2d66-4235-9347-d7da49d8dc0b",
|
|
126
|
+
"subject_identifier": {
|
|
127
|
+
"bytes_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3",
|
|
128
|
+
"metadata_hash": "88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63"
|
|
129
|
+
},
|
|
130
|
+
"artifact_reference": "c801497e4020ea3913e94541c2c869fac9422735686a6d3c4b75c4298145b237",
|
|
131
|
+
"current_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
132
|
+
"sealed_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
133
|
+
"drift_detected": false,
|
|
134
|
+
"drift_description": null,
|
|
135
|
+
"enforcement_action": null,
|
|
136
|
+
"measurement_type": "EXECUTABLE_IMAGE",
|
|
137
|
+
"timestamp": "2026-06-02T08:30:36.180Z",
|
|
138
|
+
"sequence_number": 1,
|
|
139
|
+
"previous_leaf_hash": "5ed1a416eb4c97b358cfceb1b3a0a662a698f1be2425b0063f546e4a2402f398",
|
|
140
|
+
"portal_signature": "lAhEHV/upEaWALRHCaqVc+thPmKdHHlGDGiFYvdv1TTtGzZLQ8S5Zjgm3jqGVactI+ntlwIbdNp6qhOY+CFkBA=="
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"receipt_id": "e06a3914-392b-4b6d-870b-48d9058cde2c",
|
|
144
|
+
"subject_identifier": {
|
|
145
|
+
"bytes_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3",
|
|
146
|
+
"metadata_hash": "88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63"
|
|
147
|
+
},
|
|
148
|
+
"artifact_reference": "c801497e4020ea3913e94541c2c869fac9422735686a6d3c4b75c4298145b237",
|
|
149
|
+
"current_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
150
|
+
"sealed_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
151
|
+
"drift_detected": false,
|
|
152
|
+
"drift_description": null,
|
|
153
|
+
"enforcement_action": null,
|
|
154
|
+
"measurement_type": "EXECUTABLE_IMAGE",
|
|
155
|
+
"timestamp": "2026-06-02T08:30:36.182Z",
|
|
156
|
+
"sequence_number": 2,
|
|
157
|
+
"previous_leaf_hash": "893cefa6bddc9c8a40c6655e938f912cec5daef0f6a9074bd4d27ca09ad4af9c",
|
|
158
|
+
"portal_signature": "HGpzkE4CK1QNXngHafIe4sr7ydqs7ZY0ZD7+me8vPw4RxtqBMAWfi200A9dC5OvDcN4zwZcwWF51oqBNk1l2Cw=="
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"receipt_id": "0040e0cc-e560-4014-86d0-1b294f3d4b4c",
|
|
162
|
+
"subject_identifier": {
|
|
163
|
+
"bytes_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3",
|
|
164
|
+
"metadata_hash": "88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63"
|
|
165
|
+
},
|
|
166
|
+
"artifact_reference": "c801497e4020ea3913e94541c2c869fac9422735686a6d3c4b75c4298145b237",
|
|
167
|
+
"current_hash": "950e5c3896a95e42d179500a6b61543f707285b8ee889327eb1be92daeec7f60",
|
|
168
|
+
"sealed_hash": "d75f39ce7c674f6e63ed0d36113262a5b3411c7974c4962f8a4b545996edd7d3||88fa13c6d55d6642c88dc4cc49ed53be26bafd26a3d67fdfd0b70c8ee39dbd63",
|
|
169
|
+
"drift_detected": true,
|
|
170
|
+
"drift_description": "Behavioral drift: forbidden tool sequence (data exfiltration pattern)",
|
|
171
|
+
"enforcement_action": "TERMINATE",
|
|
172
|
+
"measurement_type": "EXECUTABLE_IMAGE",
|
|
173
|
+
"timestamp": "2026-06-02T08:30:36.192Z",
|
|
174
|
+
"sequence_number": 3,
|
|
175
|
+
"previous_leaf_hash": "838f59b7c940b3061018f6c70c13c2e2ef17554659fb48dafd33af267d5d2026",
|
|
176
|
+
"portal_signature": "vMEgOydXm63Hgwm4+A75VKYsJ0BceNfR/juEEXmQh9/KmXpVDlTuXoHi3EHOUOv/t8Qey7rxfuldAq8hp+FkCA=="
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"merkle_proofs": [
|
|
180
|
+
{
|
|
181
|
+
"leafHash": "5ed1a416eb4c97b358cfceb1b3a0a662a698f1be2425b0063f546e4a2402f398",
|
|
182
|
+
"leafIndex": 2,
|
|
183
|
+
"siblings": [
|
|
184
|
+
{
|
|
185
|
+
"hash": "893cefa6bddc9c8a40c6655e938f912cec5daef0f6a9074bd4d27ca09ad4af9c",
|
|
186
|
+
"position": "right"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"hash": "db44e24f99b8086c7194eabcd240ef2b38f9d1214d49cc31337c45462a494082",
|
|
190
|
+
"position": "left"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"hash": "0463d91f9baebddee57414e78c8d22507a9d33a0ce5a7bfa882e2ccb4c98a13c",
|
|
194
|
+
"position": "right"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"hash": "71b83667104222412e522e83ce89978fbde04d0c02eabf0e1e759aadbde0dac2",
|
|
198
|
+
"position": "right"
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"root": "e1cc28411c41f749ab3ff8160c465bca538d3c1c0883eac56c3821ff1c7574c9"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"leafHash": "893cefa6bddc9c8a40c6655e938f912cec5daef0f6a9074bd4d27ca09ad4af9c",
|
|
205
|
+
"leafIndex": 3,
|
|
206
|
+
"siblings": [
|
|
207
|
+
{
|
|
208
|
+
"hash": "5ed1a416eb4c97b358cfceb1b3a0a662a698f1be2425b0063f546e4a2402f398",
|
|
209
|
+
"position": "left"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"hash": "db44e24f99b8086c7194eabcd240ef2b38f9d1214d49cc31337c45462a494082",
|
|
213
|
+
"position": "left"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"hash": "0463d91f9baebddee57414e78c8d22507a9d33a0ce5a7bfa882e2ccb4c98a13c",
|
|
217
|
+
"position": "right"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"hash": "71b83667104222412e522e83ce89978fbde04d0c02eabf0e1e759aadbde0dac2",
|
|
221
|
+
"position": "right"
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
"root": "e1cc28411c41f749ab3ff8160c465bca538d3c1c0883eac56c3821ff1c7574c9"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"leafHash": "56bda530fe8e6813c6fef0577596c9f35aa21f8f90d6f8593d3831b39e565fc3",
|
|
228
|
+
"leafIndex": 4,
|
|
229
|
+
"siblings": [
|
|
230
|
+
{
|
|
231
|
+
"hash": "6f98d48d7117d51064d72ff13c2c84d379c906f540e370d01e1759e957653788",
|
|
232
|
+
"position": "right"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"hash": "74d7296252184052fcf08c23c6c60ef6efd34c86e743c441886c3f44fbd3ef28",
|
|
236
|
+
"position": "right"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"hash": "36a5c22125121fb0f9c7645f06454c48151970c297942e9025d2e8a0d0bc2a2b",
|
|
240
|
+
"position": "left"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"hash": "71b83667104222412e522e83ce89978fbde04d0c02eabf0e1e759aadbde0dac2",
|
|
244
|
+
"position": "right"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"root": "e1cc28411c41f749ab3ff8160c465bca538d3c1c0883eac56c3821ff1c7574c9"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"leafHash": "13d4765f15b72d5e4192fc6f3c2efcc2d46b00df515d86a23b4c38c896a415b3",
|
|
251
|
+
"leafIndex": 9,
|
|
252
|
+
"siblings": [
|
|
253
|
+
{
|
|
254
|
+
"hash": "838f59b7c940b3061018f6c70c13c2e2ef17554659fb48dafd33af267d5d2026",
|
|
255
|
+
"position": "left"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"hash": "69479007abe86c6e09f6453dfa2762996a3cdc10b9df03e31b477958898877c8",
|
|
259
|
+
"position": "right"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"hash": "1b19cbeb9a5ecdb1c209447c9292a00d8699a18211fa3a97d0dc9776f9c64575",
|
|
263
|
+
"position": "right"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"hash": "e9599515a3379cc1ae106b51c80424db87db3c8ee57855f282843afdc12eda40",
|
|
267
|
+
"position": "left"
|
|
268
|
+
}
|
|
269
|
+
],
|
|
270
|
+
"root": "e1cc28411c41f749ab3ff8160c465bca538d3c1c0883eac56c3821ff1c7574c9"
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
"checkpoint_reference": {
|
|
274
|
+
"merkle_root": "e1cc28411c41f749ab3ff8160c465bca538d3c1c0883eac56c3821ff1c7574c9",
|
|
275
|
+
"batch_start_sequence": 0,
|
|
276
|
+
"batch_end_sequence": 9,
|
|
277
|
+
"anchor_network": "local",
|
|
278
|
+
"transaction_id": "local:04bb72ce-9af3-4406-95c9-0ad269d87696",
|
|
279
|
+
"timestamp": "2026-06-02T08:30:36.195Z"
|
|
280
|
+
},
|
|
281
|
+
"public_key": "6a65398e54dcecb4a8a3a38d351ce4ba947bae215a78d38a259959d609abd4fe",
|
|
282
|
+
"verification_tier": "GOLD",
|
|
283
|
+
"bundle_signature": "O6erEOh0UwH9vIEpkrdRKJl3wYb6d8RlS30I4jXeV+aKQOFSaOHuc4JF1KM73pLcGpqEv5c3slDxEs4loinMCg=="
|
|
284
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@attested-intelligence/aga-verify",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Standalone, AGA-independent verifier for AGA Evidence Bundles (Ed25519 + SHA-256 via @noble; zero AGA imports).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Attested Intelligence Holdings LLC",
|
|
8
|
+
"homepage": "https://attestedintelligence.com/technology",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"aga",
|
|
11
|
+
"evidence-bundle",
|
|
12
|
+
"verifier",
|
|
13
|
+
"ed25519",
|
|
14
|
+
"merkle",
|
|
15
|
+
"offline-verification",
|
|
16
|
+
"attested-intelligence",
|
|
17
|
+
"ai-governance"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"aga-verify": "dist/aga-verify.mjs"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"verify.ts",
|
|
25
|
+
"example-bundle.json",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node build.mjs",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"prepublishOnly": "node build.mjs && vitest run"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@noble/ed25519": "^2.1.0",
|
|
38
|
+
"@noble/hashes": "^1.7.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"esbuild": "^0.27.4",
|
|
42
|
+
"tsx": "^4.19.0",
|
|
43
|
+
"vitest": "^2.1.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/verify.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGA Independent Verifier
|
|
3
|
+
*
|
|
4
|
+
* Standalone verification of AGA Evidence Bundles using ONLY standard
|
|
5
|
+
* cryptographic libraries. This verifier imports ZERO modules from the
|
|
6
|
+
* AGA codebase (../src/).
|
|
7
|
+
*
|
|
8
|
+
* Implements the full 4-step verification process:
|
|
9
|
+
* 1. Verify artifact signature (Ed25519 over RFC 8785 canonical JSON)
|
|
10
|
+
* 2. Verify each receipt signature (Ed25519)
|
|
11
|
+
* 3. Verify Merkle inclusion proofs (structural metadata leaf hashes vs checkpoint root)
|
|
12
|
+
* 4. (Optional) Verify checkpoint anchor
|
|
13
|
+
*
|
|
14
|
+
* Steps 1-3 work fully offline. Step 4 is optional.
|
|
15
|
+
*
|
|
16
|
+
* Attested Intelligence Holdings LLC
|
|
17
|
+
*/
|
|
18
|
+
import * as ed from '@noble/ed25519';
|
|
19
|
+
import { sha512 } from '@noble/hashes/sha512';
|
|
20
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
21
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
22
|
+
|
|
23
|
+
// ── Ed25519 setup ────────────────────────────────────────────
|
|
24
|
+
ed.etc.sha512Sync = (...m: Uint8Array[]) => {
|
|
25
|
+
const total = m.reduce((n, a) => n + a.length, 0);
|
|
26
|
+
const buf = new Uint8Array(total);
|
|
27
|
+
let off = 0;
|
|
28
|
+
for (const a of m) { buf.set(a, off); off += a.length; }
|
|
29
|
+
return sha512(buf);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const enc = new TextEncoder();
|
|
33
|
+
|
|
34
|
+
// ── Types (reimplemented, no AGA imports) ────────────────────
|
|
35
|
+
|
|
36
|
+
export interface VerificationResult {
|
|
37
|
+
step1_artifact_sig: boolean;
|
|
38
|
+
step2_receipt_sigs: boolean;
|
|
39
|
+
step3_merkle_proofs: boolean;
|
|
40
|
+
step4_anchor: 'VERIFIED' | 'SKIPPED';
|
|
41
|
+
overall: boolean;
|
|
42
|
+
errors: string[];
|
|
43
|
+
details: {
|
|
44
|
+
receipt_results: boolean[];
|
|
45
|
+
proof_results: boolean[];
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface MerkleProof {
|
|
50
|
+
leafHash: string;
|
|
51
|
+
leafIndex: number;
|
|
52
|
+
siblings: Array<{ hash: string; position: 'left' | 'right' }>;
|
|
53
|
+
root: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface EvidenceBundle {
|
|
57
|
+
artifact: Record<string, unknown> & { signature: string; issuer_identifier: string };
|
|
58
|
+
receipts: Array<Record<string, unknown> & { portal_signature: string; receipt_id: string }>;
|
|
59
|
+
merkle_proofs: MerkleProof[];
|
|
60
|
+
checkpoint_reference: { merkle_root: string; [key: string]: unknown };
|
|
61
|
+
public_key: string;
|
|
62
|
+
bundle_signature: string;
|
|
63
|
+
verification_tier?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Crypto helpers (reimplemented from scratch) ──────────────
|
|
67
|
+
|
|
68
|
+
function deepSortKeys(obj: unknown): unknown {
|
|
69
|
+
if (obj === null || obj === undefined || typeof obj !== 'object') return obj;
|
|
70
|
+
if (Array.isArray(obj)) return obj.map(deepSortKeys);
|
|
71
|
+
if (obj instanceof Uint8Array) return obj;
|
|
72
|
+
const sorted: Record<string, unknown> = {};
|
|
73
|
+
for (const key of Object.keys(obj as Record<string, unknown>).sort()) {
|
|
74
|
+
sorted[key] = deepSortKeys((obj as Record<string, unknown>)[key]);
|
|
75
|
+
}
|
|
76
|
+
return sorted;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function canonicalize(obj: unknown): string {
|
|
80
|
+
return JSON.stringify(deepSortKeys(obj));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sha256Hex(data: string): string {
|
|
84
|
+
return bytesToHex(sha256(enc.encode(data)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function verifyEd25519(sigBase64: string, message: string, publicKeyHex: string): boolean {
|
|
88
|
+
try {
|
|
89
|
+
const sig = new Uint8Array(Buffer.from(sigBase64, 'base64'));
|
|
90
|
+
const pk = hexToBytes(publicKeyHex);
|
|
91
|
+
return ed.verify(sig, enc.encode(message), pk);
|
|
92
|
+
} catch { return false; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function merkleParentHash(left: string, right: string): string {
|
|
96
|
+
return sha256Hex(left + right);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Step 1: Verify artifact signature (Ed25519) ─────────────
|
|
100
|
+
|
|
101
|
+
export function verifyArtifactSignature(artifact: EvidenceBundle['artifact']): boolean {
|
|
102
|
+
const { signature, ...unsigned } = artifact;
|
|
103
|
+
const canonical = canonicalize(unsigned);
|
|
104
|
+
return verifyEd25519(signature, canonical, artifact.issuer_identifier);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Step 2: Verify each receipt signature (Ed25519) ──────────
|
|
108
|
+
|
|
109
|
+
export function verifyReceiptSignatures(receipts: EvidenceBundle['receipts'], portalPublicKey: string): boolean[] {
|
|
110
|
+
return receipts.map(receipt => {
|
|
111
|
+
const { portal_signature, ...unsigned } = receipt;
|
|
112
|
+
const canonical = canonicalize(unsigned);
|
|
113
|
+
return verifyEd25519(portal_signature, canonical, portalPublicKey);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Step 3: Verify Merkle inclusion proofs ───────────────────
|
|
118
|
+
|
|
119
|
+
export function verifyMerkleProofs(proofs: MerkleProof[], checkpointRoot: string): boolean[] {
|
|
120
|
+
return proofs.map(proof => {
|
|
121
|
+
let hash = proof.leafHash;
|
|
122
|
+
for (const sibling of proof.siblings) {
|
|
123
|
+
hash = sibling.position === 'left'
|
|
124
|
+
? merkleParentHash(sibling.hash, hash)
|
|
125
|
+
: merkleParentHash(hash, sibling.hash);
|
|
126
|
+
}
|
|
127
|
+
return hash === checkpointRoot;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Step 4 (optional): Verify checkpoint anchor ─────────────
|
|
132
|
+
|
|
133
|
+
export function verifyCheckpointAnchor(_checkpoint: Record<string, unknown>): 'VERIFIED' | 'SKIPPED' {
|
|
134
|
+
// Offline mode - no network access to verify on-chain anchor
|
|
135
|
+
return 'SKIPPED';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Main entry point ─────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
// validateBundleShape returns a human-readable reason if the parsed value is
|
|
141
|
+
// not a recognizable AGA evidence bundle, or null if the required fields are
|
|
142
|
+
// present. This lets the verifier fail cleanly on a wrong-format file instead
|
|
143
|
+
// of throwing on a missing field.
|
|
144
|
+
function validateBundleShape(b: any): string | null {
|
|
145
|
+
if (b === null || typeof b !== 'object' || Array.isArray(b)) return 'not a JSON object';
|
|
146
|
+
if (typeof b.artifact !== 'object' || b.artifact === null) return 'missing "artifact" object';
|
|
147
|
+
if (typeof b.artifact.signature !== 'string') return 'missing "artifact.signature"';
|
|
148
|
+
if (typeof b.artifact.issuer_identifier !== 'string') return 'missing "artifact.issuer_identifier"';
|
|
149
|
+
if (!Array.isArray(b.receipts)) return 'missing "receipts" array';
|
|
150
|
+
if (typeof b.public_key !== 'string') return 'missing "public_key"';
|
|
151
|
+
if (!Array.isArray(b.merkle_proofs)) return 'missing "merkle_proofs" array';
|
|
152
|
+
if (typeof b.checkpoint_reference !== 'object' || b.checkpoint_reference === null ||
|
|
153
|
+
typeof b.checkpoint_reference.merkle_root !== 'string') {
|
|
154
|
+
return 'missing "checkpoint_reference.merkle_root"';
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function verifyEvidenceBundle(bundleJson: string): VerificationResult {
|
|
160
|
+
const errors: string[] = [];
|
|
161
|
+
let bundle: EvidenceBundle;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
bundle = JSON.parse(bundleJson);
|
|
165
|
+
} catch {
|
|
166
|
+
return {
|
|
167
|
+
step1_artifact_sig: false, step2_receipt_sigs: false,
|
|
168
|
+
step3_merkle_proofs: false, step4_anchor: 'SKIPPED',
|
|
169
|
+
overall: false, errors: ['Failed to parse bundle JSON'],
|
|
170
|
+
details: { receipt_results: [], proof_results: [] },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const shapeError = validateBundleShape(bundle);
|
|
175
|
+
if (shapeError) {
|
|
176
|
+
return {
|
|
177
|
+
step1_artifact_sig: false, step2_receipt_sigs: false,
|
|
178
|
+
step3_merkle_proofs: false, step4_anchor: 'SKIPPED',
|
|
179
|
+
overall: false, errors: [`unrecognized evidence-bundle format: ${shapeError}`],
|
|
180
|
+
details: { receipt_results: [], proof_results: [] },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Step 1: Artifact signature
|
|
185
|
+
const step1 = verifyArtifactSignature(bundle.artifact);
|
|
186
|
+
if (!step1) errors.push('Artifact signature verification failed');
|
|
187
|
+
|
|
188
|
+
// Step 2: Receipt signatures
|
|
189
|
+
const receiptResults = verifyReceiptSignatures(bundle.receipts, bundle.public_key);
|
|
190
|
+
const step2 = receiptResults.every(r => r);
|
|
191
|
+
receiptResults.forEach((r, i) => {
|
|
192
|
+
if (!r) errors.push(`Receipt ${bundle.receipts[i].receipt_id} signature failed`);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Step 3: Merkle inclusion proofs
|
|
196
|
+
const proofResults = verifyMerkleProofs(bundle.merkle_proofs, bundle.checkpoint_reference.merkle_root);
|
|
197
|
+
const step3 = proofResults.length === 0 ? true : proofResults.every(r => r);
|
|
198
|
+
proofResults.forEach((r, i) => {
|
|
199
|
+
if (!r) errors.push(`Merkle proof ${i} failed`);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Step 4: Checkpoint anchor
|
|
203
|
+
const step4 = verifyCheckpointAnchor(bundle.checkpoint_reference as Record<string, unknown>);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
step1_artifact_sig: step1,
|
|
207
|
+
step2_receipt_sigs: step2,
|
|
208
|
+
step3_merkle_proofs: step3,
|
|
209
|
+
step4_anchor: step4,
|
|
210
|
+
overall: step1 && step2 && step3,
|
|
211
|
+
errors,
|
|
212
|
+
details: { receipt_results: receiptResults, proof_results: proofResults },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── CLI mode ─────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
if (typeof process !== 'undefined' && process.argv[1]?.includes('verify')) {
|
|
219
|
+
const { readFileSync } = await import('node:fs');
|
|
220
|
+
const bundlePath = process.argv[2];
|
|
221
|
+
if (!bundlePath) {
|
|
222
|
+
console.error('Usage: npx tsx verify.ts <bundle.json>');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const bundleJson = readFileSync(bundlePath, 'utf-8');
|
|
226
|
+
const result = verifyEvidenceBundle(bundleJson);
|
|
227
|
+
|
|
228
|
+
console.log('\nAGA Independent Verifier\n');
|
|
229
|
+
console.log(`Step 1 - Artifact signature: ${result.step1_artifact_sig ? 'PASS' : 'FAIL'}`);
|
|
230
|
+
console.log(`Step 2 - Receipt signatures: ${result.step2_receipt_sigs ? 'PASS' : 'FAIL'} (${result.details.receipt_results.filter(r => r).length}/${result.details.receipt_results.length})`);
|
|
231
|
+
console.log(`Step 3 - Merkle inclusion proofs: ${result.step3_merkle_proofs ? 'PASS' : 'FAIL'} (${result.details.proof_results.filter(r => r).length}/${result.details.proof_results.length})`);
|
|
232
|
+
console.log(`Step 4 - Checkpoint anchor: ${result.step4_anchor}`);
|
|
233
|
+
console.log(`\nOVERALL: ${result.overall ? 'VERIFIED' : 'FAILED'}`);
|
|
234
|
+
if (result.errors.length) {
|
|
235
|
+
console.log('\nErrors:');
|
|
236
|
+
result.errors.forEach(e => console.log(` - ${e}`));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
process.exit(result.overall ? 0 : 1);
|
|
240
|
+
}
|