@enbox/agent 0.1.4 → 0.1.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 (66) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/anonymous-dwn-api.js +184 -0
  4. package/dist/esm/anonymous-dwn-api.js.map +1 -0
  5. package/dist/esm/dwn-api.js +86 -777
  6. package/dist/esm/dwn-api.js.map +1 -1
  7. package/dist/esm/dwn-encryption.js +342 -0
  8. package/dist/esm/dwn-encryption.js.map +1 -0
  9. package/dist/esm/dwn-key-delivery.js +256 -0
  10. package/dist/esm/dwn-key-delivery.js.map +1 -0
  11. package/dist/esm/dwn-record-upgrade.js +119 -0
  12. package/dist/esm/dwn-record-upgrade.js.map +1 -0
  13. package/dist/esm/dwn-type-guards.js +23 -0
  14. package/dist/esm/dwn-type-guards.js.map +1 -0
  15. package/dist/esm/index.js +6 -0
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/protocol-utils.js +158 -0
  18. package/dist/esm/protocol-utils.js.map +1 -0
  19. package/dist/esm/store-data-protocols.js +1 -1
  20. package/dist/esm/store-data-protocols.js.map +1 -1
  21. package/dist/esm/sync-engine-level.js +22 -353
  22. package/dist/esm/sync-engine-level.js.map +1 -1
  23. package/dist/esm/sync-messages.js +234 -0
  24. package/dist/esm/sync-messages.js.map +1 -0
  25. package/dist/esm/sync-topological-sort.js +143 -0
  26. package/dist/esm/sync-topological-sort.js.map +1 -0
  27. package/dist/esm/test-harness.js +20 -0
  28. package/dist/esm/test-harness.js.map +1 -1
  29. package/dist/types/anonymous-dwn-api.d.ts +140 -0
  30. package/dist/types/anonymous-dwn-api.d.ts.map +1 -0
  31. package/dist/types/dwn-api.d.ts +36 -179
  32. package/dist/types/dwn-api.d.ts.map +1 -1
  33. package/dist/types/dwn-encryption.d.ts +144 -0
  34. package/dist/types/dwn-encryption.d.ts.map +1 -0
  35. package/dist/types/dwn-key-delivery.d.ts +112 -0
  36. package/dist/types/dwn-key-delivery.d.ts.map +1 -0
  37. package/dist/types/dwn-record-upgrade.d.ts +33 -0
  38. package/dist/types/dwn-record-upgrade.d.ts.map +1 -0
  39. package/dist/types/dwn-type-guards.d.ts +9 -0
  40. package/dist/types/dwn-type-guards.d.ts.map +1 -0
  41. package/dist/types/index.d.ts +6 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/protocol-utils.d.ts +70 -0
  44. package/dist/types/protocol-utils.d.ts.map +1 -0
  45. package/dist/types/sync-engine-level.d.ts +5 -42
  46. package/dist/types/sync-engine-level.d.ts.map +1 -1
  47. package/dist/types/sync-messages.d.ts +76 -0
  48. package/dist/types/sync-messages.d.ts.map +1 -0
  49. package/dist/types/sync-topological-sort.d.ts +15 -0
  50. package/dist/types/sync-topological-sort.d.ts.map +1 -0
  51. package/dist/types/test-harness.d.ts +10 -0
  52. package/dist/types/test-harness.d.ts.map +1 -1
  53. package/package.json +5 -5
  54. package/src/anonymous-dwn-api.ts +263 -0
  55. package/src/dwn-api.ts +160 -1015
  56. package/src/dwn-encryption.ts +481 -0
  57. package/src/dwn-key-delivery.ts +370 -0
  58. package/src/dwn-record-upgrade.ts +166 -0
  59. package/src/dwn-type-guards.ts +43 -0
  60. package/src/index.ts +6 -0
  61. package/src/protocol-utils.ts +185 -0
  62. package/src/store-data-protocols.ts +1 -1
  63. package/src/sync-engine-level.ts +24 -413
  64. package/src/sync-messages.ts +277 -0
  65. package/src/sync-topological-sort.ts +167 -0
  66. package/src/test-harness.ts +19 -0
@@ -0,0 +1,481 @@
1
+ import type {
2
+ DerivedPrivateJwk,
3
+ EncryptionInput,
4
+ EncryptionKeyDeriver,
5
+ KeyDecrypter,
6
+ RecordsQueryReply,
7
+ RecordsReadReply,
8
+ RecordsWriteMessage,
9
+ } from '@enbox/dwn-sdk-js';
10
+ import type { KeyIdentifier, PublicKeyJwk } from '@enbox/crypto';
11
+
12
+ import type { Web5PlatformAgent } from './types/agent.js';
13
+ import type {
14
+ DwnMessageReply,
15
+ ProcessDwnRequest,
16
+ SendDwnRequest,
17
+ } from './types/dwn.js';
18
+
19
+ import { X25519 } from '@enbox/crypto';
20
+ import {
21
+ Cid,
22
+ ContentEncryptionAlgorithm,
23
+ DataStream,
24
+ Encoder,
25
+ Encryption,
26
+ KeyDerivationScheme,
27
+ Records,
28
+ } from '@enbox/dwn-sdk-js';
29
+
30
+ import { DwnInterface } from './types/dwn.js';
31
+ import { isDwnRequest } from './dwn-type-guards.js';
32
+
33
+ /**
34
+ * Returns the correct nonce/IV byte length for the given content encryption algorithm.
35
+ * A256GCM uses 96-bit (12-byte) nonces; XC20P uses 192-bit (24-byte) nonces.
36
+ */
37
+ export function ivLength(algorithm: ContentEncryptionAlgorithm): number {
38
+ return algorithm === ContentEncryptionAlgorithm.XC20P ? 24 : 12;
39
+ }
40
+
41
+ /**
42
+ * Builds a partial EncryptionInput object for a single key-encryption entry.
43
+ * The `authenticationTag` is NOT set here — the caller must set it after
44
+ * AEAD encryption produces the tag.
45
+ */
46
+ export function buildEncryptionInput(
47
+ dek: Uint8Array,
48
+ iv: Uint8Array,
49
+ publicKeyId: string,
50
+ publicKey: PublicKeyJwk,
51
+ derivationScheme: typeof KeyDerivationScheme.ProtocolPath | typeof KeyDerivationScheme.ProtocolContext,
52
+ ): Omit<EncryptionInput, 'authenticationTag'> {
53
+ return {
54
+ initializationVector : iv,
55
+ key : dek,
56
+ keyEncryptionInputs : [{
57
+ publicKeyId,
58
+ publicKey,
59
+ derivationScheme,
60
+ }],
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Encrypts plaintext bytes with AEAD and computes the CID of the resulting ciphertext.
66
+ * Returns everything needed to attach the encrypted data to a DWN message, including
67
+ * the authentication tag.
68
+ */
69
+ export async function encryptAndComputeCid(
70
+ plaintextBytes: Uint8Array,
71
+ dek: Uint8Array,
72
+ iv: Uint8Array,
73
+ algorithm: ContentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256GCM,
74
+ ): Promise<{ encryptedBytes: Uint8Array; dataCid: string; dataSize: number; authenticationTag: Uint8Array }> {
75
+ const { ciphertextStream, tag: authenticationTag } = await Encryption.aeadEncryptStream(
76
+ algorithm, dek, iv, DataStream.fromBytes(plaintextBytes),
77
+ );
78
+ const encryptedBytes = await DataStream.toBytes(ciphertextStream);
79
+ const cidStream = DataStream.fromBytes(encryptedBytes);
80
+ const dataCid = await Cid.computeDagPbCidFromStream(cidStream);
81
+ return { encryptedBytes, dataCid, dataSize: encryptedBytes.length, authenticationTag };
82
+ }
83
+
84
+ /**
85
+ * Resolves the encryption key info for a given DID.
86
+ * Looks up the keyAgreement verification method in the DID document,
87
+ * then resolves the corresponding KMS key URI.
88
+ *
89
+ * @param agent - The platform agent to use for DID resolution and key management
90
+ * @param didUri - The DID URI to resolve encryption key info for
91
+ * @returns keyId (fully qualified verification method ID), keyUri (KMS reference),
92
+ * and publicKeyJwk. No private key material is returned.
93
+ * @throws If the DID has no keyAgreement verification method or it's not X25519.
94
+ */
95
+ export async function getEncryptionKeyInfo(
96
+ agent: Web5PlatformAgent,
97
+ didUri: string,
98
+ ): Promise<{
99
+ keyId: string;
100
+ keyUri: KeyIdentifier;
101
+ publicKeyJwk: PublicKeyJwk;
102
+ }> {
103
+ // 1. Resolve the DID document
104
+ const { didDocument, didResolutionMetadata } = await agent.did.resolve(didUri);
105
+ if (!didDocument) {
106
+ throw new Error(
107
+ `AgentDwnApi: Failed to resolve DID '${didUri}': ` +
108
+ `${JSON.stringify(didResolutionMetadata)}`
109
+ );
110
+ }
111
+
112
+ // 2. Find the keyAgreement verification method
113
+ const keyAgreementRefs = didDocument.keyAgreement;
114
+ if (!keyAgreementRefs || keyAgreementRefs.length === 0) {
115
+ throw new Error(
116
+ `AgentDwnApi: DID '${didUri}' does not have a keyAgreement ` +
117
+ `verification method. Create the identity with an X25519 key ` +
118
+ `with keyAgreement purpose to use protocol encryption.`
119
+ );
120
+ }
121
+
122
+ // 3. Resolve the verification method (handle both inline and string refs)
123
+ const keyAgreementRef = keyAgreementRefs[0];
124
+ let verificationMethod;
125
+ if (typeof keyAgreementRef === 'string') {
126
+ const fragment = keyAgreementRef.includes('#')
127
+ ? keyAgreementRef.split('#').pop()
128
+ : keyAgreementRef;
129
+ verificationMethod = didDocument.verificationMethod?.find(
130
+ vm => vm.id.endsWith(`#${fragment}`)
131
+ );
132
+ } else {
133
+ verificationMethod = keyAgreementRef;
134
+ }
135
+
136
+ if (!verificationMethod?.publicKeyJwk) {
137
+ throw new Error(
138
+ `AgentDwnApi: keyAgreement verification method for '${didUri}' ` +
139
+ `does not contain a public key in JWK format.`
140
+ );
141
+ }
142
+
143
+ // 4. Verify it's an X25519 key
144
+ const publicKeyJwk = verificationMethod.publicKeyJwk;
145
+ if (publicKeyJwk.crv !== 'X25519') {
146
+ throw new Error(
147
+ `AgentDwnApi: keyAgreement key for '${didUri}' uses curve ` +
148
+ `'${publicKeyJwk.crv}', but DWN encryption requires 'X25519'.`
149
+ );
150
+ }
151
+
152
+ // 5. Compute the KMS key URI (does NOT export the key)
153
+ const keyUri = await agent.keyManager.getKeyUri({ key: publicKeyJwk });
154
+
155
+ return {
156
+ keyId : verificationMethod.id,
157
+ keyUri,
158
+ publicKeyJwk : publicKeyJwk as PublicKeyJwk,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Derives a ProtocolContext public key for a given DID and context ID,
164
+ * then returns a fully-formed EncryptionInput. Consolidates the repeated
165
+ * getEncryptionKeyInfo -> constructKeyDerivationPath -> derivePublicKey
166
+ * -> build EncryptionInput sequence.
167
+ *
168
+ * @param agent - The platform agent
169
+ * @param didUri - The DID URI to derive encryption key for
170
+ * @param contextId - The context ID
171
+ * @param dek - Data encryption key
172
+ * @param iv - Initialization vector
173
+ */
174
+ export async function deriveContextEncryptionInput(
175
+ agent: Web5PlatformAgent,
176
+ didUri: string,
177
+ contextId: string,
178
+ dek: Uint8Array,
179
+ iv: Uint8Array,
180
+ ): Promise<{
181
+ encryptionInput: Omit<EncryptionInput, 'authenticationTag'>;
182
+ keyId: string;
183
+ keyUri: KeyIdentifier;
184
+ contextDerivationPath: string[];
185
+ }> {
186
+ const { keyId, keyUri } = await getEncryptionKeyInfo(agent, didUri);
187
+ const contextDerivationPath =
188
+ Records.constructKeyDerivationPathUsingProtocolContextScheme(contextId);
189
+ const contextPublicKey = await agent.keyManager.derivePublicKey({
190
+ keyUri,
191
+ derivationPath: contextDerivationPath,
192
+ });
193
+
194
+ const encryptionInput = buildEncryptionInput(
195
+ dek, iv, keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext,
196
+ );
197
+
198
+ return { encryptionInput, keyId, keyUri, contextDerivationPath };
199
+ }
200
+
201
+ /**
202
+ * Builds a KMS-backed JWE key unwrap callback. Used for both ProtocolPath
203
+ * and ProtocolContext decryption where the KMS holds the root private key.
204
+ *
205
+ * @param agent - The platform agent with access to the key manager
206
+ * @param keyId - The root key ID
207
+ * @param keyUri - The KMS key URI
208
+ * @param derivationScheme - The key derivation scheme
209
+ */
210
+ export function buildKmsDecryptCallback(
211
+ agent: Web5PlatformAgent,
212
+ keyId: string,
213
+ keyUri: KeyIdentifier,
214
+ derivationScheme: typeof KeyDerivationScheme.ProtocolPath | typeof KeyDerivationScheme.ProtocolContext,
215
+ ): KeyDecrypter {
216
+ const keyManager = agent.keyManager;
217
+ return {
218
+ rootKeyId : keyId,
219
+ derivationScheme,
220
+ decrypt : async (fullDerivationPath, jwePayload): Promise<Uint8Array> => {
221
+ return keyManager.jweKeyUnwrap({
222
+ keyUri,
223
+ derivationPath : fullDerivationPath,
224
+ encryptedKey : jwePayload.encryptedKey,
225
+ ephemeralPublicKey : jwePayload.ephemeralPublicKey,
226
+ });
227
+ },
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Constructs an EncryptionKeyDeriver callback for the SDK.
233
+ * The SDK calls derivePublicKey(path), the KMS performs HKDF + public key
234
+ * computation internally. The private key never leaves the KMS.
235
+ *
236
+ * Analogous to getSigner() for signing operations.
237
+ *
238
+ * @param agent - The platform agent
239
+ * @param didUri - The DID URI to create the key deriver for
240
+ * @returns An EncryptionKeyDeriver callback object
241
+ */
242
+ export async function getEncryptionKeyDeriver(
243
+ agent: Web5PlatformAgent,
244
+ didUri: string,
245
+ ): Promise<EncryptionKeyDeriver> {
246
+ const { keyId, keyUri } = await getEncryptionKeyInfo(agent, didUri);
247
+ const keyManager = agent.keyManager;
248
+
249
+ return {
250
+ rootKeyId : keyId,
251
+ derivationScheme : KeyDerivationScheme.ProtocolPath,
252
+ derivePublicKey : async (fullDerivationPath: string[]): Promise<PublicKeyJwk> => {
253
+ return keyManager.derivePublicKey({
254
+ keyUri,
255
+ derivationPath: fullDerivationPath,
256
+ });
257
+ },
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Constructs a ProtocolPath KeyDecrypter.
263
+ *
264
+ * @param agent - The platform agent
265
+ * @param didUri - The DID URI to create the key decrypter for
266
+ * @returns A KeyDecrypter callback object
267
+ */
268
+ export async function getKeyDecrypter(
269
+ agent: Web5PlatformAgent,
270
+ didUri: string,
271
+ ): Promise<KeyDecrypter> {
272
+ const { keyId, keyUri } = await getEncryptionKeyInfo(agent, didUri);
273
+ return buildKmsDecryptCallback(agent, keyId, keyUri, KeyDerivationScheme.ProtocolPath);
274
+ }
275
+
276
+ /**
277
+ * Builds a KeyDecrypter from a context-derived private key.
278
+ * Uses the raw key directly (since it was shared with us via the key-delivery protocol).
279
+ *
280
+ * @param contextKey - The derived private key for the context
281
+ */
282
+ export function buildContextKeyDecrypter(
283
+ contextKey: DerivedPrivateJwk,
284
+ ): KeyDecrypter {
285
+ return {
286
+ rootKeyId : contextKey.rootKeyId,
287
+ derivationScheme : contextKey.derivationScheme,
288
+ decrypt : async (fullDerivationPath, jwePayload): Promise<Uint8Array> => {
289
+ const leafPrivateKeyBytes = await Records.derivePrivateKey(
290
+ contextKey, fullDerivationPath,
291
+ );
292
+ const leafPrivateKeyJwk = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
293
+ return Encryption.ecdhEsUnwrapKey(leafPrivateKeyJwk, jwePayload.ephemeralPublicKey, jwePayload.encryptedKey);
294
+ },
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Resolves the appropriate KeyDecrypter for a record's encryption scheme.
300
+ * Handles both single-party (ProtocolPath) and multi-party (ProtocolContext).
301
+ *
302
+ * For ProtocolContext records:
303
+ * - Context creator: derives key directly from KMS
304
+ * - Participant: fetches contextKey via key-delivery protocol, caches it
305
+ *
306
+ * @param agent - The platform agent
307
+ * @param authorDid - The DID of the author attempting to decrypt
308
+ * @param recordsWrite - The records write message containing encryption info
309
+ * @param targetDid - The target DID (DWN owner), if known
310
+ * @param contextDerivedKeyCache - Cache for context-derived private keys
311
+ * @param fetchContextKeyRecordFn - Function to fetch context key records from key-delivery protocol
312
+ */
313
+ export async function resolveKeyDecrypter(
314
+ agent: Web5PlatformAgent,
315
+ authorDid: string,
316
+ recordsWrite: RecordsWriteMessage,
317
+ targetDid: string | undefined,
318
+ contextDerivedKeyCache: { get(key: string): DerivedPrivateJwk | undefined; set(key: string, value: DerivedPrivateJwk): void },
319
+ fetchContextKeyRecordFn: (params: {
320
+ ownerDid: string;
321
+ requesterDid: string;
322
+ sourceProtocol: string;
323
+ sourceContextId: string;
324
+ }) => Promise<DerivedPrivateJwk | undefined>,
325
+ ): Promise<KeyDecrypter> {
326
+ const { encryption } = recordsWrite;
327
+
328
+ // Check if the record uses context-derived encryption
329
+ const hasContextKey = encryption?.recipients.some(
330
+ (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext
331
+ );
332
+
333
+ if (!hasContextKey || !recordsWrite.contextId) {
334
+ // Single-party protocol-path encryption
335
+ return getKeyDecrypter(agent, authorDid);
336
+ }
337
+
338
+ // --- Multi-party context encryption ---
339
+ const contextKeyEntry = encryption!.recipients.find(
340
+ (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext
341
+ )!;
342
+
343
+ const rootContextId = recordsWrite.contextId.split('/')[0];
344
+
345
+ // Case 1: I am the context creator — rootKeyId matches my encryption key
346
+ const { keyId, keyUri } = await getEncryptionKeyInfo(agent, authorDid);
347
+ if (contextKeyEntry.header.kid === keyId) {
348
+ return buildKmsDecryptCallback(agent, keyId, keyUri, KeyDerivationScheme.ProtocolContext);
349
+ }
350
+
351
+ // Case 2: I am a participant — fetch my context key from the key-delivery protocol
352
+ const cacheKey = `ctx~${authorDid}~${rootContextId}`;
353
+ const cached = contextDerivedKeyCache.get(cacheKey);
354
+ if (cached) {
355
+ return buildContextKeyDecrypter(cached);
356
+ }
357
+
358
+ // Fetch context key via the key-delivery protocol — local first, then remote
359
+ const protocol = recordsWrite.descriptor.protocol!;
360
+
361
+ // Try local: I may be the DWN owner with a contextKey addressed to myself
362
+ let contextDerivedPrivateKey = await fetchContextKeyRecordFn({
363
+ ownerDid : authorDid,
364
+ requesterDid : authorDid,
365
+ sourceProtocol : protocol,
366
+ sourceContextId : rootContextId,
367
+ });
368
+
369
+ // Try remote: query the DWN owner's DWN for my contextKey record.
370
+ // For cross-DWN records, targetDid is the DWN owner (e.g., Alice) where the
371
+ // contextKey was written. For same-DWN records, fall back to the record's
372
+ // authorization signer.
373
+ if (!contextDerivedPrivateKey) {
374
+ const { Jws } = await import('@enbox/dwn-sdk-js');
375
+ const contextOwnerDid = targetDid ?? Jws.getSignerDid(
376
+ recordsWrite.authorization.signature.signatures[0]
377
+ );
378
+ contextDerivedPrivateKey = await fetchContextKeyRecordFn({
379
+ ownerDid : contextOwnerDid,
380
+ requesterDid : authorDid,
381
+ sourceProtocol : protocol,
382
+ sourceContextId : rootContextId,
383
+ });
384
+ }
385
+
386
+ if (!contextDerivedPrivateKey) {
387
+ throw new Error(
388
+ `AgentDwnApi: Failed to decrypt record '${recordsWrite.recordId}'. ` +
389
+ `Record uses context-derived encryption but no contextKey record ` +
390
+ `could be found via the key-delivery protocol.`
391
+ );
392
+ }
393
+
394
+ contextDerivedKeyCache.set(cacheKey, contextDerivedPrivateKey);
395
+ return buildContextKeyDecrypter(contextDerivedPrivateKey);
396
+ }
397
+
398
+ /**
399
+ * Post-processes a DWN reply, auto-decrypting data if encryption is enabled.
400
+ * Delegates to the SDK's Records.decrypt() with the appropriate KeyDecrypter —
401
+ * resolveKeyDecrypter() selects between ProtocolPath and ProtocolContext schemes.
402
+ *
403
+ * @param request - The original DWN request
404
+ * @param reply - The DWN reply to process
405
+ * @param agent - The platform agent
406
+ * @param contextDerivedKeyCache - Cache for context-derived private keys
407
+ * @param fetchContextKeyRecordFn - Function to fetch context key records
408
+ */
409
+ export async function maybeDecryptReply<T extends DwnInterface>(
410
+ request: ProcessDwnRequest<T> | SendDwnRequest<T>,
411
+ reply: DwnMessageReply[T],
412
+ agent: Web5PlatformAgent,
413
+ contextDerivedKeyCache: { get(key: string): DerivedPrivateJwk | undefined; set(key: string, value: DerivedPrivateJwk): void },
414
+ fetchContextKeyRecordFn: (params: {
415
+ ownerDid: string;
416
+ requesterDid: string;
417
+ sourceProtocol: string;
418
+ sourceContextId: string;
419
+ }) => Promise<DerivedPrivateJwk | undefined>,
420
+ ): Promise<void> {
421
+ if (!('encryption' in request) || !request.encryption) {
422
+ return;
423
+ }
424
+
425
+ // Auto-decrypt RecordsRead replies
426
+ if (isDwnRequest(request as ProcessDwnRequest<DwnInterface>, DwnInterface.RecordsRead)) {
427
+ const readReply = reply as RecordsReadReply;
428
+ if (readReply.status.code === 200
429
+ && readReply.entry?.recordsWrite?.encryption
430
+ && readReply.entry?.data) {
431
+ const keyDecrypter = await resolveKeyDecrypter(
432
+ agent, request.author, readReply.entry.recordsWrite, request.target,
433
+ contextDerivedKeyCache, fetchContextKeyRecordFn,
434
+ );
435
+
436
+ try {
437
+ readReply.entry.data = await Records.decrypt(
438
+ readReply.entry.recordsWrite,
439
+ keyDecrypter,
440
+ readReply.entry.data,
441
+ );
442
+ } catch (error: any) {
443
+ throw new Error(
444
+ `AgentDwnApi: Failed to decrypt record ` +
445
+ `'${readReply.entry.recordsWrite.recordId}'. ` +
446
+ `Original error: ${error.message}`
447
+ );
448
+ }
449
+ }
450
+ }
451
+
452
+ // Auto-decrypt RecordsQuery replies (small records inline as encodedData)
453
+ if (isDwnRequest(request as ProcessDwnRequest<DwnInterface>, DwnInterface.RecordsQuery)) {
454
+ const queryReply = reply as RecordsQueryReply;
455
+ if (queryReply.status.code === 200 && queryReply.entries) {
456
+ for (const entry of queryReply.entries) {
457
+ if (entry.encryption && entry.encodedData) {
458
+ const keyDecrypter = await resolveKeyDecrypter(
459
+ agent, request.author, entry as RecordsWriteMessage, request.target,
460
+ contextDerivedKeyCache, fetchContextKeyRecordFn,
461
+ );
462
+
463
+ try {
464
+ const cipherBytes = Encoder.base64UrlToBytes(entry.encodedData);
465
+ const cipherStream = DataStream.fromBytes(cipherBytes);
466
+ const plainStream = await Records.decrypt(
467
+ entry as RecordsWriteMessage, keyDecrypter, cipherStream,
468
+ );
469
+ const plainBytes = await DataStream.toBytes(plainStream);
470
+ entry.encodedData = Encoder.bytesToBase64Url(plainBytes);
471
+ } catch (error: any) {
472
+ throw new Error(
473
+ `AgentDwnApi: Failed to decrypt record ` +
474
+ `'${entry.recordId}'. Original error: ${error.message}`
475
+ );
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }