@agirails/sdk 4.4.8 → 4.5.2

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 (99) hide show
  1. package/dist/builders/DeliveryProofBuilder.d.ts +224 -13
  2. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
  3. package/dist/builders/DeliveryProofBuilder.js +247 -13
  4. package/dist/builders/DeliveryProofBuilder.js.map +1 -1
  5. package/dist/cli/agirails.d.ts +85 -1
  6. package/dist/cli/agirails.d.ts.map +1 -1
  7. package/dist/cli/agirails.js +429 -154
  8. package/dist/cli/agirails.js.map +1 -1
  9. package/dist/cli/commands/init.d.ts +54 -0
  10. package/dist/cli/commands/init.d.ts.map +1 -1
  11. package/dist/cli/commands/init.js +193 -1
  12. package/dist/cli/commands/init.js.map +1 -1
  13. package/dist/cli/commands/receipt.d.ts +70 -2
  14. package/dist/cli/commands/receipt.d.ts.map +1 -1
  15. package/dist/cli/commands/receipt.js +218 -3
  16. package/dist/cli/commands/receipt.js.map +1 -1
  17. package/dist/cli/commands/test.d.ts +77 -1
  18. package/dist/cli/commands/test.d.ts.map +1 -1
  19. package/dist/cli/commands/test.js +264 -2
  20. package/dist/cli/commands/test.js.map +1 -1
  21. package/dist/cli/lib/runRequest.d.ts +90 -0
  22. package/dist/cli/lib/runRequest.d.ts.map +1 -1
  23. package/dist/cli/lib/runRequest.js +300 -9
  24. package/dist/cli/lib/runRequest.js.map +1 -1
  25. package/dist/cli/lib/sentinelReflections.d.ts +111 -0
  26. package/dist/cli/lib/sentinelReflections.d.ts.map +1 -0
  27. package/dist/cli/lib/sentinelReflections.js +193 -0
  28. package/dist/cli/lib/sentinelReflections.js.map +1 -0
  29. package/dist/delivery/MockDeliveryChannel.d.ts +208 -0
  30. package/dist/delivery/MockDeliveryChannel.d.ts.map +1 -0
  31. package/dist/delivery/MockDeliveryChannel.js +445 -0
  32. package/dist/delivery/MockDeliveryChannel.js.map +1 -0
  33. package/dist/delivery/RelayDeliveryChannel.d.ts +176 -0
  34. package/dist/delivery/RelayDeliveryChannel.d.ts.map +1 -0
  35. package/dist/delivery/RelayDeliveryChannel.js +377 -0
  36. package/dist/delivery/RelayDeliveryChannel.js.map +1 -0
  37. package/dist/delivery/channel.d.ts +282 -0
  38. package/dist/delivery/channel.d.ts.map +1 -0
  39. package/dist/delivery/channel.js +76 -0
  40. package/dist/delivery/channel.js.map +1 -0
  41. package/dist/delivery/channelLog.d.ts +115 -0
  42. package/dist/delivery/channelLog.d.ts.map +1 -0
  43. package/dist/delivery/channelLog.js +94 -0
  44. package/dist/delivery/channelLog.js.map +1 -0
  45. package/dist/delivery/crypto.d.ts +312 -0
  46. package/dist/delivery/crypto.d.ts.map +1 -0
  47. package/dist/delivery/crypto.js +495 -0
  48. package/dist/delivery/crypto.js.map +1 -0
  49. package/dist/delivery/eip712.d.ts +248 -0
  50. package/dist/delivery/eip712.d.ts.map +1 -0
  51. package/dist/delivery/eip712.js +397 -0
  52. package/dist/delivery/eip712.js.map +1 -0
  53. package/dist/delivery/envelopeBuilder.d.ts +531 -0
  54. package/dist/delivery/envelopeBuilder.d.ts.map +1 -0
  55. package/dist/delivery/envelopeBuilder.js +832 -0
  56. package/dist/delivery/envelopeBuilder.js.map +1 -0
  57. package/dist/delivery/index.d.ts +53 -0
  58. package/dist/delivery/index.d.ts.map +1 -0
  59. package/dist/delivery/index.js +143 -0
  60. package/dist/delivery/index.js.map +1 -0
  61. package/dist/delivery/keys.d.ts +344 -0
  62. package/dist/delivery/keys.d.ts.map +1 -0
  63. package/dist/delivery/keys.js +513 -0
  64. package/dist/delivery/keys.js.map +1 -0
  65. package/dist/delivery/nonce-keys.d.ts +93 -0
  66. package/dist/delivery/nonce-keys.d.ts.map +1 -0
  67. package/dist/delivery/nonce-keys.js +88 -0
  68. package/dist/delivery/nonce-keys.js.map +1 -0
  69. package/dist/delivery/setupBuilder.d.ts +403 -0
  70. package/dist/delivery/setupBuilder.d.ts.map +1 -0
  71. package/dist/delivery/setupBuilder.js +554 -0
  72. package/dist/delivery/setupBuilder.js.map +1 -0
  73. package/dist/delivery/types.d.ts +722 -0
  74. package/dist/delivery/types.d.ts.map +1 -0
  75. package/dist/delivery/types.js +150 -0
  76. package/dist/delivery/types.js.map +1 -0
  77. package/dist/delivery/validate.d.ts +288 -0
  78. package/dist/delivery/validate.d.ts.map +1 -0
  79. package/dist/delivery/validate.js +648 -0
  80. package/dist/delivery/validate.js.map +1 -0
  81. package/dist/level1/Agent.d.ts +130 -0
  82. package/dist/level1/Agent.d.ts.map +1 -1
  83. package/dist/level1/Agent.js +248 -0
  84. package/dist/level1/Agent.js.map +1 -1
  85. package/dist/level1/types/Options.d.ts +62 -0
  86. package/dist/level1/types/Options.d.ts.map +1 -1
  87. package/dist/level1/types/Options.js +22 -0
  88. package/dist/level1/types/Options.js.map +1 -1
  89. package/dist/runtime/MockRuntime.d.ts +32 -0
  90. package/dist/runtime/MockRuntime.d.ts.map +1 -1
  91. package/dist/runtime/MockRuntime.js +44 -0
  92. package/dist/runtime/MockRuntime.js.map +1 -1
  93. package/dist/wallet/aa/BundlerClient.d.ts.map +1 -1
  94. package/dist/wallet/aa/BundlerClient.js +18 -3
  95. package/dist/wallet/aa/BundlerClient.js.map +1 -1
  96. package/dist/wallet/aa/PaymasterClient.d.ts.map +1 -1
  97. package/dist/wallet/aa/PaymasterClient.js +4 -1
  98. package/dist/wallet/aa/PaymasterClient.js.map +1 -1
  99. package/package.json +6 -1
@@ -0,0 +1,832 @@
1
+ "use strict";
2
+ /**
3
+ * AIP-16 Delivery Surface — Provider Envelope Builder + Verifier + Decryptor (Phase 2b)
4
+ * =======================================================================================
5
+ *
6
+ * Constructs and verifies the provider-signed `DeliveryEnvelopeV1` payload —
7
+ * the load-bearing artifact of the AIP-16 Rev 5 delivery surface. The
8
+ * provider posts this signed object to the delivery channel after the
9
+ * buyer's setup is received (and after the on-chain transaction reaches
10
+ * `COMMITTED`), carrying:
11
+ *
12
+ * - the EIP-712 binding to a specific kernel, chain, and `txId`,
13
+ * - the on-chain identity acting as the provider, plus the EOA that
14
+ * signed (smart-wallet two-step auth),
15
+ * - the cryptographic scheme used to protect the body (`public-v1` or
16
+ * `x25519-aes256gcm-v1`),
17
+ * - the provider's ephemeral X25519 pubkey (encrypted scheme) or
18
+ * canonical-empty bytes32 (public scheme),
19
+ * - the AES-256-GCM nonce + tag (encrypted) or canonical-empty
20
+ * bytes12 / bytes16 (public),
21
+ * - `payloadHash = keccak256(bodyBytes)` — the on-chain anchor for the
22
+ * exact bytes the buyer will receive,
23
+ * - the body bytes themselves (alongside the signed projection, in the
24
+ * wire envelope).
25
+ *
26
+ * ## Body encoding (SCHEME-AWARE — FIX-1, AIP-16 Phase 3.5)
27
+ *
28
+ * The wire encoding is scheme-dependent so the SDK and the Platform
29
+ * verifier (`Platform/agirails.app/web/lib/delivery/auth.ts`) hash the
30
+ * SAME bytes in BOTH locations:
31
+ *
32
+ * - `public-v1`: `wire.body = JSON.stringify(payload)` — plaintext
33
+ * UTF-8 JSON string, NOT hex. `payloadHash = keccak256(utf8Bytes(body))`.
34
+ * The Platform verifier computes `keccak256(toUtf8Bytes(body))`
35
+ * directly on the wire body. Hex-encoding the plaintext here would
36
+ * make the Platform recompute `keccak256(utf8Bytes("0x7b22…"))` —
37
+ * a different digest — and every public envelope would 400 with
38
+ * `payload_hash_mismatch`.
39
+ *
40
+ * - `x25519-aes256gcm-v1`: `wire.body = "0x" + hex(aesGcmCiphertext)`.
41
+ * `payloadHash = keccak256(rawCiphertextBytes)`. The Platform
42
+ * verifier hex-decodes `wire.body` and then keccak256s the bytes.
43
+ * Ciphertext is a byte string that does NOT round-trip through UTF-8
44
+ * (it contains arbitrary 0..255 bytes incl. 0x00 and high bits),
45
+ * so hex is the only safe text encoding — base64 would also work but
46
+ * AIP-16 standardizes on hex for cross-language SDK consistency.
47
+ *
48
+ * Rationale for the asymmetry:
49
+ * 1. **Public payloads are already text.** JSON serializes to valid
50
+ * UTF-8 — no encoding wrapper needed. The plaintext IS the wire.
51
+ * 2. **Ciphertext is binary.** Cannot travel naked in JSON; hex is the
52
+ * chosen text encoding (matches `nonce`, `tag`, `payloadHash`,
53
+ * `providerEphemeralPubkey`).
54
+ * 3. **Spec match.** AIP-16 §6.2 sign-side and the Platform verify-
55
+ * side both implement this exact rule.
56
+ *
57
+ * ## Builder shape (matches DeliverySetupBuilder)
58
+ *
59
+ * - `constructor(signer?)` — signer required for `build*()`, optional
60
+ * for `verify()` / `computeHash()` / `decryptPayload()`. The static
61
+ * helpers do not consult builder state.
62
+ * - `buildPublic(params)` / `buildEncrypted(params)` are `async`
63
+ * because EIP-712 signing on real wallets is async.
64
+ * - `verify`, `decryptPayload`, `verifyAndDecrypt`, `computeHash` are
65
+ * `static` — mirrors {@link DeliverySetupBuilder} and the existing
66
+ * delivery-proof / quote builders.
67
+ *
68
+ * ## Smart-wallet two-step auth (DEC-10 / V2 receipts)
69
+ *
70
+ * `providerAddress` (on-chain participant — possibly a Smart Wallet)
71
+ * and `signerAddress` (the EOA that actually signed) are accepted
72
+ * SEPARATELY. The SDK does NOT derive one from the other; the smart-
73
+ * wallet equality check
74
+ * computeSmartWalletFromSigner(signerAddress) === providerAddress
75
+ * is the SERVER'S responsibility (cross-vendor factory ABI agnostic).
76
+ *
77
+ * What the SDK enforces in `build*()` is the cheaper invariant:
78
+ * `signerAddress === await signer.getAddress()`.
79
+ *
80
+ * ## payloadHash defends against body-after-sign tamper
81
+ *
82
+ * Because `payloadHash = keccak256(bodyBytes)` is part of the SIGNED
83
+ * EIP-712 projection, any post-signature mutation of `wire.body`
84
+ * causes `bodyHash(wire.body)` to diverge from `signed.payloadHash`.
85
+ * The verifier short-circuits with `envelope_payload_hash_mismatch`
86
+ * before signature recovery, so a malicious relay cannot swap bodies
87
+ * even within an otherwise valid signed envelope.
88
+ *
89
+ * ## Verification order (first failure short-circuits)
90
+ *
91
+ * 1. `validateEnvelopeWire(wire)` — structural shape, types, lengths,
92
+ * and scheme/canonical-empty consistency in one pass.
93
+ * 2. `validateSchemeConsistency(signed)` — defense-in-depth re-check
94
+ * (already covered by step 1 but called explicitly so future
95
+ * refactors of the validator do not silently weaken the contract).
96
+ * 3. `signed.chainId === expectedChainId` → `envelope_chain_mismatch`.
97
+ * 4. `signed.kernelAddress` (lc) === `expectedKernelAddress` (lc)
98
+ * → `envelope_kernel_mismatch`.
99
+ * 5. `bodyHash(wire.body) === signed.payloadHash`
100
+ * → `envelope_payload_hash_mismatch`.
101
+ * 6. `recoverEnvelopeSigner(signed, providerSig, expectedKernel)`
102
+ * (lc) === `signed.signerAddress` (lc)
103
+ * → `envelope_signature_invalid`.
104
+ * 7. `|now - signed.createdAt| <= ENVELOPE_TIMESTAMP_SKEW_SEC`
105
+ * → `envelope_timestamp_skew`.
106
+ *
107
+ * Note that timestamp skew is checked LAST — a forged signature is a
108
+ * more severe error than a stale-but-genuine envelope and we want the
109
+ * caller to see the more severe failure first.
110
+ *
111
+ * @module delivery/envelopeBuilder
112
+ * @see ./types — signed/wire interfaces and {@link BuildEnvelopeResult}.
113
+ * @see ./eip712 — domain, types, and {@link recoverEnvelopeSigner}.
114
+ * @see ./validate — {@link validateEnvelopeWire},
115
+ * {@link validateSchemeConsistency}.
116
+ * @see ./keys — X25519 keygen + ECDH + HKDF.
117
+ * @see ./crypto — AES-256-GCM seal/open + {@link bodyHash}.
118
+ * @see ./setupBuilder — sibling builder; same shape conventions.
119
+ */
120
+ Object.defineProperty(exports, "__esModule", { value: true });
121
+ exports.DeliveryEnvelopeBuilder = exports.resetSecondsNowForTests = exports.setSecondsNowForTests = exports.buildEnvelopeAad = exports.ENVELOPE_AAD_LENGTH = exports.ENVELOPE_TIMESTAMP_SKEW_SEC = void 0;
122
+ const ethers_1 = require("ethers");
123
+ const canonicalJson_1 = require("../utils/canonicalJson");
124
+ const crypto_1 = require("./crypto");
125
+ const eip712_1 = require("./eip712");
126
+ const keys_1 = require("./keys");
127
+ const types_1 = require("./types");
128
+ const validate_1 = require("./validate");
129
+ // ============================================================================
130
+ // Constants
131
+ // ============================================================================
132
+ /**
133
+ * Maximum tolerated clock-skew, in seconds, between the signed
134
+ * `createdAt` and the verifier's wall clock. Symmetric (past + future).
135
+ *
136
+ * 900s (15 min) matches {@link DeliverySetupBuilder}'s
137
+ * `SETUP_TIMESTAMP_SKEW_SEC`, the receipts-V2 freshness window, and
138
+ * the AIP-3 anchor-receipt skew bound.
139
+ */
140
+ exports.ENVELOPE_TIMESTAMP_SKEW_SEC = 900;
141
+ /**
142
+ * Length (in bytes) of the AES-256-GCM AAD used by the encrypted
143
+ * scheme — `txId_bytes (32) || signerAddress_bytes (20) = 52`.
144
+ *
145
+ * H5 binding: GCM authenticates the AAD; a misrouted envelope (correct
146
+ * ciphertext + nonce + tag + sessionKey, but delivered as if for a
147
+ * different `txId` or `signerAddress`) fails the tag check on decrypt.
148
+ * The hash-input `payloadHash` in the EIP-712 signature already binds
149
+ * the body to a specific `txId` at the signature layer; this AAD adds
150
+ * the same binding INSIDE the GCM authentication, defense-in-depth.
151
+ *
152
+ * Bytes layout (network byte order, no padding):
153
+ * - [0..32): `txId` raw 32 bytes (from the 0x + 64 hex chars).
154
+ * - [32..52): `signerAddress` raw 20 bytes (from the 0x + 40 hex chars).
155
+ */
156
+ exports.ENVELOPE_AAD_LENGTH = 52;
157
+ /**
158
+ * Construct the AES-256-GCM AAD for the `x25519-aes256gcm-v1` scheme.
159
+ *
160
+ * Format: `txId (32 bytes) || signerAddress (20 bytes) = 52 bytes`.
161
+ *
162
+ * Both the build side (in {@link DeliveryEnvelopeBuilder.buildEncrypted})
163
+ * and the decrypt side (in {@link DeliveryEnvelopeBuilder.decryptPayload})
164
+ * call this helper with the SAME txId/signerAddress so the GCM tag
165
+ * commits to identical AAD bytes. Address case is normalized via
166
+ * `bytesFromHex` (which is case-insensitive on the hex characters), so
167
+ * checksum vs lowercase inputs round-trip to the same 20 raw bytes.
168
+ *
169
+ * @param txId - On-chain transaction id, `0x` + 64 hex chars.
170
+ * @param signerAddress - EOA address, `0x` + 40 hex chars.
171
+ * @returns 52-byte AAD buffer (`txId_bytes || signerAddress_bytes`).
172
+ * @throws {DeliveryCryptoError} `crypto_decrypt_failed` if either
173
+ * parameter has the wrong byte length (decoded via `bytesFromHex`
174
+ * from `./crypto`); the underlying helper raises this code.
175
+ *
176
+ * @internal Used by the envelope builder; exposed for cross-SDK
177
+ * fixtures and the H5 test suite.
178
+ */
179
+ function buildEnvelopeAad(txId, signerAddress) {
180
+ const txIdBytes = (0, crypto_1.bytesFromHex)(txId);
181
+ if (txIdBytes.length !== 32) {
182
+ throw new eip712_1.DeliveryEip712Error('BUILDER_AAD_TXID_INVALID_LENGTH', `txId must decode to 32 bytes, got ${txIdBytes.length}`, { actualLength: txIdBytes.length });
183
+ }
184
+ const signerBytes = (0, crypto_1.bytesFromHex)(signerAddress);
185
+ if (signerBytes.length !== 20) {
186
+ throw new eip712_1.DeliveryEip712Error('BUILDER_AAD_SIGNER_INVALID_LENGTH', `signerAddress must decode to 20 bytes, got ${signerBytes.length}`, { actualLength: signerBytes.length });
187
+ }
188
+ const aad = new Uint8Array(exports.ENVELOPE_AAD_LENGTH);
189
+ aad.set(txIdBytes, 0);
190
+ aad.set(signerBytes, 32);
191
+ return aad;
192
+ }
193
+ exports.buildEnvelopeAad = buildEnvelopeAad;
194
+ // ============================================================================
195
+ // secondsNow — Injectable Clock
196
+ // ============================================================================
197
+ //
198
+ // Every timestamp read inside this module flows through `secondsNow()`.
199
+ // Tests inject deterministic clocks via {@link setSecondsNowForTests};
200
+ // production calls fall through to the real wall clock.
201
+ //
202
+ // This is the ONLY allowed wall-clock site in this file — the same
203
+ // single-seam discipline used by `setupBuilder.ts` and the other
204
+ // builders. Forbidden-token lint depends on this.
205
+ //
206
+ let secondsNowImpl = () => {
207
+ // Single allowed wall-clock site in this module. `Math.floor` produces
208
+ // an integer; integer Unix seconds round-trip cleanly through the
209
+ // EIP-712 `uint64` field.
210
+ return Math.floor(Date.now() / 1000);
211
+ };
212
+ /**
213
+ * Return the current wall-clock time in Unix seconds.
214
+ *
215
+ * Production: real `Date.now()` via {@link secondsNowImpl}.
216
+ * Tests: injected via {@link setSecondsNowForTests}.
217
+ *
218
+ * @returns Integer seconds since the Unix epoch.
219
+ */
220
+ function secondsNow() {
221
+ return secondsNowImpl();
222
+ }
223
+ /**
224
+ * Replace the wall-clock implementation used inside this module.
225
+ *
226
+ * **TEST-ONLY.** Production code MUST NOT call this. Pass `null` (or
227
+ * call {@link resetSecondsNowForTests}) to restore the real-clock
228
+ * implementation.
229
+ *
230
+ * @param impl - Replacement function returning Unix seconds, or `null`
231
+ * to restore the default real-clock implementation.
232
+ */
233
+ function setSecondsNowForTests(impl) {
234
+ if (impl === null) {
235
+ resetSecondsNowForTests();
236
+ return;
237
+ }
238
+ secondsNowImpl = impl;
239
+ }
240
+ exports.setSecondsNowForTests = setSecondsNowForTests;
241
+ /**
242
+ * Restore {@link secondsNow} to its default real-clock implementation.
243
+ *
244
+ * **TEST-ONLY.** Safe to call when no override is active.
245
+ */
246
+ function resetSecondsNowForTests() {
247
+ secondsNowImpl = () => Math.floor(Date.now() / 1000);
248
+ }
249
+ exports.resetSecondsNowForTests = resetSecondsNowForTests;
250
+ // ============================================================================
251
+ // Envelope Builder
252
+ // ============================================================================
253
+ /**
254
+ * Builder + verifier + decryptor for AIP-16 delivery envelopes.
255
+ *
256
+ * Instances are cheap to construct and have no I/O side effects.
257
+ * `verify()`, `decryptPayload()`, `verifyAndDecrypt()`, and
258
+ * `computeHash()` are static — call them without constructing an
259
+ * instance.
260
+ *
261
+ * @example Provider build (public)
262
+ * ```typescript
263
+ * const builder = new DeliveryEnvelopeBuilder(wallet);
264
+ * const { wire } = await builder.buildPublic({
265
+ * txId, chainId: 84532, kernelAddress: KERNEL,
266
+ * providerAddress: SMART_WALLET, signerAddress: EOA,
267
+ * payload: { result: 'ok' },
268
+ * });
269
+ * await postToRelay(wire);
270
+ * ```
271
+ *
272
+ * @example Provider build (encrypted)
273
+ * ```typescript
274
+ * const { wire, blobKey } = await builder.buildEncrypted({
275
+ * txId, chainId: 84532, kernelAddress: KERNEL,
276
+ * providerAddress: SMART_WALLET, signerAddress: EOA,
277
+ * payload: { secret: 'data' },
278
+ * buyerEphemeralPubkey: setupSigned.buyerEphemeralPubkey,
279
+ * });
280
+ * ```
281
+ *
282
+ * @example Buyer decrypt
283
+ * ```typescript
284
+ * const result = await DeliveryEnvelopeBuilder.verifyAndDecrypt(
285
+ * wire,
286
+ * buyerEphemeralPrivKey,
287
+ * { expectedKernelAddress: KERNEL, expectedChainId: 84532 },
288
+ * );
289
+ * if (!result.ok) throw new Error(result.code);
290
+ * const payload = result.payload;
291
+ * ```
292
+ */
293
+ class DeliveryEnvelopeBuilder {
294
+ /**
295
+ * Construct a new builder.
296
+ *
297
+ * @param signer - EOA signer required for {@link buildPublic} and
298
+ * {@link buildEncrypted}. Pass `undefined` for a verify-/decrypt-only
299
+ * instance; all verification / decryption helpers are static and
300
+ * do not consult builder state.
301
+ */
302
+ constructor(signer) {
303
+ this.signer = signer;
304
+ }
305
+ // --------------------------------------------------------------------------
306
+ // buildPublic
307
+ // --------------------------------------------------------------------------
308
+ /**
309
+ * Construct, sign, and return a {@link DeliveryEnvelopeWireV1} using
310
+ * the `public-v1` scheme.
311
+ *
312
+ * Encoding (FIX-1, AIP-16 Phase 3.5):
313
+ * - `bodyString = JSON.stringify(params.payload)`.
314
+ * - `wire.body = bodyString` (plaintext UTF-8 JSON, NOT hex).
315
+ * - `payloadHash = keccak256(utf8Bytes(bodyString))`.
316
+ * - The Platform verifier recomputes `keccak256(toUtf8Bytes(body))`
317
+ * on the wire body directly, so the SDK and verifier hash the
318
+ * same bytes byte-for-byte.
319
+ *
320
+ * Canonical-empty enforcement:
321
+ * - `providerEphemeralPubkey = CANONICAL_EMPTY_BYTES32`.
322
+ * - `nonce = CANONICAL_EMPTY_BYTES12`.
323
+ * - `tag = CANONICAL_EMPTY_BYTES16`.
324
+ *
325
+ * Pre-checks:
326
+ * - signer MUST be present.
327
+ * - `signerAddress` MUST equal `await signer.getAddress()`.
328
+ *
329
+ * @param params - {@link BuildPublicEnvelopeParams}.
330
+ * @returns A {@link BuildEnvelopeResult} carrying the signed wire
331
+ * envelope and the raw plaintext bytes (`bodyBytes`) that the
332
+ * `payloadHash` was computed over. `blobKey` is `undefined` for
333
+ * the public scheme.
334
+ * @throws {DeliveryEip712Error} on signer absence or signer/address
335
+ * mismatch.
336
+ */
337
+ async buildPublic(params) {
338
+ if (!this.signer) {
339
+ throw new eip712_1.DeliveryEip712Error('BUILDER_NO_SIGNER', 'DeliveryEnvelopeBuilder.buildPublic requires a signer; construct the builder with a Signer to sign envelopes.');
340
+ }
341
+ // ----- Timestamps -----
342
+ const createdAt = params.createdAt ?? secondsNow();
343
+ if (!Number.isInteger(createdAt) || createdAt <= 0) {
344
+ throw new eip712_1.DeliveryEip712Error('BUILDER_INVALID_CREATED_AT', `createdAt must be a positive integer, got ${String(createdAt)}`, { createdAt });
345
+ }
346
+ // ----- Signer-address binding -----
347
+ //
348
+ // Caught at build time so a wrong-EOA bug surfaces here rather than
349
+ // later at relay-side verification (where the error would be much
350
+ // harder to diagnose).
351
+ const actualSigner = await this.signer.getAddress();
352
+ if (actualSigner.toLowerCase() !== params.signerAddress.toLowerCase()) {
353
+ throw new eip712_1.DeliveryEip712Error('BUILDER_SIGNER_ADDRESS_MISMATCH', 'params.signerAddress does not match signer.getAddress()', {
354
+ expected: actualSigner.toLowerCase(),
355
+ got: params.signerAddress.toLowerCase(),
356
+ });
357
+ }
358
+ // ----- Smart-wallet nonce (H4 fix) -----
359
+ const smartWalletNonce = params.smartWalletNonce ?? 0;
360
+ if (!Number.isInteger(smartWalletNonce) || smartWalletNonce < 0) {
361
+ throw new eip712_1.DeliveryEip712Error('BUILDER_INVALID_SMART_WALLET_NONCE', `smartWalletNonce must be a non-negative integer, got ${String(smartWalletNonce)}`, { smartWalletNonce });
362
+ }
363
+ // ----- Encode body -----
364
+ //
365
+ // JSON.stringify is the canonical serializer for the public scheme;
366
+ // we do NOT use canonicalJsonStringify here because the body is a
367
+ // user payload (any JSON value), and the BUYER needs to recover the
368
+ // exact object the provider wrote — sorting keys post-hoc would
369
+ // silently mutate the structure.
370
+ //
371
+ // FIX-1 (AIP-16 Phase 3.5 HIGH): wire.body for public-v1 is the
372
+ // plaintext UTF-8 JSON STRING — NOT hex. Hashing path is
373
+ // `keccak256(utf8Bytes(bodyString))`, which `bodyHash(string)`
374
+ // produces by routing through `toBytes(string, 'body')`. The
375
+ // Platform verifier at `lib/delivery/auth.ts` for `public-v1`
376
+ // computes `keccak256(toUtf8Bytes(body))` — wire-compatible iff
377
+ // `wire.body` is the plaintext string. (Encrypted scheme below
378
+ // remains hex; the encrypted recompute hex-decodes first.)
379
+ const bodyString = JSON.stringify(params.payload);
380
+ const plaintextBytes = new Uint8Array(Buffer.from(bodyString, 'utf8'));
381
+ const wireBody = bodyString; // plaintext UTF-8 JSON, NOT hex
382
+ const payloadHash = (0, crypto_1.bodyHash)(bodyString); // bodyHash(string) → utf8 bytes
383
+ // ----- Build signed projection -----
384
+ //
385
+ // Field order in the OBJECT does not matter — EIP-712 hashes by the
386
+ // type schema. We mirror the schema order in the source for
387
+ // readability and to make drift against
388
+ // `DELIVERY_ENVELOPE_TYPES_V1` easy to spot.
389
+ const signed = {
390
+ version: 1,
391
+ txId: params.txId,
392
+ chainId: params.chainId,
393
+ kernelAddress: params.kernelAddress,
394
+ providerAddress: params.providerAddress,
395
+ signerAddress: params.signerAddress,
396
+ scheme: 'public-v1',
397
+ providerEphemeralPubkey: types_1.CANONICAL_EMPTY_BYTES32,
398
+ nonce: types_1.CANONICAL_EMPTY_BYTES12,
399
+ payloadHash,
400
+ tag: types_1.CANONICAL_EMPTY_BYTES16,
401
+ createdAt,
402
+ smartWalletNonce,
403
+ };
404
+ // ----- Sign -----
405
+ const domain = (0, eip712_1.buildDeliveryDomain)(params.chainId, params.kernelAddress);
406
+ const providerSig = (await this.signer.signTypedData(domain, eip712_1.DELIVERY_ENVELOPE_TYPES_V1, signed));
407
+ const wire = {
408
+ signed,
409
+ body: wireBody,
410
+ providerSig,
411
+ };
412
+ return {
413
+ wire,
414
+ bodyBytes: plaintextBytes,
415
+ // blobKey intentionally omitted for public scheme.
416
+ };
417
+ }
418
+ // --------------------------------------------------------------------------
419
+ // buildEncrypted
420
+ // --------------------------------------------------------------------------
421
+ /**
422
+ * Construct, sign, and return a {@link DeliveryEnvelopeWireV1} using
423
+ * the `x25519-aes256gcm-v1` scheme.
424
+ *
425
+ * Crypto flow:
426
+ * 1. Generate (or accept) a provider ephemeral X25519 keypair.
427
+ * 2. `shared = X25519(providerPriv, buyerPub)`.
428
+ * 3. `sessionKey = HKDF-SHA256(ikm=shared, salt=txId, info="agirails-delivery-v1", L=32)`.
429
+ * 4. `plaintextBytes = utf8Bytes(JSON.stringify(payload))`.
430
+ * 5. `{ciphertext, nonce, tag} = AES-256-GCM(plaintextBytes, sessionKey)`.
431
+ * 6. `wire.body = bytesToHex(ciphertext)`.
432
+ * 7. `payloadHash = keccak256(ciphertext)`.
433
+ * 8. Sign the EIP-712 projection containing `scheme = "x25519-aes256gcm-v1"`,
434
+ * the provider's ephemeral pubkey, the AES-GCM nonce + tag, and
435
+ * `payloadHash`.
436
+ *
437
+ * The provider's ephemeral PRIVATE key is dropped after step 5;
438
+ * forward secrecy w.r.t. provider long-term keys is provided by the
439
+ * fresh keypair per delivery.
440
+ *
441
+ * Pre-checks:
442
+ * - signer MUST be present.
443
+ * - `signerAddress` MUST equal `await signer.getAddress()`.
444
+ * - `buyerEphemeralPubkey` MUST NOT be {@link CANONICAL_EMPTY_BYTES32}.
445
+ *
446
+ * @param params - {@link BuildEncryptedEnvelopeParams}.
447
+ * @returns A {@link BuildEnvelopeResult} carrying the signed wire
448
+ * envelope, the ciphertext bytes (`bodyBytes`) the `payloadHash`
449
+ * was computed over, and the symmetric session `blobKey` (for
450
+ * provider-side observability / future reference-mode flows; the
451
+ * buyer derives the key independently).
452
+ * @throws {DeliveryEip712Error} on signer absence, signer/address
453
+ * mismatch, or canonical-empty buyer pubkey.
454
+ */
455
+ async buildEncrypted(params) {
456
+ if (!this.signer) {
457
+ throw new eip712_1.DeliveryEip712Error('BUILDER_NO_SIGNER', 'DeliveryEnvelopeBuilder.buildEncrypted requires a signer; construct the builder with a Signer to sign envelopes.');
458
+ }
459
+ // ----- Buyer pubkey canonical-empty rejection -----
460
+ if (params.buyerEphemeralPubkey.toLowerCase() ===
461
+ types_1.CANONICAL_EMPTY_BYTES32.toLowerCase()) {
462
+ throw new eip712_1.DeliveryEip712Error('BUILDER_ENCRYPTED_BUYER_PUBKEY_IS_CANONICAL_EMPTY', 'x25519-aes256gcm-v1 requires a non-zero X25519 buyer pubkey (RFC 7748 §6.1).', { buyerEphemeralPubkey: params.buyerEphemeralPubkey });
463
+ }
464
+ // ----- Timestamps -----
465
+ const createdAt = params.createdAt ?? secondsNow();
466
+ if (!Number.isInteger(createdAt) || createdAt <= 0) {
467
+ throw new eip712_1.DeliveryEip712Error('BUILDER_INVALID_CREATED_AT', `createdAt must be a positive integer, got ${String(createdAt)}`, { createdAt });
468
+ }
469
+ // ----- Signer-address binding -----
470
+ const actualSigner = await this.signer.getAddress();
471
+ if (actualSigner.toLowerCase() !== params.signerAddress.toLowerCase()) {
472
+ throw new eip712_1.DeliveryEip712Error('BUILDER_SIGNER_ADDRESS_MISMATCH', 'params.signerAddress does not match signer.getAddress()', {
473
+ expected: actualSigner.toLowerCase(),
474
+ got: params.signerAddress.toLowerCase(),
475
+ });
476
+ }
477
+ // ----- Ephemeral keypair (generate or accept) -----
478
+ //
479
+ // Production callers omit `providerEphemeralKeyPair` so the private
480
+ // key never crosses a call boundary. Tests pass an explicit pair
481
+ // for determinism / known-answer-test vectors.
482
+ const providerKp = params.providerEphemeralKeyPair ?? (0, keys_1.generateEphemeralKeyPair)();
483
+ // ----- ECDH + HKDF -----
484
+ const peerPubkey = (0, keys_1.pubkeyFromHex)(params.buyerEphemeralPubkey);
485
+ const shared = (0, keys_1.deriveSharedSecret)(providerKp.privateKey, peerPubkey);
486
+ const sessionKey = (0, keys_1.deriveSessionKey)(shared, params.txId);
487
+ // ----- Encrypt (with H5 AAD binding) -----
488
+ //
489
+ // AAD = txId_bytes (32) || signerAddress_bytes (20) = 52 bytes.
490
+ // Bound INSIDE the GCM authentication tag — a misrouted envelope
491
+ // delivered as if for a different txId or signerAddress cannot be
492
+ // opened even if the attacker possesses the ciphertext + nonce +
493
+ // tag + session key. Defense-in-depth on top of the EIP-712
494
+ // signature binding via `payloadHash` and `txId`.
495
+ const aad = buildEnvelopeAad(params.txId, params.signerAddress);
496
+ const bodyString = JSON.stringify(params.payload);
497
+ const plaintextBytes = new Uint8Array(Buffer.from(bodyString, 'utf8'));
498
+ const { ciphertext, nonce, tag } = (0, crypto_1.encryptBody)(plaintextBytes, sessionKey, aad);
499
+ // ----- Wire body + payloadHash -----
500
+ //
501
+ // `payloadHash` is computed over the CIPHERTEXT bytes (not the
502
+ // plaintext) so the signature commits to exactly what travels on
503
+ // the wire. See AIP-16 §6.2 and `bodyHash` JSDoc.
504
+ const wireBodyHex = (0, crypto_1.bytesToHex)(ciphertext);
505
+ const payloadHash = (0, crypto_1.bodyHash)(ciphertext);
506
+ // ----- Smart-wallet nonce (H4 fix) -----
507
+ const smartWalletNonce = params.smartWalletNonce ?? 0;
508
+ if (!Number.isInteger(smartWalletNonce) || smartWalletNonce < 0) {
509
+ throw new eip712_1.DeliveryEip712Error('BUILDER_INVALID_SMART_WALLET_NONCE', `smartWalletNonce must be a non-negative integer, got ${String(smartWalletNonce)}`, { smartWalletNonce });
510
+ }
511
+ // ----- Build signed projection -----
512
+ const signed = {
513
+ version: 1,
514
+ txId: params.txId,
515
+ chainId: params.chainId,
516
+ kernelAddress: params.kernelAddress,
517
+ providerAddress: params.providerAddress,
518
+ signerAddress: params.signerAddress,
519
+ scheme: 'x25519-aes256gcm-v1',
520
+ providerEphemeralPubkey: (0, keys_1.pubkeyToHex)(providerKp.publicKey),
521
+ nonce: (0, crypto_1.bytesToHex)(nonce),
522
+ payloadHash,
523
+ tag: (0, crypto_1.bytesToHex)(tag),
524
+ createdAt,
525
+ smartWalletNonce,
526
+ };
527
+ // ----- Sign -----
528
+ const domain = (0, eip712_1.buildDeliveryDomain)(params.chainId, params.kernelAddress);
529
+ const providerSig = (await this.signer.signTypedData(domain, eip712_1.DELIVERY_ENVELOPE_TYPES_V1, signed));
530
+ const wire = {
531
+ signed,
532
+ body: wireBodyHex,
533
+ providerSig,
534
+ };
535
+ return {
536
+ wire,
537
+ bodyBytes: ciphertext,
538
+ blobKey: sessionKey,
539
+ };
540
+ }
541
+ // --------------------------------------------------------------------------
542
+ // verify (static)
543
+ // --------------------------------------------------------------------------
544
+ /**
545
+ * Verify a {@link DeliveryEnvelopeWireV1} received from the relay.
546
+ *
547
+ * See the module-level header for the full check ordering and rationale.
548
+ *
549
+ * @param wire - The wire envelope received from the relay.
550
+ * @param opts.expectedKernelAddress - Trusted kernel address for the
551
+ * target chain (from the verifier's allowlist).
552
+ * @param opts.expectedChainId - Trusted chainId for the target chain.
553
+ * @param opts.now - Override for the verifier's wall clock (Unix
554
+ * seconds). Tests use this for deterministic timestamp-skew paths;
555
+ * production callers SHOULD omit.
556
+ * @returns `{ ok: true, signed }` on success, `{ ok: false, code, error }`
557
+ * on failure.
558
+ */
559
+ static verify(wire, opts) {
560
+ // Step 1: structural / shape validation (includes scheme/canonical-
561
+ // empty consistency under the hood via `validateEnvelopeSigned`).
562
+ // Any structural defect surfaces as `envelope_signature_invalid`
563
+ // — see setupBuilder.ts for the same rationale.
564
+ const shapeResult = (0, validate_1.validateEnvelopeWire)(wire);
565
+ if (!shapeResult.ok) {
566
+ return {
567
+ ok: false,
568
+ code: 'envelope_signature_invalid',
569
+ error: shapeResult.error,
570
+ };
571
+ }
572
+ const signed = wire.signed;
573
+ // Step 2: defense-in-depth scheme/canonical-empty re-check. The
574
+ // wire-validator already enforces this, but calling it explicitly
575
+ // here means a future refactor of the validator that accidentally
576
+ // weakens the check is caught by THIS file's tests rather than
577
+ // silently shipping. Cheap enough to be worth the explicitness.
578
+ const consistencyResult = (0, validate_1.validateSchemeConsistency)(signed);
579
+ if (!consistencyResult.ok) {
580
+ return {
581
+ ok: false,
582
+ code: 'envelope_signature_invalid',
583
+ error: consistencyResult.error,
584
+ };
585
+ }
586
+ // Step 3: chainId match (trusted vs payload).
587
+ if (signed.chainId !== opts.expectedChainId) {
588
+ return {
589
+ ok: false,
590
+ code: 'envelope_chain_mismatch',
591
+ error: `expected chainId ${opts.expectedChainId}, got ${signed.chainId}`,
592
+ };
593
+ }
594
+ // Step 4: kernel-address match (allowlist anchor).
595
+ const expectedKernelLc = opts.expectedKernelAddress.toLowerCase();
596
+ const payloadKernelLc = signed.kernelAddress.toLowerCase();
597
+ if (payloadKernelLc !== expectedKernelLc) {
598
+ return {
599
+ ok: false,
600
+ code: 'envelope_kernel_mismatch',
601
+ error: `expected kernel ${expectedKernelLc}, got ${payloadKernelLc}`,
602
+ };
603
+ }
604
+ // Step 5: payloadHash binding. This is THE defense against post-
605
+ // signature body tamper: any change to `wire.body` propagates into
606
+ // `recomputedHash` and diverges from the signed `payloadHash`.
607
+ //
608
+ // Scheme-aware (FIX-1, AIP-16 Phase 3.5 HIGH):
609
+ // - public-v1: wire.body IS the plaintext UTF-8 JSON string.
610
+ // `bodyHash(string)` routes through `toBytes(string, 'body')` →
611
+ // `keccak256(utf8Bytes(body))`. This matches the build path
612
+ // above AND the Platform verifier in `lib/delivery/auth.ts`.
613
+ // - x25519-aes256gcm-v1: wire.body is `0x`-prefixed hex of the
614
+ // raw ciphertext bytes. We hex-decode first, then hash — that
615
+ // is exactly what `bodyHash(ciphertext)` did on the build side.
616
+ let recomputedHash;
617
+ try {
618
+ // Branch on scheme: public-v1 hashes UTF-8 of plaintext string,
619
+ // encrypted hashes hex-decoded ciphertext bytes. The same SDK
620
+ // and Platform paths must agree byte-for-byte.
621
+ if (signed.scheme === 'public-v1') {
622
+ recomputedHash = (0, crypto_1.bodyHash)(wire.body);
623
+ }
624
+ else {
625
+ const bodyBytes = (0, crypto_1.bytesFromHex)(wire.body);
626
+ recomputedHash = (0, crypto_1.bodyHash)(bodyBytes);
627
+ }
628
+ }
629
+ catch (e) {
630
+ const msg = e instanceof Error ? e.message : String(e);
631
+ return {
632
+ ok: false,
633
+ code: 'envelope_payload_hash_mismatch',
634
+ error: `failed to decode wire.body for payloadHash recomputation: ${msg}`,
635
+ };
636
+ }
637
+ if (recomputedHash.toLowerCase() !== signed.payloadHash.toLowerCase()) {
638
+ return {
639
+ ok: false,
640
+ code: 'envelope_payload_hash_mismatch',
641
+ error: `recomputed ${recomputedHash.toLowerCase()} does not match signed.payloadHash ${signed.payloadHash.toLowerCase()}`,
642
+ };
643
+ }
644
+ // Step 6: signature recovery. We pass the TRUSTED kernel address
645
+ // (already proven to equal the payload's at step 4) so future
646
+ // refactors cannot accidentally let an attacker control the
647
+ // recovery domain.
648
+ let recovered;
649
+ try {
650
+ recovered = (0, eip712_1.recoverEnvelopeSigner)(signed, wire.providerSig, opts.expectedKernelAddress);
651
+ }
652
+ catch (e) {
653
+ const msg = e instanceof Error ? e.message : String(e);
654
+ return {
655
+ ok: false,
656
+ code: 'envelope_signature_invalid',
657
+ error: msg,
658
+ };
659
+ }
660
+ if (recovered.toLowerCase() !== signed.signerAddress.toLowerCase()) {
661
+ return {
662
+ ok: false,
663
+ code: 'envelope_signature_invalid',
664
+ error: `recovered signer ${recovered.toLowerCase()} does not match signed.signerAddress ${signed.signerAddress.toLowerCase()}`,
665
+ };
666
+ }
667
+ // Step 7: timestamp skew. Symmetric — both past and future. Checked
668
+ // LAST so a forged signature is surfaced first (more severe class
669
+ // of failure).
670
+ const now = opts.now ?? secondsNow();
671
+ if (Math.abs(now - signed.createdAt) > exports.ENVELOPE_TIMESTAMP_SKEW_SEC) {
672
+ return {
673
+ ok: false,
674
+ code: 'envelope_timestamp_skew',
675
+ error: `|now (${now}) - createdAt (${signed.createdAt})| > ${exports.ENVELOPE_TIMESTAMP_SKEW_SEC}s`,
676
+ };
677
+ }
678
+ return { ok: true, signed };
679
+ }
680
+ // --------------------------------------------------------------------------
681
+ // decryptPayload (static)
682
+ // --------------------------------------------------------------------------
683
+ /**
684
+ * Decrypt the payload of an `x25519-aes256gcm-v1` envelope using the
685
+ * buyer's ephemeral private key.
686
+ *
687
+ * Does NOT verify the EIP-712 signature, the chainId / kernel binding,
688
+ * or the payloadHash. Callers that have not already run {@link verify}
689
+ * SHOULD use {@link verifyAndDecrypt} instead.
690
+ *
691
+ * Throws on:
692
+ * - non-encrypted scheme (`public-v1` envelopes are not decrypted),
693
+ * - malformed buyerEphemeralPrivKey length,
694
+ * - ECDH failure (low-order peer pubkey, etc.),
695
+ * - HKDF failure,
696
+ * - AES-GCM authentication failure (tag mismatch).
697
+ *
698
+ * @param wire - The envelope to decrypt.
699
+ * @param buyerEphemeralPrivKey - The buyer's 32-byte X25519 private
700
+ * key (the one whose pubkey was embedded in the setup).
701
+ * @returns The JSON-parsed payload (`unknown`).
702
+ * @throws {DeliveryCryptoError} via the underlying crypto helpers.
703
+ * @throws {DeliveryEip712Error} `BUILDER_PUBLIC_DECRYPT_NOT_APPLICABLE`
704
+ * when called on a `public-v1` envelope.
705
+ */
706
+ static async decryptPayload(wire, buyerEphemeralPrivKey) {
707
+ const signed = wire.signed;
708
+ if (signed.scheme !== 'x25519-aes256gcm-v1') {
709
+ throw new eip712_1.DeliveryEip712Error('BUILDER_PUBLIC_DECRYPT_NOT_APPLICABLE', `decryptPayload requires scheme=x25519-aes256gcm-v1; got ${signed.scheme}`, { scheme: signed.scheme });
710
+ }
711
+ // ECDH + HKDF → session key.
712
+ const providerPubkey = (0, keys_1.pubkeyFromHex)(signed.providerEphemeralPubkey);
713
+ const shared = (0, keys_1.deriveSharedSecret)(buyerEphemeralPrivKey, providerPubkey);
714
+ const sessionKey = (0, keys_1.deriveSessionKey)(shared, signed.txId);
715
+ // Decode wire-form values (ciphertext / nonce / tag).
716
+ const ciphertext = (0, crypto_1.bytesFromHex)(wire.body);
717
+ const nonce = (0, crypto_1.bytesFromHex)(signed.nonce);
718
+ const tag = (0, crypto_1.bytesFromHex)(signed.tag);
719
+ // H5 binding: reconstruct the same AAD the encrypt side used.
720
+ // If the envelope was misrouted (txId or signerAddress in
721
+ // `signed` does not match what the encrypt side committed to),
722
+ // the AAD differs and the GCM tag fails to verify — surfaced as
723
+ // `crypto_decrypt_failed`. Both signed.txId and signed.signerAddress
724
+ // are themselves protected by the EIP-712 signature (verified upstream
725
+ // in `verify`), so an attacker cannot lie about them without invalidating
726
+ // the envelope at the signature layer first.
727
+ const aad = buildEnvelopeAad(signed.txId, signed.signerAddress);
728
+ // Authenticated decrypt. `decryptBody` throws on tag mismatch via
729
+ // `crypto_decrypt_failed` — propagated as-is to the caller.
730
+ const plaintextBytes = (0, crypto_1.decryptBody)(ciphertext, sessionKey, nonce, tag, aad);
731
+ // Decode UTF-8 and JSON-parse. We use the global TextDecoder so the
732
+ // implementation is portable across Node and Bun.
733
+ const decoder = new TextDecoder('utf-8', { fatal: true });
734
+ const json = decoder.decode(plaintextBytes);
735
+ return JSON.parse(json);
736
+ }
737
+ // --------------------------------------------------------------------------
738
+ // verifyAndDecrypt (static)
739
+ // --------------------------------------------------------------------------
740
+ /**
741
+ * Combined {@link verify} + payload extraction.
742
+ *
743
+ * For `public-v1`: after a successful `verify`, the wire body (hex)
744
+ * is decoded to bytes, UTF-8 → string, JSON-parsed, and returned.
745
+ *
746
+ * For `x25519-aes256gcm-v1`: after a successful `verify`,
747
+ * {@link decryptPayload} is invoked with `buyerEphemeralPrivKey`.
748
+ *
749
+ * Verification failures are returned as `{ ok: false, code, error }`
750
+ * — the structured shape matches `verify`. Decryption failures are
751
+ * also returned as `{ ok: false, code: "envelope_decrypt_failed", ...}`
752
+ * (catching the underlying `DeliveryCryptoError`).
753
+ *
754
+ * @param wire - The envelope to verify + decrypt.
755
+ * @param buyerEphemeralPrivKey - 32-byte X25519 private key. Ignored
756
+ * for `public-v1` envelopes (pass `new Uint8Array(32)` if you do
757
+ * not have one — the value is never read in that branch).
758
+ * @param opts - Same as {@link verify}.
759
+ * @returns `{ ok: true, payload }` on success, `{ ok: false, code, error }`
760
+ * on any failure.
761
+ */
762
+ static async verifyAndDecrypt(wire, buyerEphemeralPrivKey, opts) {
763
+ const verifyResult = DeliveryEnvelopeBuilder.verify(wire, opts);
764
+ if (!verifyResult.ok) {
765
+ return verifyResult;
766
+ }
767
+ const signed = verifyResult.signed;
768
+ if (signed.scheme === 'public-v1') {
769
+ // FIX-1 (AIP-16 Phase 3.5 HIGH): wire.body for public-v1 is the
770
+ // plaintext UTF-8 JSON string itself — JSON.parse directly.
771
+ try {
772
+ const payload = JSON.parse(wire.body);
773
+ return { ok: true, payload };
774
+ }
775
+ catch (e) {
776
+ const msg = e instanceof Error ? e.message : String(e);
777
+ return {
778
+ ok: false,
779
+ code: 'envelope_decrypt_failed',
780
+ error: `failed to parse public-v1 body as JSON: ${msg}`,
781
+ };
782
+ }
783
+ }
784
+ // Encrypted scheme — run the decrypt helper. Catch the underlying
785
+ // DeliveryCryptoError / TypeError shape and surface as
786
+ // `envelope_decrypt_failed` so the caller's structured-code surface
787
+ // does not have to know about crypto_* codes.
788
+ try {
789
+ const payload = await DeliveryEnvelopeBuilder.decryptPayload(wire, buyerEphemeralPrivKey);
790
+ return { ok: true, payload };
791
+ }
792
+ catch (e) {
793
+ const msg = e instanceof Error ? e.message : String(e);
794
+ return {
795
+ ok: false,
796
+ code: 'envelope_decrypt_failed',
797
+ error: msg,
798
+ };
799
+ }
800
+ }
801
+ // --------------------------------------------------------------------------
802
+ // computeHash (static)
803
+ // --------------------------------------------------------------------------
804
+ /**
805
+ * Compute a stable, cross-SDK identifier for an envelope wire object.
806
+ *
807
+ * The hash is `keccak256(utf8Bytes(canonicalJsonStringify(wire.signed)))`:
808
+ *
809
+ * - canonical JSON (sorted keys, no whitespace) guarantees byte-for-
810
+ * byte identical input across SDK languages,
811
+ * - `keccak256` matches the on-chain hashing convention,
812
+ * - hashing the SIGNED projection (not the full wire) excludes the
813
+ * signature, body, and any `serverMeta` so the hash is stable
814
+ * across relay-side decoration and signature malleability.
815
+ *
816
+ * This is NOT the EIP-712 signing hash; it is a content-addressing
817
+ * helper for logs, dedup sets, and cross-SDK fixtures.
818
+ *
819
+ * @param wire - The wire envelope to hash.
820
+ * @returns 32-byte hex-encoded keccak256 hash (`0x` + 64 hex chars).
821
+ */
822
+ static computeHash(wire) {
823
+ return (0, ethers_1.keccak256)((0, ethers_1.toUtf8Bytes)((0, canonicalJson_1.canonicalJsonStringify)(wire.signed)));
824
+ }
825
+ }
826
+ exports.DeliveryEnvelopeBuilder = DeliveryEnvelopeBuilder;
827
+ // Re-affirm ethers import is used. Like setupBuilder.ts, we keep this
828
+ // guard so very aggressive tree-shakers do not drop the `ethers` symbol
829
+ // while we still depend on its types.
830
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
831
+ const _ethersUsed = ethers_1.ethers;
832
+ //# sourceMappingURL=envelopeBuilder.js.map