@sanning/proof 0.19.1 → 0.20.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.
Files changed (98) hide show
  1. package/README.md +30 -29
  2. package/dist/agent-record.d.ts +62 -0
  3. package/dist/agent-record.js +527 -0
  4. package/dist/agent-schemas.generated.d.ts +15 -0
  5. package/dist/agent-schemas.generated.js +5759 -0
  6. package/dist/case-file.d.ts +32 -89
  7. package/dist/case-file.js +41 -93
  8. package/dist/case.d.ts +339 -822
  9. package/dist/case.js +667 -1160
  10. package/dist/chain.d.ts +19 -47
  11. package/dist/chain.js +78 -205
  12. package/dist/claims.d.ts +211 -0
  13. package/dist/claims.js +1081 -0
  14. package/dist/cli.d.ts +45 -68
  15. package/dist/cli.js +618 -1022
  16. package/dist/compose.js +23 -65
  17. package/dist/crypto.d.ts +7 -8
  18. package/dist/crypto.js +60 -147
  19. package/dist/declaration.d.ts +68 -194
  20. package/dist/declaration.js +175 -518
  21. package/dist/environment.d.ts +9 -30
  22. package/dist/environment.js +17 -66
  23. package/dist/evidence.d.ts +119 -160
  24. package/dist/evidence.js +744 -1103
  25. package/dist/exit-codes.d.ts +11 -27
  26. package/dist/exit-codes.js +24 -59
  27. package/dist/file-match.d.ts +33 -79
  28. package/dist/file-match.js +33 -69
  29. package/dist/grading.d.ts +98 -135
  30. package/dist/grading.js +224 -380
  31. package/dist/handoff.d.ts +62 -160
  32. package/dist/handoff.js +153 -393
  33. package/dist/index.d.ts +14 -10
  34. package/dist/index.js +60 -167
  35. package/dist/json-schema.d.ts +29 -0
  36. package/dist/json-schema.js +298 -0
  37. package/dist/merkle.js +16 -28
  38. package/dist/message-parts.d.ts +56 -212
  39. package/dist/message-parts.js +142 -502
  40. package/dist/side-content.d.ts +11 -22
  41. package/dist/side-content.js +22 -65
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.js +19 -5
  44. package/dist/verifier.d.ts +40 -114
  45. package/dist/verifier.js +196 -535
  46. package/dist/witness-cache.d.ts +24 -45
  47. package/dist/witness-cache.js +38 -81
  48. package/dist/witness-lookup.d.ts +36 -57
  49. package/dist/witness-lookup.js +89 -139
  50. package/dist/zip-reader.d.ts +18 -33
  51. package/dist/zip-reader.js +19 -34
  52. package/package.json +11 -3
  53. package/src/agent-record.ts +608 -0
  54. package/src/agent-schemas.generated.ts +5764 -0
  55. package/src/case-file.ts +45 -109
  56. package/src/case.ts +814 -1328
  57. package/src/chain.ts +85 -224
  58. package/src/claims.ts +1253 -0
  59. package/src/cli.ts +714 -1176
  60. package/src/compose.ts +33 -79
  61. package/src/crypto.ts +60 -147
  62. package/src/declaration.ts +219 -656
  63. package/src/environment.ts +17 -65
  64. package/src/evidence.ts +958 -1381
  65. package/src/exit-codes.ts +24 -63
  66. package/src/file-match.ts +40 -89
  67. package/src/grading.ts +287 -421
  68. package/src/handoff.ts +175 -427
  69. package/src/index.ts +131 -177
  70. package/src/json-schema.ts +295 -0
  71. package/src/merkle.ts +16 -28
  72. package/src/message-parts.ts +155 -533
  73. package/src/side-content.ts +25 -68
  74. package/src/types.ts +55 -36
  75. package/src/verifier.ts +202 -526
  76. package/src/witness-cache.ts +45 -94
  77. package/src/witness-lookup.ts +107 -177
  78. package/src/zip-reader.ts +20 -38
  79. package/vectors/README.md +52 -34
  80. package/vectors/agent-record.json +2175 -0
  81. package/vectors/case-file-index.json +1 -1
  82. package/vectors/case-split.json +2 -2
  83. package/vectors/chain-reading.json +3 -3
  84. package/vectors/file-match.json +285 -148
  85. package/vectors/jcs-reader.json +6 -6
  86. package/vectors/pack-grading.json +102 -25
  87. package/vectors/sandbox-evidence.json +573 -581
  88. package/vectors/stamp-on-chain.json +18 -18
  89. package/vectors/witness-cache.json +49 -49
  90. package/dist/agent-proof.d.ts +0 -28
  91. package/dist/agent-proof.js +0 -226
  92. package/dist/run-adapters.d.ts +0 -10
  93. package/dist/run-adapters.js +0 -35
  94. package/dist/run.d.ts +0 -209
  95. package/dist/run.js +0 -1405
  96. package/src/agent-proof.ts +0 -264
  97. package/src/run-adapters.ts +0 -44
  98. package/src/run.ts +0 -1598
@@ -1,264 +0,0 @@
1
- // Agent inclusion proof verification (`sanning.agent.proof/v1`).
2
- //
3
- // The `sanning.agent.proof/v1` bundle is the agent's ~1.4 KB self-contained
4
- // inclusion proof: a signed checkpoint envelope (sanning.agent/v1, INLINE payload)
5
- // plus a single leaf object and its RFC 9162 audit path. Verifying it is the
6
- // inverse of `sanningd proof`. The CLI sniffs `spec_version` and routes here so
7
- // ONE verifier covers both the agent (this) and the anchor SDK (evidence.ts).
8
- //
9
- // Algorithm:
10
- // 1. Verify the checkpoint envelope (signature + inline payload_hash bind).
11
- // 2. leaf_hash = SHA-256(0x00 || JCS(leaf)).
12
- // 3. Walk audit_path with leaf_index + leaf_count (from the checkpoint
13
- // payload) to reconstruct the root.
14
- // 4. Confirm the reconstructed root == checkpoint_envelope.payload.merkle_root.
15
- // 5. (optional) Re-fetch checkpoint_tx_id from a gateway and byte-compare.
16
-
17
- import { hexToBytes, utf8 } from "./crypto.js";
18
- import { safeInteger } from "./grading.js";
19
- import { leafHash, verifyInclusion } from "./merkle.js";
20
- import { MAX_CANONICAL_DEPTH, exceedsDepth, jcs, jcsParse, verifyEnvelope } from "./verifier.js";
21
- import type { Envelope } from "./types.js";
22
-
23
- export const AGENT_PROOF_SPEC_VERSION = "sanning.agent.proof/v1";
24
-
25
- export interface AgentProofBundle {
26
- spec_version: string;
27
- checkpoint_envelope: Envelope;
28
- checkpoint_tx_id: string;
29
- leaf: Record<string, unknown>;
30
- leaf_index: number;
31
- audit_path: string[];
32
- [k: string]: unknown;
33
- }
34
-
35
- export interface AgentProofResult {
36
- status: "verified" | "failed" | "partial" | "malformed";
37
- // The checkpoint envelope is authentic (signature + inline payload bind).
38
- checkpointOk: boolean;
39
- // The leaf's RFC 9162 audit path reconstructs the checkpoint's merkle_root.
40
- inclusionOk: boolean;
41
- // On-chain re-fetch matched (null when not requested).
42
- onChainOk: boolean | null;
43
- onChainChecked: boolean;
44
- leafIndex: number;
45
- leafCount: number | null;
46
- checkpointTxId: string | null;
47
- errors: string[];
48
- }
49
-
50
- export interface VerifyAgentProofOptions {
51
- gateways?: string[];
52
- fetchImpl?: typeof fetch;
53
- }
54
-
55
- /** The sentence `agent_proof.py` writes when the proof's own fields are not usable. */
56
- const MALFORMED_PROOF = "merkle_root / audit_path / leaf_index malformed";
57
-
58
- function toHex(bytes: Uint8Array): string {
59
- let out = "";
60
- for (const x of bytes) out += x.toString(16).padStart(2, "0");
61
- return out;
62
- }
63
-
64
- export function isAgentProofSpec(specVersion: unknown): boolean {
65
- if (typeof specVersion !== "string") return false;
66
- return specVersion === AGENT_PROOF_SPEC_VERSION || specVersion.startsWith(`${AGENT_PROOF_SPEC_VERSION}.`);
67
- }
68
-
69
- export async function verifyAgentProof(
70
- bundle: unknown,
71
- options: VerifyAgentProofOptions = {},
72
- ): Promise<AgentProofResult> {
73
- const fail = (error: string): AgentProofResult => ({
74
- status: "malformed",
75
- checkpointOk: false,
76
- inclusionOk: false,
77
- onChainOk: null,
78
- onChainChecked: false,
79
- leafIndex: -1,
80
- leafCount: null,
81
- checkpointTxId: null,
82
- errors: [error],
83
- });
84
-
85
- if (bundle === null || typeof bundle !== "object" || Array.isArray(bundle)) {
86
- return fail("bundle is not a JSON object");
87
- }
88
- // Bound nesting before any canonicalization (checkpoint envelope + Merkle
89
- // leaves are canonicalized recursively) — a fixed shared limit keeps this a
90
- // `malformed` verdict in every kernel. See MAX_CANONICAL_DEPTH.
91
- if (exceedsDepth(bundle, MAX_CANONICAL_DEPTH)) {
92
- return fail(
93
- `bundle nesting exceeds the ${MAX_CANONICAL_DEPTH}-level canonicalization depth bound`,
94
- );
95
- }
96
- const b = bundle as AgentProofBundle;
97
- if (!isAgentProofSpec(b.spec_version)) {
98
- // An absent one reads `null`, as in `agent_proof.py` (`dict.get` gives
99
- // None); `JSON.stringify(undefined)` would write the word `undefined`.
100
- return fail(`unsupported bundle spec_version: ${JSON.stringify(b.spec_version ?? null)}`);
101
- }
102
- if (b.checkpoint_envelope === null || typeof b.checkpoint_envelope !== "object") {
103
- return fail("bundle missing checkpoint_envelope");
104
- }
105
- if (b.leaf === null || typeof b.leaf !== "object") {
106
- return fail("bundle missing leaf");
107
- }
108
- if (!Array.isArray(b.audit_path)) {
109
- return fail(MALFORMED_PROOF);
110
- }
111
-
112
- const errors: string[] = [];
113
-
114
- // Step 1: verify the checkpoint envelope (inline payload — no payloadBytes).
115
- let checkpointOk = false;
116
- let merkleRoot: string | undefined;
117
- let leafCount: number | undefined;
118
- try {
119
- const res = await verifyEnvelope(b.checkpoint_envelope);
120
- checkpointOk = res.ok && res.payloadHashOk === true;
121
- if (!res.signatureOk) errors.push("checkpoint envelope signature failed");
122
- if (res.payloadHashOk !== true) errors.push("checkpoint envelope payload binding failed");
123
- // A malformed skeleton field (proof#95) fails the envelope with a valid
124
- // signature and binding; name it rather than fail with no reason.
125
- for (const m of res.errors) if (m.startsWith("malformed ")) errors.push(`checkpoint envelope ${m}`);
126
- const payload = (b.checkpoint_envelope.payload ?? {}) as {
127
- merkle_root?: unknown;
128
- leaf_count?: unknown;
129
- };
130
- if (typeof payload.merkle_root === "string") merkleRoot = payload.merkle_root;
131
- // `leaf_count` and `leaf_index` are read BY VALUE, the rule every
132
- // inclusion reader in both kernels follows (evidence-bundle A38): `3.0` is
133
- // 3, and a fraction, a value past 2**53 - 1, a string or a boolean is not
134
- // an integer. `agent_proof.py` reads them through `_safe_integer`.
135
- leafCount = safeInteger(payload.leaf_count) ?? undefined;
136
- if (merkleRoot === undefined) errors.push("checkpoint payload missing merkle_root");
137
- if (leafCount === undefined) errors.push("checkpoint payload leaf_count is not an integer");
138
- } catch {
139
- // The sentence `agent_proof.py` writes for the same failure. The thrown
140
- // text is the host's, and a public result carries the kernel's words only
141
- // (proof#150).
142
- errors.push("checkpoint envelope verification failed");
143
- }
144
-
145
- // Steps 2-4: leaf hash, audit-path walk, root match.
146
- let inclusionOk = false;
147
- if (merkleRoot !== undefined && leafCount !== undefined) {
148
- let lh: Uint8Array | undefined;
149
- try {
150
- lh = await leafHash(utf8(jcs(b.leaf)));
151
- } catch {
152
- errors.push("leaf is not JCS-canonicalizable");
153
- }
154
- const leafIndex = safeInteger(b.leaf_index);
155
- if (lh !== undefined && leafIndex === null) {
156
- // `agent_proof.py` refuses a leaf_index that is not an integer with this
157
- // sentence. Without the check a string, `null` or `1.5` reaches the walk
158
- // and reads as a failed inclusion proof instead (proof#150).
159
- errors.push(MALFORMED_PROOF);
160
- } else if (lh !== undefined) {
161
- try {
162
- inclusionOk = await verifyInclusion(
163
- lh,
164
- leafIndex as number,
165
- leafCount,
166
- b.audit_path.map((h) => hexToBytes(h)),
167
- hexToBytes(merkleRoot),
168
- );
169
- if (!inclusionOk) errors.push("audit_path does not reconstruct merkle_root");
170
- } catch {
171
- // `agent_proof.py`'s sentence for the same failure; the thrown text is
172
- // the host's (proof#150).
173
- errors.push(MALFORMED_PROOF);
174
- }
175
- }
176
- }
177
-
178
- // Step 5: optional on-chain re-fetch.
179
- const gateways = options.gateways ?? [];
180
- const onChainChecked = gateways.length > 0;
181
- let onChainOk: boolean | null = null;
182
- let gatewayUnavailable = false;
183
- if (onChainChecked) {
184
- const fetchImpl = options.fetchImpl ?? (globalThis.fetch as typeof fetch | undefined);
185
- if (typeof fetchImpl !== "function") {
186
- gatewayUnavailable = true;
187
- errors.push("on-chain re-fetch requested but no fetch implementation is available");
188
- } else {
189
- const outcome = await refetch(b.checkpoint_envelope, b.checkpoint_tx_id, gateways, fetchImpl);
190
- onChainOk = outcome.ok;
191
- if (outcome.unavailable) gatewayUnavailable = true;
192
- if (outcome.error) errors.push(outcome.error);
193
- }
194
- }
195
-
196
- let status: AgentProofResult["status"];
197
- if (!checkpointOk || !inclusionOk || onChainOk === false) {
198
- status = "failed";
199
- } else if (gatewayUnavailable) {
200
- status = "partial";
201
- } else {
202
- status = "verified";
203
- }
204
-
205
- return {
206
- status,
207
- checkpointOk,
208
- inclusionOk,
209
- onChainOk,
210
- onChainChecked,
211
- // Read by value, as the walk above read it: a `2.5` or `1e+21` index is
212
- // reported as no index, never printed beside a verdict that refused it.
213
- leafIndex: safeInteger(b.leaf_index) ?? -1,
214
- leafCount: leafCount ?? null,
215
- checkpointTxId: typeof b.checkpoint_tx_id === "string" ? b.checkpoint_tx_id : null,
216
- errors,
217
- };
218
- }
219
-
220
- async function refetch(
221
- envelope: Envelope,
222
- txId: string,
223
- gateways: string[],
224
- fetchImpl: typeof fetch,
225
- ): Promise<{ ok: boolean | null; unavailable: boolean; error?: string }> {
226
- let expectedHex: string;
227
- try {
228
- expectedHex = toHex(utf8(jcs(envelope)));
229
- } catch {
230
- return { ok: false, unavailable: false, error: "checkpoint envelope is not JCS-canonicalizable" };
231
- }
232
- let anyReachable = false;
233
- for (const gw of gateways) {
234
- const url = `${gw.replace(/\/+$/, "")}/${txId}`;
235
- try {
236
- const resp = await fetchImpl(url);
237
- if (!resp.ok) continue;
238
- anyReachable = true;
239
- const onChain = new Uint8Array(await resp.arrayBuffer());
240
- if (toHex(onChain) === expectedHex) return { ok: true, unavailable: false };
241
- try {
242
- // `jcsParse`, matching `_probe_gateway` in
243
- // `src/sanning_proof/evidence.py`. These bytes arrive from a gateway or
244
- // a CDN in front of one, so a lenient decoder is reachable without any
245
- // party building a pack: a CDN serving the envelope with a byte-order
246
- // mark split this probe from the one in `evidence.ts`, and two honest
247
- // readers of ONE transaction disagreed (proof#140 gate 5).
248
- const parsed = jcsParse(onChain);
249
- if (toHex(utf8(jcs(parsed))) === expectedHex) return { ok: true, unavailable: false };
250
- } catch {
251
- // fall through
252
- }
253
- return {
254
- ok: false,
255
- unavailable: false,
256
- error: `on-chain bytes at ${txId} do not match the checkpoint envelope`,
257
- };
258
- } catch {
259
- // try next gateway
260
- }
261
- }
262
- if (anyReachable) return { ok: false, unavailable: false, error: `checkpoint ${txId} mismatched on every gateway` };
263
- return { ok: null, unavailable: true, error: `checkpoint ${txId} unreachable on all gateways` };
264
- }
@@ -1,44 +0,0 @@
1
- // Loading the enclave attestation adapters a reader has installed. Kept out of
2
- // `index.ts` on purpose: it imports packages by name, which a browser bundle
3
- // cannot resolve, and only the command line needs it. `run.ts` imports nothing.
4
- //
5
- // The packages are named by `ADAPTER_MODULES` and by nothing else: a provider
6
- // id read from a pack is never turned into a module name.
7
-
8
- import { ADAPTER_MODULES, declaredKinds, type EnclaveAdapter } from "./run.js";
9
-
10
- /** Import a module by name. A parameter so a test can watch what is asked for. */
11
- export type Importer = (name: string) => Promise<unknown>;
12
-
13
- const dynamicImport: Importer = (name) => import(/* @vite-ignore */ name);
14
-
15
- /**
16
- * The adapters this environment holds, loaded from `ADAPTER_MODULES` and
17
- * nothing else. A module counts only when it exports the provider it was
18
- * loaded for as `provider`, a `check` function, and the failure kinds it
19
- * declares as `kinds` (`declaredKinds`).
20
- */
21
- export async function loadInstalledAdapters(
22
- importer: Importer = dynamicImport,
23
- ): Promise<Map<string, EnclaveAdapter>> {
24
- const found = new Map<string, EnclaveAdapter>();
25
- for (const [provider, name] of Object.entries(ADAPTER_MODULES)) {
26
- let module: unknown;
27
- try {
28
- module = await importer(name);
29
- } catch {
30
- continue;
31
- }
32
- const m = module as Partial<EnclaveAdapter> | null;
33
- if (
34
- m !== null &&
35
- typeof m === "object" &&
36
- m.provider === provider &&
37
- typeof m.check === "function" &&
38
- declaredKinds(m) !== null
39
- ) {
40
- found.set(provider, m as EnclaveAdapter);
41
- }
42
- }
43
- return found;
44
- }