@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
package/src/dwn-api.ts CHANGED
@@ -3,16 +3,13 @@ import type {
3
3
  DwnConfig,
4
4
  EncryptionInput,
5
5
  EncryptionKeyDeriver,
6
- GenericMessage,
7
6
  KeyDecrypter,
8
7
  ProtocolDefinition,
9
- ProtocolRuleSet,
10
8
  ProtocolsQueryReply,
11
9
  RecordsQueryReply,
12
- RecordsQueryReplyEntry,
13
- RecordsReadReply,
14
10
  RecordsWrite,
15
- RecordsWriteMessage } from '@enbox/dwn-sdk-js';
11
+ RecordsWriteMessage,
12
+ } from '@enbox/dwn-sdk-js';
16
13
  import type { KeyIdentifier, PrivateKeyJwk, PublicKeyJwk } from '@enbox/crypto';
17
14
 
18
15
  import { TtlCache } from '@enbox/common';
@@ -22,10 +19,7 @@ import {
22
19
  DataStoreLevel,
23
20
  DataStream,
24
21
  Dwn,
25
- DwnInterfaceName,
26
22
  DwnMethodName,
27
- Encoder,
28
- Encryption,
29
23
  EventEmitterStream,
30
24
  Jws,
31
25
  KeyDerivationScheme,
@@ -34,7 +28,7 @@ import {
34
28
  Protocols,
35
29
  Records,
36
30
  ResumableTaskStoreLevel,
37
- StateIndexLevel
31
+ StateIndexLevel,
38
32
  } from '@enbox/dwn-sdk-js';
39
33
  import { CryptoUtils, X25519 } from '@enbox/crypto';
40
34
  import { DidDht, DidJwk, DidResolverCacheLevel, UniversalResolver } from '@enbox/dids';
@@ -45,22 +39,54 @@ import type {
45
39
  DwnMessageInstance,
46
40
  DwnMessageParams,
47
41
  DwnMessageReply,
48
- DwnMessagesPermissionScope,
49
42
  DwnMessageWithData,
50
- DwnPermissionScope,
51
- DwnRecordsInterfaces,
52
- DwnRecordsPermissionScope,
53
43
  DwnResponse,
54
44
  DwnSigner,
55
45
  MessageHandler,
56
46
  ProcessDwnRequest,
57
- SendDwnRequest
47
+ SendDwnRequest,
58
48
  } from './types/dwn.js';
59
49
 
60
50
  import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
61
51
  import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
62
52
  import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
63
53
 
54
+ // Re-export type guards for backward compatibility
55
+ export { isDwnMessage, isDwnRequest, isMessagesPermissionScope, isRecordPermissionScope, isRecordsType } from './dwn-type-guards.js';
56
+
57
+ // Import type guards for internal use
58
+ import { isDwnRequest } from './dwn-type-guards.js';
59
+
60
+ // Import extracted encryption functions
61
+ import {
62
+ buildEncryptionInput as buildEncryptionInputFn,
63
+ deriveContextEncryptionInput as deriveContextEncryptionInputFn,
64
+ encryptAndComputeCid as encryptAndComputeCidFn,
65
+ getEncryptionKeyDeriver as getEncryptionKeyDeriverFn,
66
+ getEncryptionKeyInfo as getEncryptionKeyInfoFn,
67
+ getKeyDecrypter as getKeyDecrypterFn,
68
+ ivLength as ivLengthFn,
69
+ maybeDecryptReply as maybeDecryptReplyFn,
70
+ } from './dwn-encryption.js';
71
+
72
+ // Import extracted protocol utilities
73
+ import {
74
+ detectNewParticipants as detectNewParticipantsFn,
75
+ hasRelationalReadAccess as hasRelationalReadAccessFn,
76
+ isMultiPartyContext as isMultiPartyContextFn,
77
+ } from './protocol-utils.js';
78
+
79
+ // Import extracted key delivery functions
80
+ import {
81
+ eagerSendContextKeyRecord as eagerSendContextKeyRecordFn,
82
+ ensureKeyDeliveryProtocol as ensureKeyDeliveryProtocolFn,
83
+ fetchContextKeyRecord as fetchContextKeyRecordFn,
84
+ writeContextKeyRecord as writeContextKeyRecordFn,
85
+ } from './dwn-key-delivery.js';
86
+
87
+ // Import extracted record upgrade function
88
+ import { upgradeExternalRootRecord as upgradeExternalRootRecordFn } from './dwn-record-upgrade.js';
89
+
64
90
  type DwnMessageWithBlob<T extends DwnInterface> = {
65
91
  message: DwnMessage[T];
66
92
  data?: Blob;
@@ -75,35 +101,6 @@ interface DwnApiCreateDwnParams extends Partial<DwnConfig> {
75
101
  dataPath?: string;
76
102
  }
77
103
 
78
- export function isDwnRequest<T extends DwnInterface>(
79
- dwnRequest: ProcessDwnRequest<DwnInterface>, messageType: T
80
- ): dwnRequest is ProcessDwnRequest<T> {
81
- return dwnRequest.messageType === messageType;
82
- }
83
-
84
- export function isDwnMessage<T extends DwnInterface>(
85
- messageType: T, message: GenericMessage
86
- ): message is DwnMessage[T] {
87
- const incomingMessageInterfaceName = message.descriptor.interface + message.descriptor.method;
88
- return incomingMessageInterfaceName === messageType;
89
- }
90
-
91
- export function isRecordsType(messageType: DwnInterface): messageType is DwnRecordsInterfaces {
92
- return messageType === DwnInterface.RecordsDelete ||
93
- messageType === DwnInterface.RecordsQuery ||
94
- messageType === DwnInterface.RecordsRead ||
95
- messageType === DwnInterface.RecordsSubscribe ||
96
- messageType === DwnInterface.RecordsWrite;
97
- }
98
-
99
- export function isRecordPermissionScope(scope: DwnPermissionScope): scope is DwnRecordsPermissionScope {
100
- return scope.interface === DwnInterfaceName.Records;
101
- }
102
-
103
- export function isMessagesPermissionScope(scope: DwnPermissionScope): scope is DwnMessagesPermissionScope {
104
- return scope.interface === DwnInterfaceName.Messages;
105
- }
106
-
107
104
  export class AgentDwnApi {
108
105
  /**
109
106
  * Holds the instance of a `Web5PlatformAgent` that represents the current execution context for
@@ -258,11 +255,14 @@ export class AgentDwnApi {
258
255
  const isExternallyAuthored = authorDid !== request.target;
259
256
  const isRootRecord = !writeParams.parentContextId;
260
257
  const rootPathSegment = writeParams.protocolPath.split('/')[0];
261
- const isMultiParty = this.isMultiPartyContext(protocolDefinition, rootPathSegment);
258
+ const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
262
259
 
263
260
  if (isExternallyAuthored && isRootRecord && isMultiParty) {
264
261
  try {
265
- await this.upgradeExternalRootRecord(request.target, recordsWriteMessage);
262
+ await upgradeExternalRootRecordFn(
263
+ this.agent, request.target, recordsWriteMessage,
264
+ this._dwn, this.getSigner.bind(this), this._contextKeyCache,
265
+ );
266
266
  } catch (upgradeError: any) {
267
267
  console.warn(
268
268
  `AgentDwnApi: Reactive root-record upgrade failed for ` +
@@ -271,7 +271,7 @@ export class AgentDwnApi {
271
271
  }
272
272
  }
273
273
 
274
- const newParticipants = this.detectNewParticipants({
274
+ const newParticipants = detectNewParticipantsFn({
275
275
  protocolDefinition,
276
276
  protocolPath : writeParams.protocolPath,
277
277
  recipient : writeParams.recipient,
@@ -285,7 +285,7 @@ export class AgentDwnApi {
285
285
  || recordsWriteMessage.contextId
286
286
  || recordsWriteMessage.recordId;
287
287
 
288
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(request.target);
288
+ const { keyId, keyUri } = await getEncryptionKeyInfoFn(this.agent, request.target);
289
289
  const contextDerivationPath = [
290
290
  KeyDerivationScheme.ProtocolContext,
291
291
  rootContextId,
@@ -515,7 +515,7 @@ export class AgentDwnApi {
515
515
  // Auto-inject encryption keys into protocol definition (Component 5)
516
516
  if (isDwnRequest(request, DwnInterface.ProtocolsConfigure) && request.encryption && !rawMessage) {
517
517
  const messageParams = request.messageParams!;
518
- const keyDeriver = await this.getEncryptionKeyDeriver(request.author);
518
+ const keyDeriver = await getEncryptionKeyDeriverFn(this.agent, request.author);
519
519
 
520
520
  // SDK walks the protocol structure and calls our callback for each path.
521
521
  // The KMS performs HKDF derivation + public key computation internally.
@@ -557,19 +557,6 @@ export class AgentDwnApi {
557
557
  // public key (derivedPublicKey) from existing ProtocolContext-encrypted records
558
558
  // in the same context on the target's DWN.
559
559
 
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
560
  // Tracks deferred context encryption info for root multi-party records.
574
561
  let deferredContextEncryption: {
575
562
  dataEncryptionKey: Uint8Array;
@@ -630,7 +617,7 @@ export class AgentDwnApi {
630
617
  messageParams.protocol === KeyDeliveryProtocolDefinition.protocol;
631
618
  const isMultiPartyContext = isKeyDeliveryProtocol
632
619
  ? false
633
- : this.isMultiPartyContext(protocolDefinition, rootPathSegment);
620
+ : isMultiPartyContextFn(protocolDefinition, rootPathSegment);
634
621
  const isRootRecord = !messageParams.parentContextId;
635
622
 
636
623
  // 4. Get plaintext bytes (normalize from all supported input types)
@@ -647,31 +634,26 @@ export class AgentDwnApi {
647
634
  throw new Error('AgentDwnApi: Data must be provided for encrypted records.');
648
635
  }
649
636
 
650
- // 5. Generate random DEK and IV
637
+ // 5. Generate random DEK and IV (IV size depends on content encryption algorithm)
638
+ const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256GCM;
651
639
  const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
652
- const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(12));
640
+ const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLengthFn(contentEncryptionAlgorithm)));
653
641
 
654
642
  // 6. Build partial EncryptionInput (authenticationTag added after AEAD encryption)
655
643
  let encryptionInput: (Omit<EncryptionInput, 'authenticationTag'> & { authenticationTag?: Uint8Array }) | undefined;
656
644
 
657
- const buildProtocolPathInput = (): Omit<EncryptionInput, 'authenticationTag'> => this.buildEncryptionInput(
645
+ const buildProtocolPathInput = (): Omit<EncryptionInput, 'authenticationTag'> => buildEncryptionInputFn(
658
646
  dataEncryptionKey, dataEncryptionIV,
659
647
  ruleSet.$encryption.rootKeyId, ruleSet.$encryption.publicKeyJwk,
660
648
  KeyDerivationScheme.ProtocolPath,
661
649
  );
662
650
 
663
651
  if (isCrossDwn && isMultiPartyContext && isRootRecord) {
664
- // --- Cross-DWN root record in multi-party context Target's ProtocolPath key ---
665
- // External authors cannot derive the target's context key (HKDF requires
666
- // the private key). Use the target's ProtocolPath public key from their
667
- // protocol definition. The target's agent will reactively upgrade the record
668
- // to include a ProtocolContext recipient entry.
652
+ // --- Cross-DWN root record in multi-party context -> Target's ProtocolPath key ---
669
653
  encryptionInput = buildProtocolPathInput();
670
654
 
671
655
  } else if (isCrossDwn && isMultiPartyContext && !isRootRecord) {
672
- // --- Cross-DWN non-root record in multi-party context derivedPublicKey ---
673
- // Read an existing ProtocolContext-encrypted record from the target's DWN
674
- // and extract the derivedPublicKey (the context public key).
656
+ // --- Cross-DWN non-root record in multi-party context -> derivedPublicKey ---
675
657
  const rootContextId = messageParams.parentContextId!.split('/')[0]
676
658
  || messageParams.parentContextId!;
677
659
 
@@ -680,7 +662,7 @@ export class AgentDwnApi {
680
662
  );
681
663
 
682
664
  if (derivedPublicKeyInfo) {
683
- encryptionInput = this.buildEncryptionInput(
665
+ encryptionInput = buildEncryptionInputFn(
684
666
  dataEncryptionKey, dataEncryptionIV,
685
667
  derivedPublicKeyInfo.rootKeyId, derivedPublicKeyInfo.derivedPublicKey,
686
668
  KeyDerivationScheme.ProtocolContext,
@@ -692,17 +674,17 @@ export class AgentDwnApi {
692
674
  }
693
675
 
694
676
  } else if (isCrossDwn) {
695
- // --- Cross-DWN single-party Target's ProtocolPath key ---
677
+ // --- Cross-DWN single-party -> Target's ProtocolPath key ---
696
678
  encryptionInput = buildProtocolPathInput();
697
679
 
698
680
  } else if (isMultiPartyContext && !isRootRecord) {
699
- // --- Local non-root record in a multi-party context Context key ---
681
+ // --- Local non-root record in a multi-party context -> Context key ---
700
682
  const rootContextId = messageParams.parentContextId!.split('/')[0]
701
683
  || messageParams.parentContextId!;
702
684
 
703
685
  let contextKeyInfo = this._contextKeyCache.get(rootContextId);
704
686
  if (!contextKeyInfo) {
705
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(request.author);
687
+ const { keyId, keyUri } = await getEncryptionKeyInfoFn(this.agent, request.author);
706
688
  const contextDerivationPath =
707
689
  Records.constructKeyDerivationPathUsingProtocolContextScheme(rootContextId);
708
690
  contextKeyInfo = { keyId, keyUri, contextDerivationPath };
@@ -714,26 +696,26 @@ export class AgentDwnApi {
714
696
  derivationPath : contextKeyInfo.contextDerivationPath,
715
697
  });
716
698
 
717
- encryptionInput = this.buildEncryptionInput(
699
+ encryptionInput = buildEncryptionInputFn(
718
700
  dataEncryptionKey, dataEncryptionIV,
719
701
  contextKeyInfo.keyId, contextPublicKey,
720
702
  KeyDerivationScheme.ProtocolContext,
721
703
  );
722
704
 
723
705
  } else if (isMultiPartyContext && isRootRecord) {
724
- // --- Local root record in multi-party context Deferred context encryption ---
706
+ // --- Local root record in multi-party context -> Deferred context encryption ---
725
707
  // contextId = recordId, which is only known after message creation.
726
708
  // Skip encryptionInput here; apply it after create() below.
727
709
  encryptionInput = undefined;
728
710
 
729
711
  } else {
730
- // --- Local single-party ProtocolPath key (existing logic) ---
712
+ // --- Local single-party -> ProtocolPath key (existing logic) ---
731
713
  encryptionInput = buildProtocolPathInput();
732
714
  }
733
715
 
734
- // 7. Encrypt data with AEAD (AES-256-GCM) and compute CID
716
+ // 7. Encrypt data with AEAD and compute CID
735
717
  const { encryptedBytes, dataCid, dataSize, authenticationTag } =
736
- await this.encryptAndComputeCid(plaintextBytes, dataEncryptionKey, dataEncryptionIV);
718
+ await encryptAndComputeCidFn(plaintextBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
737
719
 
738
720
  // 8. Replace plaintext with encrypted data
739
721
  messageParams.dataCid = dataCid;
@@ -755,7 +737,7 @@ export class AgentDwnApi {
755
737
  // context keys back to the author without querying the author's DWN.
756
738
  if (isCrossDwn && isMultiPartyContext) {
757
739
  const { keyId: authorKeyId, keyUri: authorKeyUri } =
758
- await this.getEncryptionKeyInfo(request.author);
740
+ await getEncryptionKeyInfoFn(this.agent, request.author);
759
741
  const keyDeliveryDerivationPath = [
760
742
  KeyDerivationScheme.ProtocolPath,
761
743
  KeyDeliveryProtocolDefinition.protocol,
@@ -793,14 +775,14 @@ export class AgentDwnApi {
793
775
 
794
776
  // Deferred context encryption for root multi-party records (Component 9).
795
777
  // Now that the message exists, we know recordId = contextId.
796
- // Following the SDK two-pass pattern: encryptSymmetricEncryptionKey sign.
778
+ // Following the SDK two-pass pattern: encryptSymmetricEncryptionKey -> sign.
797
779
  if (deferredContextEncryption && isDwnRequest(request, DwnInterface.RecordsWrite)) {
798
780
  const recordsWriteInstance = dwnMessage as unknown as RecordsWrite;
799
781
  const contextId = recordsWriteInstance.message.recordId;
800
782
 
801
783
  const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } =
802
- await this.deriveContextEncryptionInput(
803
- request.author, contextId,
784
+ await deriveContextEncryptionInputFn(
785
+ this.agent, request.author, contextId,
804
786
  deferredContextEncryption.dataEncryptionKey,
805
787
  deferredContextEncryption.dataEncryptionIV,
806
788
  );
@@ -886,219 +868,88 @@ export class AgentDwnApi {
886
868
  }
887
869
 
888
870
  /**
889
- * Resolves the encryption key info for a given DID.
890
- * Looks up the keyAgreement verification method in the DID document,
891
- * then resolves the corresponding KMS key URI.
871
+ * Constructs an EncryptionKeyDeriver callback for the SDK.
872
+ * Delegates to the standalone function in `dwn-encryption.ts`.
892
873
  *
893
- * @param didUri - The DID URI to resolve encryption key info for
894
- * @returns keyId (fully qualified verification method ID), keyUri (KMS reference),
895
- * and publicKeyJwk. No private key material is returned.
896
- * @throws If the DID has no keyAgreement verification method or it's not X25519.
897
- */
898
- private async getEncryptionKeyInfo(didUri: string): Promise<{
899
- keyId: string;
900
- keyUri: KeyIdentifier;
901
- publicKeyJwk: PublicKeyJwk;
902
- }> {
903
- // 1. Resolve the DID document
904
- const { didDocument, didResolutionMetadata } = await this.agent.did.resolve(didUri);
905
- if (!didDocument) {
906
- throw new Error(
907
- `AgentDwnApi: Failed to resolve DID '${didUri}': ` +
908
- `${JSON.stringify(didResolutionMetadata)}`
909
- );
910
- }
911
-
912
- // 2. Find the keyAgreement verification method
913
- const keyAgreementRefs = didDocument.keyAgreement;
914
- if (!keyAgreementRefs || keyAgreementRefs.length === 0) {
915
- throw new Error(
916
- `AgentDwnApi: DID '${didUri}' does not have a keyAgreement ` +
917
- `verification method. Create the identity with an X25519 key ` +
918
- `with keyAgreement purpose to use protocol encryption.`
919
- );
920
- }
921
-
922
- // 3. Resolve the verification method (handle both inline and string refs)
923
- const keyAgreementRef = keyAgreementRefs[0];
924
- let verificationMethod;
925
- if (typeof keyAgreementRef === 'string') {
926
- const fragment = keyAgreementRef.includes('#')
927
- ? keyAgreementRef.split('#').pop()
928
- : keyAgreementRef;
929
- verificationMethod = didDocument.verificationMethod?.find(
930
- vm => vm.id.endsWith(`#${fragment}`)
931
- );
932
- } else {
933
- verificationMethod = keyAgreementRef;
934
- }
935
-
936
- if (!verificationMethod?.publicKeyJwk) {
937
- throw new Error(
938
- `AgentDwnApi: keyAgreement verification method for '${didUri}' ` +
939
- `does not contain a public key in JWK format.`
940
- );
941
- }
942
-
943
- // 4. Verify it's an X25519 key
944
- const publicKeyJwk = verificationMethod.publicKeyJwk;
945
- if (publicKeyJwk.crv !== 'X25519') {
946
- throw new Error(
947
- `AgentDwnApi: keyAgreement key for '${didUri}' uses curve ` +
948
- `'${publicKeyJwk.crv}', but DWN encryption requires 'X25519'.`
949
- );
950
- }
951
-
952
- // 5. Compute the KMS key URI (does NOT export the key)
953
- const keyUri = await this.agent.keyManager.getKeyUri({ key: publicKeyJwk });
954
-
955
- return {
956
- keyId : verificationMethod.id,
957
- keyUri,
958
- publicKeyJwk : publicKeyJwk as PublicKeyJwk,
959
- };
960
- }
961
-
962
- /**
963
- * Builds a partial EncryptionInput object for a single key-encryption entry.
964
- * The `authenticationTag` is NOT set here — the caller must set it after
965
- * AEAD encryption produces the tag.
874
+ * @param didUri - The DID URI to create the key deriver for
875
+ * @returns An EncryptionKeyDeriver callback object
966
876
  */
967
- private buildEncryptionInput(
968
- dek: Uint8Array,
969
- iv: Uint8Array,
970
- publicKeyId: string,
971
- publicKey: PublicKeyJwk,
972
- derivationScheme: typeof KeyDerivationScheme.ProtocolPath | typeof KeyDerivationScheme.ProtocolContext,
973
- ): Omit<EncryptionInput, 'authenticationTag'> {
974
- return {
975
- initializationVector : iv,
976
- key : dek,
977
- keyEncryptionInputs : [{
978
- publicKeyId,
979
- publicKey,
980
- derivationScheme,
981
- }],
982
- };
877
+ public async getEncryptionKeyDeriver(
878
+ didUri: string
879
+ ): Promise<EncryptionKeyDeriver> {
880
+ return getEncryptionKeyDeriverFn(this.agent, didUri);
983
881
  }
984
882
 
985
883
  /**
986
- * Encrypts plaintext bytes with AEAD (AES-256-GCM by default) and computes
987
- * the CID of the resulting ciphertext. Returns everything needed to attach
988
- * the encrypted data to a DWN message, including the authentication tag.
884
+ * Resolves the keyAgreement verification method for the given DID and returns
885
+ * the key ID, key URI, and public key JWK.
886
+ *
887
+ * @param didUri - The DID URI to look up
989
888
  */
990
- private async encryptAndComputeCid(
991
- plaintextBytes: Uint8Array,
992
- dek: Uint8Array,
993
- iv: Uint8Array,
994
- ): Promise<{ encryptedBytes: Uint8Array; dataCid: string; dataSize: number; authenticationTag: Uint8Array }> {
995
- const { ciphertextStream, tag: authenticationTag } = await Encryption.aeadEncryptStream(
996
- ContentEncryptionAlgorithm.A256GCM, dek, iv, DataStream.fromBytes(plaintextBytes),
997
- );
998
- const encryptedBytes = await DataStream.toBytes(ciphertextStream);
999
- const cidStream = DataStream.fromBytes(encryptedBytes);
1000
- const dataCid = await Cid.computeDagPbCidFromStream(cidStream);
1001
- return { encryptedBytes, dataCid, dataSize: encryptedBytes.length, authenticationTag };
889
+ private async getEncryptionKeyInfo(
890
+ didUri: string
891
+ ): Promise<{ keyId: string; keyUri: KeyIdentifier; publicKeyJwk: PublicKeyJwk }> {
892
+ return getEncryptionKeyInfoFn(this.agent, didUri);
1002
893
  }
1003
894
 
1004
895
  /**
1005
- * Derives a ProtocolContext public key for a given DID and context ID,
1006
- * then returns a fully-formed EncryptionInput. Consolidates the repeated
1007
- * getEncryptionKeyInfo constructKeyDerivationPath derivePublicKey
1008
- * → build EncryptionInput sequence.
896
+ * Constructs a ProtocolPath KeyDecrypter for the given DID.
897
+ *
898
+ * @param didUri - The DID URI to build a decrypter for
1009
899
  */
1010
- private async deriveContextEncryptionInput(
1011
- didUri: string,
1012
- contextId: string,
1013
- dek: Uint8Array,
1014
- iv: Uint8Array,
1015
- ): Promise<{ encryptionInput: Omit<EncryptionInput, 'authenticationTag'>; keyId: string; keyUri: KeyIdentifier; contextDerivationPath: string[] }> {
1016
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(didUri);
1017
- const contextDerivationPath =
1018
- Records.constructKeyDerivationPathUsingProtocolContextScheme(contextId);
1019
- const contextPublicKey = await this.agent.keyManager.derivePublicKey({
1020
- keyUri,
1021
- derivationPath: contextDerivationPath,
1022
- });
1023
-
1024
- const encryptionInput = this.buildEncryptionInput(
1025
- dek, iv, keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext,
1026
- );
1027
-
1028
- return { encryptionInput, keyId, keyUri, contextDerivationPath };
900
+ private async getKeyDecrypter(
901
+ didUri: string
902
+ ): Promise<KeyDecrypter> {
903
+ return getKeyDecrypterFn(this.agent, didUri);
1029
904
  }
1030
905
 
1031
906
  /**
1032
- * Builds a KMS-backed JWE key unwrap callback. Used for both ProtocolPath
1033
- * and ProtocolContext decryption where the KMS holds the root private key.
907
+ * Checks if a protocol path represents a multi-party context.
908
+ *
909
+ * @param protocolDefinition - The full protocol definition
910
+ * @param rootProtocolPath - The root protocol path to check
1034
911
  */
1035
- private buildKmsDecryptCallback(
1036
- keyId: string,
1037
- keyUri: KeyIdentifier,
1038
- derivationScheme: typeof KeyDerivationScheme.ProtocolPath | typeof KeyDerivationScheme.ProtocolContext,
1039
- ): KeyDecrypter {
1040
- const keyManager = this.agent.keyManager;
1041
- return {
1042
- rootKeyId : keyId,
1043
- derivationScheme,
1044
- decrypt : async (fullDerivationPath, jwePayload): Promise<Uint8Array> => {
1045
- return keyManager.jweKeyUnwrap({
1046
- keyUri,
1047
- derivationPath : fullDerivationPath,
1048
- encryptedKey : jwePayload.encryptedKey,
1049
- ephemeralPublicKey : jwePayload.ephemeralPublicKey,
1050
- });
1051
- },
1052
- };
912
+ private isMultiPartyContext(
913
+ protocolDefinition: ProtocolDefinition,
914
+ rootProtocolPath: string,
915
+ ): boolean {
916
+ return isMultiPartyContextFn(protocolDefinition, rootProtocolPath);
1053
917
  }
1054
918
 
1055
919
  /**
1056
- * Constructs an EncryptionKeyDeriver callback for the SDK.
1057
- * The SDK calls derivePublicKey(path), the KMS performs HKDF + public key
1058
- * computation internally. The private key never leaves the KMS.
920
+ * Checks if any `$actions` rule in the protocol grants read access
921
+ * via `who: '<actorType>'` and `of: '<path>'`.
1059
922
  *
1060
- * Analogous to getSigner() for signing operations.
1061
- *
1062
- * @param didUri - The DID URI to create the key deriver for
1063
- * @returns An EncryptionKeyDeriver callback object
923
+ * @param actorType - The actor type to check ('author', 'recipient', or undefined for any)
924
+ * @param ofPath - The protocol path to check
925
+ * @param protocolDefinition - The protocol definition
1064
926
  */
1065
- public async getEncryptionKeyDeriver(
1066
- didUri: string
1067
- ): Promise<EncryptionKeyDeriver> {
1068
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(didUri);
1069
- const keyManager = this.agent.keyManager;
1070
-
1071
- return {
1072
- rootKeyId : keyId,
1073
- derivationScheme : KeyDerivationScheme.ProtocolPath,
1074
- derivePublicKey : async (fullDerivationPath: string[]): Promise<PublicKeyJwk> => {
1075
- return keyManager.derivePublicKey({
1076
- keyUri,
1077
- derivationPath: fullDerivationPath,
1078
- });
1079
- },
1080
- };
927
+ private hasRelationalReadAccess(
928
+ actorType: 'author' | 'recipient' | undefined,
929
+ ofPath: string,
930
+ protocolDefinition: ProtocolDefinition,
931
+ ): boolean {
932
+ return hasRelationalReadAccessFn(actorType, ofPath, protocolDefinition);
1081
933
  }
1082
934
 
1083
935
  /**
1084
- * Constructs a KeyDecrypter callback for the SDK.
1085
- * The SDK calls decrypt(path, eciesParams), the KMS performs HKDF + ECIES
1086
- * decryption internally. The private key never leaves the KMS.
1087
- *
1088
- * Analogous to getSigner() for signing operations.
936
+ * Analyses a record write to determine which DIDs need context key delivery.
1089
937
  *
1090
- * @param didUri - The DID URI to create the key decrypter for
1091
- * @returns A KeyDecrypter callback object
938
+ * @param params - Parameters for participant detection
939
+ * @returns Set of DIDs that need context key delivery
1092
940
  */
1093
- private async getKeyDecrypter(
1094
- didUri: string
1095
- ): Promise<KeyDecrypter> {
1096
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(didUri);
1097
- return this.buildKmsDecryptCallback(keyId, keyUri, KeyDerivationScheme.ProtocolPath);
941
+ public detectNewParticipants(params: {
942
+ protocolDefinition: ProtocolDefinition;
943
+ protocolPath: string;
944
+ recipient?: string;
945
+ tenantDid: string;
946
+ authorDid?: string;
947
+ }): Set<string> {
948
+ return detectNewParticipantsFn(params);
1098
949
  }
1099
950
 
1100
951
  /**
1101
- * Fetches a protocol definition from the local DWN, with caching.
952
+ * Fetches a protocol definition from the local DWN, with caching.
1102
953
  * Returns undefined if the protocol is not installed.
1103
954
  *
1104
955
  * @param tenantDid - The tenant DID to query
@@ -1136,175 +987,6 @@ export class AgentDwnApi {
1136
987
  return definition;
1137
988
  }
1138
989
 
1139
- /**
1140
- * Checks if a protocol path represents a multi-party context. Returns true
1141
- * if the root path's subtree contains:
1142
- * (a) any `$role: true` descendants, OR
1143
- * (b) any relational `who`/`of` `$actions` rules that grant `read` access
1144
- * (indicating external authors or recipients need context keys).
1145
- *
1146
- * This generalises the earlier `protocolPathHasRoles()` to cover protocols
1147
- * that use relational access without explicit role definitions.
1148
- */
1149
- private isMultiPartyContext(
1150
- protocolDefinition: ProtocolDefinition,
1151
- rootProtocolPath: string,
1152
- ): boolean {
1153
- const segments = rootProtocolPath.split('/');
1154
- let ruleSet: ProtocolRuleSet | undefined =
1155
- protocolDefinition.structure as unknown as ProtocolRuleSet;
1156
- for (const segment of segments) {
1157
- ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
1158
- if (!ruleSet) { return false; }
1159
- }
1160
-
1161
- // (a) Check for $role descendants in the subtree
1162
- function hasRoleRecursive(rs: ProtocolRuleSet): boolean {
1163
- for (const key in rs) {
1164
- if (!key.startsWith('$')) {
1165
- const child = rs[key] as ProtocolRuleSet;
1166
- if (child.$role === true) { return true; }
1167
- if (hasRoleRecursive(child)) { return true; }
1168
- }
1169
- }
1170
- return false;
1171
- }
1172
-
1173
- if (hasRoleRecursive(ruleSet)) {
1174
- return true;
1175
- }
1176
-
1177
- // (b) Check for relational who/of read rules anywhere in the protocol
1178
- // that reference a path within this subtree. A rule like
1179
- // { who: 'recipient', of: 'email', can: ['read'] } on any record
1180
- // type means the email recipient needs a context key.
1181
- return this.hasRelationalReadAccess(
1182
- undefined, rootProtocolPath, protocolDefinition,
1183
- );
1184
- }
1185
-
1186
- /**
1187
- * Checks whether any relational `who`/`of` rule in the protocol grants
1188
- * `read` access for a given actor type and ancestor path.
1189
- *
1190
- * Walks the *entire* protocol structure looking for any `$actions` rule that:
1191
- * - Has `who` equal to `actorType` ('recipient' or 'author'), or any actor
1192
- * type if `actorType` is `undefined`
1193
- * - Has `of` equal to `ofPath`
1194
- * - Has `can` including 'read'
1195
- *
1196
- * The search covers all record types in the protocol, since a relational
1197
- * rule can appear at any level (e.g. `{ who: 'recipient', of: 'thread',
1198
- * can: ['read'] }` might be defined on `thread/message`).
1199
- *
1200
- * @param actorType - 'author' | 'recipient', or undefined for any
1201
- * @param ofPath - The protocol path to check (e.g. 'thread', 'email')
1202
- * @param protocolDefinition - The full protocol definition
1203
- * @returns true if a matching relational read rule exists
1204
- */
1205
- private hasRelationalReadAccess(
1206
- actorType: 'author' | 'recipient' | undefined,
1207
- ofPath: string,
1208
- protocolDefinition: ProtocolDefinition,
1209
- ): boolean {
1210
- const structure = protocolDefinition.structure as unknown as ProtocolRuleSet;
1211
-
1212
- function walkRuleSet(rs: ProtocolRuleSet): boolean {
1213
- // Check $actions on this node
1214
- if (rs.$actions) {
1215
- for (const rule of rs.$actions) {
1216
- if (
1217
- rule.who &&
1218
- rule.who !== 'anyone' &&
1219
- (actorType === undefined || rule.who === actorType) &&
1220
- rule.of === ofPath &&
1221
- rule.can?.includes('read')
1222
- ) {
1223
- return true;
1224
- }
1225
- }
1226
- }
1227
-
1228
- // Recurse into child record types
1229
- for (const key in rs) {
1230
- if (!key.startsWith('$')) {
1231
- if (walkRuleSet(rs[key] as ProtocolRuleSet)) {
1232
- return true;
1233
- }
1234
- }
1235
- }
1236
- return false;
1237
- }
1238
-
1239
- return walkRuleSet(structure);
1240
- }
1241
-
1242
- /**
1243
- * Analyses a record write to determine which DIDs need context key delivery.
1244
- *
1245
- * Returns a set of participant DIDs that should receive `contextKey` records.
1246
- * The DWN owner (tenantDid) is always excluded — they have ProtocolPath access.
1247
- *
1248
- * Cases handled:
1249
- * 1. `$role` record with a recipient → recipient is a participant
1250
- * 2. Record has a recipient and a relational read rule grants access
1251
- * via `{ who: 'recipient', of: '<path>', can: ['read'] }`
1252
- * 3. Record is authored by an external party → if `{ who: 'author', of:
1253
- * '<path>', can: ['read'] }` rules grant read access, the author needs
1254
- * a context key.
1255
- *
1256
- * @param params.protocolDefinition - The installed protocol definition
1257
- * @param params.protocolPath - The written record's protocol path
1258
- * @param params.recipient - Recipient DID from the record, if any
1259
- * @param params.tenantDid - The DWN owner's DID (excluded from results)
1260
- * @param params.authorDid - Author DID if externally authored, undefined otherwise
1261
- * @returns Set of DIDs that need context key delivery
1262
- */
1263
- detectNewParticipants({ protocolDefinition, protocolPath, recipient, tenantDid, authorDid }: {
1264
- protocolDefinition: ProtocolDefinition;
1265
- protocolPath: string;
1266
- recipient?: string;
1267
- tenantDid: string;
1268
- authorDid?: string;
1269
- }): Set<string> {
1270
- const participants = new Set<string>();
1271
-
1272
- // Navigate to the rule set at the given protocol path
1273
- const pathSegments = protocolPath.split('/');
1274
- let ruleSet: ProtocolRuleSet | undefined =
1275
- protocolDefinition.structure as unknown as ProtocolRuleSet;
1276
- for (const segment of pathSegments) {
1277
- ruleSet = ruleSet[segment] as ProtocolRuleSet | undefined;
1278
- if (!ruleSet) { return participants; }
1279
- }
1280
-
1281
- // Case 1: $role record → recipient is a participant
1282
- if (ruleSet.$role === true && recipient) {
1283
- participants.add(recipient);
1284
- }
1285
-
1286
- // Case 2: Record has a recipient → check if relational read rules exist
1287
- if (recipient && recipient !== tenantDid) {
1288
- if (this.hasRelationalReadAccess('recipient', protocolPath, protocolDefinition)) {
1289
- participants.add(recipient);
1290
- }
1291
- }
1292
-
1293
- // Case 3: External author → check if author-based relational read rules exist.
1294
- // If `{ who: 'author', of: '<path>', can: ['read'] }` is defined anywhere
1295
- // in the protocol, the external author needs a context key to decrypt.
1296
- if (authorDid && authorDid !== tenantDid) {
1297
- if (this.hasRelationalReadAccess('author', protocolPath, protocolDefinition)) {
1298
- participants.add(authorDid);
1299
- }
1300
- }
1301
-
1302
- // Remove the DWN owner — they always have ProtocolPath access
1303
- participants.delete(tenantDid);
1304
-
1305
- return participants;
1306
- }
1307
-
1308
990
  /**
1309
991
  * Fetches a protocol definition from a remote DWN.
1310
992
  * Uses an unsigned ProtocolsQuery (public protocols can be queried anonymously).
@@ -1403,333 +1085,21 @@ export class AgentDwnApi {
1403
1085
  return undefined;
1404
1086
  }
1405
1087
 
1406
- /**
1407
- * Reactively upgrades an externally-authored root record that has only
1408
- * ProtocolPath encryption by appending a ProtocolContext recipient entry.
1409
- *
1410
- * After the upgrade, both the owner (ProtocolPath) and context key holders —
1411
- * including the external author (ProtocolContext) — can decrypt the record.
1412
- *
1413
- * Steps:
1414
- * 1. Decrypt the DEK using the owner's ProtocolPath-derived private key
1415
- * 2. Derive the context public key from the owner's #enc key
1416
- * 3. ECIES-encrypt the same DEK to the context public key
1417
- * 4. Append the ProtocolContext recipient entry (using PR 0b append mode)
1418
- * 5. Re-sign the record as owner
1419
- *
1420
- * The author's signature payload includes an `encryptionCid` that becomes
1421
- * stale after step 4. The SDK's `validateIntegrity()` skips the encryptionCid
1422
- * check on the author's signature when an ownerSignature is present (step 5),
1423
- * since the owner vouches for the updated encryption property.
1424
- *
1425
- * NOTE: An alternative design would deliver the DEK out-of-band via the
1426
- * key-delivery protocol (as a field on the contextKey record) instead of
1427
- * mutating the record's encryption property. That avoids the stale
1428
- * encryptionCid concern entirely but adds complexity to the read path and
1429
- * the contextKey schema. We chose the in-record approach because it keeps
1430
- * records self-contained and the read/decrypt path unchanged.
1431
- *
1432
- * @param tenantDid - The DWN owner's DID
1433
- * @param recordsWrite - The RecordsWrite message to upgrade
1434
- */
1435
- private async upgradeExternalRootRecord(
1436
- tenantDid: string,
1437
- recordsWrite: RecordsWriteMessage,
1438
- ): Promise<void> {
1439
- const { encryption } = recordsWrite;
1440
- if (!encryption) { return; }
1441
-
1442
- // Verify: has ProtocolPath but NOT ProtocolContext
1443
- const hasProtocolPath = encryption.recipients.some(
1444
- (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolPath
1445
- );
1446
- const hasProtocolContext = encryption.recipients.some(
1447
- (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext
1448
- );
1449
- if (!hasProtocolPath || hasProtocolContext) { return; }
1450
-
1451
- // 1. Decrypt the DEK using the owner's ProtocolPath key
1452
- const keyDecrypter = await this.getKeyDecrypter(tenantDid);
1453
-
1454
- // Find the ProtocolPath recipient entry
1455
- const pathRecipient = encryption.recipients.find(
1456
- (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolPath
1457
- )!;
1458
-
1459
- const fullDerivationPath = Records.constructKeyDerivationPathUsingProtocolPathScheme(
1460
- recordsWrite.descriptor,
1461
- );
1462
-
1463
- const dataEncryptionKey = await keyDecrypter.decrypt(
1464
- fullDerivationPath,
1465
- {
1466
- encryptedKey : Encoder.base64UrlToBytes(pathRecipient.encrypted_key),
1467
- ephemeralPublicKey : pathRecipient.header.epk,
1468
- },
1469
- );
1470
-
1471
- // 2. Derive the context public key — contextId = recordId for root records
1472
- const contextId = recordsWrite.recordId;
1473
- const encryptionIV = Encoder.base64UrlToBytes(encryption.iv);
1474
-
1475
- // 3 & 4. Append the ProtocolContext recipient entry using append mode.
1476
- // Append mode preserves the author's identity and authorization so that
1477
- // signAsOwner() can be called in step 5.
1478
- const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } =
1479
- await this.deriveContextEncryptionInput(tenantDid, contextId, dataEncryptionKey, encryptionIV);
1480
-
1481
- // Set the authentication tag from the existing JWE encryption property
1482
- const fullContextInput = { ...contextEncryptionInput, authenticationTag: Encoder.base64UrlToBytes(encryption.tag) };
1483
-
1484
- // Parse the message to get a RecordsWrite instance we can mutate
1485
- const recordsWriteInstance = await dwnMessageConstructors[DwnInterface.RecordsWrite].parse(
1486
- recordsWrite,
1487
- ) as unknown as RecordsWrite;
1488
-
1489
- await recordsWriteInstance.encryptSymmetricEncryptionKey(
1490
- fullContextInput as EncryptionInput,
1491
- { append: true },
1492
- );
1493
-
1494
- // 5. Re-sign as owner — the author's signature is preserved but its
1495
- // encryptionCid is now stale; the owner's signature vouches for the
1496
- // updated encryption property.
1497
- const signer = await this.getSigner(tenantDid);
1498
- await recordsWriteInstance.signAsOwner(signer);
1499
-
1500
- // Store the upgraded message directly via the message store, bypassing
1501
- // the handler's conflict resolution which doesn't support same-timestamp
1502
- // owner-augmented replacements. The data is unchanged — only the encryption
1503
- // metadata and authorization are updated.
1504
- //
1505
- // We must also update the state index and event stream to keep sync and
1506
- // real-time subscribers consistent — without this, the upgraded record
1507
- // would never propagate to remote DWNs or notify subscribers.
1508
- const { messageStore, stateIndex, eventStream } = this._dwn.storage;
1509
-
1510
- // Validate the upgrade only changed encryption and authorization fields.
1511
- // The descriptor, recordId, contextId, and data must remain identical.
1512
- // Note: parse() may produce a new descriptor object, so we compare by value.
1513
- const upgradedMessage = recordsWriteInstance.message as RecordsQueryReplyEntry;
1514
- if (JSON.stringify(upgradedMessage.descriptor) !== JSON.stringify(recordsWrite.descriptor)) {
1515
- throw new Error('AgentDwnApi: upgradeExternalRootRecord() must not modify the descriptor.');
1516
- }
1517
- if (upgradedMessage.recordId !== recordsWrite.recordId) {
1518
- throw new Error('AgentDwnApi: upgradeExternalRootRecord() must not modify the recordId.');
1519
- }
1520
-
1521
- // Fetch the stored original (which carries encodedData for small payloads)
1522
- const originalCid = await Message.getCid(recordsWrite);
1523
- const storedOriginal = await messageStore.get(tenantDid, originalCid) as RecordsQueryReplyEntry | undefined;
1524
-
1525
- // Build indexes for the upgraded message (mark as latest base state)
1526
- const isLatestBaseState = true;
1527
- const upgradedIndexes = await recordsWriteInstance.constructIndexes(isLatestBaseState);
1528
-
1529
- // Carry over the encoded data from the stored original (the handler
1530
- // base64url-encodes small payloads into encodedData during processMessage)
1531
- if (storedOriginal?.encodedData) {
1532
- upgradedMessage.encodedData = storedOriginal.encodedData;
1533
- }
1534
-
1535
- // Use put-before-delete ordering: if a crash occurs after the put but
1536
- // before the delete, we end up with a duplicate (recoverable via the
1537
- // isLatestBaseState index) rather than data loss (unrecoverable).
1538
- const upgradedCid = await Message.getCid(upgradedMessage);
1539
- await messageStore.put(tenantDid, upgradedMessage, upgradedIndexes);
1540
- await stateIndex.insert(tenantDid, upgradedCid, upgradedIndexes);
1541
-
1542
- // Now remove the original message and its state index entry.
1543
- await messageStore.delete(tenantDid, originalCid);
1544
- await stateIndex.delete(tenantDid, [originalCid]);
1545
-
1546
- // Notify real-time subscribers (mirrors handler behavior)
1547
- if (eventStream !== undefined) {
1548
- eventStream.emit(tenantDid, { message: upgradedMessage }, upgradedIndexes);
1549
- }
1550
-
1551
- // Cache context key info for subsequent writes in this context
1552
- this._contextKeyCache.set(contextId, { keyId, keyUri, contextDerivationPath });
1553
- }
1554
-
1555
- /**
1556
- * Resolves the appropriate KeyDecrypter for a record's encryption scheme.
1557
- * Handles both single-party (ProtocolPath) and multi-party (ProtocolContext).
1558
- *
1559
- * For ProtocolContext records:
1560
- * - Context creator: derives key directly from KMS
1561
- * - Participant: fetches contextKey via key-delivery protocol, caches it
1562
- */
1563
- private async resolveKeyDecrypter(
1564
- authorDid: string,
1565
- recordsWrite: RecordsWriteMessage,
1566
- targetDid?: string,
1567
- ): Promise<KeyDecrypter> {
1568
- const { encryption } = recordsWrite;
1569
-
1570
- // Check if the record uses context-derived encryption
1571
- const hasContextKey = encryption?.recipients.some(
1572
- (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext
1573
- );
1574
-
1575
- if (!hasContextKey || !recordsWrite.contextId) {
1576
- // Single-party protocol-path encryption
1577
- return this.getKeyDecrypter(authorDid);
1578
- }
1579
-
1580
- // --- Multi-party context encryption ---
1581
- const contextKeyEntry = encryption!.recipients.find(
1582
- (r: { header: { derivationScheme: string } }) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext
1583
- )!;
1584
-
1585
- const rootContextId = recordsWrite.contextId.split('/')[0];
1586
-
1587
- // Case 1: I am the context creator — rootKeyId matches my encryption key
1588
- const { keyId, keyUri } = await this.getEncryptionKeyInfo(authorDid);
1589
- if (contextKeyEntry.header.kid === keyId) {
1590
- return this.buildKmsDecryptCallback(keyId, keyUri, KeyDerivationScheme.ProtocolContext);
1591
- }
1592
-
1593
- // Case 2: I am a participant — fetch my context key from the key-delivery protocol
1594
- const cacheKey = `ctx~${authorDid}~${rootContextId}`;
1595
- const cached = this._contextDerivedKeyCache.get(cacheKey);
1596
- if (cached) {
1597
- return this.buildContextKeyDecrypter(cached);
1598
- }
1599
-
1600
- // Fetch context key via the key-delivery protocol — local first, then remote
1601
- const protocol = recordsWrite.descriptor.protocol!;
1602
-
1603
- // Try local: I may be the DWN owner with a contextKey addressed to myself
1604
- let contextDerivedPrivateKey = await this.fetchContextKeyRecord({
1605
- ownerDid : authorDid,
1606
- requesterDid : authorDid,
1607
- sourceProtocol : protocol,
1608
- sourceContextId : rootContextId,
1609
- });
1610
-
1611
- // Try remote: query the DWN owner's DWN for my contextKey record.
1612
- // For cross-DWN records, targetDid is the DWN owner (e.g., Alice) where the
1613
- // contextKey was written. For same-DWN records, fall back to the record's
1614
- // authorization signer.
1615
- if (!contextDerivedPrivateKey) {
1616
- const contextOwnerDid = targetDid ?? Jws.getSignerDid(
1617
- recordsWrite.authorization.signature.signatures[0]
1618
- );
1619
- contextDerivedPrivateKey = await this.fetchContextKeyRecord({
1620
- ownerDid : contextOwnerDid,
1621
- requesterDid : authorDid,
1622
- sourceProtocol : protocol,
1623
- sourceContextId : rootContextId,
1624
- });
1625
- }
1626
-
1627
- if (!contextDerivedPrivateKey) {
1628
- throw new Error(
1629
- `AgentDwnApi: Failed to decrypt record '${recordsWrite.recordId}'. ` +
1630
- `Record uses context-derived encryption but no contextKey record ` +
1631
- `could be found via the key-delivery protocol.`
1632
- );
1633
- }
1634
-
1635
- this._contextDerivedKeyCache.set(cacheKey, contextDerivedPrivateKey);
1636
- return this.buildContextKeyDecrypter(contextDerivedPrivateKey);
1637
- }
1638
-
1639
-
1640
- /**
1641
- * Builds a KeyDecrypter from a context-derived private key.
1642
- * Uses the raw key directly (since it was shared with us via the key-delivery protocol).
1643
- */
1644
- private buildContextKeyDecrypter(
1645
- contextKey: DerivedPrivateJwk,
1646
- ): KeyDecrypter {
1647
- return {
1648
- rootKeyId : contextKey.rootKeyId,
1649
- derivationScheme : contextKey.derivationScheme,
1650
- decrypt : async (fullDerivationPath, jwePayload): Promise<Uint8Array> => {
1651
- const leafPrivateKeyBytes = await Records.derivePrivateKey(
1652
- contextKey, fullDerivationPath,
1653
- );
1654
- const leafPrivateKeyJwk = await X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
1655
- return Encryption.ecdhEsUnwrapKey(leafPrivateKeyJwk, jwePayload.ephemeralPublicKey, jwePayload.encryptedKey);
1656
- },
1657
- };
1658
- }
1659
-
1660
1088
  /**
1661
1089
  * Post-processes a DWN reply, auto-decrypting data if encryption is enabled.
1662
- * Delegates to the SDK's Records.decrypt() with the appropriate KeyDecrypter —
1663
- * resolveKeyDecrypter() selects between ProtocolPath and ProtocolContext schemes.
1090
+ * Delegates to the standalone function in `dwn-encryption.ts`.
1664
1091
  */
1665
1092
  private async maybeDecryptReply<T extends DwnInterface>(
1666
1093
  request: ProcessDwnRequest<T> | SendDwnRequest<T>,
1667
1094
  reply: DwnMessageReply[T],
1668
1095
  ): Promise<void> {
1669
- if (!('encryption' in request) || !request.encryption) {
1670
- return;
1671
- }
1672
-
1673
- // Auto-decrypt RecordsRead replies
1674
- if (isDwnRequest(request as ProcessDwnRequest<DwnInterface>, DwnInterface.RecordsRead)) {
1675
- const readReply = reply as RecordsReadReply;
1676
- if (readReply.status.code === 200
1677
- && readReply.entry?.recordsWrite?.encryption
1678
- && readReply.entry?.data) {
1679
- const keyDecrypter = await this.resolveKeyDecrypter(
1680
- request.author, readReply.entry.recordsWrite, request.target,
1681
- );
1682
-
1683
- try {
1684
- readReply.entry.data = await Records.decrypt(
1685
- readReply.entry.recordsWrite,
1686
- keyDecrypter,
1687
- readReply.entry.data,
1688
- );
1689
- } catch (error: any) {
1690
- throw new Error(
1691
- `AgentDwnApi: Failed to decrypt record ` +
1692
- `'${readReply.entry.recordsWrite.recordId}'. ` +
1693
- `Original error: ${error.message}`
1694
- );
1695
- }
1696
- }
1697
- }
1698
-
1699
- // Auto-decrypt RecordsQuery replies (small records inline as encodedData)
1700
- if (isDwnRequest(request as ProcessDwnRequest<DwnInterface>, DwnInterface.RecordsQuery)) {
1701
- const queryReply = reply as RecordsQueryReply;
1702
- if (queryReply.status.code === 200 && queryReply.entries) {
1703
- for (const entry of queryReply.entries) {
1704
- if (entry.encryption && entry.encodedData) {
1705
- const keyDecrypter = await this.resolveKeyDecrypter(
1706
- request.author, entry as RecordsWriteMessage, request.target,
1707
- );
1708
-
1709
- try {
1710
- const cipherBytes = Encoder.base64UrlToBytes(entry.encodedData);
1711
- const cipherStream = DataStream.fromBytes(cipherBytes);
1712
- const plainStream = await Records.decrypt(
1713
- entry as RecordsWriteMessage, keyDecrypter, cipherStream,
1714
- );
1715
- const plainBytes = await DataStream.toBytes(plainStream);
1716
- entry.encodedData = Encoder.bytesToBase64Url(plainBytes);
1717
- } catch (error: any) {
1718
- throw new Error(
1719
- `AgentDwnApi: Failed to decrypt record ` +
1720
- `'${entry.recordId}'. Original error: ${error.message}`
1721
- );
1722
- }
1723
- }
1724
- }
1725
- }
1726
- }
1096
+ return maybeDecryptReplyFn(
1097
+ request, reply, this.agent,
1098
+ this._contextDerivedKeyCache,
1099
+ this.fetchContextKeyRecord.bind(this),
1100
+ );
1727
1101
  }
1728
1102
 
1729
- /**
1730
- * FURTHER REFACTORING NEEDED BELOW THIS LINE
1731
- */
1732
-
1733
1103
  private async getDwnMessage<T extends DwnInterface>({ author, messageCid }: {
1734
1104
  author: string;
1735
1105
  messageType: T;
@@ -1778,70 +1148,28 @@ export class AgentDwnApi {
1778
1148
 
1779
1149
  /**
1780
1150
  * Ensures the key delivery protocol is installed on the given tenant's DWN,
1781
- * with `$encryption` keys injected. Uses the same lazy initialization pattern
1782
- * as `DwnDataStore.initialize()`.
1151
+ * with `$encryption` keys injected.
1783
1152
  *
1784
1153
  * @param tenantDid - The DID of the DWN owner
1785
1154
  */
1786
- async ensureKeyDeliveryProtocol(tenantDid: string): Promise<void> {
1787
- if (this._keyDeliveryProtocolInstalledCache.get(tenantDid)) {
1788
- return;
1789
- }
1790
-
1791
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1792
- const existing = await this.getProtocolDefinition(tenantDid, protocolUri);
1793
-
1794
- if (!existing) {
1795
- // Derive and inject $encryption keys for each type path
1796
- const keyDeriver = await this.getEncryptionKeyDeriver(tenantDid);
1797
- const definitionWithKeys = await Protocols.deriveAndInjectPublicEncryptionKeys(
1798
- KeyDeliveryProtocolDefinition,
1799
- keyDeriver,
1800
- );
1801
-
1802
- const { reply: { status } } = await this.processRequest({
1803
- author : tenantDid,
1804
- target : tenantDid,
1805
- messageType : DwnInterface.ProtocolsConfigure,
1806
- messageParams : { definition: definitionWithKeys },
1807
- });
1808
-
1809
- if (status.code !== 202) {
1810
- throw new Error(`AgentDwnApi: Failed to install key delivery protocol: ${status.code} - ${status.detail}`);
1811
- }
1812
-
1813
- // Invalidate protocol definition cache so subsequent reads pick up the new definition
1814
- this._protocolDefinitionCache.delete(`${tenantDid}~${protocolUri}`);
1815
- }
1816
-
1817
- this._keyDeliveryProtocolInstalledCache.set(tenantDid, true);
1155
+ public async ensureKeyDeliveryProtocol(tenantDid: string): Promise<void> {
1156
+ return ensureKeyDeliveryProtocolFn(
1157
+ this.agent, tenantDid,
1158
+ this.processRequest.bind(this),
1159
+ this.getProtocolDefinition.bind(this),
1160
+ this._keyDeliveryProtocolInstalledCache,
1161
+ this._protocolDefinitionCache,
1162
+ );
1818
1163
  }
1819
1164
 
1820
1165
  /**
1821
1166
  * Writes a `contextKey` record to the owner's DWN, delivering an encrypted
1822
1167
  * context key to a participant.
1823
1168
  *
1824
- * The payload is encrypted to the **recipient's** ProtocolPath-derived public
1825
- * key on the key-delivery protocol, so only the recipient can decrypt it.
1826
- * The recipient's key is supplied via `recipientKeyDeliveryPublicKey` (which
1827
- * the external author attached as `authorKeyDeliveryPublicKey` on the
1828
- * original cross-DWN record).
1829
- *
1830
- * When `recipientKeyDeliveryPublicKey` is not provided (e.g. the owner is
1831
- * writing a contextKey for themselves), the record is encrypted to the
1832
- * owner's own ProtocolPath key using the generic `processRequest` path.
1833
- *
1834
- * @param params.tenantDid - The DWN owner's DID (who is delivering the key)
1835
- * @param params.recipientDid - The participant's DID (who will receive the key)
1836
- * @param params.contextKeyData - The `DerivedPrivateJwk` to deliver
1837
- * @param params.sourceProtocol - The URI of the source protocol (tag)
1838
- * @param params.sourceContextId - The root context ID (tag)
1839
- * @param params.recipientKeyDeliveryPublicKey - The recipient's ProtocolPath-
1840
- * derived public key for `key-delivery/contextKey`. When provided,
1841
- * the contextKey record is encrypted directly to this key.
1169
+ * @param params - The write parameters
1842
1170
  * @returns The recordId of the written contextKey record
1843
1171
  */
1844
- async writeContextKeyRecord({ tenantDid, recipientDid, contextKeyData, sourceProtocol, sourceContextId, recipientKeyDeliveryPublicKey }: {
1172
+ public async writeContextKeyRecord(params: {
1845
1173
  tenantDid: string;
1846
1174
  recipientDid: string;
1847
1175
  contextKeyData: DerivedPrivateJwk;
@@ -1849,84 +1177,12 @@ export class AgentDwnApi {
1849
1177
  sourceContextId: string;
1850
1178
  recipientKeyDeliveryPublicKey?: { rootKeyId: string; publicKeyJwk: PublicKeyJwk };
1851
1179
  }): Promise<string> {
1852
- // Ensure the key delivery protocol is installed on the owner's DWN
1853
- await this.ensureKeyDeliveryProtocol(tenantDid);
1854
-
1855
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1856
-
1857
- // Serialize the payload to JSON bytes
1858
- const plaintextBytes = new TextEncoder().encode(JSON.stringify(contextKeyData));
1859
-
1860
- // Common contextKey record parameters
1861
- const contextKeyParams = {
1862
- protocol : protocolUri,
1863
- protocolPath : 'contextKey',
1864
- dataFormat : 'application/json',
1865
- recipient : recipientDid,
1866
- tags : { protocol: sourceProtocol, contextId: sourceContextId },
1867
- };
1868
-
1869
- let message: any;
1870
- let status: { code: number; detail: string };
1871
-
1872
- if (recipientKeyDeliveryPublicKey) {
1873
- // --- Encrypt to the recipient's ProtocolPath key (cross-DWN delivery) ---
1874
- // Manually build encryption input targeting the recipient's key so the
1875
- // record is decryptable only by the recipient.
1876
- const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
1877
- const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(12));
1878
-
1879
- const { encryptedBytes, dataCid, dataSize, authenticationTag } =
1880
- await this.encryptAndComputeCid(plaintextBytes, dataEncryptionKey, dataEncryptionIV);
1881
-
1882
- const encryptionInput = {
1883
- ...this.buildEncryptionInput(
1884
- dataEncryptionKey, dataEncryptionIV,
1885
- recipientKeyDeliveryPublicKey.rootKeyId,
1886
- recipientKeyDeliveryPublicKey.publicKeyJwk,
1887
- KeyDerivationScheme.ProtocolPath,
1888
- ),
1889
- authenticationTag,
1890
- } as EncryptionInput;
1891
-
1892
- ({ message, reply: { status } } = await this.processRequest({
1893
- author : tenantDid,
1894
- target : tenantDid,
1895
- messageType : DwnInterface.RecordsWrite,
1896
- messageParams : { ...contextKeyParams, dataCid, dataSize, encryptionInput },
1897
- dataStream : new Blob([encryptedBytes]),
1898
- }));
1899
- } else {
1900
- // --- Fallback: encrypt to the owner's key (local self-delivery) ---
1901
- // When no recipient key is provided, use the generic processRequest
1902
- // encryption path which encrypts to the DWN owner's ProtocolPath key.
1903
- ({ message, reply: { status } } = await this.processRequest({
1904
- author : tenantDid,
1905
- target : tenantDid,
1906
- messageType : DwnInterface.RecordsWrite,
1907
- messageParams : contextKeyParams,
1908
- dataStream : new Blob([plaintextBytes], { type: 'application/json' }),
1909
- encryption : true,
1910
- }));
1911
- }
1912
-
1913
- if (!(message && status.code === 202)) {
1914
- throw new Error(
1915
- `AgentDwnApi: Failed to write contextKey record for ${recipientDid}: ${status.code} - ${status.detail}`,
1916
- );
1917
- }
1918
-
1919
- // Eagerly send the contextKey record to the tenant's remote DWN so that
1920
- // participants can fetch it immediately without waiting for sync.
1921
- // This is fire-and-forget — sync will guarantee eventual consistency.
1922
- this.eagerSendContextKeyRecord(tenantDid, message).catch((err: Error) => {
1923
- console.warn(
1924
- `AgentDwnApi: Eager send of contextKey record '${message.recordId}' ` +
1925
- `to remote DWN failed: ${err.message}. Sync will deliver it later.`
1926
- );
1927
- });
1928
-
1929
- return message.recordId;
1180
+ return writeContextKeyRecordFn(
1181
+ this.agent, params,
1182
+ this.processRequest.bind(this),
1183
+ this.ensureKeyDeliveryProtocol.bind(this),
1184
+ this.eagerSendContextKeyRecord.bind(this),
1185
+ );
1930
1186
  }
1931
1187
 
1932
1188
  /**
@@ -1937,142 +1193,31 @@ export class AgentDwnApi {
1937
1193
  tenantDid: string,
1938
1194
  contextKeyMessage: DwnMessage[DwnInterface.RecordsWrite],
1939
1195
  ): Promise<void> {
1940
- let dwnEndpointUrls: string[];
1941
- try {
1942
- dwnEndpointUrls = await getDwnServiceEndpointUrls(tenantDid, this.agent.did);
1943
- } catch {
1944
- // DID resolution or endpoint lookup failed — not fatal, sync will handle it.
1945
- return;
1946
- }
1947
-
1948
- if (dwnEndpointUrls.length === 0) {
1949
- return;
1950
- }
1951
-
1952
- // Read the full message (including data blob) from the local DWN
1953
- const { data } = await this.getDwnMessage({
1954
- author : tenantDid,
1955
- messageType : DwnInterface.RecordsWrite,
1956
- messageCid : await Message.getCid(contextKeyMessage),
1957
- });
1958
-
1959
- await this.sendDwnRpcRequest({
1960
- targetDid : tenantDid,
1961
- dwnEndpointUrls,
1962
- message : contextKeyMessage,
1963
- data,
1964
- });
1196
+ return eagerSendContextKeyRecordFn(
1197
+ this.agent, tenantDid, contextKeyMessage,
1198
+ this.getDwnMessage.bind(this),
1199
+ this.sendDwnRpcRequest.bind(this),
1200
+ );
1965
1201
  }
1966
1202
 
1967
1203
  /**
1968
1204
  * Fetches and decrypts a `contextKey` record from a DWN, returning the
1969
1205
  * `DerivedPrivateJwk` payload.
1970
1206
  *
1971
- * Supports both local reads (tenant queries own DWN) and remote reads
1972
- * (participant queries the context owner's DWN).
1973
- *
1974
- * @param params.ownerDid - The DWN owner's DID (where contextKey records live)
1975
- * @param params.requesterDid - The DID of the requester (used for signing and decryption)
1976
- * @param params.sourceProtocol - The URI of the source protocol (tag filter)
1977
- * @param params.sourceContextId - The root context ID (tag filter)
1207
+ * @param params - The fetch parameters
1978
1208
  * @returns The decrypted `DerivedPrivateJwk`, or `undefined` if no matching record found
1979
1209
  */
1980
- async fetchContextKeyRecord({ ownerDid, requesterDid, sourceProtocol, sourceContextId }: {
1210
+ public async fetchContextKeyRecord(params: {
1981
1211
  ownerDid: string;
1982
1212
  requesterDid: string;
1983
1213
  sourceProtocol: string;
1984
1214
  sourceContextId: string;
1985
1215
  }): Promise<DerivedPrivateJwk | undefined> {
1986
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1987
- const isLocal = ownerDid === requesterDid;
1988
-
1989
- // Shared query filter for both local and remote paths
1990
- const contextKeyFilter = {
1991
- protocol : protocolUri,
1992
- protocolPath : 'contextKey',
1993
- recipient : requesterDid,
1994
- tags : { protocol: sourceProtocol, contextId: sourceContextId },
1995
- };
1996
-
1997
- /** Parse decrypted bytes into a DerivedPrivateJwk. */
1998
- const parsePayload = (bytes: Uint8Array): DerivedPrivateJwk =>
1999
- JSON.parse(new TextDecoder().decode(bytes)) as DerivedPrivateJwk;
2000
-
2001
- if (isLocal) {
2002
- // Local query: owner queries their own DWN
2003
- const { reply } = await this.processRequest({
2004
- author : requesterDid,
2005
- target : ownerDid,
2006
- messageType : DwnInterface.RecordsQuery,
2007
- messageParams : { filter: contextKeyFilter },
2008
- });
2009
-
2010
- if (reply.status.code !== 200 || !reply.entries?.length) {
2011
- return undefined;
2012
- }
2013
-
2014
- // Read the full record to get the data (auto-decrypted by processRequest)
2015
- const recordId = reply.entries[0].recordId;
2016
- const { reply: readReply } = await this.processRequest({
2017
- author : requesterDid,
2018
- target : ownerDid,
2019
- messageType : DwnInterface.RecordsRead,
2020
- messageParams : { filter: { recordId } },
2021
- encryption : true,
2022
- });
2023
-
2024
- const readResult = readReply as RecordsReadReply;
2025
- if (!readResult.entry?.data) {
2026
- return undefined;
2027
- }
2028
-
2029
- return parsePayload(await DataStream.toBytes(readResult.entry.data));
2030
- } else {
2031
- // Remote query: participant queries the context owner's DWN
2032
- const signer = await this.getSigner(requesterDid);
2033
- const dwnEndpointUrls = await getDwnServiceEndpointUrls(ownerDid, this.agent.did);
2034
-
2035
- const recordsQuery = await dwnMessageConstructors[DwnInterface.RecordsQuery].create({
2036
- signer,
2037
- filter: contextKeyFilter,
2038
- });
2039
-
2040
- const queryReply = await this.sendDwnRpcRequest<DwnInterface.RecordsQuery>({
2041
- targetDid : ownerDid,
2042
- dwnEndpointUrls,
2043
- message : recordsQuery.message,
2044
- }) as RecordsQueryReply;
2045
-
2046
- if (queryReply.status.code !== 200 || !queryReply.entries?.length) {
2047
- return undefined;
2048
- }
2049
-
2050
- // Read the full record remotely
2051
- const recordId = queryReply.entries[0].recordId;
2052
- const recordsRead = await dwnMessageConstructors[DwnInterface.RecordsRead].create({
2053
- signer,
2054
- filter: { recordId },
2055
- });
2056
-
2057
- const readReply = await this.sendDwnRpcRequest<DwnInterface.RecordsRead>({
2058
- targetDid : ownerDid,
2059
- dwnEndpointUrls,
2060
- message : recordsRead.message,
2061
- }) as RecordsReadReply;
2062
-
2063
- if (!readReply.entry?.data || !readReply.entry?.recordsWrite) {
2064
- return undefined;
2065
- }
2066
-
2067
- // Decrypt the contextKey payload using the requester's key-delivery protocol path key
2068
- const keyDecrypter = await this.getKeyDecrypter(requesterDid);
2069
- const decryptedStream = await Records.decrypt(
2070
- readReply.entry.recordsWrite,
2071
- keyDecrypter,
2072
- readReply.entry.data as ReadableStream<Uint8Array>,
2073
- );
2074
-
2075
- return parsePayload(await DataStream.toBytes(decryptedStream));
2076
- }
1216
+ return fetchContextKeyRecordFn(
1217
+ this.agent, params,
1218
+ this.processRequest.bind(this),
1219
+ this.getSigner.bind(this),
1220
+ this.sendDwnRpcRequest.bind(this),
1221
+ );
2077
1222
  }
2078
- }
1223
+ }