@attested-intelligence/aga-verify 1.0.0 → 2.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 CHANGED
@@ -1,56 +1,72 @@
1
1
  # AGA Independent Verifier (`@attested-intelligence/aga-verify`)
2
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`.
3
+ Standalone verification of canonical **AGA SEP Evidence Bundles**. **Zero AGA imports
4
+ and zero third-party dependencies** it uses only Node's built-in `crypto` (Ed25519 +
5
+ SHA-256). The trust chain dead-ends at the Node runtime and the gateway public key you
6
+ pin; nothing else.
6
7
 
7
8
  ## Why this exists
8
9
 
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.
10
+ AGA claims its Evidence Bundles are tamper-evident and offline-verifiable. This tool
11
+ proves that claim is checkable by **anyone**, with no trust in AGA's own code or in any
12
+ npm dependency: it re-implements the complete verification from scratch and runs against
13
+ a bundle you provide.
13
14
 
14
15
  ## Quickstart
15
16
 
16
17
  ```bash
17
- # verify any AGA evidence bundle
18
+ # integrity only (proves the bundle is internally authentic + complete-as-presented):
18
19
  npx @attested-intelligence/aga-verify <bundle.json>
19
20
 
20
- # or smoke-test against the bundled canonical example
21
- npx @attested-intelligence/aga-verify example-bundle.json
21
+ # integrity + PROVENANCE (proves it came from a specific gateway you trust out of band):
22
+ npx @attested-intelligence/aga-verify <bundle.json> --pubkey <64-hex-gateway-key>
23
+
24
+ # smoke-test against the bundled canonical example (a real signed bundle):
25
+ npx @attested-intelligence/aga-verify example-bundle.json \
26
+ --pubkey ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c
22
27
  ```
23
28
 
24
29
  Exit code is `0` on `VERIFIED`, `1` on `FAILED` — usable directly in CI.
25
30
 
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
31
  ## What it verifies
31
32
 
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).
33
+ Implements the canonical construction in
34
+ [`aga-receipt-spec/CANONICAL_CONSTRUCTION_v2.md`](https://attestedintelligence.com/technology) §6:
35
+
36
+ 1. **Structural floor** — algorithm, well-formed (non-small-order) key, receipt/proof counts.
37
+ 2. **Receipt signatures** — Ed25519 over the canonical receipt bytes, for every receipt.
38
+ 3. **Chain + ordering** — each receipt links to the previous leaf; monotonic ids/timestamps.
39
+ 4. **Merkle + bijection** — every leaf is **recomputed from receipt content**, walked to one root, and the proof set is the complete contiguous `0..N-1`.
40
+ 5. **Signed checkpoint (mandatory)** — a gateway-signed checkpoint binds the root, the receipt count, and the chain head, so adding/dropping/reordering receipts fails.
41
+ 6. **Provenance (only with `--pubkey`)** — the bundle key equals the key you pinned.
42
+
43
+ All steps are fully offline. No network calls, ever.
44
+
45
+ ## What a PASS proves — and what it does not
46
+
47
+ A PASS proves every **present** receipt is authentic, correctly chained, Merkle-included
48
+ under a signed checkpoint, and (with `--pubkey`) issued by the pinned gateway — nothing
49
+ present was added, reordered, or truncated.
36
50
 
37
- Steps 1–3 are fully offline. Step 4 is optional.
51
+ A PASS does **not** prove **non-omission**: it cannot establish that the signer recorded
52
+ *every* action it took. Completeness is bounded by the tamper-evidence of the interception
53
+ point, which is outside the bundle. Without `--pubkey`, a PASS proves integrity and
54
+ self-consistency under the bundle's own key, **not** provenance.
38
55
 
39
56
  ## Independence guarantee
40
57
 
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`.
58
+ `npm ls` shows **zero runtime dependencies**. The verifier is one source file
59
+ (`verify.ts`, bundled to `dist/aga-verify.mjs`) using only `node:crypto`. No AGA code, no
60
+ third-party packages, no network for verification.
45
61
 
46
62
  ## From source
47
63
 
48
64
  ```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
65
+ npm install # devDeps only (esbuild, vitest, tsx) — zero runtime deps
66
+ npm test # vitest: genuine VERIFIES + every tamper/truncation/wrong-key FAILS
67
+ npm run build # bundles verify.ts -> dist/aga-verify.mjs (esbuild)
68
+ node dist/aga-verify.mjs example-bundle.json --pubkey <key>
53
69
  ```
54
70
 
55
71
  ---
56
- Attested Intelligence Holdings LLC. Implements the AGA four-step offline verification process.
72
+ Attested Intelligence Holdings LLC · MIT. Implements the canonical AGA SEP Evidence Bundle verification (`aga-receipt-spec` v2).
@@ -1,163 +1,251 @@
1
1
  #!/usr/bin/env node
2
2
 
3
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;
4
+ import { createHash, createPublicKey, verify as edVerify } from "node:crypto";
5
+ var ALGORITHM = "Ed25519-SHA256-JCS";
6
+ var SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
7
+ var MAX_CANON_DEPTH = 100;
8
+ var SEP_RECEIPT_FIELDS = [
9
+ "receipt_id",
10
+ "receipt_version",
11
+ "algorithm",
12
+ "timestamp",
13
+ "request_id",
14
+ "method",
15
+ "tool_name",
16
+ "decision",
17
+ "reason",
18
+ "policy_reference",
19
+ "arguments_hash",
20
+ "previous_receipt_hash",
21
+ "gateway_id",
22
+ "public_key",
23
+ "signature"
24
+ ];
25
+ var SEP_CHECKPOINT_FIELDS = [
26
+ "algorithm",
27
+ "gateway_id",
28
+ "generated_at",
29
+ "head_leaf_hash",
30
+ "leaf_count",
31
+ "merkle_root",
32
+ "signature"
33
+ ];
34
+ var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
35
+ function canon(o) {
36
+ const rec = (v, depth) => {
37
+ if (depth > MAX_CANON_DEPTH) throw new Error(`canon: input nesting exceeds ${MAX_CANON_DEPTH} levels`);
38
+ if (typeof v === "string" && LONE_SURROGATE.test(v)) throw new Error("canonicalize: lone surrogate");
39
+ if (v === null || typeof v !== "object") return JSON.stringify(v);
40
+ if (Array.isArray(v)) return "[" + v.map((x) => rec(x, depth + 1)).join(",") + "]";
41
+ const m = v;
42
+ return "{" + Object.keys(m).sort().map((k) => JSON.stringify(k) + ":" + rec(m[k], depth + 1)).join(",") + "}";
43
+ };
44
+ return rec(o, 0);
28
45
  }
29
- function canonicalize(obj) {
30
- return JSON.stringify(deepSortKeys(obj));
46
+ function hasExactKeys(o, fields) {
47
+ if (!o || typeof o !== "object" || Array.isArray(o)) return false;
48
+ const keys = Object.keys(o);
49
+ return keys.length === fields.length && fields.every((f) => Object.prototype.hasOwnProperty.call(o, f));
31
50
  }
32
- function sha256Hex(data) {
33
- return bytesToHex(sha256(enc.encode(data)));
51
+ var sha = (b) => createHash("sha256").update(b).digest("hex");
52
+ var u8 = (s) => Buffer.from(s, "utf8");
53
+ var leafHash = (r) => sha(u8(canon(r)));
54
+ var nodeHash = (l, r) => sha(Buffer.concat([Buffer.from(l, "hex"), Buffer.from(r, "hex")]));
55
+ var stripField = (o, f) => Object.fromEntries(Object.entries(o).filter(([k]) => k !== f));
56
+ var isHex = (h, n) => typeof h === "string" && new RegExp(`^[0-9a-f]{${n}}$`).test(h);
57
+ var SMALL_ORDER_KEYS = /* @__PURE__ */ new Set([
58
+ "00".repeat(32),
59
+ "00".repeat(31) + "80",
60
+ "01" + "00".repeat(31),
61
+ "01" + "00".repeat(30) + "80",
62
+ "ec" + "ff".repeat(30) + "7f",
63
+ "ec" + "ff".repeat(31),
64
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
65
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
66
+ "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85",
67
+ "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa"
68
+ ]);
69
+ var ED25519_P = (1n << 255n) - 19n;
70
+ function isCanonicalY(hex) {
71
+ const b = Buffer.from(hex, "hex");
72
+ let y = 0n;
73
+ for (let i = 0; i < 32; i++) y |= BigInt(i === 31 ? b[i] & 127 : b[i]) << BigInt(8 * i);
74
+ return y < ED25519_P;
34
75
  }
35
- function verifyEd25519(sigBase64, message, publicKeyHex) {
76
+ var TS_CANONICAL = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/;
77
+ var isLeap = (y) => y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
78
+ var daysInMonth = (y, m) => [31, isLeap(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][m - 1];
79
+ function isCanonicalTimestamp(ts) {
80
+ if (typeof ts !== "string" || !TS_CANONICAL.test(ts)) return false;
81
+ const year = parseInt(ts.slice(0, 4), 10);
82
+ const month = parseInt(ts.slice(5, 7), 10);
83
+ const day = parseInt(ts.slice(8, 10), 10);
84
+ const hour = parseInt(ts.slice(11, 13), 10);
85
+ const minute = parseInt(ts.slice(14, 16), 10);
86
+ const second = parseInt(ts.slice(17, 19), 10);
87
+ if (month < 1 || month > 12) return false;
88
+ if (day < 1 || day > daysInMonth(year, month)) return false;
89
+ if (hour > 23) return false;
90
+ if (minute > 59) return false;
91
+ if (second > 59) return false;
92
+ return true;
93
+ }
94
+ function wellFormedKey(hex) {
95
+ if (!isHex(hex, 64)) return false;
96
+ if (SMALL_ORDER_KEYS.has(hex) || !isCanonicalY(hex)) return false;
36
97
  try {
37
- const sig = new Uint8Array(Buffer.from(sigBase64, "base64"));
38
- const pk = hexToBytes(publicKeyHex);
39
- return ed.verify(sig, enc.encode(message), pk);
98
+ createPublicKey({ key: Buffer.concat([SPKI_PREFIX, Buffer.from(hex, "hex")]), format: "der", type: "spki" });
99
+ return true;
40
100
  } catch {
41
101
  return false;
42
102
  }
43
103
  }
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";
104
+ function sigOk(pubHex, msg, sigHex) {
105
+ if (!wellFormedKey(pubHex) || !isHex(sigHex, 128) || /^0+$/.test(sigHex)) return false;
106
+ try {
107
+ const pk = createPublicKey({ key: Buffer.concat([SPKI_PREFIX, Buffer.from(pubHex, "hex")]), format: "der", type: "spki" });
108
+ return edVerify(null, u8(msg), pk, Buffer.from(sigHex, "hex"));
109
+ } catch {
110
+ return false;
111
+ }
70
112
  }
71
- function validateBundleShape(b) {
113
+ function validateShape(b) {
72
114
  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';
115
+ if (b.algorithm !== ALGORITHM) return `algorithm must be "${ALGORITHM}"`;
77
116
  if (typeof b.public_key !== "string") return 'missing "public_key"';
117
+ if (!Array.isArray(b.receipts)) return 'missing "receipts" array';
78
118
  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
- }
119
+ if (typeof b.checkpoint !== "object" || b.checkpoint === null || typeof b.checkpoint.signature !== "string")
120
+ return 'missing signed "checkpoint"';
82
121
  return null;
83
122
  }
84
- function verifyEvidenceBundle(bundleJson) {
85
- const errors = [];
86
- let bundle;
123
+ function verifySepBundle(bundle, expectedPublicKey) {
124
+ const pinned = isHex(expectedPublicKey, 64);
87
125
  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: [] }
126
+ const steps = [];
127
+ const errors = [];
128
+ const add = (name, ok, err) => {
129
+ steps.push({ name, ok });
130
+ if (!ok && err) errors.push(err);
131
+ return ok;
98
132
  };
99
- }
100
- const shapeError = validateBundleShape(bundle);
101
- if (shapeError) {
133
+ const receipts = Array.isArray(bundle?.receipts) ? bundle.receipts : [];
134
+ const proofs = Array.isArray(bundle?.merkle_proofs) ? bundle.merkle_proofs : [];
135
+ const pub = bundle?.public_key;
136
+ add(
137
+ "structural",
138
+ bundle?.algorithm === ALGORITHM && wellFormedKey(pub) && receipts.length > 0 && proofs.length === receipts.length && receipts.every((r) => hasExactKeys(r, SEP_RECEIPT_FIELDS)),
139
+ "structural floor failed (algorithm/key/receipt-count/receipt-schema)"
140
+ );
141
+ add(
142
+ "receipt_signatures",
143
+ receipts.length > 0 && receipts.every((r) => sigOk(pub, canon(stripField(r, "signature")), r.signature)),
144
+ "one or more receipt signatures invalid"
145
+ );
146
+ const leaves = receipts.map(leafHash);
147
+ let chain = receipts.length > 0;
148
+ let prevTs = "";
149
+ for (let i = 0; i < receipts.length; i++) {
150
+ if ((receipts[i].previous_receipt_hash || "") !== (i === 0 ? "" : leaves[i - 1])) chain = false;
151
+ const ts = receipts[i].timestamp;
152
+ if (!isCanonicalTimestamp(ts)) chain = false;
153
+ else {
154
+ if (i > 0 && ts < prevTs) chain = false;
155
+ prevTs = ts;
156
+ }
157
+ }
158
+ add("chain_and_ordering", chain, "chain linkage, non-canonical timestamp, or ordering broken");
159
+ let root = null, merkle = proofs.length === receipts.length && proofs.length > 0;
160
+ const seen = /* @__PURE__ */ new Set();
161
+ for (const p of proofs) {
162
+ seen.add(p.leaf_index);
163
+ if (receipts[p.leaf_index] === void 0 || leaves[p.leaf_index] !== p.leaf_hash) merkle = false;
164
+ let cur = p.leaf_hash;
165
+ const sib = Array.isArray(p.siblings) ? p.siblings : [];
166
+ const dir = Array.isArray(p.directions) ? p.directions : [];
167
+ if (dir.length !== sib.length || !dir.every((d) => d === "left" || d === "right")) merkle = false;
168
+ for (let j = 0; j < sib.length; j++) cur = dir[j] === "left" ? nodeHash(sib[j], cur) : nodeHash(cur, sib[j]);
169
+ if (p.merkle_root !== cur) merkle = false;
170
+ if (root === null) root = cur;
171
+ else if (root !== cur) merkle = false;
172
+ }
173
+ const bijection = seen.size === receipts.length && [...seen].every((n) => Number.isInteger(n) && n >= 0 && n < receipts.length);
174
+ add("merkle_and_bijection", merkle && bijection, "Merkle proof, per-proof root, leaf recompute, or index bijection failed");
175
+ const cp = bundle.checkpoint;
176
+ let cpOk = false;
177
+ if (hasExactKeys(cp, SEP_CHECKPOINT_FIELDS)) {
178
+ cpOk = cp.algorithm === ALGORITHM && sigOk(pub, canon(stripField(cp, "signature")), cp.signature) && root !== null && cp.merkle_root === root && cp.leaf_count === receipts.length && cp.head_leaf_hash === (leaves.length ? leaves[leaves.length - 1] : "");
179
+ }
180
+ add("signed_checkpoint", cpOk, "signed checkpoint missing, mis-schema, wrong algorithm, or does not anchor the bundle");
181
+ const cpGatewayId = cp && typeof cp === "object" ? cp.gateway_id : void 0;
182
+ const cpGeneratedAt = cp && typeof cp === "object" ? cp.generated_at : void 0;
183
+ add(
184
+ "envelope_consistency",
185
+ receipts.length > 0 && receipts.every((r) => r.public_key === pub) && receipts.every((r) => r.gateway_id === bundle?.gateway_id) && cpGatewayId === bundle?.gateway_id && cpGeneratedAt === bundle?.generated_at && root !== null && bundle?.merkle_root === root,
186
+ // envelope merkle_root <-> recomputed
187
+ "envelope gateway_id/generated_at/merkle_root or a receipt public_key/gateway_id disagrees with the signed/recomputed values"
188
+ );
189
+ const issuerVerified = pinned && pub === expectedPublicKey;
190
+ if (pinned) add("gateway_key_match", issuerVerified, "bundle key does not match the pinned gateway key");
191
+ return { verdict: steps.every((s) => s.ok) ? "VERIFIED" : "FAILED", issuerVerified, pinned, steps, errors };
192
+ } catch (e) {
102
193
  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: [] }
194
+ verdict: "FAILED",
195
+ issuerVerified: false,
196
+ pinned,
197
+ steps: [{ name: "verifier_exception", ok: false }],
198
+ errors: [`verifier rejected a malformed bundle: ${String(e)}`]
110
199
  };
111
200
  }
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
- };
201
+ }
202
+ function verifyEvidenceBundle(bundleJson, expectedPublicKey) {
203
+ let parsed;
204
+ try {
205
+ parsed = JSON.parse(bundleJson);
206
+ } catch {
207
+ return { verdict: "FAILED", issuerVerified: false, pinned: false, steps: [], errors: ["failed to parse bundle JSON"] };
208
+ }
209
+ const shapeErr = validateShape(parsed);
210
+ if (shapeErr) return { verdict: "FAILED", issuerVerified: false, pinned: false, steps: [], errors: [`unrecognized evidence-bundle format: ${shapeErr}`] };
211
+ return verifySepBundle(parsed, expectedPublicKey);
134
212
  }
135
213
  if (typeof process !== "undefined" && process.argv[1]?.includes("verify")) {
136
214
  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>");
215
+ const args = process.argv.slice(2);
216
+ const file = args.find((a) => !a.startsWith("--"));
217
+ const pk = args.includes("--pubkey") ? args[args.indexOf("--pubkey") + 1] : void 0;
218
+ if (!file) {
219
+ console.error("Usage: aga-verify <bundle.json> [--pubkey <64-hex-gateway-key>]");
220
+ process.exit(2);
221
+ }
222
+ let raw;
223
+ try {
224
+ raw = readFileSync(file, "utf-8");
225
+ } catch (e) {
226
+ console.log("\nAGA Independent Verifier\n");
227
+ console.log("OVERALL: FAILED (could not read bundle file)");
228
+ console.log(`
229
+ Errors:
230
+ - ${String(e)}`);
140
231
  process.exit(1);
141
232
  }
142
- const bundleJson = readFileSync(bundlePath, "utf-8");
143
- const result = verifyEvidenceBundle(bundleJson);
233
+ const result = verifyEvidenceBundle(raw, pk);
144
234
  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}`);
235
+ for (const s of result.steps) console.log(` ${s.ok ? "PASS" : "FAIL"} ${s.name}`);
236
+ const prov = result.verdict === "VERIFIED" ? result.pinned ? " (provenance verified)" : " (integrity only \u2014 no --pubkey given)" : "";
149
237
  console.log(`
150
- OVERALL: ${result.overall ? "VERIFIED" : "FAILED"}`);
238
+ OVERALL: ${result.verdict}${prov}`);
239
+ if (!result.pinned && result.verdict === "VERIFIED") {
240
+ console.log("NOTE: integrity + self-consistency proven, but NOT provenance. Re-run with --pubkey <gateway-key> to prove WHO issued it.");
241
+ }
151
242
  if (result.errors.length) {
152
243
  console.log("\nErrors:");
153
244
  result.errors.forEach((e) => console.log(` - ${e}`));
154
245
  }
155
- process.exit(result.overall ? 0 : 1);
246
+ process.exit(result.verdict === "VERIFIED" ? 0 : 1);
156
247
  }
157
248
  export {
158
- verifyArtifactSignature,
159
- verifyCheckpointAnchor,
160
249
  verifyEvidenceBundle,
161
- verifyMerkleProofs,
162
- verifyReceiptSignatures
250
+ verifySepBundle
163
251
  };