@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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/receipt.d.ts +39 -0
- package/dist/receipt.d.ts.map +1 -1
- package/dist/receipt.js +132 -0
- package/dist/receipt.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +71 -68
- package/src/receipt.ts +308 -171
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
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,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;
|
|
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) };
|
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;
|
|
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.
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const
|
|
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
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|