@zanii/core 0.5.2 → 0.5.3
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 +1 -0
- package/dist/receipt.d.ts.map +1 -1
- package/dist/receipt.js +3 -5
- package/dist/receipt.js.map +1 -1
- package/package.json +1 -1
- package/src/receipt.ts +340 -344
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ revocations, and on-chain anchor consistency.
|
|
|
61
61
|
|
|
62
62
|
## Changelog
|
|
63
63
|
|
|
64
|
+
- **0.5.3** — protocol 1.3: `payloadBytes` encodes non-integer numbers as RFC 8785 specifies instead of refusing them; whole numbers beyond ±(2^53−1) stay refused.
|
|
64
65
|
- **0.5.2** — optional `risk` level on receipts (`RISK_LEVELS`, `isRiskLevel`); session-close receipts (`SESSION_CLOSE_TARGET`, `isSessionClose`) reported by `verifyAuditBundle`; swarm receipt verification moved here so an honest swarm co-signer's audit bundle verifies; integers beyond ±(2^53−1) refused in payloads (protocol 1.2).
|
|
65
66
|
- **0.4.0** — optional signature-covered `subject_tag` receipt field (per-subject auditability, `@zanii/subject`).
|
|
66
67
|
- **0.3.0** — `saltedPayloadHash`/`verifyPayload`, `createConfirmation`/`verifyConfirmation`, `createCoSignature`/`verifyCoSignature`, and optional receipt provenance fields (`runtime_hash`/`model_id`/`manifest_hash`).
|
package/dist/receipt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"AAKA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAE1D,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAGxD;AAED,0MAA0M;AAC1M,eAAO,MAAM,WAAW,gDAAiD,CAAC;AAC1E,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AACrD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,SAAS,CAEtD;AAED,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,CAAC,CAAC;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;0FACsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC;IACzB,iFAAiF;IACjF,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,MAAM,CAGhE;
|
|
1
|
+
{"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
|
package/dist/receipt.js
CHANGED
|
@@ -37,11 +37,9 @@ function assertPayloadCanonicalizable(v, path = 'payload') {
|
|
|
37
37
|
if (typeof v === 'number') {
|
|
38
38
|
if (!Number.isFinite(v))
|
|
39
39
|
throw new Error(`${path}: ${v} has no JSON representation`);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
if (!Number.isSafeInteger(v)) {
|
|
40
|
+
// A non-integer encodes as RFC 8785 §3.2.2.3 (ECMAScript's own form, which Python's
|
|
41
|
+
// SDK reproduces byte for byte since protocol 1.3), so it needs no check here.
|
|
42
|
+
if (Number.isInteger(v) && !Number.isSafeInteger(v)) {
|
|
45
43
|
// JavaScript has already rounded it by now; another SDK would keep it exact.
|
|
46
44
|
throw new Error(`${path}: integer ${v} is outside +/-(2^53-1): not every JSON implementation can ` +
|
|
47
45
|
'represent it exactly, so the SDKs would disagree on its bytes. Pass it as a string.');
|
package/dist/receipt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAuB,MAAM,iBAAiB,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAE1D,qDAAqD;AACrD,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,MAAM,CAAC,GAAG,OAAqE,CAAC;IAChF,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,oBAAoB,IAAI,CAAC,CAAC,MAAM,KAAK,eAAe,CAAC;AAC1G,CAAC;AAED,0MAA0M;AAC1M,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAE1E,MAAM,UAAU,WAAW,CAAC,CAAU;IACpC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAK,WAAiC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC;AAqCD,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,MAAM,KAAK,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACxF,OAAO,UAAU,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,SAAS,4BAA4B,CAAC,CAAU,EAAE,IAAI,GAAG,SAAS;IAChE,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO;IAC1E,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACrF,IAAI,
|
|
1
|
+
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanii/core",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Zanii protocol: verifiable identity, signed action receipts, Merkle transparency-log proofs. Pure, no I/O.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ledger.zanii.agency",
|
package/src/receipt.ts
CHANGED
|
@@ -1,344 +1,340 @@
|
|
|
1
|
-
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
-
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
-
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils';
|
|
4
|
-
import { canonicalBytes, decodeSig, encodeSig, jcsHash } from './encoding.js';
|
|
5
|
-
import { publicKeyFromDid } from './keys.js';
|
|
6
|
-
import { scopesAllow, verifyChain, type DelegationCert } from './delegation.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The target of a session-close receipt (SPEC §15). An agent records one when it ends a
|
|
10
|
-
* session deliberately, so a verifier can tell a run that finished from one that was cut
|
|
11
|
-
* off. The per-agent `prev` chain already proves nothing is missing between receipts;
|
|
12
|
-
* this marks where a session was declared over.
|
|
13
|
-
*/
|
|
14
|
-
export const SESSION_CLOSE_TARGET = 'zanii.session.close';
|
|
15
|
-
|
|
16
|
-
/** True for a solo receipt that closes a session. */
|
|
17
|
-
export function isSessionClose(receipt: unknown): boolean {
|
|
18
|
-
const r = receipt as { type?: string; target?: string; action?: string } | null;
|
|
19
|
-
return !!r && r.type === undefined && r.target === SESSION_CLOSE_TARGET && r.action === 'session.close';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** What is at stake in an action (SPEC §3). A label the recorder sets, signed with the receipt: useful for policy and triage, and a control only where something independent of the agent enforces it. */
|
|
23
|
-
export const RISK_LEVELS = ['low', 'medium', 'high', 'critical'] as const;
|
|
24
|
-
export type RiskLevel = (typeof RISK_LEVELS)[number];
|
|
25
|
-
export function isRiskLevel(v: unknown): v is RiskLevel {
|
|
26
|
-
return typeof v === 'string' && (RISK_LEVELS as readonly string[]).includes(v);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Receipt {
|
|
30
|
-
v: 1;
|
|
31
|
-
agent_id: string;
|
|
32
|
-
delegation: DelegationCert[];
|
|
33
|
-
action: string;
|
|
34
|
-
target: string;
|
|
35
|
-
payload_hash: string;
|
|
36
|
-
ts: string;
|
|
37
|
-
prev: string | null;
|
|
38
|
-
/** Optional provenance (SPEC §3). Present when set; covered by the signature. */
|
|
39
|
-
runtime_hash?: string;
|
|
40
|
-
model_id?: string;
|
|
41
|
-
/** Hash of the product's tool/scope manifest that governed this action. */
|
|
42
|
-
manifest_hash?: string;
|
|
43
|
-
/** Optional pseudonymous data-subject tag (SPEC §3) — lets the end user the action was
|
|
44
|
-
* about find + audit their own slice (`@zanii/subject`). Covered by the signature. */
|
|
45
|
-
subject_tag?: string;
|
|
46
|
-
/**
|
|
47
|
-
* How `payload_hash` committed to the payload (SPEC §3). `'jcs'` means the payload was
|
|
48
|
-
* encoded with `payloadBytes` (RFC 8785) before hashing — the only value written since
|
|
49
|
-
* `@zanii/sdk` 0.5.0. ABSENT means a pre-0.5.0 receipt, whose bytes came from the SDK's
|
|
50
|
-
* own `JSON.stringify`/`json.dumps` and therefore differ between the two SDKs; those
|
|
51
|
-
* are checked with `verifyPayloadLegacy`, never with `verifyPayload`.
|
|
52
|
-
*/
|
|
53
|
-
payload_encoding?: 'jcs';
|
|
54
|
-
/** What is at stake (SPEC §3). Signed; absent means the recorder did not say. */
|
|
55
|
-
risk?: RiskLevel;
|
|
56
|
-
sig: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface VerifyResult {
|
|
60
|
-
ok: boolean;
|
|
61
|
-
error?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function hashPayload(payload: Uint8Array | string): string {
|
|
65
|
-
const bytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
|
|
66
|
-
return `sha256:${bytesToHex(sha256(bytes))}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Reject anything whose canonical form the two SDKs would not agree on. Mirrors
|
|
71
|
-
* `_check_jcs_safe` in `zanii.encoding`: non-integer numbers are the one place JSON
|
|
72
|
-
* serialisers legitimately disagree, so they are refused rather than hashed into a
|
|
73
|
-
* receipt no Python verifier can reproduce. Use integer minor units (`@zanii/payments`).
|
|
74
|
-
*/
|
|
75
|
-
function assertPayloadCanonicalizable(v: unknown, path = 'payload'): void {
|
|
76
|
-
if (v === null || typeof v === 'string' || typeof v === 'boolean') return;
|
|
77
|
-
if (typeof v === 'number') {
|
|
78
|
-
if (!Number.isFinite(v)) throw new Error(`${path}: ${v} has no JSON representation`);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
*
|
|
131
|
-
* `
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (ch === '
|
|
174
|
-
else if (
|
|
175
|
-
else if (
|
|
176
|
-
else if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
else
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (v
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
/** Optional
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
...(fields.
|
|
268
|
-
...(fields.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
export function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
receipt:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (
|
|
318
|
-
return { ok: false, error:
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
receipts
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
expectedPrev = receiptHash(r);
|
|
342
|
-
}
|
|
343
|
-
return { ok: true };
|
|
344
|
-
}
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
+
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils';
|
|
4
|
+
import { canonicalBytes, decodeSig, encodeSig, jcsHash } from './encoding.js';
|
|
5
|
+
import { publicKeyFromDid } from './keys.js';
|
|
6
|
+
import { scopesAllow, verifyChain, type DelegationCert } from './delegation.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The target of a session-close receipt (SPEC §15). An agent records one when it ends a
|
|
10
|
+
* session deliberately, so a verifier can tell a run that finished from one that was cut
|
|
11
|
+
* off. The per-agent `prev` chain already proves nothing is missing between receipts;
|
|
12
|
+
* this marks where a session was declared over.
|
|
13
|
+
*/
|
|
14
|
+
export const SESSION_CLOSE_TARGET = 'zanii.session.close';
|
|
15
|
+
|
|
16
|
+
/** True for a solo receipt that closes a session. */
|
|
17
|
+
export function isSessionClose(receipt: unknown): boolean {
|
|
18
|
+
const r = receipt as { type?: string; target?: string; action?: string } | null;
|
|
19
|
+
return !!r && r.type === undefined && r.target === SESSION_CLOSE_TARGET && r.action === 'session.close';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** What is at stake in an action (SPEC §3). A label the recorder sets, signed with the receipt: useful for policy and triage, and a control only where something independent of the agent enforces it. */
|
|
23
|
+
export const RISK_LEVELS = ['low', 'medium', 'high', 'critical'] as const;
|
|
24
|
+
export type RiskLevel = (typeof RISK_LEVELS)[number];
|
|
25
|
+
export function isRiskLevel(v: unknown): v is RiskLevel {
|
|
26
|
+
return typeof v === 'string' && (RISK_LEVELS as readonly string[]).includes(v);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Receipt {
|
|
30
|
+
v: 1;
|
|
31
|
+
agent_id: string;
|
|
32
|
+
delegation: DelegationCert[];
|
|
33
|
+
action: string;
|
|
34
|
+
target: string;
|
|
35
|
+
payload_hash: string;
|
|
36
|
+
ts: string;
|
|
37
|
+
prev: string | null;
|
|
38
|
+
/** Optional provenance (SPEC §3). Present when set; covered by the signature. */
|
|
39
|
+
runtime_hash?: string;
|
|
40
|
+
model_id?: string;
|
|
41
|
+
/** Hash of the product's tool/scope manifest that governed this action. */
|
|
42
|
+
manifest_hash?: string;
|
|
43
|
+
/** Optional pseudonymous data-subject tag (SPEC §3) — lets the end user the action was
|
|
44
|
+
* about find + audit their own slice (`@zanii/subject`). Covered by the signature. */
|
|
45
|
+
subject_tag?: string;
|
|
46
|
+
/**
|
|
47
|
+
* How `payload_hash` committed to the payload (SPEC §3). `'jcs'` means the payload was
|
|
48
|
+
* encoded with `payloadBytes` (RFC 8785) before hashing — the only value written since
|
|
49
|
+
* `@zanii/sdk` 0.5.0. ABSENT means a pre-0.5.0 receipt, whose bytes came from the SDK's
|
|
50
|
+
* own `JSON.stringify`/`json.dumps` and therefore differ between the two SDKs; those
|
|
51
|
+
* are checked with `verifyPayloadLegacy`, never with `verifyPayload`.
|
|
52
|
+
*/
|
|
53
|
+
payload_encoding?: 'jcs';
|
|
54
|
+
/** What is at stake (SPEC §3). Signed; absent means the recorder did not say. */
|
|
55
|
+
risk?: RiskLevel;
|
|
56
|
+
sig: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface VerifyResult {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
error?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function hashPayload(payload: Uint8Array | string): string {
|
|
65
|
+
const bytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
|
|
66
|
+
return `sha256:${bytesToHex(sha256(bytes))}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reject anything whose canonical form the two SDKs would not agree on. Mirrors
|
|
71
|
+
* `_check_jcs_safe` in `zanii.encoding`: non-integer numbers are the one place JSON
|
|
72
|
+
* serialisers legitimately disagree, so they are refused rather than hashed into a
|
|
73
|
+
* receipt no Python verifier can reproduce. Use integer minor units (`@zanii/payments`).
|
|
74
|
+
*/
|
|
75
|
+
function assertPayloadCanonicalizable(v: unknown, path = 'payload'): void {
|
|
76
|
+
if (v === null || typeof v === 'string' || typeof v === 'boolean') return;
|
|
77
|
+
if (typeof v === 'number') {
|
|
78
|
+
if (!Number.isFinite(v)) throw new Error(`${path}: ${v} has no JSON representation`);
|
|
79
|
+
// A non-integer encodes as RFC 8785 §3.2.2.3 (ECMAScript's own form, which Python's
|
|
80
|
+
// SDK reproduces byte for byte since protocol 1.3), so it needs no check here.
|
|
81
|
+
if (Number.isInteger(v) && !Number.isSafeInteger(v)) {
|
|
82
|
+
// JavaScript has already rounded it by now; another SDK would keep it exact.
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${path}: integer ${v} is outside +/-(2^53-1): not every JSON implementation can ` +
|
|
85
|
+
'represent it exactly, so the SDKs would disagree on its bytes. Pass it as a string.',
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(v)) {
|
|
91
|
+
v.forEach((x, i) => assertPayloadCanonicalizable(x, `${path}[${i}]`));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (typeof v === 'object') {
|
|
95
|
+
for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
|
|
96
|
+
// An undefined member is DROPPED, per JSON.stringify: `{a:1,b:undefined}` encodes
|
|
97
|
+
// as `{"a":1}`, which is byte-identical to what Python produces for the equivalent
|
|
98
|
+
// input (the key simply absent). A Python `None` means JSON null, a different
|
|
99
|
+
// value, and correctly hashes differently. So this needs no special case.
|
|
100
|
+
if (val === undefined) continue;
|
|
101
|
+
assertPayloadCanonicalizable(val, `${path}.${k}`);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`${path}: ${typeof v} is not canonicalizable JSON`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The bytes a `payload_hash` commits to (SPEC §3): RFC 8785 JCS, UTF-8.
|
|
110
|
+
*
|
|
111
|
+
* This exists because "hash the payload" was under-specified: each SDK reached for its
|
|
112
|
+
* own serialiser, so the same object hashed differently in TypeScript and Python and
|
|
113
|
+
* `verifyPayload` returned false across languages for honest data. One encoder, both
|
|
114
|
+
* languages, byte-identical — pinned by the cross-language vectors.
|
|
115
|
+
*
|
|
116
|
+
* A top-level `undefined` (or `null`) encodes as `null`, which is what both SDKs already
|
|
117
|
+
* hashed, so null payloads are unaffected by the change.
|
|
118
|
+
*/
|
|
119
|
+
export function payloadBytes(payload: unknown): Uint8Array {
|
|
120
|
+
const value = payload === undefined ? null : payload;
|
|
121
|
+
assertPayloadCanonicalizable(value);
|
|
122
|
+
return canonicalBytes(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Privacy-preserving payload commitment: `sha256(nonce ‖ payload)`. A bare
|
|
127
|
+
* `hashPayload` of a low-entropy payload ("phone X, amount 500") is dictionary-
|
|
128
|
+
* attackable from the public anchored log, and a linkable hash can itself be
|
|
129
|
+
* personal data (PDPL). The per-receipt `nonce` is kept operator-side (NOT in the
|
|
130
|
+
* receipt), so the on-chain value reveals nothing; keep it to later prove the
|
|
131
|
+
* payload with `verifyPayload`. If `nonce` is omitted a fresh 16-byte one is drawn.
|
|
132
|
+
*/
|
|
133
|
+
export function saltedPayloadHash(
|
|
134
|
+
payload: Uint8Array | string,
|
|
135
|
+
nonce?: string,
|
|
136
|
+
): { payload_hash: string; nonce: string } {
|
|
137
|
+
const nonceHex = nonce ?? bytesToHex(randomBytes(16));
|
|
138
|
+
const nonceBytes = hexToBytes(nonceHex);
|
|
139
|
+
const payloadBytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
|
|
140
|
+
const combined = new Uint8Array(nonceBytes.length + payloadBytes.length);
|
|
141
|
+
combined.set(nonceBytes, 0);
|
|
142
|
+
combined.set(payloadBytes, nonceBytes.length);
|
|
143
|
+
return { payload_hash: `sha256:${bytesToHex(sha256(combined))}`, nonce: nonceHex };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Prove a payload matches a salted `payload_hash`, given the nonce kept at record time. */
|
|
147
|
+
export function verifyPayload(payload: Uint8Array | string, nonce: string, payloadHash: string): boolean {
|
|
148
|
+
try {
|
|
149
|
+
return saltedPayloadHash(payload, nonce).payload_hash === payloadHash;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Which pre-0.5.0 SDK's serialiser reproduced a legacy `payload_hash`. */
|
|
156
|
+
export type PayloadDialect = 'jcs' | 'legacy-ts' | 'legacy-py';
|
|
157
|
+
|
|
158
|
+
/** Old `@zanii/sdk`: `JSON.stringify`, insertion order, no spaces, raw UTF-8. */
|
|
159
|
+
function legacyTsEncode(payload: unknown): string | undefined {
|
|
160
|
+
return JSON.stringify(payload) ?? 'null';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Old `zanii` (Python): `json.dumps(sort_keys=True)` — `, ` / `: ` separators, \uXXXX escapes. */
|
|
164
|
+
function legacyPyEncode(value: unknown): string {
|
|
165
|
+
const esc = (str: string): string => {
|
|
166
|
+
let out = '';
|
|
167
|
+
for (const ch of str) {
|
|
168
|
+
const c = ch.codePointAt(0)!;
|
|
169
|
+
if (ch === '"') out += '\\"';
|
|
170
|
+
else if (ch === '\\') out += '\\\\';
|
|
171
|
+
else if (ch === '\n') out += '\\n';
|
|
172
|
+
else if (ch === '\r') out += '\\r';
|
|
173
|
+
else if (ch === '\t') out += '\\t';
|
|
174
|
+
else if (c < 0x20) out += `\\u${c.toString(16).padStart(4, '0')}`;
|
|
175
|
+
else if (c < 0x7f) out += ch;
|
|
176
|
+
else if (c > 0xffff) {
|
|
177
|
+
// Python escapes astral characters as a surrogate pair
|
|
178
|
+
const u = c - 0x10000;
|
|
179
|
+
out += `\\u${(0xd800 + (u >> 10)).toString(16)}\\u${(0xdc00 + (u & 0x3ff)).toString(16)}`;
|
|
180
|
+
} else out += `\\u${c.toString(16).padStart(4, '0')}`;
|
|
181
|
+
}
|
|
182
|
+
return `"${out}"`;
|
|
183
|
+
};
|
|
184
|
+
const enc = (v: unknown): string => {
|
|
185
|
+
if (v === null || v === undefined) return 'null';
|
|
186
|
+
if (typeof v === 'boolean') return v ? 'true' : 'false';
|
|
187
|
+
if (typeof v === 'number') return Number.isInteger(v) ? String(v) : String(v);
|
|
188
|
+
if (typeof v === 'string') return esc(v);
|
|
189
|
+
if (Array.isArray(v)) return `[${v.map(enc).join(', ')}]`;
|
|
190
|
+
const o = v as Record<string, unknown>;
|
|
191
|
+
const keys = Object.keys(o).sort();
|
|
192
|
+
return `{${keys.map((k) => `${esc(k)}: ${enc(o[k])}`).join(', ')}}`;
|
|
193
|
+
};
|
|
194
|
+
return enc(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Verify a payload against a receipt recorded BEFORE the JCS rule (no `payload_encoding`
|
|
199
|
+
* field). Such a receipt does not record which SDK wrote it, so this tries each old
|
|
200
|
+
* dialect and reports which one matched.
|
|
201
|
+
*
|
|
202
|
+
* Honest limits: the `legacy-ts` dialect depends on the object's original KEY INSERTION
|
|
203
|
+
* ORDER, which is not recoverable from a re-parsed payload, so it only matches when the
|
|
204
|
+
* caller reconstructs the object in the order it was recorded. Float formatting is
|
|
205
|
+
* likewise best-effort. A `null` result means no dialect reproduced the hash; it does not
|
|
206
|
+
* prove the payload wrong.
|
|
207
|
+
*/
|
|
208
|
+
export function verifyPayloadLegacy(
|
|
209
|
+
payload: unknown,
|
|
210
|
+
nonce: string,
|
|
211
|
+
payloadHash: string,
|
|
212
|
+
): { ok: boolean; dialect: PayloadDialect | null } {
|
|
213
|
+
const attempts: Array<[PayloadDialect, () => Uint8Array | string]> = [
|
|
214
|
+
['jcs', () => payloadBytes(payload)],
|
|
215
|
+
['legacy-ts', () => legacyTsEncode(payload)!],
|
|
216
|
+
['legacy-py', () => legacyPyEncode(payload)],
|
|
217
|
+
];
|
|
218
|
+
for (const [dialect, encode] of attempts) {
|
|
219
|
+
try {
|
|
220
|
+
if (saltedPayloadHash(encode(), nonce).payload_hash === payloadHash) return { ok: true, dialect };
|
|
221
|
+
} catch {
|
|
222
|
+
/* this dialect cannot encode it — try the next */
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { ok: false, dialect: null };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function createReceipt(
|
|
229
|
+
fields: {
|
|
230
|
+
agentId: string;
|
|
231
|
+
delegation: DelegationCert[];
|
|
232
|
+
action: string;
|
|
233
|
+
target: string;
|
|
234
|
+
payloadHash: string;
|
|
235
|
+
ts: string;
|
|
236
|
+
prev: string | null;
|
|
237
|
+
/** Optional provenance — included in the signed receipt only when provided. */
|
|
238
|
+
runtimeHash?: string;
|
|
239
|
+
modelId?: string;
|
|
240
|
+
manifestHash?: string;
|
|
241
|
+
/** Optional pseudonymous data-subject tag — per-subject auditability (`@zanii/subject`). */
|
|
242
|
+
subjectTag?: string;
|
|
243
|
+
/** How `payloadHash` committed to the payload. Set `'jcs'` when it came from `payloadBytes`. */
|
|
244
|
+
payloadEncoding?: 'jcs';
|
|
245
|
+
/** What is at stake. Signed into the receipt when set. */
|
|
246
|
+
risk?: RiskLevel;
|
|
247
|
+
},
|
|
248
|
+
agentPrivateKey: Uint8Array,
|
|
249
|
+
): Receipt {
|
|
250
|
+
if (Number.isNaN(Date.parse(fields.ts))) throw new Error(`invalid ts timestamp: ${fields.ts}`);
|
|
251
|
+
if (fields.risk !== undefined && !isRiskLevel(fields.risk)) {
|
|
252
|
+
throw new Error(`invalid risk '${String(fields.risk)}': expected one of low, medium, high, critical`);
|
|
253
|
+
}
|
|
254
|
+
const unsigned = {
|
|
255
|
+
v: 1 as const,
|
|
256
|
+
agent_id: fields.agentId,
|
|
257
|
+
delegation: fields.delegation,
|
|
258
|
+
action: fields.action,
|
|
259
|
+
target: fields.target,
|
|
260
|
+
payload_hash: fields.payloadHash,
|
|
261
|
+
ts: fields.ts,
|
|
262
|
+
prev: fields.prev,
|
|
263
|
+
...(fields.runtimeHash ? { runtime_hash: fields.runtimeHash } : {}),
|
|
264
|
+
...(fields.modelId ? { model_id: fields.modelId } : {}),
|
|
265
|
+
...(fields.manifestHash ? { manifest_hash: fields.manifestHash } : {}),
|
|
266
|
+
...(fields.subjectTag ? { subject_tag: fields.subjectTag } : {}),
|
|
267
|
+
...(fields.payloadEncoding ? { payload_encoding: fields.payloadEncoding } : {}),
|
|
268
|
+
...(fields.risk ? { risk: fields.risk } : {}),
|
|
269
|
+
};
|
|
270
|
+
const sig = ed25519.sign(canonicalBytes(unsigned), agentPrivateKey);
|
|
271
|
+
return { ...unsigned, sig: encodeSig(sig) };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Hash of the full receipt (incl. sig) — what the next receipt's `prev` points to. */
|
|
275
|
+
export function receiptHash(receipt: Receipt): string {
|
|
276
|
+
return jcsHash(receipt);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function verifyReceiptSignature(receipt: Receipt): boolean {
|
|
280
|
+
const pub = publicKeyFromDid(receipt.agent_id);
|
|
281
|
+
const sig = decodeSig(receipt.sig);
|
|
282
|
+
if (!pub || !sig) return false;
|
|
283
|
+
const { sig: _omitted, ...unsigned } = receipt;
|
|
284
|
+
try {
|
|
285
|
+
return ed25519.verify(sig, canonicalBytes(unsigned), pub);
|
|
286
|
+
} catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Full single-receipt verification per SPEC §4:
|
|
293
|
+
* signature → delegation chain (at receipt time) → scope covers target → prev linkage.
|
|
294
|
+
*/
|
|
295
|
+
export function verifyReceipt(
|
|
296
|
+
receipt: Receipt,
|
|
297
|
+
opts: { expectedOwner?: string; revoked?: Set<string>; expectedPrev?: string | null } = {},
|
|
298
|
+
): VerifyResult {
|
|
299
|
+
if (receipt.v !== 1) return { ok: false, error: 'unsupported receipt version' };
|
|
300
|
+
if (!verifyReceiptSignature(receipt)) return { ok: false, error: 'bad receipt signature' };
|
|
301
|
+
|
|
302
|
+
const ts = new Date(receipt.ts);
|
|
303
|
+
if (Number.isNaN(ts.getTime())) return { ok: false, error: 'invalid ts' };
|
|
304
|
+
|
|
305
|
+
const chain = verifyChain(receipt.delegation, {
|
|
306
|
+
agentId: receipt.agent_id,
|
|
307
|
+
expectedRoot: opts.expectedOwner,
|
|
308
|
+
revoked: opts.revoked,
|
|
309
|
+
at: ts,
|
|
310
|
+
});
|
|
311
|
+
if (!chain.ok) return { ok: false, error: `delegation: ${chain.error}` };
|
|
312
|
+
|
|
313
|
+
if (!scopesAllow(chain.effectiveScopes!, receipt.target)) {
|
|
314
|
+
return { ok: false, error: `target ${receipt.target} outside effective scopes` };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (opts.expectedPrev !== undefined && receipt.prev !== opts.expectedPrev) {
|
|
318
|
+
return { ok: false, error: 'prev hash does not match preceding receipt (chain broken)' };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { ok: true };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Verify an agent's full receipt sequence: every receipt valid and hash-linked
|
|
326
|
+
* to the one before it. Returns the index of the first bad receipt on failure.
|
|
327
|
+
*/
|
|
328
|
+
export function verifyReceiptChain(
|
|
329
|
+
receipts: Receipt[],
|
|
330
|
+
opts: { expectedOwner?: string; revoked?: Set<string> } = {},
|
|
331
|
+
): VerifyResult & { failedIndex?: number } {
|
|
332
|
+
let expectedPrev: string | null = null;
|
|
333
|
+
for (let i = 0; i < receipts.length; i++) {
|
|
334
|
+
const r = receipts[i]!;
|
|
335
|
+
const result = verifyReceipt(r, { ...opts, expectedPrev });
|
|
336
|
+
if (!result.ok) return { ok: false, error: `receipt ${i}: ${result.error}`, failedIndex: i };
|
|
337
|
+
expectedPrev = receiptHash(r);
|
|
338
|
+
}
|
|
339
|
+
return { ok: true };
|
|
340
|
+
}
|