@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
@@ -8,32 +8,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { TtlCache } from '@enbox/common';
11
- import { Cid, ContentEncryptionAlgorithm, DataStoreLevel, DataStream, Dwn, DwnInterfaceName, DwnMethodName, Encoder, Encryption, EventEmitterStream, Jws, KeyDerivationScheme, Message, MessageStoreLevel, Protocols, Records, ResumableTaskStoreLevel, StateIndexLevel } from '@enbox/dwn-sdk-js';
11
+ import { Cid, ContentEncryptionAlgorithm, DataStoreLevel, DataStream, Dwn, DwnMethodName, EventEmitterStream, Jws, KeyDerivationScheme, Message, MessageStoreLevel, Protocols, Records, ResumableTaskStoreLevel, StateIndexLevel, } from '@enbox/dwn-sdk-js';
12
12
  import { CryptoUtils, X25519 } from '@enbox/crypto';
13
13
  import { DidDht, DidJwk, DidResolverCacheLevel, UniversalResolver } from '@enbox/dids';
14
14
  import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
15
15
  import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
16
16
  import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
17
- export function isDwnRequest(dwnRequest, messageType) {
18
- return dwnRequest.messageType === messageType;
19
- }
20
- export function isDwnMessage(messageType, message) {
21
- const incomingMessageInterfaceName = message.descriptor.interface + message.descriptor.method;
22
- return incomingMessageInterfaceName === messageType;
23
- }
24
- export function isRecordsType(messageType) {
25
- return messageType === DwnInterface.RecordsDelete ||
26
- messageType === DwnInterface.RecordsQuery ||
27
- messageType === DwnInterface.RecordsRead ||
28
- messageType === DwnInterface.RecordsSubscribe ||
29
- messageType === DwnInterface.RecordsWrite;
30
- }
31
- export function isRecordPermissionScope(scope) {
32
- return scope.interface === DwnInterfaceName.Records;
33
- }
34
- export function isMessagesPermissionScope(scope) {
35
- return scope.interface === DwnInterfaceName.Messages;
36
- }
17
+ // Re-export type guards for backward compatibility
18
+ export { isDwnMessage, isDwnRequest, isMessagesPermissionScope, isRecordPermissionScope, isRecordsType } from './dwn-type-guards.js';
19
+ // Import type guards for internal use
20
+ import { isDwnRequest } from './dwn-type-guards.js';
21
+ // Import extracted encryption functions
22
+ import { buildEncryptionInput as buildEncryptionInputFn, deriveContextEncryptionInput as deriveContextEncryptionInputFn, encryptAndComputeCid as encryptAndComputeCidFn, getEncryptionKeyDeriver as getEncryptionKeyDeriverFn, getEncryptionKeyInfo as getEncryptionKeyInfoFn, getKeyDecrypter as getKeyDecrypterFn, ivLength as ivLengthFn, maybeDecryptReply as maybeDecryptReplyFn, } from './dwn-encryption.js';
23
+ // Import extracted protocol utilities
24
+ import { detectNewParticipants as detectNewParticipantsFn, hasRelationalReadAccess as hasRelationalReadAccessFn, isMultiPartyContext as isMultiPartyContextFn, } from './protocol-utils.js';
25
+ // Import extracted key delivery functions
26
+ import { eagerSendContextKeyRecord as eagerSendContextKeyRecordFn, ensureKeyDeliveryProtocol as ensureKeyDeliveryProtocolFn, fetchContextKeyRecord as fetchContextKeyRecordFn, writeContextKeyRecord as writeContextKeyRecordFn, } from './dwn-key-delivery.js';
27
+ // Import extracted record upgrade function
28
+ import { upgradeExternalRootRecord as upgradeExternalRootRecordFn } from './dwn-record-upgrade.js';
37
29
  export class AgentDwnApi {
38
30
  constructor({ agent, dwn }) {
39
31
  /**
@@ -154,17 +146,17 @@ export class AgentDwnApi {
154
146
  const isExternallyAuthored = authorDid !== request.target;
155
147
  const isRootRecord = !writeParams.parentContextId;
156
148
  const rootPathSegment = writeParams.protocolPath.split('/')[0];
157
- const isMultiParty = this.isMultiPartyContext(protocolDefinition, rootPathSegment);
149
+ const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
158
150
  if (isExternallyAuthored && isRootRecord && isMultiParty) {
159
151
  try {
160
- yield this.upgradeExternalRootRecord(request.target, recordsWriteMessage);
152
+ yield upgradeExternalRootRecordFn(this.agent, request.target, recordsWriteMessage, this._dwn, this.getSigner.bind(this), this._contextKeyCache);
161
153
  }
162
154
  catch (upgradeError) {
163
155
  console.warn(`AgentDwnApi: Reactive root-record upgrade failed for ` +
164
156
  `'${recordsWriteMessage.recordId}': ${upgradeError.message}`);
165
157
  }
166
158
  }
167
- const newParticipants = this.detectNewParticipants({
159
+ const newParticipants = detectNewParticipantsFn({
168
160
  protocolDefinition,
169
161
  protocolPath: writeParams.protocolPath,
170
162
  recipient: writeParams.recipient,
@@ -176,7 +168,7 @@ export class AgentDwnApi {
176
168
  const rootContextId = ((_a = recordsWriteMessage.contextId) === null || _a === void 0 ? void 0 : _a.split('/')[0])
177
169
  || recordsWriteMessage.contextId
178
170
  || recordsWriteMessage.recordId;
179
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(request.target);
171
+ const { keyId, keyUri } = yield getEncryptionKeyInfoFn(this.agent, request.target);
180
172
  const contextDerivationPath = [
181
173
  KeyDerivationScheme.ProtocolContext,
182
174
  rootContextId,
@@ -371,7 +363,7 @@ export class AgentDwnApi {
371
363
  // Auto-inject encryption keys into protocol definition (Component 5)
372
364
  if (isDwnRequest(request, DwnInterface.ProtocolsConfigure) && request.encryption && !rawMessage) {
373
365
  const messageParams = request.messageParams;
374
- const keyDeriver = yield this.getEncryptionKeyDeriver(request.author);
366
+ const keyDeriver = yield getEncryptionKeyDeriverFn(this.agent, request.author);
375
367
  // SDK walks the protocol structure and calls our callback for each path.
376
368
  // The KMS performs HKDF derivation + public key computation internally.
377
369
  messageParams.definition = yield Protocols.deriveAndInjectPublicEncryptionKeys(messageParams.definition, keyDeriver);
@@ -445,7 +437,7 @@ export class AgentDwnApi {
445
437
  const isKeyDeliveryProtocol = messageParams.protocol === KeyDeliveryProtocolDefinition.protocol;
446
438
  const isMultiPartyContext = isKeyDeliveryProtocol
447
439
  ? false
448
- : this.isMultiPartyContext(protocolDefinition, rootPathSegment);
440
+ : isMultiPartyContextFn(protocolDefinition, rootPathSegment);
449
441
  const isRootRecord = !messageParams.parentContextId;
450
442
  // 4. Get plaintext bytes (normalize from all supported input types)
451
443
  let plaintextBytes;
@@ -463,29 +455,24 @@ export class AgentDwnApi {
463
455
  else {
464
456
  throw new Error('AgentDwnApi: Data must be provided for encrypted records.');
465
457
  }
466
- // 5. Generate random DEK and IV
458
+ // 5. Generate random DEK and IV (IV size depends on content encryption algorithm)
459
+ const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256GCM;
467
460
  const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
468
- const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(12));
461
+ const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLengthFn(contentEncryptionAlgorithm)));
469
462
  // 6. Build partial EncryptionInput (authenticationTag added after AEAD encryption)
470
463
  let encryptionInput;
471
- const buildProtocolPathInput = () => this.buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, ruleSet.$encryption.rootKeyId, ruleSet.$encryption.publicKeyJwk, KeyDerivationScheme.ProtocolPath);
464
+ const buildProtocolPathInput = () => buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, ruleSet.$encryption.rootKeyId, ruleSet.$encryption.publicKeyJwk, KeyDerivationScheme.ProtocolPath);
472
465
  if (isCrossDwn && isMultiPartyContext && isRootRecord) {
473
- // --- Cross-DWN root record in multi-party context Target's ProtocolPath key ---
474
- // External authors cannot derive the target's context key (HKDF requires
475
- // the private key). Use the target's ProtocolPath public key from their
476
- // protocol definition. The target's agent will reactively upgrade the record
477
- // to include a ProtocolContext recipient entry.
466
+ // --- Cross-DWN root record in multi-party context -> Target's ProtocolPath key ---
478
467
  encryptionInput = buildProtocolPathInput();
479
468
  }
480
469
  else if (isCrossDwn && isMultiPartyContext && !isRootRecord) {
481
- // --- Cross-DWN non-root record in multi-party context derivedPublicKey ---
482
- // Read an existing ProtocolContext-encrypted record from the target's DWN
483
- // and extract the derivedPublicKey (the context public key).
470
+ // --- Cross-DWN non-root record in multi-party context -> derivedPublicKey ---
484
471
  const rootContextId = messageParams.parentContextId.split('/')[0]
485
472
  || messageParams.parentContextId;
486
473
  const derivedPublicKeyInfo = yield this.extractDerivedPublicKey(request.target, messageParams.protocol, rootContextId, request.author);
487
474
  if (derivedPublicKeyInfo) {
488
- encryptionInput = this.buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, derivedPublicKeyInfo.rootKeyId, derivedPublicKeyInfo.derivedPublicKey, KeyDerivationScheme.ProtocolContext);
475
+ encryptionInput = buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, derivedPublicKeyInfo.rootKeyId, derivedPublicKeyInfo.derivedPublicKey, KeyDerivationScheme.ProtocolContext);
489
476
  }
490
477
  else {
491
478
  // Fallback: no ProtocolContext-encrypted record exists yet (owner hasn't
@@ -494,16 +481,16 @@ export class AgentDwnApi {
494
481
  }
495
482
  }
496
483
  else if (isCrossDwn) {
497
- // --- Cross-DWN single-party Target's ProtocolPath key ---
484
+ // --- Cross-DWN single-party -> Target's ProtocolPath key ---
498
485
  encryptionInput = buildProtocolPathInput();
499
486
  }
500
487
  else if (isMultiPartyContext && !isRootRecord) {
501
- // --- Local non-root record in a multi-party context Context key ---
488
+ // --- Local non-root record in a multi-party context -> Context key ---
502
489
  const rootContextId = messageParams.parentContextId.split('/')[0]
503
490
  || messageParams.parentContextId;
504
491
  let contextKeyInfo = this._contextKeyCache.get(rootContextId);
505
492
  if (!contextKeyInfo) {
506
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(request.author);
493
+ const { keyId, keyUri } = yield getEncryptionKeyInfoFn(this.agent, request.author);
507
494
  const contextDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(rootContextId);
508
495
  contextKeyInfo = { keyId, keyUri, contextDerivationPath };
509
496
  this._contextKeyCache.set(rootContextId, contextKeyInfo);
@@ -512,20 +499,20 @@ export class AgentDwnApi {
512
499
  keyUri: contextKeyInfo.keyUri,
513
500
  derivationPath: contextKeyInfo.contextDerivationPath,
514
501
  });
515
- encryptionInput = this.buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, contextKeyInfo.keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext);
502
+ encryptionInput = buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, contextKeyInfo.keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext);
516
503
  }
517
504
  else if (isMultiPartyContext && isRootRecord) {
518
- // --- Local root record in multi-party context Deferred context encryption ---
505
+ // --- Local root record in multi-party context -> Deferred context encryption ---
519
506
  // contextId = recordId, which is only known after message creation.
520
507
  // Skip encryptionInput here; apply it after create() below.
521
508
  encryptionInput = undefined;
522
509
  }
523
510
  else {
524
- // --- Local single-party ProtocolPath key (existing logic) ---
511
+ // --- Local single-party -> ProtocolPath key (existing logic) ---
525
512
  encryptionInput = buildProtocolPathInput();
526
513
  }
527
- // 7. Encrypt data with AEAD (AES-256-GCM) and compute CID
528
- const { encryptedBytes, dataCid, dataSize, authenticationTag } = yield this.encryptAndComputeCid(plaintextBytes, dataEncryptionKey, dataEncryptionIV);
514
+ // 7. Encrypt data with AEAD and compute CID
515
+ const { encryptedBytes, dataCid, dataSize, authenticationTag } = yield encryptAndComputeCidFn(plaintextBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
529
516
  // 8. Replace plaintext with encrypted data
530
517
  messageParams.dataCid = dataCid;
531
518
  messageParams.dataSize = dataSize;
@@ -544,7 +531,7 @@ export class AgentDwnApi {
544
531
  // key-delivery ProtocolPath public key so the DWN owner can encrypt
545
532
  // context keys back to the author without querying the author's DWN.
546
533
  if (isCrossDwn && isMultiPartyContext) {
547
- const { keyId: authorKeyId, keyUri: authorKeyUri } = yield this.getEncryptionKeyInfo(request.author);
534
+ const { keyId: authorKeyId, keyUri: authorKeyUri } = yield getEncryptionKeyInfoFn(this.agent, request.author);
548
535
  const keyDeliveryDerivationPath = [
549
536
  KeyDerivationScheme.ProtocolPath,
550
537
  KeyDeliveryProtocolDefinition.protocol,
@@ -573,11 +560,11 @@ export class AgentDwnApi {
573
560
  dwnMessage = yield dwnMessageConstructor.create(Object.assign(Object.assign({}, request.messageParams), { signer }));
574
561
  // Deferred context encryption for root multi-party records (Component 9).
575
562
  // Now that the message exists, we know recordId = contextId.
576
- // Following the SDK two-pass pattern: encryptSymmetricEncryptionKey sign.
563
+ // Following the SDK two-pass pattern: encryptSymmetricEncryptionKey -> sign.
577
564
  if (deferredContextEncryption && isDwnRequest(request, DwnInterface.RecordsWrite)) {
578
565
  const recordsWriteInstance = dwnMessage;
579
566
  const contextId = recordsWriteInstance.message.recordId;
580
- const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } = yield this.deriveContextEncryptionInput(request.author, contextId, deferredContextEncryption.dataEncryptionKey, deferredContextEncryption.dataEncryptionIV);
567
+ const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } = yield deriveContextEncryptionInputFn(this.agent, request.author, contextId, deferredContextEncryption.dataEncryptionKey, deferredContextEncryption.dataEncryptionIV);
581
568
  const fullContextInput = Object.assign(Object.assign({}, contextEncryptionInput), { authenticationTag: deferredContextEncryption.authenticationTag });
582
569
  yield recordsWriteInstance.encryptSymmetricEncryptionKey(fullContextInput);
583
570
  yield recordsWriteInstance.sign({ signer });
@@ -652,173 +639,69 @@ export class AgentDwnApi {
652
639
  });
653
640
  }
654
641
  /**
655
- * Resolves the encryption key info for a given DID.
656
- * Looks up the keyAgreement verification method in the DID document,
657
- * then resolves the corresponding KMS key URI.
642
+ * Constructs an EncryptionKeyDeriver callback for the SDK.
643
+ * Delegates to the standalone function in `dwn-encryption.ts`.
658
644
  *
659
- * @param didUri - The DID URI to resolve encryption key info for
660
- * @returns keyId (fully qualified verification method ID), keyUri (KMS reference),
661
- * and publicKeyJwk. No private key material is returned.
662
- * @throws If the DID has no keyAgreement verification method or it's not X25519.
645
+ * @param didUri - The DID URI to create the key deriver for
646
+ * @returns An EncryptionKeyDeriver callback object
663
647
  */
664
- getEncryptionKeyInfo(didUri) {
648
+ getEncryptionKeyDeriver(didUri) {
665
649
  return __awaiter(this, void 0, void 0, function* () {
666
- var _a;
667
- // 1. Resolve the DID document
668
- const { didDocument, didResolutionMetadata } = yield this.agent.did.resolve(didUri);
669
- if (!didDocument) {
670
- throw new Error(`AgentDwnApi: Failed to resolve DID '${didUri}': ` +
671
- `${JSON.stringify(didResolutionMetadata)}`);
672
- }
673
- // 2. Find the keyAgreement verification method
674
- const keyAgreementRefs = didDocument.keyAgreement;
675
- if (!keyAgreementRefs || keyAgreementRefs.length === 0) {
676
- throw new Error(`AgentDwnApi: DID '${didUri}' does not have a keyAgreement ` +
677
- `verification method. Create the identity with an X25519 key ` +
678
- `with keyAgreement purpose to use protocol encryption.`);
679
- }
680
- // 3. Resolve the verification method (handle both inline and string refs)
681
- const keyAgreementRef = keyAgreementRefs[0];
682
- let verificationMethod;
683
- if (typeof keyAgreementRef === 'string') {
684
- const fragment = keyAgreementRef.includes('#')
685
- ? keyAgreementRef.split('#').pop()
686
- : keyAgreementRef;
687
- verificationMethod = (_a = didDocument.verificationMethod) === null || _a === void 0 ? void 0 : _a.find(vm => vm.id.endsWith(`#${fragment}`));
688
- }
689
- else {
690
- verificationMethod = keyAgreementRef;
691
- }
692
- if (!(verificationMethod === null || verificationMethod === void 0 ? void 0 : verificationMethod.publicKeyJwk)) {
693
- throw new Error(`AgentDwnApi: keyAgreement verification method for '${didUri}' ` +
694
- `does not contain a public key in JWK format.`);
695
- }
696
- // 4. Verify it's an X25519 key
697
- const publicKeyJwk = verificationMethod.publicKeyJwk;
698
- if (publicKeyJwk.crv !== 'X25519') {
699
- throw new Error(`AgentDwnApi: keyAgreement key for '${didUri}' uses curve ` +
700
- `'${publicKeyJwk.crv}', but DWN encryption requires 'X25519'.`);
701
- }
702
- // 5. Compute the KMS key URI (does NOT export the key)
703
- const keyUri = yield this.agent.keyManager.getKeyUri({ key: publicKeyJwk });
704
- return {
705
- keyId: verificationMethod.id,
706
- keyUri,
707
- publicKeyJwk: publicKeyJwk,
708
- };
650
+ return getEncryptionKeyDeriverFn(this.agent, didUri);
709
651
  });
710
652
  }
711
653
  /**
712
- * Builds a partial EncryptionInput object for a single key-encryption entry.
713
- * The `authenticationTag` is NOT set here the caller must set it after
714
- * AEAD encryption produces the tag.
715
- */
716
- buildEncryptionInput(dek, iv, publicKeyId, publicKey, derivationScheme) {
717
- return {
718
- initializationVector: iv,
719
- key: dek,
720
- keyEncryptionInputs: [{
721
- publicKeyId,
722
- publicKey,
723
- derivationScheme,
724
- }],
725
- };
726
- }
727
- /**
728
- * Encrypts plaintext bytes with AEAD (AES-256-GCM by default) and computes
729
- * the CID of the resulting ciphertext. Returns everything needed to attach
730
- * the encrypted data to a DWN message, including the authentication tag.
654
+ * Resolves the keyAgreement verification method for the given DID and returns
655
+ * the key ID, key URI, and public key JWK.
656
+ *
657
+ * @param didUri - The DID URI to look up
731
658
  */
732
- encryptAndComputeCid(plaintextBytes, dek, iv) {
659
+ getEncryptionKeyInfo(didUri) {
733
660
  return __awaiter(this, void 0, void 0, function* () {
734
- const { ciphertextStream, tag: authenticationTag } = yield Encryption.aeadEncryptStream(ContentEncryptionAlgorithm.A256GCM, dek, iv, DataStream.fromBytes(plaintextBytes));
735
- const encryptedBytes = yield DataStream.toBytes(ciphertextStream);
736
- const cidStream = DataStream.fromBytes(encryptedBytes);
737
- const dataCid = yield Cid.computeDagPbCidFromStream(cidStream);
738
- return { encryptedBytes, dataCid, dataSize: encryptedBytes.length, authenticationTag };
661
+ return getEncryptionKeyInfoFn(this.agent, didUri);
739
662
  });
740
663
  }
741
664
  /**
742
- * Derives a ProtocolContext public key for a given DID and context ID,
743
- * then returns a fully-formed EncryptionInput. Consolidates the repeated
744
- * getEncryptionKeyInfo constructKeyDerivationPath derivePublicKey
745
- * → build EncryptionInput sequence.
665
+ * Constructs a ProtocolPath KeyDecrypter for the given DID.
666
+ *
667
+ * @param didUri - The DID URI to build a decrypter for
746
668
  */
747
- deriveContextEncryptionInput(didUri, contextId, dek, iv) {
669
+ getKeyDecrypter(didUri) {
748
670
  return __awaiter(this, void 0, void 0, function* () {
749
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(didUri);
750
- const contextDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(contextId);
751
- const contextPublicKey = yield this.agent.keyManager.derivePublicKey({
752
- keyUri,
753
- derivationPath: contextDerivationPath,
754
- });
755
- const encryptionInput = this.buildEncryptionInput(dek, iv, keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext);
756
- return { encryptionInput, keyId, keyUri, contextDerivationPath };
671
+ return getKeyDecrypterFn(this.agent, didUri);
757
672
  });
758
673
  }
759
674
  /**
760
- * Builds a KMS-backed JWE key unwrap callback. Used for both ProtocolPath
761
- * and ProtocolContext decryption where the KMS holds the root private key.
675
+ * Checks if a protocol path represents a multi-party context.
676
+ *
677
+ * @param protocolDefinition - The full protocol definition
678
+ * @param rootProtocolPath - The root protocol path to check
762
679
  */
763
- buildKmsDecryptCallback(keyId, keyUri, derivationScheme) {
764
- const keyManager = this.agent.keyManager;
765
- return {
766
- rootKeyId: keyId,
767
- derivationScheme,
768
- decrypt: (fullDerivationPath, jwePayload) => __awaiter(this, void 0, void 0, function* () {
769
- return keyManager.jweKeyUnwrap({
770
- keyUri,
771
- derivationPath: fullDerivationPath,
772
- encryptedKey: jwePayload.encryptedKey,
773
- ephemeralPublicKey: jwePayload.ephemeralPublicKey,
774
- });
775
- }),
776
- };
680
+ isMultiPartyContext(protocolDefinition, rootProtocolPath) {
681
+ return isMultiPartyContextFn(protocolDefinition, rootProtocolPath);
777
682
  }
778
683
  /**
779
- * Constructs an EncryptionKeyDeriver callback for the SDK.
780
- * The SDK calls derivePublicKey(path), the KMS performs HKDF + public key
781
- * computation internally. The private key never leaves the KMS.
782
- *
783
- * Analogous to getSigner() for signing operations.
684
+ * Checks if any `$actions` rule in the protocol grants read access
685
+ * via `who: '<actorType>'` and `of: '<path>'`.
784
686
  *
785
- * @param didUri - The DID URI to create the key deriver for
786
- * @returns An EncryptionKeyDeriver callback object
687
+ * @param actorType - The actor type to check ('author', 'recipient', or undefined for any)
688
+ * @param ofPath - The protocol path to check
689
+ * @param protocolDefinition - The protocol definition
787
690
  */
788
- getEncryptionKeyDeriver(didUri) {
789
- return __awaiter(this, void 0, void 0, function* () {
790
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(didUri);
791
- const keyManager = this.agent.keyManager;
792
- return {
793
- rootKeyId: keyId,
794
- derivationScheme: KeyDerivationScheme.ProtocolPath,
795
- derivePublicKey: (fullDerivationPath) => __awaiter(this, void 0, void 0, function* () {
796
- return keyManager.derivePublicKey({
797
- keyUri,
798
- derivationPath: fullDerivationPath,
799
- });
800
- }),
801
- };
802
- });
691
+ hasRelationalReadAccess(actorType, ofPath, protocolDefinition) {
692
+ return hasRelationalReadAccessFn(actorType, ofPath, protocolDefinition);
803
693
  }
804
694
  /**
805
- * Constructs a KeyDecrypter callback for the SDK.
806
- * The SDK calls decrypt(path, eciesParams), the KMS performs HKDF + ECIES
807
- * decryption internally. The private key never leaves the KMS.
808
- *
809
- * Analogous to getSigner() for signing operations.
695
+ * Analyses a record write to determine which DIDs need context key delivery.
810
696
  *
811
- * @param didUri - The DID URI to create the key decrypter for
812
- * @returns A KeyDecrypter callback object
697
+ * @param params - Parameters for participant detection
698
+ * @returns Set of DIDs that need context key delivery
813
699
  */
814
- getKeyDecrypter(didUri) {
815
- return __awaiter(this, void 0, void 0, function* () {
816
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(didUri);
817
- return this.buildKmsDecryptCallback(keyId, keyUri, KeyDerivationScheme.ProtocolPath);
818
- });
700
+ detectNewParticipants(params) {
701
+ return detectNewParticipantsFn(params);
819
702
  }
820
703
  /**
821
- * Fetches a protocol definition from the local DWN, with caching.
704
+ * Fetches a protocol definition from the local DWN, with caching.
822
705
  * Returns undefined if the protocol is not installed.
823
706
  *
824
707
  * @param tenantDid - The tenant DID to query
@@ -847,150 +730,6 @@ export class AgentDwnApi {
847
730
  return definition;
848
731
  });
849
732
  }
850
- /**
851
- * Checks if a protocol path represents a multi-party context. Returns true
852
- * if the root path's subtree contains:
853
- * (a) any `$role: true` descendants, OR
854
- * (b) any relational `who`/`of` `$actions` rules that grant `read` access
855
- * (indicating external authors or recipients need context keys).
856
- *
857
- * This generalises the earlier `protocolPathHasRoles()` to cover protocols
858
- * that use relational access without explicit role definitions.
859
- */
860
- isMultiPartyContext(protocolDefinition, rootProtocolPath) {
861
- const segments = rootProtocolPath.split('/');
862
- let ruleSet = protocolDefinition.structure;
863
- for (const segment of segments) {
864
- ruleSet = ruleSet[segment];
865
- if (!ruleSet) {
866
- return false;
867
- }
868
- }
869
- // (a) Check for $role descendants in the subtree
870
- function hasRoleRecursive(rs) {
871
- for (const key in rs) {
872
- if (!key.startsWith('$')) {
873
- const child = rs[key];
874
- if (child.$role === true) {
875
- return true;
876
- }
877
- if (hasRoleRecursive(child)) {
878
- return true;
879
- }
880
- }
881
- }
882
- return false;
883
- }
884
- if (hasRoleRecursive(ruleSet)) {
885
- return true;
886
- }
887
- // (b) Check for relational who/of read rules anywhere in the protocol
888
- // that reference a path within this subtree. A rule like
889
- // { who: 'recipient', of: 'email', can: ['read'] } on any record
890
- // type means the email recipient needs a context key.
891
- return this.hasRelationalReadAccess(undefined, rootProtocolPath, protocolDefinition);
892
- }
893
- /**
894
- * Checks whether any relational `who`/`of` rule in the protocol grants
895
- * `read` access for a given actor type and ancestor path.
896
- *
897
- * Walks the *entire* protocol structure looking for any `$actions` rule that:
898
- * - Has `who` equal to `actorType` ('recipient' or 'author'), or any actor
899
- * type if `actorType` is `undefined`
900
- * - Has `of` equal to `ofPath`
901
- * - Has `can` including 'read'
902
- *
903
- * The search covers all record types in the protocol, since a relational
904
- * rule can appear at any level (e.g. `{ who: 'recipient', of: 'thread',
905
- * can: ['read'] }` might be defined on `thread/message`).
906
- *
907
- * @param actorType - 'author' | 'recipient', or undefined for any
908
- * @param ofPath - The protocol path to check (e.g. 'thread', 'email')
909
- * @param protocolDefinition - The full protocol definition
910
- * @returns true if a matching relational read rule exists
911
- */
912
- hasRelationalReadAccess(actorType, ofPath, protocolDefinition) {
913
- const structure = protocolDefinition.structure;
914
- function walkRuleSet(rs) {
915
- var _a;
916
- // Check $actions on this node
917
- if (rs.$actions) {
918
- for (const rule of rs.$actions) {
919
- if (rule.who &&
920
- rule.who !== 'anyone' &&
921
- (actorType === undefined || rule.who === actorType) &&
922
- rule.of === ofPath &&
923
- ((_a = rule.can) === null || _a === void 0 ? void 0 : _a.includes('read'))) {
924
- return true;
925
- }
926
- }
927
- }
928
- // Recurse into child record types
929
- for (const key in rs) {
930
- if (!key.startsWith('$')) {
931
- if (walkRuleSet(rs[key])) {
932
- return true;
933
- }
934
- }
935
- }
936
- return false;
937
- }
938
- return walkRuleSet(structure);
939
- }
940
- /**
941
- * Analyses a record write to determine which DIDs need context key delivery.
942
- *
943
- * Returns a set of participant DIDs that should receive `contextKey` records.
944
- * The DWN owner (tenantDid) is always excluded — they have ProtocolPath access.
945
- *
946
- * Cases handled:
947
- * 1. `$role` record with a recipient → recipient is a participant
948
- * 2. Record has a recipient and a relational read rule grants access
949
- * via `{ who: 'recipient', of: '<path>', can: ['read'] }`
950
- * 3. Record is authored by an external party → if `{ who: 'author', of:
951
- * '<path>', can: ['read'] }` rules grant read access, the author needs
952
- * a context key.
953
- *
954
- * @param params.protocolDefinition - The installed protocol definition
955
- * @param params.protocolPath - The written record's protocol path
956
- * @param params.recipient - Recipient DID from the record, if any
957
- * @param params.tenantDid - The DWN owner's DID (excluded from results)
958
- * @param params.authorDid - Author DID if externally authored, undefined otherwise
959
- * @returns Set of DIDs that need context key delivery
960
- */
961
- detectNewParticipants({ protocolDefinition, protocolPath, recipient, tenantDid, authorDid }) {
962
- const participants = new Set();
963
- // Navigate to the rule set at the given protocol path
964
- const pathSegments = protocolPath.split('/');
965
- let ruleSet = protocolDefinition.structure;
966
- for (const segment of pathSegments) {
967
- ruleSet = ruleSet[segment];
968
- if (!ruleSet) {
969
- return participants;
970
- }
971
- }
972
- // Case 1: $role record → recipient is a participant
973
- if (ruleSet.$role === true && recipient) {
974
- participants.add(recipient);
975
- }
976
- // Case 2: Record has a recipient → check if relational read rules exist
977
- if (recipient && recipient !== tenantDid) {
978
- if (this.hasRelationalReadAccess('recipient', protocolPath, protocolDefinition)) {
979
- participants.add(recipient);
980
- }
981
- }
982
- // Case 3: External author → check if author-based relational read rules exist.
983
- // If `{ who: 'author', of: '<path>', can: ['read'] }` is defined anywhere
984
- // in the protocol, the external author needs a context key to decrypt.
985
- if (authorDid && authorDid !== tenantDid) {
986
- if (this.hasRelationalReadAccess('author', protocolPath, protocolDefinition)) {
987
- participants.add(authorDid);
988
- }
989
- }
990
- // Remove the DWN owner — they always have ProtocolPath access
991
- participants.delete(tenantDid);
992
- return participants;
993
- }
994
733
  /**
995
734
  * Fetches a protocol definition from a remote DWN.
996
735
  * Uses an unsigned ProtocolsQuery (public protocols can be queried anonymously).
@@ -1069,252 +808,15 @@ export class AgentDwnApi {
1069
808
  return undefined;
1070
809
  });
1071
810
  }
1072
- /**
1073
- * Reactively upgrades an externally-authored root record that has only
1074
- * ProtocolPath encryption by appending a ProtocolContext recipient entry.
1075
- *
1076
- * After the upgrade, both the owner (ProtocolPath) and context key holders —
1077
- * including the external author (ProtocolContext) — can decrypt the record.
1078
- *
1079
- * Steps:
1080
- * 1. Decrypt the DEK using the owner's ProtocolPath-derived private key
1081
- * 2. Derive the context public key from the owner's #enc key
1082
- * 3. ECIES-encrypt the same DEK to the context public key
1083
- * 4. Append the ProtocolContext recipient entry (using PR 0b append mode)
1084
- * 5. Re-sign the record as owner
1085
- *
1086
- * The author's signature payload includes an `encryptionCid` that becomes
1087
- * stale after step 4. The SDK's `validateIntegrity()` skips the encryptionCid
1088
- * check on the author's signature when an ownerSignature is present (step 5),
1089
- * since the owner vouches for the updated encryption property.
1090
- *
1091
- * NOTE: An alternative design would deliver the DEK out-of-band via the
1092
- * key-delivery protocol (as a field on the contextKey record) instead of
1093
- * mutating the record's encryption property. That avoids the stale
1094
- * encryptionCid concern entirely but adds complexity to the read path and
1095
- * the contextKey schema. We chose the in-record approach because it keeps
1096
- * records self-contained and the read/decrypt path unchanged.
1097
- *
1098
- * @param tenantDid - The DWN owner's DID
1099
- * @param recordsWrite - The RecordsWrite message to upgrade
1100
- */
1101
- upgradeExternalRootRecord(tenantDid, recordsWrite) {
1102
- return __awaiter(this, void 0, void 0, function* () {
1103
- const { encryption } = recordsWrite;
1104
- if (!encryption) {
1105
- return;
1106
- }
1107
- // Verify: has ProtocolPath but NOT ProtocolContext
1108
- const hasProtocolPath = encryption.recipients.some((r) => r.header.derivationScheme === KeyDerivationScheme.ProtocolPath);
1109
- const hasProtocolContext = encryption.recipients.some((r) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext);
1110
- if (!hasProtocolPath || hasProtocolContext) {
1111
- return;
1112
- }
1113
- // 1. Decrypt the DEK using the owner's ProtocolPath key
1114
- const keyDecrypter = yield this.getKeyDecrypter(tenantDid);
1115
- // Find the ProtocolPath recipient entry
1116
- const pathRecipient = encryption.recipients.find((r) => r.header.derivationScheme === KeyDerivationScheme.ProtocolPath);
1117
- const fullDerivationPath = Records.constructKeyDerivationPathUsingProtocolPathScheme(recordsWrite.descriptor);
1118
- const dataEncryptionKey = yield keyDecrypter.decrypt(fullDerivationPath, {
1119
- encryptedKey: Encoder.base64UrlToBytes(pathRecipient.encrypted_key),
1120
- ephemeralPublicKey: pathRecipient.header.epk,
1121
- });
1122
- // 2. Derive the context public key — contextId = recordId for root records
1123
- const contextId = recordsWrite.recordId;
1124
- const encryptionIV = Encoder.base64UrlToBytes(encryption.iv);
1125
- // 3 & 4. Append the ProtocolContext recipient entry using append mode.
1126
- // Append mode preserves the author's identity and authorization so that
1127
- // signAsOwner() can be called in step 5.
1128
- const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } = yield this.deriveContextEncryptionInput(tenantDid, contextId, dataEncryptionKey, encryptionIV);
1129
- // Set the authentication tag from the existing JWE encryption property
1130
- const fullContextInput = Object.assign(Object.assign({}, contextEncryptionInput), { authenticationTag: Encoder.base64UrlToBytes(encryption.tag) });
1131
- // Parse the message to get a RecordsWrite instance we can mutate
1132
- const recordsWriteInstance = yield dwnMessageConstructors[DwnInterface.RecordsWrite].parse(recordsWrite);
1133
- yield recordsWriteInstance.encryptSymmetricEncryptionKey(fullContextInput, { append: true });
1134
- // 5. Re-sign as owner — the author's signature is preserved but its
1135
- // encryptionCid is now stale; the owner's signature vouches for the
1136
- // updated encryption property.
1137
- const signer = yield this.getSigner(tenantDid);
1138
- yield recordsWriteInstance.signAsOwner(signer);
1139
- // Store the upgraded message directly via the message store, bypassing
1140
- // the handler's conflict resolution which doesn't support same-timestamp
1141
- // owner-augmented replacements. The data is unchanged — only the encryption
1142
- // metadata and authorization are updated.
1143
- //
1144
- // We must also update the state index and event stream to keep sync and
1145
- // real-time subscribers consistent — without this, the upgraded record
1146
- // would never propagate to remote DWNs or notify subscribers.
1147
- const { messageStore, stateIndex, eventStream } = this._dwn.storage;
1148
- // Validate the upgrade only changed encryption and authorization fields.
1149
- // The descriptor, recordId, contextId, and data must remain identical.
1150
- // Note: parse() may produce a new descriptor object, so we compare by value.
1151
- const upgradedMessage = recordsWriteInstance.message;
1152
- if (JSON.stringify(upgradedMessage.descriptor) !== JSON.stringify(recordsWrite.descriptor)) {
1153
- throw new Error('AgentDwnApi: upgradeExternalRootRecord() must not modify the descriptor.');
1154
- }
1155
- if (upgradedMessage.recordId !== recordsWrite.recordId) {
1156
- throw new Error('AgentDwnApi: upgradeExternalRootRecord() must not modify the recordId.');
1157
- }
1158
- // Fetch the stored original (which carries encodedData for small payloads)
1159
- const originalCid = yield Message.getCid(recordsWrite);
1160
- const storedOriginal = yield messageStore.get(tenantDid, originalCid);
1161
- // Build indexes for the upgraded message (mark as latest base state)
1162
- const isLatestBaseState = true;
1163
- const upgradedIndexes = yield recordsWriteInstance.constructIndexes(isLatestBaseState);
1164
- // Carry over the encoded data from the stored original (the handler
1165
- // base64url-encodes small payloads into encodedData during processMessage)
1166
- if (storedOriginal === null || storedOriginal === void 0 ? void 0 : storedOriginal.encodedData) {
1167
- upgradedMessage.encodedData = storedOriginal.encodedData;
1168
- }
1169
- // Use put-before-delete ordering: if a crash occurs after the put but
1170
- // before the delete, we end up with a duplicate (recoverable via the
1171
- // isLatestBaseState index) rather than data loss (unrecoverable).
1172
- const upgradedCid = yield Message.getCid(upgradedMessage);
1173
- yield messageStore.put(tenantDid, upgradedMessage, upgradedIndexes);
1174
- yield stateIndex.insert(tenantDid, upgradedCid, upgradedIndexes);
1175
- // Now remove the original message and its state index entry.
1176
- yield messageStore.delete(tenantDid, originalCid);
1177
- yield stateIndex.delete(tenantDid, [originalCid]);
1178
- // Notify real-time subscribers (mirrors handler behavior)
1179
- if (eventStream !== undefined) {
1180
- eventStream.emit(tenantDid, { message: upgradedMessage }, upgradedIndexes);
1181
- }
1182
- // Cache context key info for subsequent writes in this context
1183
- this._contextKeyCache.set(contextId, { keyId, keyUri, contextDerivationPath });
1184
- });
1185
- }
1186
- /**
1187
- * Resolves the appropriate KeyDecrypter for a record's encryption scheme.
1188
- * Handles both single-party (ProtocolPath) and multi-party (ProtocolContext).
1189
- *
1190
- * For ProtocolContext records:
1191
- * - Context creator: derives key directly from KMS
1192
- * - Participant: fetches contextKey via key-delivery protocol, caches it
1193
- */
1194
- resolveKeyDecrypter(authorDid, recordsWrite, targetDid) {
1195
- return __awaiter(this, void 0, void 0, function* () {
1196
- const { encryption } = recordsWrite;
1197
- // Check if the record uses context-derived encryption
1198
- const hasContextKey = encryption === null || encryption === void 0 ? void 0 : encryption.recipients.some((r) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext);
1199
- if (!hasContextKey || !recordsWrite.contextId) {
1200
- // Single-party protocol-path encryption
1201
- return this.getKeyDecrypter(authorDid);
1202
- }
1203
- // --- Multi-party context encryption ---
1204
- const contextKeyEntry = encryption.recipients.find((r) => r.header.derivationScheme === KeyDerivationScheme.ProtocolContext);
1205
- const rootContextId = recordsWrite.contextId.split('/')[0];
1206
- // Case 1: I am the context creator — rootKeyId matches my encryption key
1207
- const { keyId, keyUri } = yield this.getEncryptionKeyInfo(authorDid);
1208
- if (contextKeyEntry.header.kid === keyId) {
1209
- return this.buildKmsDecryptCallback(keyId, keyUri, KeyDerivationScheme.ProtocolContext);
1210
- }
1211
- // Case 2: I am a participant — fetch my context key from the key-delivery protocol
1212
- const cacheKey = `ctx~${authorDid}~${rootContextId}`;
1213
- const cached = this._contextDerivedKeyCache.get(cacheKey);
1214
- if (cached) {
1215
- return this.buildContextKeyDecrypter(cached);
1216
- }
1217
- // Fetch context key via the key-delivery protocol — local first, then remote
1218
- const protocol = recordsWrite.descriptor.protocol;
1219
- // Try local: I may be the DWN owner with a contextKey addressed to myself
1220
- let contextDerivedPrivateKey = yield this.fetchContextKeyRecord({
1221
- ownerDid: authorDid,
1222
- requesterDid: authorDid,
1223
- sourceProtocol: protocol,
1224
- sourceContextId: rootContextId,
1225
- });
1226
- // Try remote: query the DWN owner's DWN for my contextKey record.
1227
- // For cross-DWN records, targetDid is the DWN owner (e.g., Alice) where the
1228
- // contextKey was written. For same-DWN records, fall back to the record's
1229
- // authorization signer.
1230
- if (!contextDerivedPrivateKey) {
1231
- const contextOwnerDid = targetDid !== null && targetDid !== void 0 ? targetDid : Jws.getSignerDid(recordsWrite.authorization.signature.signatures[0]);
1232
- contextDerivedPrivateKey = yield this.fetchContextKeyRecord({
1233
- ownerDid: contextOwnerDid,
1234
- requesterDid: authorDid,
1235
- sourceProtocol: protocol,
1236
- sourceContextId: rootContextId,
1237
- });
1238
- }
1239
- if (!contextDerivedPrivateKey) {
1240
- throw new Error(`AgentDwnApi: Failed to decrypt record '${recordsWrite.recordId}'. ` +
1241
- `Record uses context-derived encryption but no contextKey record ` +
1242
- `could be found via the key-delivery protocol.`);
1243
- }
1244
- this._contextDerivedKeyCache.set(cacheKey, contextDerivedPrivateKey);
1245
- return this.buildContextKeyDecrypter(contextDerivedPrivateKey);
1246
- });
1247
- }
1248
- /**
1249
- * Builds a KeyDecrypter from a context-derived private key.
1250
- * Uses the raw key directly (since it was shared with us via the key-delivery protocol).
1251
- */
1252
- buildContextKeyDecrypter(contextKey) {
1253
- return {
1254
- rootKeyId: contextKey.rootKeyId,
1255
- derivationScheme: contextKey.derivationScheme,
1256
- decrypt: (fullDerivationPath, jwePayload) => __awaiter(this, void 0, void 0, function* () {
1257
- const leafPrivateKeyBytes = yield Records.derivePrivateKey(contextKey, fullDerivationPath);
1258
- const leafPrivateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
1259
- return Encryption.ecdhEsUnwrapKey(leafPrivateKeyJwk, jwePayload.ephemeralPublicKey, jwePayload.encryptedKey);
1260
- }),
1261
- };
1262
- }
1263
811
  /**
1264
812
  * Post-processes a DWN reply, auto-decrypting data if encryption is enabled.
1265
- * Delegates to the SDK's Records.decrypt() with the appropriate KeyDecrypter —
1266
- * resolveKeyDecrypter() selects between ProtocolPath and ProtocolContext schemes.
813
+ * Delegates to the standalone function in `dwn-encryption.ts`.
1267
814
  */
1268
815
  maybeDecryptReply(request, reply) {
1269
816
  return __awaiter(this, void 0, void 0, function* () {
1270
- var _a, _b, _c;
1271
- if (!('encryption' in request) || !request.encryption) {
1272
- return;
1273
- }
1274
- // Auto-decrypt RecordsRead replies
1275
- if (isDwnRequest(request, DwnInterface.RecordsRead)) {
1276
- const readReply = reply;
1277
- if (readReply.status.code === 200
1278
- && ((_b = (_a = readReply.entry) === null || _a === void 0 ? void 0 : _a.recordsWrite) === null || _b === void 0 ? void 0 : _b.encryption)
1279
- && ((_c = readReply.entry) === null || _c === void 0 ? void 0 : _c.data)) {
1280
- const keyDecrypter = yield this.resolveKeyDecrypter(request.author, readReply.entry.recordsWrite, request.target);
1281
- try {
1282
- readReply.entry.data = yield Records.decrypt(readReply.entry.recordsWrite, keyDecrypter, readReply.entry.data);
1283
- }
1284
- catch (error) {
1285
- throw new Error(`AgentDwnApi: Failed to decrypt record ` +
1286
- `'${readReply.entry.recordsWrite.recordId}'. ` +
1287
- `Original error: ${error.message}`);
1288
- }
1289
- }
1290
- }
1291
- // Auto-decrypt RecordsQuery replies (small records inline as encodedData)
1292
- if (isDwnRequest(request, DwnInterface.RecordsQuery)) {
1293
- const queryReply = reply;
1294
- if (queryReply.status.code === 200 && queryReply.entries) {
1295
- for (const entry of queryReply.entries) {
1296
- if (entry.encryption && entry.encodedData) {
1297
- const keyDecrypter = yield this.resolveKeyDecrypter(request.author, entry, request.target);
1298
- try {
1299
- const cipherBytes = Encoder.base64UrlToBytes(entry.encodedData);
1300
- const cipherStream = DataStream.fromBytes(cipherBytes);
1301
- const plainStream = yield Records.decrypt(entry, keyDecrypter, cipherStream);
1302
- const plainBytes = yield DataStream.toBytes(plainStream);
1303
- entry.encodedData = Encoder.bytesToBase64Url(plainBytes);
1304
- }
1305
- catch (error) {
1306
- throw new Error(`AgentDwnApi: Failed to decrypt record ` +
1307
- `'${entry.recordId}'. Original error: ${error.message}`);
1308
- }
1309
- }
1310
- }
1311
- }
1312
- }
817
+ return maybeDecryptReplyFn(request, reply, this.agent, this._contextDerivedKeyCache, this.fetchContextKeyRecord.bind(this));
1313
818
  });
1314
819
  }
1315
- /**
1316
- * FURTHER REFACTORING NEEDED BELOW THIS LINE
1317
- */
1318
820
  getDwnMessage(_a) {
1319
821
  return __awaiter(this, arguments, void 0, function* ({ author, messageCid }) {
1320
822
  const signer = yield this.getSigner(author);
@@ -1340,118 +842,25 @@ export class AgentDwnApi {
1340
842
  }
1341
843
  /**
1342
844
  * Ensures the key delivery protocol is installed on the given tenant's DWN,
1343
- * with `$encryption` keys injected. Uses the same lazy initialization pattern
1344
- * as `DwnDataStore.initialize()`.
845
+ * with `$encryption` keys injected.
1345
846
  *
1346
847
  * @param tenantDid - The DID of the DWN owner
1347
848
  */
1348
849
  ensureKeyDeliveryProtocol(tenantDid) {
1349
850
  return __awaiter(this, void 0, void 0, function* () {
1350
- if (this._keyDeliveryProtocolInstalledCache.get(tenantDid)) {
1351
- return;
1352
- }
1353
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1354
- const existing = yield this.getProtocolDefinition(tenantDid, protocolUri);
1355
- if (!existing) {
1356
- // Derive and inject $encryption keys for each type path
1357
- const keyDeriver = yield this.getEncryptionKeyDeriver(tenantDid);
1358
- const definitionWithKeys = yield Protocols.deriveAndInjectPublicEncryptionKeys(KeyDeliveryProtocolDefinition, keyDeriver);
1359
- const { reply: { status } } = yield this.processRequest({
1360
- author: tenantDid,
1361
- target: tenantDid,
1362
- messageType: DwnInterface.ProtocolsConfigure,
1363
- messageParams: { definition: definitionWithKeys },
1364
- });
1365
- if (status.code !== 202) {
1366
- throw new Error(`AgentDwnApi: Failed to install key delivery protocol: ${status.code} - ${status.detail}`);
1367
- }
1368
- // Invalidate protocol definition cache so subsequent reads pick up the new definition
1369
- this._protocolDefinitionCache.delete(`${tenantDid}~${protocolUri}`);
1370
- }
1371
- this._keyDeliveryProtocolInstalledCache.set(tenantDid, true);
851
+ return ensureKeyDeliveryProtocolFn(this.agent, tenantDid, this.processRequest.bind(this), this.getProtocolDefinition.bind(this), this._keyDeliveryProtocolInstalledCache, this._protocolDefinitionCache);
1372
852
  });
1373
853
  }
1374
854
  /**
1375
855
  * Writes a `contextKey` record to the owner's DWN, delivering an encrypted
1376
856
  * context key to a participant.
1377
857
  *
1378
- * The payload is encrypted to the **recipient's** ProtocolPath-derived public
1379
- * key on the key-delivery protocol, so only the recipient can decrypt it.
1380
- * The recipient's key is supplied via `recipientKeyDeliveryPublicKey` (which
1381
- * the external author attached as `authorKeyDeliveryPublicKey` on the
1382
- * original cross-DWN record).
1383
- *
1384
- * When `recipientKeyDeliveryPublicKey` is not provided (e.g. the owner is
1385
- * writing a contextKey for themselves), the record is encrypted to the
1386
- * owner's own ProtocolPath key using the generic `processRequest` path.
1387
- *
1388
- * @param params.tenantDid - The DWN owner's DID (who is delivering the key)
1389
- * @param params.recipientDid - The participant's DID (who will receive the key)
1390
- * @param params.contextKeyData - The `DerivedPrivateJwk` to deliver
1391
- * @param params.sourceProtocol - The URI of the source protocol (tag)
1392
- * @param params.sourceContextId - The root context ID (tag)
1393
- * @param params.recipientKeyDeliveryPublicKey - The recipient's ProtocolPath-
1394
- * derived public key for `key-delivery/contextKey`. When provided,
1395
- * the contextKey record is encrypted directly to this key.
858
+ * @param params - The write parameters
1396
859
  * @returns The recordId of the written contextKey record
1397
860
  */
1398
- writeContextKeyRecord(_a) {
1399
- return __awaiter(this, arguments, void 0, function* ({ tenantDid, recipientDid, contextKeyData, sourceProtocol, sourceContextId, recipientKeyDeliveryPublicKey }) {
1400
- // Ensure the key delivery protocol is installed on the owner's DWN
1401
- yield this.ensureKeyDeliveryProtocol(tenantDid);
1402
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1403
- // Serialize the payload to JSON bytes
1404
- const plaintextBytes = new TextEncoder().encode(JSON.stringify(contextKeyData));
1405
- // Common contextKey record parameters
1406
- const contextKeyParams = {
1407
- protocol: protocolUri,
1408
- protocolPath: 'contextKey',
1409
- dataFormat: 'application/json',
1410
- recipient: recipientDid,
1411
- tags: { protocol: sourceProtocol, contextId: sourceContextId },
1412
- };
1413
- let message;
1414
- let status;
1415
- if (recipientKeyDeliveryPublicKey) {
1416
- // --- Encrypt to the recipient's ProtocolPath key (cross-DWN delivery) ---
1417
- // Manually build encryption input targeting the recipient's key so the
1418
- // record is decryptable only by the recipient.
1419
- const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
1420
- const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(12));
1421
- const { encryptedBytes, dataCid, dataSize, authenticationTag } = yield this.encryptAndComputeCid(plaintextBytes, dataEncryptionKey, dataEncryptionIV);
1422
- const encryptionInput = Object.assign(Object.assign({}, this.buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, recipientKeyDeliveryPublicKey.rootKeyId, recipientKeyDeliveryPublicKey.publicKeyJwk, KeyDerivationScheme.ProtocolPath)), { authenticationTag });
1423
- ({ message, reply: { status } } = yield this.processRequest({
1424
- author: tenantDid,
1425
- target: tenantDid,
1426
- messageType: DwnInterface.RecordsWrite,
1427
- messageParams: Object.assign(Object.assign({}, contextKeyParams), { dataCid, dataSize, encryptionInput }),
1428
- dataStream: new Blob([encryptedBytes]),
1429
- }));
1430
- }
1431
- else {
1432
- // --- Fallback: encrypt to the owner's key (local self-delivery) ---
1433
- // When no recipient key is provided, use the generic processRequest
1434
- // encryption path which encrypts to the DWN owner's ProtocolPath key.
1435
- ({ message, reply: { status } } = yield this.processRequest({
1436
- author: tenantDid,
1437
- target: tenantDid,
1438
- messageType: DwnInterface.RecordsWrite,
1439
- messageParams: contextKeyParams,
1440
- dataStream: new Blob([plaintextBytes], { type: 'application/json' }),
1441
- encryption: true,
1442
- }));
1443
- }
1444
- if (!(message && status.code === 202)) {
1445
- throw new Error(`AgentDwnApi: Failed to write contextKey record for ${recipientDid}: ${status.code} - ${status.detail}`);
1446
- }
1447
- // Eagerly send the contextKey record to the tenant's remote DWN so that
1448
- // participants can fetch it immediately without waiting for sync.
1449
- // This is fire-and-forget — sync will guarantee eventual consistency.
1450
- this.eagerSendContextKeyRecord(tenantDid, message).catch((err) => {
1451
- console.warn(`AgentDwnApi: Eager send of contextKey record '${message.recordId}' ` +
1452
- `to remote DWN failed: ${err.message}. Sync will deliver it later.`);
1453
- });
1454
- return message.recordId;
861
+ writeContextKeyRecord(params) {
862
+ return __awaiter(this, void 0, void 0, function* () {
863
+ return writeContextKeyRecordFn(this.agent, params, this.processRequest.bind(this), this.ensureKeyDeliveryProtocol.bind(this), this.eagerSendContextKeyRecord.bind(this));
1455
864
  });
1456
865
  }
1457
866
  /**
@@ -1460,119 +869,19 @@ export class AgentDwnApi {
1460
869
  */
1461
870
  eagerSendContextKeyRecord(tenantDid, contextKeyMessage) {
1462
871
  return __awaiter(this, void 0, void 0, function* () {
1463
- let dwnEndpointUrls;
1464
- try {
1465
- dwnEndpointUrls = yield getDwnServiceEndpointUrls(tenantDid, this.agent.did);
1466
- }
1467
- catch (_a) {
1468
- // DID resolution or endpoint lookup failed — not fatal, sync will handle it.
1469
- return;
1470
- }
1471
- if (dwnEndpointUrls.length === 0) {
1472
- return;
1473
- }
1474
- // Read the full message (including data blob) from the local DWN
1475
- const { data } = yield this.getDwnMessage({
1476
- author: tenantDid,
1477
- messageType: DwnInterface.RecordsWrite,
1478
- messageCid: yield Message.getCid(contextKeyMessage),
1479
- });
1480
- yield this.sendDwnRpcRequest({
1481
- targetDid: tenantDid,
1482
- dwnEndpointUrls,
1483
- message: contextKeyMessage,
1484
- data,
1485
- });
872
+ return eagerSendContextKeyRecordFn(this.agent, tenantDid, contextKeyMessage, this.getDwnMessage.bind(this), this.sendDwnRpcRequest.bind(this));
1486
873
  });
1487
874
  }
1488
875
  /**
1489
876
  * Fetches and decrypts a `contextKey` record from a DWN, returning the
1490
877
  * `DerivedPrivateJwk` payload.
1491
878
  *
1492
- * Supports both local reads (tenant queries own DWN) and remote reads
1493
- * (participant queries the context owner's DWN).
1494
- *
1495
- * @param params.ownerDid - The DWN owner's DID (where contextKey records live)
1496
- * @param params.requesterDid - The DID of the requester (used for signing and decryption)
1497
- * @param params.sourceProtocol - The URI of the source protocol (tag filter)
1498
- * @param params.sourceContextId - The root context ID (tag filter)
879
+ * @param params - The fetch parameters
1499
880
  * @returns The decrypted `DerivedPrivateJwk`, or `undefined` if no matching record found
1500
881
  */
1501
- fetchContextKeyRecord(_a) {
1502
- return __awaiter(this, arguments, void 0, function* ({ ownerDid, requesterDid, sourceProtocol, sourceContextId }) {
1503
- var _b, _c, _d, _e, _f;
1504
- const protocolUri = KeyDeliveryProtocolDefinition.protocol;
1505
- const isLocal = ownerDid === requesterDid;
1506
- // Shared query filter for both local and remote paths
1507
- const contextKeyFilter = {
1508
- protocol: protocolUri,
1509
- protocolPath: 'contextKey',
1510
- recipient: requesterDid,
1511
- tags: { protocol: sourceProtocol, contextId: sourceContextId },
1512
- };
1513
- /** Parse decrypted bytes into a DerivedPrivateJwk. */
1514
- const parsePayload = (bytes) => JSON.parse(new TextDecoder().decode(bytes));
1515
- if (isLocal) {
1516
- // Local query: owner queries their own DWN
1517
- const { reply } = yield this.processRequest({
1518
- author: requesterDid,
1519
- target: ownerDid,
1520
- messageType: DwnInterface.RecordsQuery,
1521
- messageParams: { filter: contextKeyFilter },
1522
- });
1523
- if (reply.status.code !== 200 || !((_b = reply.entries) === null || _b === void 0 ? void 0 : _b.length)) {
1524
- return undefined;
1525
- }
1526
- // Read the full record to get the data (auto-decrypted by processRequest)
1527
- const recordId = reply.entries[0].recordId;
1528
- const { reply: readReply } = yield this.processRequest({
1529
- author: requesterDid,
1530
- target: ownerDid,
1531
- messageType: DwnInterface.RecordsRead,
1532
- messageParams: { filter: { recordId } },
1533
- encryption: true,
1534
- });
1535
- const readResult = readReply;
1536
- if (!((_c = readResult.entry) === null || _c === void 0 ? void 0 : _c.data)) {
1537
- return undefined;
1538
- }
1539
- return parsePayload(yield DataStream.toBytes(readResult.entry.data));
1540
- }
1541
- else {
1542
- // Remote query: participant queries the context owner's DWN
1543
- const signer = yield this.getSigner(requesterDid);
1544
- const dwnEndpointUrls = yield getDwnServiceEndpointUrls(ownerDid, this.agent.did);
1545
- const recordsQuery = yield dwnMessageConstructors[DwnInterface.RecordsQuery].create({
1546
- signer,
1547
- filter: contextKeyFilter,
1548
- });
1549
- const queryReply = yield this.sendDwnRpcRequest({
1550
- targetDid: ownerDid,
1551
- dwnEndpointUrls,
1552
- message: recordsQuery.message,
1553
- });
1554
- if (queryReply.status.code !== 200 || !((_d = queryReply.entries) === null || _d === void 0 ? void 0 : _d.length)) {
1555
- return undefined;
1556
- }
1557
- // Read the full record remotely
1558
- const recordId = queryReply.entries[0].recordId;
1559
- const recordsRead = yield dwnMessageConstructors[DwnInterface.RecordsRead].create({
1560
- signer,
1561
- filter: { recordId },
1562
- });
1563
- const readReply = yield this.sendDwnRpcRequest({
1564
- targetDid: ownerDid,
1565
- dwnEndpointUrls,
1566
- message: recordsRead.message,
1567
- });
1568
- if (!((_e = readReply.entry) === null || _e === void 0 ? void 0 : _e.data) || !((_f = readReply.entry) === null || _f === void 0 ? void 0 : _f.recordsWrite)) {
1569
- return undefined;
1570
- }
1571
- // Decrypt the contextKey payload using the requester's key-delivery protocol path key
1572
- const keyDecrypter = yield this.getKeyDecrypter(requesterDid);
1573
- const decryptedStream = yield Records.decrypt(readReply.entry.recordsWrite, keyDecrypter, readReply.entry.data);
1574
- return parsePayload(yield DataStream.toBytes(decryptedStream));
1575
- }
882
+ fetchContextKeyRecord(params) {
883
+ return __awaiter(this, void 0, void 0, function* () {
884
+ return fetchContextKeyRecordFn(this.agent, params, this.processRequest.bind(this), this.getSigner.bind(this), this.sendDwnRpcRequest.bind(this));
1576
885
  });
1577
886
  }
1578
887
  }