@zanii/core 0.4.0 → 0.5.1

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/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { leafHash, merkleRoot, inclusionProof, verifyInclusion, consistencyProof
5
5
  export { createSTH, verifySTH, type SignedTreeHead } from './sth.js';
6
6
  export { buildA2ABody, signA2ABody, assembleA2AReceipt, a2aHash, bodyOf, verifyA2AReceipt, participantsOf, prevFor, type A2AReceipt, type A2ABody, type A2AParty, } from './a2a.js';
7
7
  export { verifyAuditBundle, type AuditBundle, type BundledReceipt, type BundledRevocation, type BundledAnchor, type BundleReport, type BundleCheck, } from './bundle.js';
8
- export { createReceipt, receiptHash, hashPayload, saltedPayloadHash, verifyPayload, verifyReceipt, verifyReceiptSignature, verifyReceiptChain, type Receipt, type VerifyResult, } from './receipt.js';
8
+ export { createReceipt, receiptHash, hashPayload, payloadBytes, saltedPayloadHash, verifyPayload, verifyPayloadLegacy, type PayloadDialect, verifyReceipt, verifyReceiptSignature, verifyReceiptChain, type Receipt, type VerifyResult, } from './receipt.js';
9
9
  export { createConfirmation, verifyConfirmation, type ActionConfirmation, } from './confirmation.js';
10
10
  export { createCoSignature, verifyCoSignature, type ReceiptCoSignature, } from './cosign.js';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,QAAQ,GACd,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,KAAK,cAAc,EACnB,aAAa,EACb,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { leafHash, merkleRoot, inclusionProof, verifyInclusion, consistencyProof
5
5
  export { createSTH, verifySTH } from './sth.js';
6
6
  export { buildA2ABody, signA2ABody, assembleA2AReceipt, a2aHash, bodyOf, verifyA2AReceipt, participantsOf, prevFor, } from './a2a.js';
7
7
  export { verifyAuditBundle, } from './bundle.js';
8
- export { createReceipt, receiptHash, hashPayload, saltedPayloadHash, verifyPayload, verifyReceipt, verifyReceiptSignature, verifyReceiptChain, } from './receipt.js';
8
+ export { createReceipt, receiptHash, hashPayload, payloadBytes, saltedPayloadHash, verifyPayload, verifyPayloadLegacy, verifyReceipt, verifyReceiptSignature, verifyReceiptChain, } from './receipt.js';
9
9
  export { createConfirmation, verifyConfirmation, } from './confirmation.js';
10
10
  export { createCoSignature, verifyCoSignature, } from './cosign.js';
11
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAgB,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,GAIZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,OAAO,GAIR,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,GAOlB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,sBAAsB,EACtB,kBAAkB,GAGnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAgB,MAAM,WAAW,CAAC;AAC9F,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EACL,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,GAIZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,UAAU,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,OAAO,GAIR,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,GAOlB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EAEnB,aAAa,EACb,sBAAsB,EACtB,kBAAkB,GAGnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,GAElB,MAAM,aAAa,CAAC"}
package/dist/receipt.d.ts CHANGED
@@ -16,6 +16,14 @@ export interface Receipt {
16
16
  /** Optional pseudonymous data-subject tag (SPEC §3) — lets the end user the action was
17
17
  * about find + audit their own slice (`@zanii/subject`). Covered by the signature. */
18
18
  subject_tag?: string;
19
+ /**
20
+ * How `payload_hash` committed to the payload (SPEC §3). `'jcs'` means the payload was
21
+ * encoded with `payloadBytes` (RFC 8785) before hashing — the only value written since
22
+ * `@zanii/sdk` 0.5.0. ABSENT means a pre-0.5.0 receipt, whose bytes came from the SDK's
23
+ * own `JSON.stringify`/`json.dumps` and therefore differ between the two SDKs; those
24
+ * are checked with `verifyPayloadLegacy`, never with `verifyPayload`.
25
+ */
26
+ payload_encoding?: 'jcs';
19
27
  sig: string;
20
28
  }
21
29
  export interface VerifyResult {
@@ -23,6 +31,18 @@ export interface VerifyResult {
23
31
  error?: string;
24
32
  }
25
33
  export declare function hashPayload(payload: Uint8Array | string): string;
34
+ /**
35
+ * The bytes a `payload_hash` commits to (SPEC §3): RFC 8785 JCS, UTF-8.
36
+ *
37
+ * This exists because "hash the payload" was under-specified: each SDK reached for its
38
+ * own serialiser, so the same object hashed differently in TypeScript and Python and
39
+ * `verifyPayload` returned false across languages for honest data. One encoder, both
40
+ * languages, byte-identical — pinned by the cross-language vectors.
41
+ *
42
+ * A top-level `undefined` (or `null`) encodes as `null`, which is what both SDKs already
43
+ * hashed, so null payloads are unaffected by the change.
44
+ */
45
+ export declare function payloadBytes(payload: unknown): Uint8Array;
26
46
  /**
27
47
  * Privacy-preserving payload commitment: `sha256(nonce ‖ payload)`. A bare
28
48
  * `hashPayload` of a low-entropy payload ("phone X, amount 500") is dictionary-
@@ -37,6 +57,23 @@ export declare function saltedPayloadHash(payload: Uint8Array | string, nonce?:
37
57
  };
38
58
  /** Prove a payload matches a salted `payload_hash`, given the nonce kept at record time. */
39
59
  export declare function verifyPayload(payload: Uint8Array | string, nonce: string, payloadHash: string): boolean;
60
+ /** Which pre-0.5.0 SDK's serialiser reproduced a legacy `payload_hash`. */
61
+ export type PayloadDialect = 'jcs' | 'legacy-ts' | 'legacy-py';
62
+ /**
63
+ * Verify a payload against a receipt recorded BEFORE the JCS rule (no `payload_encoding`
64
+ * field). Such a receipt does not record which SDK wrote it, so this tries each old
65
+ * dialect and reports which one matched.
66
+ *
67
+ * Honest limits: the `legacy-ts` dialect depends on the object's original KEY INSERTION
68
+ * ORDER, which is not recoverable from a re-parsed payload, so it only matches when the
69
+ * caller reconstructs the object in the order it was recorded. Float formatting is
70
+ * likewise best-effort. A `null` result means no dialect reproduced the hash; it does not
71
+ * prove the payload wrong.
72
+ */
73
+ export declare function verifyPayloadLegacy(payload: unknown, nonce: string, payloadHash: string): {
74
+ ok: boolean;
75
+ dialect: PayloadDialect | null;
76
+ };
40
77
  export declare function createReceipt(fields: {
41
78
  agentId: string;
42
79
  delegation: DelegationCert[];
@@ -51,6 +88,8 @@ export declare function createReceipt(fields: {
51
88
  manifestHash?: string;
52
89
  /** Optional pseudonymous data-subject tag — per-subject auditability (`@zanii/subject`). */
53
90
  subjectTag?: string;
91
+ /** How `payloadHash` committed to the payload. Set `'jcs'` when it came from `payloadBytes`. */
92
+ payloadEncoding?: 'jcs';
54
93
  }, agentPrivateKey: Uint8Array): Receipt;
55
94
  /** Hash of the full receipt (incl. sig) — what the next receipt's `prev` points to. */
56
95
  export declare function receiptHash(receipt: Receipt): string;
@@ -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,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,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;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,GAAG,MAAM,EAC5B,KAAK,CAAC,EAAE,MAAM,GACb;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAQzC;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAMvG;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,EACD,eAAe,EAAE,UAAU,GAC1B,OAAO,CAkBT;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAUhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GACzF,YAAY,CAwBd;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EAAE,EACnB,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAO,GAC3D,YAAY,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CASzC"}
1
+ {"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
package/dist/receipt.js CHANGED
@@ -8,6 +8,58 @@ export function hashPayload(payload) {
8
8
  const bytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
9
9
  return `sha256:${bytesToHex(sha256(bytes))}`;
10
10
  }
11
+ /**
12
+ * Reject anything whose canonical form the two SDKs would not agree on. Mirrors
13
+ * `_check_jcs_safe` in `zanii.encoding`: non-integer numbers are the one place JSON
14
+ * serialisers legitimately disagree, so they are refused rather than hashed into a
15
+ * receipt no Python verifier can reproduce. Use integer minor units (`@zanii/payments`).
16
+ */
17
+ function assertPayloadCanonicalizable(v, path = 'payload') {
18
+ if (v === null || typeof v === 'string' || typeof v === 'boolean')
19
+ return;
20
+ if (typeof v === 'number') {
21
+ if (!Number.isFinite(v))
22
+ throw new Error(`${path}: ${v} has no JSON representation`);
23
+ if (!Number.isInteger(v)) {
24
+ throw new Error(`${path}: non-integer number ${v} cannot be committed to a payload hash — the two SDKs ` +
25
+ 'would encode it differently. Use integer minor units (see @zanii/payments) or a string.');
26
+ }
27
+ return;
28
+ }
29
+ if (Array.isArray(v)) {
30
+ v.forEach((x, i) => assertPayloadCanonicalizable(x, `${path}[${i}]`));
31
+ return;
32
+ }
33
+ if (typeof v === 'object') {
34
+ for (const [k, val] of Object.entries(v)) {
35
+ // An undefined member is DROPPED, per JSON.stringify: `{a:1,b:undefined}` encodes
36
+ // as `{"a":1}`, which is byte-identical to what Python produces for the equivalent
37
+ // input (the key simply absent). A Python `None` means JSON null, a different
38
+ // value, and correctly hashes differently. So this needs no special case.
39
+ if (val === undefined)
40
+ continue;
41
+ assertPayloadCanonicalizable(val, `${path}.${k}`);
42
+ }
43
+ return;
44
+ }
45
+ throw new Error(`${path}: ${typeof v} is not canonicalizable JSON`);
46
+ }
47
+ /**
48
+ * The bytes a `payload_hash` commits to (SPEC §3): RFC 8785 JCS, UTF-8.
49
+ *
50
+ * This exists because "hash the payload" was under-specified: each SDK reached for its
51
+ * own serialiser, so the same object hashed differently in TypeScript and Python and
52
+ * `verifyPayload` returned false across languages for honest data. One encoder, both
53
+ * languages, byte-identical — pinned by the cross-language vectors.
54
+ *
55
+ * A top-level `undefined` (or `null`) encodes as `null`, which is what both SDKs already
56
+ * hashed, so null payloads are unaffected by the change.
57
+ */
58
+ export function payloadBytes(payload) {
59
+ const value = payload === undefined ? null : payload;
60
+ assertPayloadCanonicalizable(value);
61
+ return canonicalBytes(value);
62
+ }
11
63
  /**
12
64
  * Privacy-preserving payload commitment: `sha256(nonce ‖ payload)`. A bare
13
65
  * `hashPayload` of a low-entropy payload ("phone X, amount 500") is dictionary-
@@ -34,6 +86,85 @@ export function verifyPayload(payload, nonce, payloadHash) {
34
86
  return false;
35
87
  }
36
88
  }
89
+ /** Old `@zanii/sdk`: `JSON.stringify`, insertion order, no spaces, raw UTF-8. */
90
+ function legacyTsEncode(payload) {
91
+ return JSON.stringify(payload) ?? 'null';
92
+ }
93
+ /** Old `zanii` (Python): `json.dumps(sort_keys=True)` — `, ` / `: ` separators, \uXXXX escapes. */
94
+ function legacyPyEncode(value) {
95
+ const esc = (str) => {
96
+ let out = '';
97
+ for (const ch of str) {
98
+ const c = ch.codePointAt(0);
99
+ if (ch === '"')
100
+ out += '\\"';
101
+ else if (ch === '\\')
102
+ out += '\\\\';
103
+ else if (ch === '\n')
104
+ out += '\\n';
105
+ else if (ch === '\r')
106
+ out += '\\r';
107
+ else if (ch === '\t')
108
+ out += '\\t';
109
+ else if (c < 0x20)
110
+ out += `\\u${c.toString(16).padStart(4, '0')}`;
111
+ else if (c < 0x7f)
112
+ out += ch;
113
+ else if (c > 0xffff) {
114
+ // Python escapes astral characters as a surrogate pair
115
+ const u = c - 0x10000;
116
+ out += `\\u${(0xd800 + (u >> 10)).toString(16)}\\u${(0xdc00 + (u & 0x3ff)).toString(16)}`;
117
+ }
118
+ else
119
+ out += `\\u${c.toString(16).padStart(4, '0')}`;
120
+ }
121
+ return `"${out}"`;
122
+ };
123
+ const enc = (v) => {
124
+ if (v === null || v === undefined)
125
+ return 'null';
126
+ if (typeof v === 'boolean')
127
+ return v ? 'true' : 'false';
128
+ if (typeof v === 'number')
129
+ return Number.isInteger(v) ? String(v) : String(v);
130
+ if (typeof v === 'string')
131
+ return esc(v);
132
+ if (Array.isArray(v))
133
+ return `[${v.map(enc).join(', ')}]`;
134
+ const o = v;
135
+ const keys = Object.keys(o).sort();
136
+ return `{${keys.map((k) => `${esc(k)}: ${enc(o[k])}`).join(', ')}}`;
137
+ };
138
+ return enc(value);
139
+ }
140
+ /**
141
+ * Verify a payload against a receipt recorded BEFORE the JCS rule (no `payload_encoding`
142
+ * field). Such a receipt does not record which SDK wrote it, so this tries each old
143
+ * dialect and reports which one matched.
144
+ *
145
+ * Honest limits: the `legacy-ts` dialect depends on the object's original KEY INSERTION
146
+ * ORDER, which is not recoverable from a re-parsed payload, so it only matches when the
147
+ * caller reconstructs the object in the order it was recorded. Float formatting is
148
+ * likewise best-effort. A `null` result means no dialect reproduced the hash; it does not
149
+ * prove the payload wrong.
150
+ */
151
+ export function verifyPayloadLegacy(payload, nonce, payloadHash) {
152
+ const attempts = [
153
+ ['jcs', () => payloadBytes(payload)],
154
+ ['legacy-ts', () => legacyTsEncode(payload)],
155
+ ['legacy-py', () => legacyPyEncode(payload)],
156
+ ];
157
+ for (const [dialect, encode] of attempts) {
158
+ try {
159
+ if (saltedPayloadHash(encode(), nonce).payload_hash === payloadHash)
160
+ return { ok: true, dialect };
161
+ }
162
+ catch {
163
+ /* this dialect cannot encode it — try the next */
164
+ }
165
+ }
166
+ return { ok: false, dialect: null };
167
+ }
37
168
  export function createReceipt(fields, agentPrivateKey) {
38
169
  if (Number.isNaN(Date.parse(fields.ts)))
39
170
  throw new Error(`invalid ts timestamp: ${fields.ts}`);
@@ -50,6 +181,7 @@ export function createReceipt(fields, agentPrivateKey) {
50
181
  ...(fields.modelId ? { model_id: fields.modelId } : {}),
51
182
  ...(fields.manifestHash ? { manifest_hash: fields.manifestHash } : {}),
52
183
  ...(fields.subjectTag ? { subject_tag: fields.subjectTag } : {}),
184
+ ...(fields.payloadEncoding ? { payload_encoding: fields.payloadEncoding } : {}),
53
185
  };
54
186
  const sig = ed25519.sign(canonicalBytes(unsigned), agentPrivateKey);
55
187
  return { ...unsigned, sig: encodeSig(sig) };
@@ -1 +1 @@
1
- {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
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;AAmChF,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,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,wBAAwB,CAAC,wDAAwD;gBACtF,yFAAyF,CAC5F,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,4BAA4B,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACtE,OAAO;IACT,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAA4B,CAAC,EAAE,CAAC;YACpE,kFAAkF;YAClF,mFAAmF;YACnF,8EAA8E;YAC9E,0EAA0E;YAC1E,IAAI,GAAG,KAAK,SAAS;gBAAE,SAAS;YAChC,4BAA4B,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,8BAA8B,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,KAAK,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACrD,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA4B,EAC5B,KAAc;IAEd,MAAM,QAAQ,GAAG,KAAK,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/F,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACzE,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,EAAE,YAAY,EAAE,UAAU,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACrF,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,aAAa,CAAC,OAA4B,EAAE,KAAa,EAAE,WAAmB;IAC5F,IAAI,CAAC;QACH,OAAO,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,YAAY,KAAK,WAAW,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAKD,iFAAiF;AACjF,SAAS,cAAc,CAAC,OAAgB;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC;AAC3C,CAAC;AAED,mGAAmG;AACnG,SAAS,cAAc,CAAC,KAAc;IACpC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAU,EAAE;QAClC,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;YAC7B,IAAI,EAAE,KAAK,GAAG;gBAAE,GAAG,IAAI,KAAK,CAAC;iBACxB,IAAI,EAAE,KAAK,IAAI;gBAAE,GAAG,IAAI,MAAM,CAAC;iBAC/B,IAAI,EAAE,KAAK,IAAI;gBAAE,GAAG,IAAI,KAAK,CAAC;iBAC9B,IAAI,EAAE,KAAK,IAAI;gBAAE,GAAG,IAAI,KAAK,CAAC;iBAC9B,IAAI,EAAE,KAAK,IAAI;gBAAE,GAAG,IAAI,KAAK,CAAC;iBAC9B,IAAI,CAAC,GAAG,IAAI;gBAAE,GAAG,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;iBAC7D,IAAI,CAAC,GAAG,IAAI;gBAAE,GAAG,IAAI,EAAE,CAAC;iBACxB,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;gBACpB,uDAAuD;gBACvD,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;gBACtB,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC5F,CAAC;;gBAAM,GAAG,IAAI,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,CAAC,CAAU,EAAU,EAAE;QACjC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACjD,IAAI,OAAO,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QACxD,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC9E,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1D,MAAM,CAAC,GAAG,CAA4B,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACtE,CAAC,CAAC;IACF,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAgB,EAChB,KAAa,EACb,WAAmB;IAEnB,MAAM,QAAQ,GAAuD;QACnE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,CAAE,CAAC;QAC7C,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;KAC7C,CAAC;IACF,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzC,IAAI,CAAC;YACH,IAAI,iBAAiB,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,KAAK,WAAW;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QACpG,CAAC;QAAC,MAAM,CAAC;YACP,kDAAkD;QACpD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,MAgBC,EACD,eAA2B;IAE3B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/F,MAAM,QAAQ,GAAG;QACf,CAAC,EAAE,CAAU;QACb,QAAQ,EAAE,MAAM,CAAC,OAAO;QACxB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,YAAY,EAAE,MAAM,CAAC,WAAW;QAChC,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChF,CAAC;IACF,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,CAAC;IACpE,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC/C,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,OAAwF,EAAE;IAE1F,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAC;IAChF,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;IAE3F,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAE1E,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE;QAC5C,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,YAAY,EAAE,IAAI,CAAC,aAAa;QAChC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,EAAE,EAAE,EAAE;KACP,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IAEzE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,eAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACzD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,OAAO,CAAC,MAAM,2BAA2B,EAAE,CAAC;IACnF,CAAC;IAED,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;QAC1E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2DAA2D,EAAE,CAAC;IAC3F,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAmB,EACnB,OAA0D,EAAE;IAE5D,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;QACvB,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;QAC7F,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zanii/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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/index.ts CHANGED
@@ -1,68 +1,71 @@
1
- export { generateKeypair, didFromPublicKey, publicKeyFromDid, type Keypair } from './keys.js';
2
- export { canonicalBytes, jcsHash } from './encoding.js';
3
- export {
4
- createCert,
5
- certHash,
6
- createRevocation,
7
- verifyRevocation,
8
- verifyCertSignature,
9
- verifyChain,
10
- scopeCovers,
11
- scopesAllow,
12
- type DelegationCert,
13
- type RevocationRecord,
14
- type ChainResult,
15
- } from './delegation.js';
16
- export {
17
- leafHash,
18
- merkleRoot,
19
- inclusionProof,
20
- verifyInclusion,
21
- consistencyProof,
22
- verifyConsistency,
23
- } from './merkle.js';
24
- export { createSTH, verifySTH, type SignedTreeHead } from './sth.js';
25
- export {
26
- buildA2ABody,
27
- signA2ABody,
28
- assembleA2AReceipt,
29
- a2aHash,
30
- bodyOf,
31
- verifyA2AReceipt,
32
- participantsOf,
33
- prevFor,
34
- type A2AReceipt,
35
- type A2ABody,
36
- type A2AParty,
37
- } from './a2a.js';
38
- export {
39
- verifyAuditBundle,
40
- type AuditBundle,
41
- type BundledReceipt,
42
- type BundledRevocation,
43
- type BundledAnchor,
44
- type BundleReport,
45
- type BundleCheck,
46
- } from './bundle.js';
47
- export {
48
- createReceipt,
49
- receiptHash,
50
- hashPayload,
51
- saltedPayloadHash,
52
- verifyPayload,
53
- verifyReceipt,
54
- verifyReceiptSignature,
55
- verifyReceiptChain,
56
- type Receipt,
57
- type VerifyResult,
58
- } from './receipt.js';
59
- export {
60
- createConfirmation,
61
- verifyConfirmation,
62
- type ActionConfirmation,
63
- } from './confirmation.js';
64
- export {
65
- createCoSignature,
66
- verifyCoSignature,
67
- type ReceiptCoSignature,
68
- } from './cosign.js';
1
+ export { generateKeypair, didFromPublicKey, publicKeyFromDid, type Keypair } from './keys.js';
2
+ export { canonicalBytes, jcsHash } from './encoding.js';
3
+ export {
4
+ createCert,
5
+ certHash,
6
+ createRevocation,
7
+ verifyRevocation,
8
+ verifyCertSignature,
9
+ verifyChain,
10
+ scopeCovers,
11
+ scopesAllow,
12
+ type DelegationCert,
13
+ type RevocationRecord,
14
+ type ChainResult,
15
+ } from './delegation.js';
16
+ export {
17
+ leafHash,
18
+ merkleRoot,
19
+ inclusionProof,
20
+ verifyInclusion,
21
+ consistencyProof,
22
+ verifyConsistency,
23
+ } from './merkle.js';
24
+ export { createSTH, verifySTH, type SignedTreeHead } from './sth.js';
25
+ export {
26
+ buildA2ABody,
27
+ signA2ABody,
28
+ assembleA2AReceipt,
29
+ a2aHash,
30
+ bodyOf,
31
+ verifyA2AReceipt,
32
+ participantsOf,
33
+ prevFor,
34
+ type A2AReceipt,
35
+ type A2ABody,
36
+ type A2AParty,
37
+ } from './a2a.js';
38
+ export {
39
+ verifyAuditBundle,
40
+ type AuditBundle,
41
+ type BundledReceipt,
42
+ type BundledRevocation,
43
+ type BundledAnchor,
44
+ type BundleReport,
45
+ type BundleCheck,
46
+ } from './bundle.js';
47
+ export {
48
+ createReceipt,
49
+ receiptHash,
50
+ hashPayload,
51
+ payloadBytes,
52
+ saltedPayloadHash,
53
+ verifyPayload,
54
+ verifyPayloadLegacy,
55
+ type PayloadDialect,
56
+ verifyReceipt,
57
+ verifyReceiptSignature,
58
+ verifyReceiptChain,
59
+ type Receipt,
60
+ type VerifyResult,
61
+ } from './receipt.js';
62
+ export {
63
+ createConfirmation,
64
+ verifyConfirmation,
65
+ type ActionConfirmation,
66
+ } from './confirmation.js';
67
+ export {
68
+ createCoSignature,
69
+ verifyCoSignature,
70
+ type ReceiptCoSignature,
71
+ } from './cosign.js';
package/src/receipt.ts CHANGED
@@ -1,171 +1,308 @@
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
- export interface Receipt {
9
- v: 1;
10
- agent_id: string;
11
- delegation: DelegationCert[];
12
- action: string;
13
- target: string;
14
- payload_hash: string;
15
- ts: string;
16
- prev: string | null;
17
- /** Optional provenance (SPEC §3). Present when set; covered by the signature. */
18
- runtime_hash?: string;
19
- model_id?: string;
20
- /** Hash of the product's tool/scope manifest that governed this action. */
21
- manifest_hash?: string;
22
- /** Optional pseudonymous data-subject tag (SPEC §3) — lets the end user the action was
23
- * about find + audit their own slice (`@zanii/subject`). Covered by the signature. */
24
- subject_tag?: string;
25
- sig: string;
26
- }
27
-
28
- export interface VerifyResult {
29
- ok: boolean;
30
- error?: string;
31
- }
32
-
33
- export function hashPayload(payload: Uint8Array | string): string {
34
- const bytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
35
- return `sha256:${bytesToHex(sha256(bytes))}`;
36
- }
37
-
38
- /**
39
- * Privacy-preserving payload commitment: `sha256(nonce ‖ payload)`. A bare
40
- * `hashPayload` of a low-entropy payload ("phone X, amount 500") is dictionary-
41
- * attackable from the public anchored log, and a linkable hash can itself be
42
- * personal data (PDPL). The per-receipt `nonce` is kept operator-side (NOT in the
43
- * receipt), so the on-chain value reveals nothing; keep it to later prove the
44
- * payload with `verifyPayload`. If `nonce` is omitted a fresh 16-byte one is drawn.
45
- */
46
- export function saltedPayloadHash(
47
- payload: Uint8Array | string,
48
- nonce?: string,
49
- ): { payload_hash: string; nonce: string } {
50
- const nonceHex = nonce ?? bytesToHex(randomBytes(16));
51
- const nonceBytes = hexToBytes(nonceHex);
52
- const payloadBytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
53
- const combined = new Uint8Array(nonceBytes.length + payloadBytes.length);
54
- combined.set(nonceBytes, 0);
55
- combined.set(payloadBytes, nonceBytes.length);
56
- return { payload_hash: `sha256:${bytesToHex(sha256(combined))}`, nonce: nonceHex };
57
- }
58
-
59
- /** Prove a payload matches a salted `payload_hash`, given the nonce kept at record time. */
60
- export function verifyPayload(payload: Uint8Array | string, nonce: string, payloadHash: string): boolean {
61
- try {
62
- return saltedPayloadHash(payload, nonce).payload_hash === payloadHash;
63
- } catch {
64
- return false;
65
- }
66
- }
67
-
68
- export function createReceipt(
69
- fields: {
70
- agentId: string;
71
- delegation: DelegationCert[];
72
- action: string;
73
- target: string;
74
- payloadHash: string;
75
- ts: string;
76
- prev: string | null;
77
- /** Optional provenance — included in the signed receipt only when provided. */
78
- runtimeHash?: string;
79
- modelId?: string;
80
- manifestHash?: string;
81
- /** Optional pseudonymous data-subject tag — per-subject auditability (`@zanii/subject`). */
82
- subjectTag?: string;
83
- },
84
- agentPrivateKey: Uint8Array,
85
- ): Receipt {
86
- if (Number.isNaN(Date.parse(fields.ts))) throw new Error(`invalid ts timestamp: ${fields.ts}`);
87
- const unsigned = {
88
- v: 1 as const,
89
- agent_id: fields.agentId,
90
- delegation: fields.delegation,
91
- action: fields.action,
92
- target: fields.target,
93
- payload_hash: fields.payloadHash,
94
- ts: fields.ts,
95
- prev: fields.prev,
96
- ...(fields.runtimeHash ? { runtime_hash: fields.runtimeHash } : {}),
97
- ...(fields.modelId ? { model_id: fields.modelId } : {}),
98
- ...(fields.manifestHash ? { manifest_hash: fields.manifestHash } : {}),
99
- ...(fields.subjectTag ? { subject_tag: fields.subjectTag } : {}),
100
- };
101
- const sig = ed25519.sign(canonicalBytes(unsigned), agentPrivateKey);
102
- return { ...unsigned, sig: encodeSig(sig) };
103
- }
104
-
105
- /** Hash of the full receipt (incl. sig) — what the next receipt's `prev` points to. */
106
- export function receiptHash(receipt: Receipt): string {
107
- return jcsHash(receipt);
108
- }
109
-
110
- export function verifyReceiptSignature(receipt: Receipt): boolean {
111
- const pub = publicKeyFromDid(receipt.agent_id);
112
- const sig = decodeSig(receipt.sig);
113
- if (!pub || !sig) return false;
114
- const { sig: _omitted, ...unsigned } = receipt;
115
- try {
116
- return ed25519.verify(sig, canonicalBytes(unsigned), pub);
117
- } catch {
118
- return false;
119
- }
120
- }
121
-
122
- /**
123
- * Full single-receipt verification per SPEC §4:
124
- * signature → delegation chain (at receipt time) → scope covers target → prev linkage.
125
- */
126
- export function verifyReceipt(
127
- receipt: Receipt,
128
- opts: { expectedOwner?: string; revoked?: Set<string>; expectedPrev?: string | null } = {},
129
- ): VerifyResult {
130
- if (receipt.v !== 1) return { ok: false, error: 'unsupported receipt version' };
131
- if (!verifyReceiptSignature(receipt)) return { ok: false, error: 'bad receipt signature' };
132
-
133
- const ts = new Date(receipt.ts);
134
- if (Number.isNaN(ts.getTime())) return { ok: false, error: 'invalid ts' };
135
-
136
- const chain = verifyChain(receipt.delegation, {
137
- agentId: receipt.agent_id,
138
- expectedRoot: opts.expectedOwner,
139
- revoked: opts.revoked,
140
- at: ts,
141
- });
142
- if (!chain.ok) return { ok: false, error: `delegation: ${chain.error}` };
143
-
144
- if (!scopesAllow(chain.effectiveScopes!, receipt.target)) {
145
- return { ok: false, error: `target ${receipt.target} outside effective scopes` };
146
- }
147
-
148
- if (opts.expectedPrev !== undefined && receipt.prev !== opts.expectedPrev) {
149
- return { ok: false, error: 'prev hash does not match preceding receipt (chain broken)' };
150
- }
151
-
152
- return { ok: true };
153
- }
154
-
155
- /**
156
- * Verify an agent's full receipt sequence: every receipt valid and hash-linked
157
- * to the one before it. Returns the index of the first bad receipt on failure.
158
- */
159
- export function verifyReceiptChain(
160
- receipts: Receipt[],
161
- opts: { expectedOwner?: string; revoked?: Set<string> } = {},
162
- ): VerifyResult & { failedIndex?: number } {
163
- let expectedPrev: string | null = null;
164
- for (let i = 0; i < receipts.length; i++) {
165
- const r = receipts[i]!;
166
- const result = verifyReceipt(r, { ...opts, expectedPrev });
167
- if (!result.ok) return { ok: false, error: `receipt ${i}: ${result.error}`, failedIndex: i };
168
- expectedPrev = receiptHash(r);
169
- }
170
- return { ok: true };
171
- }
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
+ export interface Receipt {
9
+ v: 1;
10
+ agent_id: string;
11
+ delegation: DelegationCert[];
12
+ action: string;
13
+ target: string;
14
+ payload_hash: string;
15
+ ts: string;
16
+ prev: string | null;
17
+ /** Optional provenance (SPEC §3). Present when set; covered by the signature. */
18
+ runtime_hash?: string;
19
+ model_id?: string;
20
+ /** Hash of the product's tool/scope manifest that governed this action. */
21
+ manifest_hash?: string;
22
+ /** Optional pseudonymous data-subject tag (SPEC §3) — lets the end user the action was
23
+ * about find + audit their own slice (`@zanii/subject`). Covered by the signature. */
24
+ subject_tag?: string;
25
+ /**
26
+ * How `payload_hash` committed to the payload (SPEC §3). `'jcs'` means the payload was
27
+ * encoded with `payloadBytes` (RFC 8785) before hashing — the only value written since
28
+ * `@zanii/sdk` 0.5.0. ABSENT means a pre-0.5.0 receipt, whose bytes came from the SDK's
29
+ * own `JSON.stringify`/`json.dumps` and therefore differ between the two SDKs; those
30
+ * are checked with `verifyPayloadLegacy`, never with `verifyPayload`.
31
+ */
32
+ payload_encoding?: 'jcs';
33
+ sig: string;
34
+ }
35
+
36
+ export interface VerifyResult {
37
+ ok: boolean;
38
+ error?: string;
39
+ }
40
+
41
+ export function hashPayload(payload: Uint8Array | string): string {
42
+ const bytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
43
+ return `sha256:${bytesToHex(sha256(bytes))}`;
44
+ }
45
+
46
+ /**
47
+ * Reject anything whose canonical form the two SDKs would not agree on. Mirrors
48
+ * `_check_jcs_safe` in `zanii.encoding`: non-integer numbers are the one place JSON
49
+ * serialisers legitimately disagree, so they are refused rather than hashed into a
50
+ * receipt no Python verifier can reproduce. Use integer minor units (`@zanii/payments`).
51
+ */
52
+ function assertPayloadCanonicalizable(v: unknown, path = 'payload'): void {
53
+ if (v === null || typeof v === 'string' || typeof v === 'boolean') return;
54
+ if (typeof v === 'number') {
55
+ if (!Number.isFinite(v)) throw new Error(`${path}: ${v} has no JSON representation`);
56
+ if (!Number.isInteger(v)) {
57
+ throw new Error(
58
+ `${path}: non-integer number ${v} cannot be committed to a payload hash — the two SDKs ` +
59
+ 'would encode it differently. Use integer minor units (see @zanii/payments) or a string.',
60
+ );
61
+ }
62
+ return;
63
+ }
64
+ if (Array.isArray(v)) {
65
+ v.forEach((x, i) => assertPayloadCanonicalizable(x, `${path}[${i}]`));
66
+ return;
67
+ }
68
+ if (typeof v === 'object') {
69
+ for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
70
+ // An undefined member is DROPPED, per JSON.stringify: `{a:1,b:undefined}` encodes
71
+ // as `{"a":1}`, which is byte-identical to what Python produces for the equivalent
72
+ // input (the key simply absent). A Python `None` means JSON null, a different
73
+ // value, and correctly hashes differently. So this needs no special case.
74
+ if (val === undefined) continue;
75
+ assertPayloadCanonicalizable(val, `${path}.${k}`);
76
+ }
77
+ return;
78
+ }
79
+ throw new Error(`${path}: ${typeof v} is not canonicalizable JSON`);
80
+ }
81
+
82
+ /**
83
+ * The bytes a `payload_hash` commits to (SPEC §3): RFC 8785 JCS, UTF-8.
84
+ *
85
+ * This exists because "hash the payload" was under-specified: each SDK reached for its
86
+ * own serialiser, so the same object hashed differently in TypeScript and Python and
87
+ * `verifyPayload` returned false across languages for honest data. One encoder, both
88
+ * languages, byte-identical — pinned by the cross-language vectors.
89
+ *
90
+ * A top-level `undefined` (or `null`) encodes as `null`, which is what both SDKs already
91
+ * hashed, so null payloads are unaffected by the change.
92
+ */
93
+ export function payloadBytes(payload: unknown): Uint8Array {
94
+ const value = payload === undefined ? null : payload;
95
+ assertPayloadCanonicalizable(value);
96
+ return canonicalBytes(value);
97
+ }
98
+
99
+ /**
100
+ * Privacy-preserving payload commitment: `sha256(nonce ‖ payload)`. A bare
101
+ * `hashPayload` of a low-entropy payload ("phone X, amount 500") is dictionary-
102
+ * attackable from the public anchored log, and a linkable hash can itself be
103
+ * personal data (PDPL). The per-receipt `nonce` is kept operator-side (NOT in the
104
+ * receipt), so the on-chain value reveals nothing; keep it to later prove the
105
+ * payload with `verifyPayload`. If `nonce` is omitted a fresh 16-byte one is drawn.
106
+ */
107
+ export function saltedPayloadHash(
108
+ payload: Uint8Array | string,
109
+ nonce?: string,
110
+ ): { payload_hash: string; nonce: string } {
111
+ const nonceHex = nonce ?? bytesToHex(randomBytes(16));
112
+ const nonceBytes = hexToBytes(nonceHex);
113
+ const payloadBytes = typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;
114
+ const combined = new Uint8Array(nonceBytes.length + payloadBytes.length);
115
+ combined.set(nonceBytes, 0);
116
+ combined.set(payloadBytes, nonceBytes.length);
117
+ return { payload_hash: `sha256:${bytesToHex(sha256(combined))}`, nonce: nonceHex };
118
+ }
119
+
120
+ /** Prove a payload matches a salted `payload_hash`, given the nonce kept at record time. */
121
+ export function verifyPayload(payload: Uint8Array | string, nonce: string, payloadHash: string): boolean {
122
+ try {
123
+ return saltedPayloadHash(payload, nonce).payload_hash === payloadHash;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /** Which pre-0.5.0 SDK's serialiser reproduced a legacy `payload_hash`. */
130
+ export type PayloadDialect = 'jcs' | 'legacy-ts' | 'legacy-py';
131
+
132
+ /** Old `@zanii/sdk`: `JSON.stringify`, insertion order, no spaces, raw UTF-8. */
133
+ function legacyTsEncode(payload: unknown): string | undefined {
134
+ return JSON.stringify(payload) ?? 'null';
135
+ }
136
+
137
+ /** Old `zanii` (Python): `json.dumps(sort_keys=True)` — `, ` / `: ` separators, \uXXXX escapes. */
138
+ function legacyPyEncode(value: unknown): string {
139
+ const esc = (str: string): string => {
140
+ let out = '';
141
+ for (const ch of str) {
142
+ const c = ch.codePointAt(0)!;
143
+ if (ch === '"') out += '\\"';
144
+ else if (ch === '\\') out += '\\\\';
145
+ else if (ch === '\n') out += '\\n';
146
+ else if (ch === '\r') out += '\\r';
147
+ else if (ch === '\t') out += '\\t';
148
+ else if (c < 0x20) out += `\\u${c.toString(16).padStart(4, '0')}`;
149
+ else if (c < 0x7f) out += ch;
150
+ else if (c > 0xffff) {
151
+ // Python escapes astral characters as a surrogate pair
152
+ const u = c - 0x10000;
153
+ out += `\\u${(0xd800 + (u >> 10)).toString(16)}\\u${(0xdc00 + (u & 0x3ff)).toString(16)}`;
154
+ } else out += `\\u${c.toString(16).padStart(4, '0')}`;
155
+ }
156
+ return `"${out}"`;
157
+ };
158
+ const enc = (v: unknown): string => {
159
+ if (v === null || v === undefined) return 'null';
160
+ if (typeof v === 'boolean') return v ? 'true' : 'false';
161
+ if (typeof v === 'number') return Number.isInteger(v) ? String(v) : String(v);
162
+ if (typeof v === 'string') return esc(v);
163
+ if (Array.isArray(v)) return `[${v.map(enc).join(', ')}]`;
164
+ const o = v as Record<string, unknown>;
165
+ const keys = Object.keys(o).sort();
166
+ return `{${keys.map((k) => `${esc(k)}: ${enc(o[k])}`).join(', ')}}`;
167
+ };
168
+ return enc(value);
169
+ }
170
+
171
+ /**
172
+ * Verify a payload against a receipt recorded BEFORE the JCS rule (no `payload_encoding`
173
+ * field). Such a receipt does not record which SDK wrote it, so this tries each old
174
+ * dialect and reports which one matched.
175
+ *
176
+ * Honest limits: the `legacy-ts` dialect depends on the object's original KEY INSERTION
177
+ * ORDER, which is not recoverable from a re-parsed payload, so it only matches when the
178
+ * caller reconstructs the object in the order it was recorded. Float formatting is
179
+ * likewise best-effort. A `null` result means no dialect reproduced the hash; it does not
180
+ * prove the payload wrong.
181
+ */
182
+ export function verifyPayloadLegacy(
183
+ payload: unknown,
184
+ nonce: string,
185
+ payloadHash: string,
186
+ ): { ok: boolean; dialect: PayloadDialect | null } {
187
+ const attempts: Array<[PayloadDialect, () => Uint8Array | string]> = [
188
+ ['jcs', () => payloadBytes(payload)],
189
+ ['legacy-ts', () => legacyTsEncode(payload)!],
190
+ ['legacy-py', () => legacyPyEncode(payload)],
191
+ ];
192
+ for (const [dialect, encode] of attempts) {
193
+ try {
194
+ if (saltedPayloadHash(encode(), nonce).payload_hash === payloadHash) return { ok: true, dialect };
195
+ } catch {
196
+ /* this dialect cannot encode it — try the next */
197
+ }
198
+ }
199
+ return { ok: false, dialect: null };
200
+ }
201
+
202
+ export function createReceipt(
203
+ fields: {
204
+ agentId: string;
205
+ delegation: DelegationCert[];
206
+ action: string;
207
+ target: string;
208
+ payloadHash: string;
209
+ ts: string;
210
+ prev: string | null;
211
+ /** Optional provenance — included in the signed receipt only when provided. */
212
+ runtimeHash?: string;
213
+ modelId?: string;
214
+ manifestHash?: string;
215
+ /** Optional pseudonymous data-subject tag — per-subject auditability (`@zanii/subject`). */
216
+ subjectTag?: string;
217
+ /** How `payloadHash` committed to the payload. Set `'jcs'` when it came from `payloadBytes`. */
218
+ payloadEncoding?: 'jcs';
219
+ },
220
+ agentPrivateKey: Uint8Array,
221
+ ): Receipt {
222
+ if (Number.isNaN(Date.parse(fields.ts))) throw new Error(`invalid ts timestamp: ${fields.ts}`);
223
+ const unsigned = {
224
+ v: 1 as const,
225
+ agent_id: fields.agentId,
226
+ delegation: fields.delegation,
227
+ action: fields.action,
228
+ target: fields.target,
229
+ payload_hash: fields.payloadHash,
230
+ ts: fields.ts,
231
+ prev: fields.prev,
232
+ ...(fields.runtimeHash ? { runtime_hash: fields.runtimeHash } : {}),
233
+ ...(fields.modelId ? { model_id: fields.modelId } : {}),
234
+ ...(fields.manifestHash ? { manifest_hash: fields.manifestHash } : {}),
235
+ ...(fields.subjectTag ? { subject_tag: fields.subjectTag } : {}),
236
+ ...(fields.payloadEncoding ? { payload_encoding: fields.payloadEncoding } : {}),
237
+ };
238
+ const sig = ed25519.sign(canonicalBytes(unsigned), agentPrivateKey);
239
+ return { ...unsigned, sig: encodeSig(sig) };
240
+ }
241
+
242
+ /** Hash of the full receipt (incl. sig) — what the next receipt's `prev` points to. */
243
+ export function receiptHash(receipt: Receipt): string {
244
+ return jcsHash(receipt);
245
+ }
246
+
247
+ export function verifyReceiptSignature(receipt: Receipt): boolean {
248
+ const pub = publicKeyFromDid(receipt.agent_id);
249
+ const sig = decodeSig(receipt.sig);
250
+ if (!pub || !sig) return false;
251
+ const { sig: _omitted, ...unsigned } = receipt;
252
+ try {
253
+ return ed25519.verify(sig, canonicalBytes(unsigned), pub);
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Full single-receipt verification per SPEC §4:
261
+ * signature → delegation chain (at receipt time) → scope covers target → prev linkage.
262
+ */
263
+ export function verifyReceipt(
264
+ receipt: Receipt,
265
+ opts: { expectedOwner?: string; revoked?: Set<string>; expectedPrev?: string | null } = {},
266
+ ): VerifyResult {
267
+ if (receipt.v !== 1) return { ok: false, error: 'unsupported receipt version' };
268
+ if (!verifyReceiptSignature(receipt)) return { ok: false, error: 'bad receipt signature' };
269
+
270
+ const ts = new Date(receipt.ts);
271
+ if (Number.isNaN(ts.getTime())) return { ok: false, error: 'invalid ts' };
272
+
273
+ const chain = verifyChain(receipt.delegation, {
274
+ agentId: receipt.agent_id,
275
+ expectedRoot: opts.expectedOwner,
276
+ revoked: opts.revoked,
277
+ at: ts,
278
+ });
279
+ if (!chain.ok) return { ok: false, error: `delegation: ${chain.error}` };
280
+
281
+ if (!scopesAllow(chain.effectiveScopes!, receipt.target)) {
282
+ return { ok: false, error: `target ${receipt.target} outside effective scopes` };
283
+ }
284
+
285
+ if (opts.expectedPrev !== undefined && receipt.prev !== opts.expectedPrev) {
286
+ return { ok: false, error: 'prev hash does not match preceding receipt (chain broken)' };
287
+ }
288
+
289
+ return { ok: true };
290
+ }
291
+
292
+ /**
293
+ * Verify an agent's full receipt sequence: every receipt valid and hash-linked
294
+ * to the one before it. Returns the index of the first bad receipt on failure.
295
+ */
296
+ export function verifyReceiptChain(
297
+ receipts: Receipt[],
298
+ opts: { expectedOwner?: string; revoked?: Set<string> } = {},
299
+ ): VerifyResult & { failedIndex?: number } {
300
+ let expectedPrev: string | null = null;
301
+ for (let i = 0; i < receipts.length; i++) {
302
+ const r = receipts[i]!;
303
+ const result = verifyReceipt(r, { ...opts, expectedPrev });
304
+ if (!result.ok) return { ok: false, error: `receipt ${i}: ${result.error}`, failedIndex: i };
305
+ expectedPrev = receiptHash(r);
306
+ }
307
+ return { ok: true };
308
+ }