@paybond/kit 0.12.4 → 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/agent/facade.d.ts +2 -0
  2. package/dist/agent/facade.js +10 -2
  3. package/dist/agent/guarded-agent.d.ts +3 -1
  4. package/dist/agent/guarded-agent.js +13 -2
  5. package/dist/agent/index.d.ts +2 -2
  6. package/dist/agent/index.js +1 -1
  7. package/dist/agent/instrument.d.ts +3 -0
  8. package/dist/agent/instrument.js +21 -10
  9. package/dist/agent/interceptor.js +19 -0
  10. package/dist/agent/types.d.ts +14 -0
  11. package/dist/agent-receipt-acta.d.ts +58 -0
  12. package/dist/agent-receipt-acta.js +140 -0
  13. package/dist/agent-receipt-inclusion.d.ts +40 -0
  14. package/dist/agent-receipt-inclusion.js +209 -0
  15. package/dist/agent-receipt-owner-disclosure.d.ts +71 -0
  16. package/dist/agent-receipt-owner-disclosure.js +277 -0
  17. package/dist/agent-receipt-pef.d.ts +69 -0
  18. package/dist/agent-receipt-pef.js +121 -0
  19. package/dist/agent-receipt-scitt.d.ts +50 -0
  20. package/dist/agent-receipt-scitt.js +382 -0
  21. package/dist/agent-receipt.d.ts +55 -0
  22. package/dist/agent-receipt.js +297 -4
  23. package/dist/claude-agents/config.d.ts +21 -2
  24. package/dist/claude-agents/config.js +37 -0
  25. package/dist/claude-agents/index.d.ts +1 -1
  26. package/dist/claude-agents/index.js +1 -1
  27. package/dist/cli/agent/demo-loaders.d.ts +1 -0
  28. package/dist/cli/agent/demo-loaders.js +12 -0
  29. package/dist/cli/command-spec.js +16 -1
  30. package/dist/cli/commands/agent.d.ts +4 -0
  31. package/dist/cli/commands/agent.js +107 -1
  32. package/dist/cli/help.js +4 -1
  33. package/dist/cloudflare-agents/config.d.ts +1 -1
  34. package/dist/cloudflare-agents/config.js +2 -2
  35. package/dist/google-adk/config.d.ts +36 -0
  36. package/dist/google-adk/config.js +172 -0
  37. package/dist/google-adk/index.d.ts +2 -0
  38. package/dist/google-adk/index.js +2 -0
  39. package/dist/google-adk/sandbox-demo.d.ts +32 -0
  40. package/dist/google-adk/sandbox-demo.js +104 -0
  41. package/dist/index.d.ts +10 -2
  42. package/dist/index.js +11 -3
  43. package/dist/init.js +29 -0
  44. package/dist/mcp-receipt-resource.js +3 -1
  45. package/dist/mcp-server.js +182 -10
  46. package/dist/policy/adapter-options.d.ts +9 -0
  47. package/dist/policy/adapter-options.js +11 -0
  48. package/dist/policy/index.d.ts +1 -0
  49. package/dist/policy/index.js +1 -0
  50. package/dist/policy/load.d.ts +7 -0
  51. package/dist/policy/load.js +13 -0
  52. package/dist/policy/merge.js +26 -0
  53. package/dist/policy/schema.d.ts +20 -0
  54. package/dist/policy/schema.js +34 -2
  55. package/dist/shopify/instrument.d.ts +8 -8
  56. package/dist/template-init.d.ts +2 -2
  57. package/dist/template-init.js +12 -4
  58. package/dist/vercel-ai/config.d.ts +2 -1
  59. package/dist/vercel-ai/config.js +2 -2
  60. package/dist/vercel-ai/index.d.ts +2 -1
  61. package/dist/vercel-ai/index.js +2 -1
  62. package/dist/vercel-ai/provider-executed.d.ts +7 -0
  63. package/dist/vercel-ai/provider-executed.js +20 -0
  64. package/dist/vercel-ai/tool-approval.d.ts +5 -0
  65. package/dist/vercel-ai/tool-approval.js +8 -1
  66. package/dist/vercel-ai/wrap-tools.d.ts +8 -1
  67. package/dist/vercel-ai/wrap-tools.js +17 -4
  68. package/glama.json +6 -0
  69. package/package.json +15 -5
  70. package/templates/manifest.json +57 -12
  71. package/templates/openai-shopping-agent/package-lock.json +379 -14
  72. package/templates/openai-shopping-agent/package.json +2 -2
  73. package/templates/paybond-aws-operator/package-lock.json +376 -11
  74. package/templates/paybond-aws-operator/package.json +2 -2
  75. package/templates/paybond-claude-agents-demo/package-lock.json +379 -14
  76. package/templates/paybond-claude-agents-demo/package.json +2 -2
  77. package/templates/paybond-cloudflare-shopping-agent/.env.example +3 -0
  78. package/templates/paybond-cloudflare-shopping-agent/.github/workflows/smoke.yml +20 -0
  79. package/templates/paybond-cloudflare-shopping-agent/LICENSE +201 -0
  80. package/templates/paybond-cloudflare-shopping-agent/README.md +29 -0
  81. package/templates/paybond-cloudflare-shopping-agent/package-lock.json +2671 -0
  82. package/templates/paybond-cloudflare-shopping-agent/package.json +23 -0
  83. package/templates/paybond-cloudflare-shopping-agent/paybond.policy.yaml +22 -0
  84. package/templates/paybond-cloudflare-shopping-agent/src/agent.ts +109 -0
  85. package/templates/paybond-cloudflare-shopping-agent/src/index.ts +22 -0
  86. package/templates/paybond-cloudflare-shopping-agent/src/paybond.config.ts +51 -0
  87. package/templates/paybond-cloudflare-shopping-agent/tsconfig.json +13 -0
  88. package/templates/paybond-crewai-procurement-agent/.env.example +3 -0
  89. package/templates/paybond-crewai-procurement-agent/.github/workflows/smoke.yml +19 -0
  90. package/templates/paybond-crewai-procurement-agent/LICENSE +201 -0
  91. package/templates/paybond-crewai-procurement-agent/README.md +73 -0
  92. package/templates/paybond-crewai-procurement-agent/app.py +79 -0
  93. package/templates/paybond-crewai-procurement-agent/crew.py +117 -0
  94. package/templates/paybond-crewai-procurement-agent/package.json +6 -0
  95. package/templates/paybond-crewai-procurement-agent/paybond.policy.yaml +28 -0
  96. package/templates/paybond-crewai-procurement-agent/paybond_config.py +45 -0
  97. package/templates/paybond-crewai-procurement-agent/requirements.txt +2 -0
  98. package/templates/paybond-invoice-agent/requirements.txt +1 -1
  99. package/templates/paybond-mastra-travel-agent/package-lock.json +379 -14
  100. package/templates/paybond-mastra-travel-agent/package.json +2 -2
  101. package/templates/paybond-mcp-coding-agent/package-lock.json +376 -11
  102. package/templates/paybond-mcp-coding-agent/package.json +2 -2
  103. package/templates/paybond-openai-agents-demo/package-lock.json +379 -14
  104. package/templates/paybond-openai-agents-demo/package.json +2 -2
  105. package/templates/paybond-procurement-agent/package-lock.json +376 -11
  106. package/templates/paybond-procurement-agent/package.json +2 -2
  107. package/templates/paybond-shopify-shopping-agent/package-lock.json +376 -11
  108. package/templates/paybond-shopify-shopping-agent/package.json +2 -2
  109. package/templates/paybond-stripe-agent-demo/package-lock.json +376 -11
  110. package/templates/paybond-stripe-agent-demo/package.json +2 -2
  111. package/templates/paybond-travel-agent/package-lock.json +376 -11
  112. package/templates/paybond-travel-agent/package.json +2 -2
  113. package/templates/paybond-vercel-shopping-agent/package-lock.json +376 -11
  114. package/templates/paybond-vercel-shopping-agent/package.json +2 -2
@@ -0,0 +1,209 @@
1
+ import { createHash } from "node:crypto";
2
+ import { verify } from "@noble/ed25519";
3
+ import { ensureEd25519Sha512Sync } from "./ed25519-sync.js";
4
+ import { AGENT_RECEIPT_SIGNING_ALGORITHM_ED25519, } from "./agent-receipt.js";
5
+ export const AGENT_RECEIPT_TRANSPARENCY_STH_KIND_V1 = "paybond.agent_receipt_transparency_sth_v1";
6
+ export const AGENT_RECEIPT_TRANSPARENCY_INCLUSION_PROOF_KIND_V1 = "paybond.agent_receipt_transparency_inclusion_proof_v1";
7
+ export const AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION = 1;
8
+ const HEX64_RE = /^[0-9a-f]{64}$/;
9
+ const HEX128_RE = /^[0-9a-f]{128}$/;
10
+ function requireHex64(value, field) {
11
+ const normalized = value.trim().toLowerCase();
12
+ if (!HEX64_RE.test(normalized)) {
13
+ throw new Error(`agent receipt transparency: ${field} must be a lowercase 64-byte hex SHA-256 digest`);
14
+ }
15
+ return normalized;
16
+ }
17
+ function requireHex128(value, field) {
18
+ const normalized = value.trim().toLowerCase();
19
+ if (!HEX128_RE.test(normalized)) {
20
+ throw new Error(`agent receipt transparency: ${field} must be a lowercase 128-char hex Ed25519 signature`);
21
+ }
22
+ return normalized;
23
+ }
24
+ function hexToBytes(hex) {
25
+ const out = new Uint8Array(hex.length / 2);
26
+ for (let i = 0; i < out.length; i += 1) {
27
+ out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
28
+ }
29
+ return out;
30
+ }
31
+ function bytesToHex(bytes) {
32
+ return Buffer.from(bytes).toString("hex");
33
+ }
34
+ function largestPowerOfTwoLessThan(n) {
35
+ if (n <= 1) {
36
+ return 0;
37
+ }
38
+ let k = 1;
39
+ while (k * 2 < n) {
40
+ k *= 2;
41
+ }
42
+ return k;
43
+ }
44
+ /** RFC 6962 leaf hash: SHA-256(0x00 || leafData). */
45
+ export function merkleLeafHashRFC6962(leafData) {
46
+ return createHash("sha256").update(Buffer.from([0x00])).update(leafData).digest();
47
+ }
48
+ /** RFC 6962 internal node hash: SHA-256(0x01 || left || right). */
49
+ export function merkleNodeHashRFC6962(left, right) {
50
+ return createHash("sha256")
51
+ .update(Buffer.from([0x01]))
52
+ .update(left)
53
+ .update(right)
54
+ .digest();
55
+ }
56
+ function reconstructMerkleRoot(leafHash, leafIndex, treeSize, auditPath) {
57
+ if (treeSize === 1) {
58
+ return { root: leafHash, consumed: 0 };
59
+ }
60
+ const k = largestPowerOfTwoLessThan(treeSize);
61
+ if (leafIndex < k) {
62
+ const left = reconstructMerkleRoot(leafHash, leafIndex, k, auditPath);
63
+ if (left.consumed >= auditPath.length) {
64
+ throw new Error("agent receipt transparency: audit path too short");
65
+ }
66
+ const right = auditPath[left.consumed];
67
+ return {
68
+ root: merkleNodeHashRFC6962(left.root, right),
69
+ consumed: left.consumed + 1,
70
+ };
71
+ }
72
+ const right = reconstructMerkleRoot(leafHash, leafIndex - k, treeSize - k, auditPath);
73
+ if (right.consumed >= auditPath.length) {
74
+ throw new Error("agent receipt transparency: audit path too short");
75
+ }
76
+ const left = auditPath[right.consumed];
77
+ return {
78
+ root: merkleNodeHashRFC6962(left, right.root),
79
+ consumed: right.consumed + 1,
80
+ };
81
+ }
82
+ function allowsSigningPublicKeyHex(keyHex, trusted) {
83
+ if (!trusted || trusted.length === 0) {
84
+ return true;
85
+ }
86
+ const normalized = keyHex.trim().toLowerCase();
87
+ return trusted.some((item) => item.trim().toLowerCase() === normalized);
88
+ }
89
+ function canonicalSthBytes(sth) {
90
+ const canonical = {
91
+ kind: AGENT_RECEIPT_TRANSPARENCY_STH_KIND_V1,
92
+ schema_version: AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION,
93
+ tenant_id: sth.tenant_id.trim(),
94
+ tree_size: sth.tree_size,
95
+ root_hash_sha256_hex: sth.root_hash_sha256_hex.trim().toLowerCase(),
96
+ issued_at: sth.issued_at.trim(),
97
+ signing_algorithm: AGENT_RECEIPT_SIGNING_ALGORITHM_ED25519,
98
+ };
99
+ return Buffer.from(JSON.stringify(canonical), "utf8");
100
+ }
101
+ /** Verifies a signed tree head (digest + Ed25519). */
102
+ export async function verifySignedTreeHeadV1(sth, options = {}) {
103
+ ensureEd25519Sha512Sync();
104
+ if (sth.kind !== AGENT_RECEIPT_TRANSPARENCY_STH_KIND_V1) {
105
+ throw new Error(`agent receipt transparency: kind must be ${AGENT_RECEIPT_TRANSPARENCY_STH_KIND_V1}`);
106
+ }
107
+ if (sth.schema_version !== AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION) {
108
+ throw new Error(`agent receipt transparency: schema_version must be ${AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION}`);
109
+ }
110
+ if (!sth.tenant_id?.trim()) {
111
+ throw new Error("agent receipt transparency: tenant_id is required");
112
+ }
113
+ if (!Number.isInteger(sth.tree_size) || sth.tree_size < 0) {
114
+ throw new Error("agent receipt transparency: tree_size must be a non-negative integer");
115
+ }
116
+ if (sth.signing_algorithm !== AGENT_RECEIPT_SIGNING_ALGORITHM_ED25519) {
117
+ throw new Error(`agent receipt transparency: signing_algorithm must be ${AGENT_RECEIPT_SIGNING_ALGORITHM_ED25519}`);
118
+ }
119
+ const rootHash = requireHex64(sth.root_hash_sha256_hex, "root_hash_sha256_hex");
120
+ const messageDigest = requireHex64(sth.message_digest_sha256_hex, "message_digest_sha256_hex");
121
+ const publicKeyHex = requireHex64(sth.signing_public_key_ed25519_hex, "signing_public_key_ed25519_hex");
122
+ if (!allowsSigningPublicKeyHex(publicKeyHex, options.expectedSigningPublicKeys)) {
123
+ throw new Error("agent receipt transparency: signing_public_key_ed25519_hex is not in the configured trusted key set");
124
+ }
125
+ const canonical = canonicalSthBytes({
126
+ ...sth,
127
+ root_hash_sha256_hex: rootHash,
128
+ });
129
+ const digest = createHash("sha256").update(canonical).digest();
130
+ if (bytesToHex(digest) !== messageDigest) {
131
+ throw new Error("agent receipt transparency: message digest mismatch");
132
+ }
133
+ const signature = hexToBytes(requireHex128(sth.ed25519_signature_hex, "ed25519_signature_hex"));
134
+ const publicKey = hexToBytes(publicKeyHex);
135
+ const valid = await verify(signature, digest, publicKey);
136
+ if (!valid) {
137
+ throw new Error("agent receipt transparency: ed25519 signature verification failed");
138
+ }
139
+ return {
140
+ ...sth,
141
+ tenant_id: sth.tenant_id.trim(),
142
+ root_hash_sha256_hex: rootHash,
143
+ message_digest_sha256_hex: messageDigest,
144
+ signing_public_key_ed25519_hex: publicKeyHex,
145
+ ed25519_signature_hex: sth.ed25519_signature_hex.trim().toLowerCase(),
146
+ issued_at: sth.issued_at.trim(),
147
+ };
148
+ }
149
+ /**
150
+ * Verifies that a receipt message digest is included in a published signed tree head
151
+ * via an RFC 6962 Merkle inclusion proof.
152
+ */
153
+ export async function verifyAgentReceiptInclusion(proof, options = {}) {
154
+ if (proof.kind !== AGENT_RECEIPT_TRANSPARENCY_INCLUSION_PROOF_KIND_V1) {
155
+ throw new Error(`agent receipt transparency: kind must be ${AGENT_RECEIPT_TRANSPARENCY_INCLUSION_PROOF_KIND_V1}`);
156
+ }
157
+ if (proof.schema_version !== AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION) {
158
+ throw new Error(`agent receipt transparency: schema_version must be ${AGENT_RECEIPT_TRANSPARENCY_SCHEMA_VERSION}`);
159
+ }
160
+ if (!proof.tenant_id?.trim()) {
161
+ throw new Error("agent receipt transparency: tenant_id is required");
162
+ }
163
+ if (!proof.receipt_id?.trim()) {
164
+ throw new Error("agent receipt transparency: receipt_id is required");
165
+ }
166
+ if (!Number.isInteger(proof.tree_size) || proof.tree_size <= 0) {
167
+ throw new Error("agent receipt transparency: tree_size must be a positive integer");
168
+ }
169
+ if (!Number.isInteger(proof.leaf_index) ||
170
+ proof.leaf_index < 0 ||
171
+ proof.leaf_index >= proof.tree_size) {
172
+ throw new Error("agent receipt transparency: leaf_index out of range");
173
+ }
174
+ const messageDigest = requireHex64(proof.message_digest_sha256_hex, "message_digest_sha256_hex");
175
+ const leafHashHex = requireHex64(proof.leaf_hash_sha256_hex, "leaf_hash_sha256_hex");
176
+ const rootHashHex = requireHex64(proof.root_hash_sha256_hex, "root_hash_sha256_hex");
177
+ const sth = await verifySignedTreeHeadV1(proof.tree_head, options);
178
+ if (sth.tenant_id !== proof.tenant_id.trim()) {
179
+ throw new Error("agent receipt transparency: tree_head.tenant_id mismatch");
180
+ }
181
+ if (sth.tree_size !== proof.tree_size) {
182
+ throw new Error("agent receipt transparency: tree_head.tree_size mismatch");
183
+ }
184
+ if (sth.root_hash_sha256_hex !== rootHashHex) {
185
+ throw new Error("agent receipt transparency: tree_head.root_hash mismatch");
186
+ }
187
+ const leafHash = merkleLeafHashRFC6962(hexToBytes(messageDigest));
188
+ if (bytesToHex(leafHash) !== leafHashHex) {
189
+ throw new Error("agent receipt transparency: leaf_hash does not match message digest");
190
+ }
191
+ const auditPath = (proof.audit_path_sha256_hex ?? []).map((entry, index) => hexToBytes(requireHex64(entry, `audit_path_sha256_hex[${index}]`)));
192
+ const { root, consumed } = reconstructMerkleRoot(leafHash, proof.leaf_index, proof.tree_size, auditPath);
193
+ if (consumed !== auditPath.length) {
194
+ throw new Error("agent receipt transparency: audit path too long");
195
+ }
196
+ if (bytesToHex(root) !== rootHashHex) {
197
+ throw new Error("agent receipt transparency: inclusion proof does not match root hash");
198
+ }
199
+ return {
200
+ ...proof,
201
+ tenant_id: proof.tenant_id.trim(),
202
+ receipt_id: proof.receipt_id.trim(),
203
+ message_digest_sha256_hex: messageDigest,
204
+ leaf_hash_sha256_hex: leafHashHex,
205
+ root_hash_sha256_hex: rootHashHex,
206
+ audit_path_sha256_hex: auditPath.map((entry) => bytesToHex(entry)),
207
+ tree_head: sth,
208
+ };
209
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Confidential HPKE owner disclosure package for Agent Receipt Standard.
3
+ *
4
+ * Separate artifact from the public `paybond.agent_receipt_v1` body. Encrypt /
5
+ * decrypt helpers are always available library exports; this is not part of the
6
+ * signed public ARS body.
7
+ */
8
+ import type { AgentReceiptV1 } from "./agent-receipt.js";
9
+ export declare const AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1 = "paybond.agent_receipt_owner_disclosure_v1";
10
+ export declare const AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1 = "paybond.agent_receipt_owner_disclosure_plaintext_v1";
11
+ export declare const AGENT_RECEIPT_OWNER_DISCLOSURE_SCHEMA_VERSION = 1;
12
+ export declare const AGENT_RECEIPT_OWNER_DISCLOSURE_HPKE_SUITE = "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM";
13
+ export declare const AGENT_RECEIPT_OWNER_DISCLOSURE_INFO = "paybond.agent_receipt_owner_disclosure_v1";
14
+ export type AgentReceiptOwnerDisclosurePlaintextV1 = {
15
+ kind: typeof AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1;
16
+ schema_version: number;
17
+ receipt_id: string;
18
+ message_digest_sha256_hex: string;
19
+ tenant_id: string;
20
+ scope: string;
21
+ derived_at: string;
22
+ authorization: unknown;
23
+ execution?: unknown;
24
+ evidence?: unknown;
25
+ outcome: unknown;
26
+ references: unknown;
27
+ extensions?: unknown;
28
+ };
29
+ export type AgentReceiptOwnerDisclosureV1 = {
30
+ kind: typeof AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1;
31
+ schema_version: number;
32
+ hpke_suite: typeof AGENT_RECEIPT_OWNER_DISCLOSURE_HPKE_SUITE;
33
+ info: typeof AGENT_RECEIPT_OWNER_DISCLOSURE_INFO;
34
+ recipient_public_key_x25519_hex: string;
35
+ encapsulated_key_hex: string;
36
+ ciphertext_hex: string;
37
+ aad_sha256_hex: string;
38
+ receipt_id: string;
39
+ message_digest_sha256_hex: string;
40
+ plaintext_content_type: string;
41
+ };
42
+ /** Seal plaintext to a 32-byte X25519 public key. */
43
+ export declare function sealHpkeBaseX25519Aes128Gcm(recipientPublicKey: Uint8Array, info: Uint8Array, aad: Uint8Array, plaintext: Uint8Array): {
44
+ enc: Buffer;
45
+ ciphertext: Buffer;
46
+ };
47
+ /** Open HPKE ciphertext for a 32-byte X25519 private key. */
48
+ export declare function openHpkeBaseX25519Aes128Gcm(recipientPrivateKey: Uint8Array, enc: Uint8Array, info: Uint8Array, aad: Uint8Array, ciphertext: Uint8Array): Buffer;
49
+ /** Generate lowercase hex X25519 key pair. */
50
+ export declare function generateX25519KeyPairHex(): {
51
+ privateKeyHex: string;
52
+ publicKeyHex: string;
53
+ };
54
+ /**
55
+ * Derive a hash-only disclosure plaintext from a public ARS receipt.
56
+ */
57
+ export declare function deriveOwnerDisclosurePlaintext(input: {
58
+ receipt: AgentReceiptV1;
59
+ derivedAt?: Date;
60
+ extensions?: unknown;
61
+ }): AgentReceiptOwnerDisclosurePlaintextV1;
62
+ /** HPKE-encrypt a derived disclosure plaintext to an owner X25519 public key. */
63
+ export declare function encryptOwnerDisclosurePackage(input: {
64
+ plaintext: AgentReceiptOwnerDisclosurePlaintextV1;
65
+ recipientPublicKeyX25519Hex: string;
66
+ }): AgentReceiptOwnerDisclosureV1;
67
+ /** Decrypt an HPKE owner disclosure package. */
68
+ export declare function decryptOwnerDisclosurePackage(input: {
69
+ package: AgentReceiptOwnerDisclosureV1;
70
+ recipientPrivateKeyX25519Hex: string;
71
+ }): AgentReceiptOwnerDisclosurePlaintextV1;
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Confidential HPKE owner disclosure package for Agent Receipt Standard.
3
+ *
4
+ * Separate artifact from the public `paybond.agent_receipt_v1` body. Encrypt /
5
+ * decrypt helpers are always available library exports; this is not part of the
6
+ * signed public ARS body.
7
+ */
8
+ import { createCipheriv, createDecipheriv, createHash, createHmac, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, } from "node:crypto";
9
+ import { canonicalAgentReceiptBytes } from "./agent-receipt.js";
10
+ export const AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1 = "paybond.agent_receipt_owner_disclosure_v1";
11
+ export const AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1 = "paybond.agent_receipt_owner_disclosure_plaintext_v1";
12
+ export const AGENT_RECEIPT_OWNER_DISCLOSURE_SCHEMA_VERSION = 1;
13
+ export const AGENT_RECEIPT_OWNER_DISCLOSURE_HPKE_SUITE = "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM";
14
+ export const AGENT_RECEIPT_OWNER_DISCLOSURE_INFO = "paybond.agent_receipt_owner_disclosure_v1";
15
+ const HPKE_KEM_ID = 0x0020;
16
+ const HPKE_KDF_ID = 0x0001;
17
+ const HPKE_AEAD_ID = 0x0001;
18
+ const HPKE_MODE_BASE = 0x00;
19
+ const HPKE_NH = 32;
20
+ const HPKE_NK = 16;
21
+ const HPKE_NN = 12;
22
+ const HPKE_NSECRET = 32;
23
+ function i2osp(n, length) {
24
+ const out = Buffer.alloc(length);
25
+ if (length === 1) {
26
+ out[0] = n & 0xff;
27
+ }
28
+ else if (length === 2) {
29
+ out.writeUInt16BE(n, 0);
30
+ }
31
+ else {
32
+ throw new Error("hpke: unsupported I2OSP length");
33
+ }
34
+ return out;
35
+ }
36
+ function suiteIdKem() {
37
+ return Buffer.concat([Buffer.from("KEM"), i2osp(HPKE_KEM_ID, 2)]);
38
+ }
39
+ function suiteIdHpke() {
40
+ return Buffer.concat([
41
+ Buffer.from("HPKE"),
42
+ i2osp(HPKE_KEM_ID, 2),
43
+ i2osp(HPKE_KDF_ID, 2),
44
+ i2osp(HPKE_AEAD_ID, 2),
45
+ ]);
46
+ }
47
+ function labeledExtract(suiteId, salt, label, ikm) {
48
+ const labeledIkm = Buffer.concat([Buffer.from("HPKE-v1"), suiteId, label, ikm]);
49
+ const effectiveSalt = salt && salt.length > 0 ? salt : Buffer.alloc(HPKE_NH);
50
+ return createHmac("sha256", effectiveSalt).update(labeledIkm).digest();
51
+ }
52
+ function labeledExpand(suiteId, prk, label, info, length) {
53
+ const labeledInfo = Buffer.concat([
54
+ i2osp(length, 2),
55
+ Buffer.from("HPKE-v1"),
56
+ suiteId,
57
+ label,
58
+ info,
59
+ ]);
60
+ // HKDF-Expand
61
+ const hashLen = HPKE_NH;
62
+ const n = Math.ceil(length / hashLen);
63
+ let prev = Buffer.alloc(0);
64
+ const out = [];
65
+ for (let i = 1; i <= n; i++) {
66
+ prev = createHmac("sha256", prk)
67
+ .update(Buffer.concat([prev, labeledInfo, Buffer.from([i])]))
68
+ .digest();
69
+ out.push(prev);
70
+ }
71
+ return Buffer.concat(out).subarray(0, length);
72
+ }
73
+ function extractAndExpand(dh, kemContext) {
74
+ const suiteId = suiteIdKem();
75
+ const eaePrk = labeledExtract(suiteId, null, Buffer.from("eae_prk"), dh);
76
+ return labeledExpand(suiteId, eaePrk, Buffer.from("shared_secret"), kemContext, HPKE_NSECRET);
77
+ }
78
+ function x25519Dh(privateKeyRaw, publicKeyRaw) {
79
+ const priv = createPrivateKey({
80
+ key: Buffer.concat([
81
+ Buffer.from("302e020100300506032b656e04220420", "hex"),
82
+ privateKeyRaw,
83
+ ]),
84
+ format: "der",
85
+ type: "pkcs8",
86
+ });
87
+ const pub = createPublicKey({
88
+ key: Buffer.concat([
89
+ Buffer.from("302a300506032b656e032100", "hex"),
90
+ publicKeyRaw,
91
+ ]),
92
+ format: "der",
93
+ type: "spki",
94
+ });
95
+ return diffieHellman({ privateKey: priv, publicKey: pub });
96
+ }
97
+ function encap(pkR) {
98
+ const { privateKey, publicKey } = generateKeyPairSync("x25519");
99
+ const skE = privateKey.export({ type: "pkcs8", format: "der" }).subarray(-32);
100
+ const enc = publicKey.export({ type: "spki", format: "der" }).subarray(-32);
101
+ const dh = x25519Dh(skE, pkR);
102
+ const kemContext = Buffer.concat([enc, pkR]);
103
+ return { sharedSecret: extractAndExpand(dh, kemContext), enc };
104
+ }
105
+ function decap(skR, enc) {
106
+ if (enc.length !== 32) {
107
+ throw new Error("owner disclosure: encapsulated key must be 32 bytes");
108
+ }
109
+ const dh = x25519Dh(skR, enc);
110
+ const pkR = createPublicKey(createPrivateKey({
111
+ key: Buffer.concat([
112
+ Buffer.from("302e020100300506032b656e04220420", "hex"),
113
+ skR,
114
+ ]),
115
+ format: "der",
116
+ type: "pkcs8",
117
+ })).export({ type: "spki", format: "der" }).subarray(-32);
118
+ const kemContext = Buffer.concat([enc, pkR]);
119
+ return extractAndExpand(dh, kemContext);
120
+ }
121
+ function keyScheduleBase(sharedSecret, info) {
122
+ const suiteId = suiteIdHpke();
123
+ const pskIdHash = labeledExtract(suiteId, null, Buffer.from("psk_id_hash"), Buffer.alloc(0));
124
+ const infoHash = labeledExtract(suiteId, null, Buffer.from("info_hash"), info);
125
+ const keyScheduleContext = Buffer.concat([
126
+ Buffer.from([HPKE_MODE_BASE]),
127
+ pskIdHash,
128
+ infoHash,
129
+ ]);
130
+ const secret = labeledExtract(suiteId, sharedSecret, Buffer.from("secret"), Buffer.alloc(0));
131
+ const key = labeledExpand(suiteId, secret, Buffer.from("key"), keyScheduleContext, HPKE_NK);
132
+ const baseNonce = labeledExpand(suiteId, secret, Buffer.from("base_nonce"), keyScheduleContext, HPKE_NN);
133
+ return { key, baseNonce };
134
+ }
135
+ function nonce(baseNonce, seq = 0) {
136
+ const out = Buffer.from(baseNonce);
137
+ for (let i = 0; i < 8; i++) {
138
+ out[out.length - 1 - i] ^= (seq >>> (8 * i)) & 0xff;
139
+ }
140
+ return out;
141
+ }
142
+ /** Seal plaintext to a 32-byte X25519 public key. */
143
+ export function sealHpkeBaseX25519Aes128Gcm(recipientPublicKey, info, aad, plaintext) {
144
+ if (recipientPublicKey.length !== 32) {
145
+ throw new Error("owner disclosure: recipient public key must be 32 bytes");
146
+ }
147
+ const { sharedSecret, enc } = encap(Buffer.from(recipientPublicKey));
148
+ const { key, baseNonce } = keyScheduleBase(sharedSecret, Buffer.from(info));
149
+ const cipher = createCipheriv("aes-128-gcm", key, nonce(baseNonce));
150
+ cipher.setAAD(Buffer.from(aad));
151
+ const encrypted = Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]);
152
+ const tag = cipher.getAuthTag();
153
+ return { enc, ciphertext: Buffer.concat([encrypted, tag]) };
154
+ }
155
+ /** Open HPKE ciphertext for a 32-byte X25519 private key. */
156
+ export function openHpkeBaseX25519Aes128Gcm(recipientPrivateKey, enc, info, aad, ciphertext) {
157
+ if (recipientPrivateKey.length !== 32) {
158
+ throw new Error("owner disclosure: recipient private key must be 32 bytes");
159
+ }
160
+ const sharedSecret = decap(Buffer.from(recipientPrivateKey), Buffer.from(enc));
161
+ const { key, baseNonce } = keyScheduleBase(sharedSecret, Buffer.from(info));
162
+ const ct = Buffer.from(ciphertext);
163
+ if (ct.length < 16) {
164
+ throw new Error("owner disclosure: ciphertext too short");
165
+ }
166
+ const body = ct.subarray(0, ct.length - 16);
167
+ const tag = ct.subarray(ct.length - 16);
168
+ const decipher = createDecipheriv("aes-128-gcm", key, nonce(baseNonce));
169
+ decipher.setAAD(Buffer.from(aad));
170
+ decipher.setAuthTag(tag);
171
+ return Buffer.concat([decipher.update(body), decipher.final()]);
172
+ }
173
+ /** Generate lowercase hex X25519 key pair. */
174
+ export function generateX25519KeyPairHex() {
175
+ const { privateKey, publicKey } = generateKeyPairSync("x25519");
176
+ const privateKeyHex = privateKey.export({ type: "pkcs8", format: "der" }).subarray(-32).toString("hex");
177
+ const publicKeyHex = publicKey.export({ type: "spki", format: "der" }).subarray(-32).toString("hex");
178
+ return { privateKeyHex, publicKeyHex };
179
+ }
180
+ function ownerDisclosureAad(receiptId, messageDigest) {
181
+ return Buffer.from(createHash("sha256")
182
+ .update(`${receiptId.trim()}\0${messageDigest.trim().toLowerCase()}`, "utf8")
183
+ .digest());
184
+ }
185
+ function formatRfc3339Seconds(value) {
186
+ return value.toISOString().replace(/\.\d{3}Z$/, "Z");
187
+ }
188
+ /**
189
+ * Derive a hash-only disclosure plaintext from a public ARS receipt.
190
+ */
191
+ export function deriveOwnerDisclosurePlaintext(input) {
192
+ const receipt = input.receipt;
193
+ const digest = receipt.message_digest_sha256_hex?.trim().toLowerCase() ||
194
+ createHash("sha256").update(canonicalAgentReceiptBytes(receipt)).digest("hex");
195
+ const derivedAt = input.derivedAt ?? new Date();
196
+ const out = {
197
+ kind: AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1,
198
+ schema_version: AGENT_RECEIPT_OWNER_DISCLOSURE_SCHEMA_VERSION,
199
+ receipt_id: receipt.receipt_id,
200
+ message_digest_sha256_hex: digest,
201
+ tenant_id: receipt.tenant_id,
202
+ scope: receipt.scope,
203
+ derived_at: formatRfc3339Seconds(derivedAt),
204
+ authorization: receipt.authorization,
205
+ outcome: receipt.outcome,
206
+ references: receipt.references,
207
+ };
208
+ if (receipt.execution) {
209
+ out.execution = receipt.execution;
210
+ }
211
+ if (receipt.evidence) {
212
+ out.evidence = receipt.evidence;
213
+ }
214
+ if (input.extensions !== undefined) {
215
+ out.extensions = input.extensions;
216
+ }
217
+ return out;
218
+ }
219
+ /** HPKE-encrypt a derived disclosure plaintext to an owner X25519 public key. */
220
+ export function encryptOwnerDisclosurePackage(input) {
221
+ if (input.plaintext.kind !== AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1) {
222
+ throw new Error(`owner disclosure: plaintext kind must be ${AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1}`);
223
+ }
224
+ const pubHex = input.recipientPublicKeyX25519Hex.trim().toLowerCase();
225
+ if (pubHex.length !== 64) {
226
+ throw new Error("owner disclosure: recipient_public_key_x25519_hex must be 32-byte hex");
227
+ }
228
+ const body = Buffer.from(JSON.stringify(input.plaintext), "utf8");
229
+ const aad = ownerDisclosureAad(input.plaintext.receipt_id, input.plaintext.message_digest_sha256_hex);
230
+ const { enc, ciphertext } = sealHpkeBaseX25519Aes128Gcm(Buffer.from(pubHex, "hex"), Buffer.from(AGENT_RECEIPT_OWNER_DISCLOSURE_INFO, "utf8"), aad, body);
231
+ return {
232
+ kind: AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1,
233
+ schema_version: AGENT_RECEIPT_OWNER_DISCLOSURE_SCHEMA_VERSION,
234
+ hpke_suite: AGENT_RECEIPT_OWNER_DISCLOSURE_HPKE_SUITE,
235
+ info: AGENT_RECEIPT_OWNER_DISCLOSURE_INFO,
236
+ recipient_public_key_x25519_hex: pubHex,
237
+ encapsulated_key_hex: enc.toString("hex"),
238
+ ciphertext_hex: ciphertext.toString("hex"),
239
+ aad_sha256_hex: aad.toString("hex"),
240
+ receipt_id: input.plaintext.receipt_id,
241
+ message_digest_sha256_hex: input.plaintext.message_digest_sha256_hex.toLowerCase(),
242
+ plaintext_content_type: `application/json; charset=utf-8; profile=${AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1}`,
243
+ };
244
+ }
245
+ /** Decrypt an HPKE owner disclosure package. */
246
+ export function decryptOwnerDisclosurePackage(input) {
247
+ const pkg = input.package;
248
+ if (pkg.kind !== AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1) {
249
+ throw new Error(`owner disclosure: kind must be ${AGENT_RECEIPT_OWNER_DISCLOSURE_KIND_V1}`);
250
+ }
251
+ if (pkg.hpke_suite !== AGENT_RECEIPT_OWNER_DISCLOSURE_HPKE_SUITE) {
252
+ throw new Error(`owner disclosure: unsupported hpke_suite ${JSON.stringify(pkg.hpke_suite)}`);
253
+ }
254
+ if (pkg.info !== AGENT_RECEIPT_OWNER_DISCLOSURE_INFO) {
255
+ throw new Error("owner disclosure: info mismatch");
256
+ }
257
+ const privHex = input.recipientPrivateKeyX25519Hex.trim().toLowerCase();
258
+ const priv = Buffer.from(privHex, "hex");
259
+ if (priv.length !== 32) {
260
+ throw new Error("owner disclosure: recipient private key must be 32-byte hex");
261
+ }
262
+ const enc = Buffer.from(pkg.encapsulated_key_hex.trim().toLowerCase(), "hex");
263
+ const ciphertext = Buffer.from(pkg.ciphertext_hex.trim().toLowerCase(), "hex");
264
+ const aad = ownerDisclosureAad(pkg.receipt_id, pkg.message_digest_sha256_hex);
265
+ if (aad.toString("hex") !== pkg.aad_sha256_hex.trim().toLowerCase()) {
266
+ throw new Error("owner disclosure: aad_sha256_hex mismatch");
267
+ }
268
+ const plaintextBytes = openHpkeBaseX25519Aes128Gcm(priv, enc, Buffer.from(AGENT_RECEIPT_OWNER_DISCLOSURE_INFO, "utf8"), aad, ciphertext);
269
+ const plaintext = JSON.parse(plaintextBytes.toString("utf8"));
270
+ if (plaintext.kind !== AGENT_RECEIPT_OWNER_DISCLOSURE_PLAINTEXT_KIND_V1) {
271
+ throw new Error("owner disclosure: decrypted kind mismatch");
272
+ }
273
+ if (plaintext.receipt_id !== pkg.receipt_id) {
274
+ throw new Error("owner disclosure: decrypted receipt binding mismatch");
275
+ }
276
+ return plaintext;
277
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Payment Evidence Frame (PEF) wrapper for agent-receipt audit handoff.
3
+ *
4
+ * Inspired by draft-hopley-x402-payment-evidence-frame: content-addressed
5
+ * `frame_id` over JCS(preimage), with `claim_type=paybond_agent_receipt_v1`.
6
+ * ARS core signing stays `ed25519-sha256-json-v1`; this adapter owns JCS.
7
+ *
8
+ * RFC 9421 detached signatures are reserved (`signature` extension point) and
9
+ * not populated by the builder in this release.
10
+ */
11
+ import type { AgentReceiptV1 } from "./agent-receipt.js";
12
+ /** PEF claim taxonomy for Paybond agent receipts (interop adapter). */
13
+ export declare const PEF_CLAIM_TYPE_AGENT_RECEIPT_V1 = "paybond_agent_receipt_v1";
14
+ /** Canonicalisation URN pinned by the x402 PEF substrate. */
15
+ export declare const PEF_CANON_VERSION_JCS_RFC8785_V1 = "urn:x402:canonicalisation:jcs-rfc8785-v1";
16
+ export declare const PEF_VERSION_V1 = "1";
17
+ /** Inner receipt format label carried in the PEF envelope. */
18
+ export declare const PEF_RECEIPT_FORMAT_AGENT_RECEIPT_V1 = "paybond.agent_receipt_v1";
19
+ export type BuildAgentReceiptPefFrameInput = {
20
+ /** Full ARS receipt JSON (verified or composed). */
21
+ receipt: AgentReceiptV1 | Record<string, unknown>;
22
+ /** DID of the party asserting the frame (audit exporter / Gateway). */
23
+ frameProviderDid: string;
24
+ /** Epoch milliseconds for the frame assertion. */
25
+ frameTimestampMs: number;
26
+ };
27
+ /**
28
+ * PEF envelope wrapping an ARS receipt for partner audit handoff.
29
+ *
30
+ * `signature` is omitted; callers MAY attach an RFC 9421 detached signature
31
+ * later without changing `frame_id` (signature is excluded from the preimage).
32
+ */
33
+ export type AgentReceiptPefFrameV1 = {
34
+ canon_version: typeof PEF_CANON_VERSION_JCS_RFC8785_V1;
35
+ claim_type: typeof PEF_CLAIM_TYPE_AGENT_RECEIPT_V1;
36
+ frame_id: string;
37
+ frame_provider_did: string;
38
+ frame_timestamp_ms: number;
39
+ pef_version: typeof PEF_VERSION_V1;
40
+ receipt: AgentReceiptV1 | Record<string, unknown>;
41
+ receipt_format: typeof PEF_RECEIPT_FORMAT_AGENT_RECEIPT_V1;
42
+ receipt_hash: string;
43
+ };
44
+ /** Returns RFC 8785 JCS bytes for an arbitrary JSON value. */
45
+ export declare function jcsCanonicalBytes(value: unknown): Uint8Array;
46
+ /**
47
+ * Computes `receipt_hash = sha256:` + hex(SHA-256(JCS(receipt))).
48
+ */
49
+ export declare function agentReceiptPefReceiptHash(receipt: AgentReceiptV1 | Record<string, unknown>): string;
50
+ /**
51
+ * Builds the PEF preimage object (excludes `frame_id` and `signature`).
52
+ */
53
+ export declare function agentReceiptPefPreimage(input: BuildAgentReceiptPefFrameInput, receiptHash: string): Record<string, unknown>;
54
+ /**
55
+ * Derives `frame_id = sha256:` + hex(SHA-256(JCS(preimage))).
56
+ */
57
+ export declare function agentReceiptPefFrameId(preimage: Record<string, unknown>): string;
58
+ /**
59
+ * Builds a content-addressed PEF wrapper around an ARS receipt.
60
+ *
61
+ * Does not modify or re-sign the inner ARS envelope.
62
+ */
63
+ export declare function buildAgentReceiptPefFrame(input: BuildAgentReceiptPefFrameInput): AgentReceiptPefFrameV1;
64
+ /**
65
+ * Recomputes `frame_id` / `receipt_hash` and checks they match the frame.
66
+ *
67
+ * @throws Error when hashes do not match
68
+ */
69
+ export declare function verifyAgentReceiptPefFrameId(frame: AgentReceiptPefFrameV1): void;