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