@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.
- package/README.md +30 -29
- package/dist/agent-record.d.ts +62 -0
- package/dist/agent-record.js +527 -0
- package/dist/agent-schemas.generated.d.ts +15 -0
- package/dist/agent-schemas.generated.js +5759 -0
- package/dist/case-file.d.ts +32 -89
- package/dist/case-file.js +41 -93
- package/dist/case.d.ts +339 -822
- package/dist/case.js +667 -1160
- package/dist/chain.d.ts +19 -47
- package/dist/chain.js +78 -205
- package/dist/claims.d.ts +211 -0
- package/dist/claims.js +1081 -0
- package/dist/cli.d.ts +45 -68
- package/dist/cli.js +618 -1022
- package/dist/compose.js +23 -65
- package/dist/crypto.d.ts +7 -8
- package/dist/crypto.js +60 -147
- package/dist/declaration.d.ts +68 -194
- package/dist/declaration.js +175 -518
- package/dist/environment.d.ts +9 -30
- package/dist/environment.js +17 -66
- package/dist/evidence.d.ts +119 -160
- package/dist/evidence.js +744 -1103
- package/dist/exit-codes.d.ts +11 -27
- package/dist/exit-codes.js +24 -59
- package/dist/file-match.d.ts +33 -79
- package/dist/file-match.js +33 -69
- package/dist/grading.d.ts +98 -135
- package/dist/grading.js +224 -380
- package/dist/handoff.d.ts +62 -160
- package/dist/handoff.js +153 -393
- package/dist/index.d.ts +14 -10
- package/dist/index.js +60 -167
- package/dist/json-schema.d.ts +29 -0
- package/dist/json-schema.js +298 -0
- package/dist/merkle.js +16 -28
- package/dist/message-parts.d.ts +56 -212
- package/dist/message-parts.js +142 -502
- package/dist/side-content.d.ts +11 -22
- package/dist/side-content.js +22 -65
- package/dist/types.d.ts +17 -0
- package/dist/types.js +19 -5
- package/dist/verifier.d.ts +40 -114
- package/dist/verifier.js +196 -535
- package/dist/witness-cache.d.ts +24 -45
- package/dist/witness-cache.js +38 -81
- package/dist/witness-lookup.d.ts +36 -57
- package/dist/witness-lookup.js +89 -139
- package/dist/zip-reader.d.ts +18 -33
- package/dist/zip-reader.js +19 -34
- package/package.json +11 -3
- package/src/agent-record.ts +608 -0
- package/src/agent-schemas.generated.ts +5764 -0
- package/src/case-file.ts +45 -109
- package/src/case.ts +814 -1328
- package/src/chain.ts +85 -224
- package/src/claims.ts +1253 -0
- package/src/cli.ts +714 -1176
- package/src/compose.ts +33 -79
- package/src/crypto.ts +60 -147
- package/src/declaration.ts +219 -656
- package/src/environment.ts +17 -65
- package/src/evidence.ts +958 -1381
- package/src/exit-codes.ts +24 -63
- package/src/file-match.ts +40 -89
- package/src/grading.ts +287 -421
- package/src/handoff.ts +175 -427
- package/src/index.ts +131 -177
- package/src/json-schema.ts +295 -0
- package/src/merkle.ts +16 -28
- package/src/message-parts.ts +155 -533
- package/src/side-content.ts +25 -68
- package/src/types.ts +55 -36
- package/src/verifier.ts +202 -526
- package/src/witness-cache.ts +45 -94
- package/src/witness-lookup.ts +107 -177
- package/src/zip-reader.ts +20 -38
- package/vectors/README.md +52 -34
- package/vectors/agent-record.json +2175 -0
- package/vectors/case-file-index.json +1 -1
- package/vectors/case-split.json +2 -2
- package/vectors/chain-reading.json +3 -3
- package/vectors/file-match.json +285 -148
- package/vectors/jcs-reader.json +6 -6
- package/vectors/pack-grading.json +102 -25
- package/vectors/sandbox-evidence.json +573 -581
- package/vectors/stamp-on-chain.json +18 -18
- package/vectors/witness-cache.json +49 -49
- package/dist/agent-proof.d.ts +0 -28
- package/dist/agent-proof.js +0 -226
- package/dist/run-adapters.d.ts +0 -10
- package/dist/run-adapters.js +0 -35
- package/dist/run.d.ts +0 -209
- package/dist/run.js +0 -1405
- package/src/agent-proof.ts +0 -264
- package/src/run-adapters.ts +0 -44
- package/src/run.ts +0 -1598
package/src/agent-proof.ts
DELETED
|
@@ -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
|
-
}
|
package/src/run-adapters.ts
DELETED
|
@@ -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
|
-
}
|