var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { logger } from '@enbox/common';
import { Cid, ContentEncryptionAlgorithm, DataStream, DwnMethodName, PermissionGrant as DwnPermissionGrant, Encoder, Encryption, ENCRYPTION_CONTROL_AUDIENCE_PATH, ENCRYPTION_CONTROL_AUDIENCE_SCHEMA_URI, ENCRYPTION_CONTROL_DELIVERY_PATH, ENCRYPTION_CONTROL_DELIVERY_SCHEMA_URI, EncryptionControlDeliveryRecipientAuthority, EncryptionProtocol, getGrantKeyDeliveryScopes, getRoleAudienceContextId, getRoleContextPrefix, grantKeyScopeCoversDeliveredScope, HdKey, isEncryptionControlPath, isGrantKeyEligibleRecordsScope, KeyAgreementAlgorithm, KeyDerivationScheme, Message, Records, ROLE_AUDIENCE_DERIVATION_SCHEME, SEAL_DERIVATION_SCHEME, Time, } from '@enbox/dwn-sdk-js';
import { Ed25519, X25519 } from '@enbox/crypto';
import { DwnInterface } from './types/dwn.js';
import { isDwnRequest } from './dwn-type-guards.js';
import { processDwnRequestWithRemoteFallback as processDwnReadThrough } from './dwn-read-through.js';
const GRANT_KEY_DERIVATION_PATH = [
    KeyDerivationScheme.ProtocolPath,
    EncryptionProtocol.uri,
    EncryptionProtocol.grantKeyPath,
];
/**
 * Returns the IV/counter byte length for DWN content encryption.
 */
export function ivLength(_algorithm) {
    return 16;
}
/**
 * Builds a partial EncryptionInput object for a single key-encryption entry.
 */
export function buildEncryptionInput(dek, iv, keyId, publicKey, derivationScheme) {
    return {
        initializationVector: iv,
        key: dek,
        keyEncryptionInputs: [{
                algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
                keyId,
                publicKey,
                derivationScheme,
            }],
    };
}
/**
 * Encrypts plaintext bytes and computes the CID of the resulting ciphertext.
 */
export function encryptAndComputeCid(plaintextBytes_1, dek_1, iv_1) {
    return __awaiter(this, arguments, void 0, function* (plaintextBytes, dek, iv, algorithm = ContentEncryptionAlgorithm.A256CTR) {
        const ciphertextStream = yield Encryption.encryptStream(algorithm, dek, iv, DataStream.fromBytes(plaintextBytes));
        const encryptedBytes = yield DataStream.toBytes(ciphertextStream);
        const cidStream = DataStream.fromBytes(encryptedBytes);
        const dataCid = yield Cid.computeDagPbCidFromStream(cidStream);
        return { encryptedBytes, dataCid, dataSize: encryptedBytes.length };
    });
}
/**
 * Resolves the encryption key info for a given DID.
 * Looks up the keyAgreement verification method in the DID document,
 * then resolves the corresponding KMS key URI.
 *
 * @param agent - The platform agent to use for DID resolution and key management
 * @param didUri - The DID URI to resolve encryption key info for
 * @returns keyId (fully qualified verification method ID), keyUri (KMS reference),
 *          and publicKeyJwk. No private key material is returned.
 * @throws If the DID has no keyAgreement verification method or it's not X25519.
 */
export function getEncryptionKeyInfo(agent, didUri) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        // 1. Resolve the DID document
        const { didDocument, didResolutionMetadata } = yield agent.did.resolve(didUri);
        if (!didDocument) {
            throw new Error(`AgentDwnApi: Failed to resolve DID '${didUri}': ` +
                `${JSON.stringify(didResolutionMetadata)}`);
        }
        // 2. Find the keyAgreement verification method
        const keyAgreementRefs = didDocument.keyAgreement;
        if (!keyAgreementRefs || keyAgreementRefs.length === 0) {
            throw new Error(`AgentDwnApi: DID '${didUri}' does not have a keyAgreement ` +
                `verification method. Create the identity with an X25519 key ` +
                `with keyAgreement purpose to use protocol encryption.`);
        }
        // 3. Resolve the verification method (handle both inline and string refs)
        const keyAgreementRef = keyAgreementRefs[0];
        let verificationMethod;
        if (typeof keyAgreementRef === 'string') {
            const fragment = keyAgreementRef.includes('#')
                ? keyAgreementRef.split('#').pop()
                : keyAgreementRef;
            verificationMethod = (_a = didDocument.verificationMethod) === null || _a === void 0 ? void 0 : _a.find(vm => vm.id.endsWith(`#${fragment}`));
        }
        else {
            verificationMethod = keyAgreementRef;
        }
        if (!(verificationMethod === null || verificationMethod === void 0 ? void 0 : verificationMethod.publicKeyJwk)) {
            throw new Error(`AgentDwnApi: keyAgreement verification method for '${didUri}' ` +
                `does not contain a public key in JWK format.`);
        }
        // 4. Resolve or derive the X25519 key for encryption.
        // Standard case: the keyAgreement VM already has an X25519 key.
        // Delegate case: did:jwk with Ed25519 only — convert to X25519.
        // The Ed25519→X25519 conversion is a standard cryptographic operation
        // (RFC 8032 / libsodium). The converted X25519 key must already be
        // present in the agent's KMS (imported via the delegate PortableDid).
        let resolvedPublicKeyJwk = verificationMethod.publicKeyJwk;
        if (resolvedPublicKeyJwk.crv === 'Ed25519') {
            resolvedPublicKeyJwk = yield Ed25519.convertPublicKeyToX25519({
                publicKey: resolvedPublicKeyJwk,
            });
        }
        else if (resolvedPublicKeyJwk.crv !== 'X25519') {
            throw new Error(`AgentDwnApi: keyAgreement key for '${didUri}' uses curve ` +
                `'${resolvedPublicKeyJwk.crv}', but DWN encryption requires ` +
                `'X25519' (or 'Ed25519' which is auto-converted).`);
        }
        // 5. Compute the KMS key URI (does NOT export the key)
        const keyUri = yield agent.keyManager.getKeyUri({ key: resolvedPublicKeyJwk });
        return {
            keyId: verificationMethod.id,
            keyUri,
            publicKeyJwk: resolvedPublicKeyJwk,
        };
    });
}
/**
 * Builds a KMS-backed key unwrap callback.
 *
 * @param agent - The platform agent with access to the key manager
 * @param keyId - The root key ID
 * @param keyUri - The KMS key URI
 * @param derivationScheme - The key derivation scheme
 */
export function buildKmsDecryptCallback(agent, keyId, keyUri, derivationScheme) {
    const keyManager = agent.keyManager;
    return {
        rootKeyId: keyId,
        derivationScheme,
        derivePublicKey: (fullDerivationPath) => __awaiter(this, void 0, void 0, function* () {
            return keyManager.derivePublicKey({
                keyUri,
                derivationPath: fullDerivationPath,
            });
        }),
        decrypt: (fullDerivationPath, keyUnwrapPayload) => __awaiter(this, void 0, void 0, function* () {
            return keyManager.unwrapContentKey({
                keyUri,
                derivationPath: fullDerivationPath,
                encryptedKey: keyUnwrapPayload.encryptedKey,
                ephemeralPublicKey: keyUnwrapPayload.ephemeralPublicKey,
            });
        }),
    };
}
/**
 * Constructs an EncryptionKeyDeriver callback for the SDK.
 * The SDK calls derivePublicKey(path), the KMS performs HKDF + public key
 * computation internally. The private key never leaves the KMS.
 *
 * Analogous to getSigner() for signing operations.
 *
 * @param agent - The platform agent
 * @param didUri - The DID URI to create the key deriver for
 * @returns An EncryptionKeyDeriver callback object
 */
export function getEncryptionKeyDeriver(agent, didUri) {
    return __awaiter(this, void 0, void 0, function* () {
        const { keyId, keyUri } = yield getEncryptionKeyInfo(agent, didUri);
        const keyManager = agent.keyManager;
        return {
            rootKeyId: keyId,
            derivationScheme: KeyDerivationScheme.ProtocolPath,
            derivePublicKey: (fullDerivationPath) => __awaiter(this, void 0, void 0, function* () {
                return keyManager.derivePublicKey({
                    keyUri,
                    derivationPath: fullDerivationPath,
                });
            }),
        };
    });
}
/**
 * Constructs a ProtocolPath KeyDecrypter.
 *
 * @param agent - The platform agent
 * @param didUri - The DID URI to create the key decrypter for
 * @returns A KeyDecrypter callback object
 */
export function getKeyDecrypter(agent, didUri) {
    return __awaiter(this, void 0, void 0, function* () {
        const { keyId, keyUri } = yield getEncryptionKeyInfo(agent, didUri);
        return buildKmsDecryptCallback(agent, keyId, keyUri, KeyDerivationScheme.ProtocolPath);
    });
}
/**
 * Builds a KeyDecrypter from a delivered protocol/path-derived private key.
 *
 * @param key - The delivered derived private key
 */
export function buildProtocolPathSubtreeDecrypter(key) {
    return {
        rootKeyId: key.rootKeyId,
        derivationScheme: key.derivationScheme,
        derivePublicKey: (fullDerivationPath) => __awaiter(this, void 0, void 0, function* () {
            const leafPrivateKeyBytes = yield Records.derivePrivateKey(key, fullDerivationPath);
            const leafPrivateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
            return yield X25519.getPublicKey({ key: leafPrivateKeyJwk });
        }),
        decrypt: (fullDerivationPath, keyUnwrapPayload) => __awaiter(this, void 0, void 0, function* () {
            const leafPrivateKeyBytes = yield Records.derivePrivateKey(key, fullDerivationPath);
            const leafPrivateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes: leafPrivateKeyBytes });
            return Encryption.unwrapKey(leafPrivateKeyJwk, keyUnwrapPayload.keyEncryption);
        }),
    };
}
export function buildFixedPrivateKeyDecrypter(params) {
    return {
        rootKeyId: params.keyId,
        derivationScheme: params.derivationScheme,
        derivePublicKey: () => __awaiter(this, void 0, void 0, function* () { return params.publicKeyJwk; }),
        decrypt: (_fullDerivationPath, keyUnwrapPayload) => __awaiter(this, void 0, void 0, function* () {
            return Encryption.unwrapKey(params.privateKeyJwk, keyUnwrapPayload.keyEncryption);
        }),
    };
}
/**
 * Creates durable grantKey records for read grants that carry encrypted protocol access.
 *
 * The record payload delivers the owner-derived ProtocolPath private key. The record
 * itself is encrypted to the grantee's own Encryption Protocol `grantKey` path.
 */
export function createGrantKeyRecordsForGrants(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const grantKeyRecords = [];
        const protocolDefinitions = new Map(((_a = params.protocolDefinitions) !== null && _a !== void 0 ? _a : []).map((definition) => [definition.protocol, definition]));
        const granteeGrantKeyPublicKey = yield derivePublicKeyFromPrivateKey(params.granteeRootPrivateKey, GRANT_KEY_DERIVATION_PATH);
        const granteeGrantKeyKeyId = yield Encryption.getKeyId(granteeGrantKeyPublicKey);
        for (const grantMessage of params.grantMessages) {
            const grant = DwnPermissionGrant.parse(grantMessage);
            if (!isGrantKeyEligibleGrant(grant)) {
                continue;
            }
            const payloads = yield buildGrantKeyPayloads(params.agent, params.ownerDid, grant, protocolDefinitions);
            for (const payload of payloads) {
                const payloadBytes = Encoder.objectToBytes(payload);
                const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256CTR;
                const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
                const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLength(contentEncryptionAlgorithm)));
                const encryptionInput = buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, granteeGrantKeyKeyId, granteeGrantKeyPublicKey, KeyDerivationScheme.ProtocolPath);
                const { encryptedBytes, dataCid, dataSize } = yield encryptAndComputeCid(payloadBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
                const { reply, message } = yield params.agent.processDwnRequest({
                    author: params.ownerDid,
                    target: params.ownerDid,
                    messageType: DwnInterface.RecordsWrite,
                    messageParams: {
                        recipient: params.granteeDid,
                        protocol: EncryptionProtocol.uri,
                        protocolPath: EncryptionProtocol.grantKeyPath,
                        schema: EncryptionProtocol.definition.types.grantKey.schema,
                        dataFormat: 'application/json',
                        dataCid,
                        dataSize,
                        encryptionInput,
                        tags: Object.assign(Object.assign({ grantId: grant.id, protocol: payload.scope.protocol }, (payload.scope.protocolPath ? { protocolPath: payload.scope.protocolPath } : {})), { keyId: payload.keyMaterial.keyId }),
                    },
                    dataStream: DataStream.fromBytes(encryptedBytes),
                });
                if (reply.status.code !== 202 && reply.status.code !== 409) {
                    throw new Error(`AgentDwnApi: Failed to create grantKey record: ${reply.status.detail}`);
                }
                grantKeyRecords.push(Object.assign(Object.assign({}, message), { encodedData: Encoder.bytesToBase64Url(encryptedBytes) }));
            }
        }
        return grantKeyRecords;
    });
}
/**
 * Writes a durable audience record whose plaintext payload seals the audience private key to the tenant role-path key.
 */
export function createAudienceRecord(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const audienceKey = (_a = params.audienceKey) !== null && _a !== void 0 ? _a : yield generateAudienceKey({
            contextId: params.contextId,
            protocol: params.protocol,
            rolePath: params.rolePath,
        });
        const { keyMaterial } = audienceKey;
        const { keyId, privateKeyJwk, publicKeyJwk } = keyMaterial;
        const sealedPrivateKey = yield sealAudiencePrivateKey({
            audienceKeyId: keyId,
            contextId: params.contextId,
            privateKeyJwk,
            protocol: params.protocol,
            rolePath: params.rolePath,
            sealingPublicKey: params.sealingPublicKey,
        });
        const payload = {
            protocol: params.protocol,
            rolePath: params.rolePath,
            contextId: params.contextId,
            keyId,
            publicKeyJwk,
            sealedPrivateKey,
        };
        const payloadBytes = Encoder.objectToBytes(payload);
        const { reply, message } = yield params.agent.processDwnRequest({
            author: params.authorDid,
            granteeDid: params.granteeDid,
            target: params.sourceDid,
            messageType: DwnInterface.RecordsWrite,
            messageParams: {
                delegatedGrant: params.delegatedGrant,
                permissionGrantId: params.permissionGrantId,
                protocol: params.protocol,
                protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
                protocolRole: params.protocolRole,
                schema: ENCRYPTION_CONTROL_AUDIENCE_SCHEMA_URI,
                dataFormat: 'application/json',
                dataSize: payloadBytes.length,
                tags: {
                    protocol: params.protocol,
                    rolePath: params.rolePath,
                    contextId: params.contextId,
                    keyId,
                },
            },
            dataStream: DataStream.fromBytes(payloadBytes),
        });
        if (reply.status.code !== 202 && reply.status.code !== 409) {
            throw new Error(`AgentDwnApi: Failed to create audience record: ${reply.status.detail}`);
        }
        return {
            audienceKey,
            message: Object.assign(Object.assign({}, message), { encodedData: Encoder.bytesToBase64Url(payloadBytes) }),
            payload,
        };
    });
}
/**
 * Generates fresh random role-audience key material for one source-protocol audience tuple.
 */
export function generateAudienceKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const privateKeyJwk = yield X25519.generateKey();
        const publicKeyJwk = yield X25519.getPublicKey({ key: privateKeyJwk });
        const keyId = yield Encryption.getKeyId(publicKeyJwk);
        const keyMaterial = {
            algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
            derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
            keyId,
            privateKeyJwk,
            publicKeyJwk,
        };
        return {
            protocol: params.protocol,
            rolePath: params.rolePath,
            contextId: params.contextId,
            keyId,
            keyMaterial,
        };
    });
}
/**
 * Writes a durable encrypted delivery record that wraps audience private key material to one recipient.
 */
export function createAudienceDeliveryRecord(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const payload = {
            protocol: params.audienceKey.protocol,
            rolePath: params.audienceKey.rolePath,
            contextId: params.audienceKey.contextId,
            keyId: params.audienceKey.keyId,
            keyMaterial: params.audienceKey.keyMaterial,
        };
        const payloadBytes = Encoder.objectToBytes(payload);
        const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256CTR;
        const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
        const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLength(contentEncryptionAlgorithm)));
        const recipientRoleKeyId = yield Encryption.getKeyId(params.recipientRolePublicKey);
        const encryptionInput = buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, recipientRoleKeyId, params.recipientRolePublicKey, KeyDerivationScheme.ProtocolPath);
        const { encryptedBytes, dataCid, dataSize } = yield encryptAndComputeCid(payloadBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
        const { reply, message } = yield params.agent.processDwnRequest({
            author: params.authorDid,
            granteeDid: params.granteeDid,
            target: params.sourceDid,
            messageType: DwnInterface.RecordsWrite,
            messageParams: {
                delegatedGrant: params.delegatedGrant,
                permissionGrantId: params.permissionGrantId,
                recipient: params.recipientDid,
                protocol: params.audienceKey.protocol,
                protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
                protocolRole: params.protocolRole,
                schema: ENCRYPTION_CONTROL_DELIVERY_SCHEMA_URI,
                dataFormat: 'application/json',
                dataCid,
                dataSize,
                encryptionInput,
                tags: {
                    protocol: params.audienceKey.protocol,
                    rolePath: params.audienceKey.rolePath,
                    contextId: params.audienceKey.contextId,
                    keyId: params.audienceKey.keyId,
                    recipientAuthority: params.recipientAuthority,
                },
            },
            dataStream: DataStream.fromBytes(encryptedBytes),
        });
        if (reply.status.code !== 202 && reply.status.code !== 409) {
            throw new Error(`AgentDwnApi: Failed to create audience delivery record: ${reply.status.detail}`);
        }
        return Object.assign(Object.assign({}, message), { encodedData: Encoder.bytesToBase64Url(encryptedBytes) });
    });
}
/**
 * Opens an audience seal and verifies the recovered private key against the audience record payload.
 */
export function unsealAudienceKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const privateKeyBytes = yield unwrapAudienceSeal({
            audienceKeyId: params.payload.keyId,
            contextId: params.payload.contextId,
            protocol: params.payload.protocol,
            rolePath: params.payload.rolePath,
            seal: params.payload.sealedPrivateKey,
            sealingPrivateKey: params.sealingPrivateKey,
        });
        const privateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes });
        const keyMaterial = {
            algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
            derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
            keyId: params.payload.keyId,
            privateKeyJwk,
            publicKeyJwk: params.payload.publicKeyJwk,
        };
        yield verifyAudienceKeyMaterial({
            keyMaterial,
            payload: {
                contextId: params.payload.contextId,
                keyId: params.payload.keyId,
                protocol: params.payload.protocol,
                rolePath: params.payload.rolePath,
            },
            publicKeyJwk: params.payload.publicKeyJwk,
        });
        return keyMaterial;
    });
}
/**
 * Verifies that delivered or unsealed audience key material matches the accepted audience record.
 */
export function verifyAudienceKeyMaterial(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const publicKeyId = yield Encryption.getKeyId(params.keyMaterial.publicKeyJwk);
        const publicKeyFromPrivate = yield X25519.getPublicKey({ key: params.keyMaterial.privateKeyJwk });
        const privateKeyId = yield Encryption.getKeyId(publicKeyFromPrivate);
        if (params.keyMaterial.algorithm !== KeyAgreementAlgorithm.X25519HkdfSha256A256Kw ||
            params.keyMaterial.derivationScheme !== ROLE_AUDIENCE_DERIVATION_SCHEME ||
            params.keyMaterial.keyId !== params.payload.keyId ||
            params.keyMaterial.keyId !== publicKeyId ||
            params.keyMaterial.keyId !== privateKeyId ||
            !isPublicKeyJwkEqual(params.keyMaterial.publicKeyJwk, params.publicKeyJwk) ||
            !isPublicKeyJwkEqual(publicKeyFromPrivate, params.publicKeyJwk)) {
            throw new Error('audience key material does not match the audience record.');
        }
    });
}
function sealAudiencePrivateKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        return Encryption.wrapSeal({
            privateKeyBytes: yield X25519.privateKeyToBytes({ privateKey: params.privateKeyJwk }),
            keyInput: {
                algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
                audienceKeyId: params.audienceKeyId,
                contextId: params.contextId,
                derivationScheme: SEAL_DERIVATION_SCHEME,
                keyId: yield Encryption.getKeyId(params.sealingPublicKey),
                protocol: params.protocol,
                publicKey: params.sealingPublicKey,
                rolePath: params.rolePath,
            },
        });
    });
}
function unwrapAudienceSeal(params) {
    return __awaiter(this, void 0, void 0, function* () {
        return Encryption.unwrapSeal({
            audienceKeyId: params.audienceKeyId,
            contextId: params.contextId,
            protocol: params.protocol,
            recipientPrivateKey: params.sealingPrivateKey,
            rolePath: params.rolePath,
            seal: params.seal,
        });
    });
}
/**
 * Resolves the appropriate KeyDecrypter for a record's encryption scheme.
 *
 * Owners derive protocol-path keys directly from KMS. Delegates use delivered
 * protocol-wide or path-subtree decryption keys when available.
 *
 * @param params - Decryption resolution inputs.
 */
export function resolveKeyDecrypter(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const { agent, audienceDecryptionKeyCache, authorDid, delegatedGrant, delegateDecryptionKeyCache, granteeDid, recordsWrite, targetDid, } = params;
        if (granteeDid !== undefined) {
            const protocol = recordsWrite.descriptor.protocol;
            const protocolPath = recordsWrite.descriptor.protocolPath;
            if (protocol && targetDid !== undefined) {
                const cacheKey = `ddk~${granteeDid}`;
                const cachedKey = findCoveringDelegateKey(delegateDecryptionKeyCache === null || delegateDecryptionKeyCache === void 0 ? void 0 : delegateDecryptionKeyCache.get(cacheKey), targetDid, protocol, protocolPath);
                if (cachedKey !== undefined) {
                    return buildProtocolPathSubtreeDecrypter(cachedKey.derivedPrivateKey);
                }
                if ((delegateDecryptionKeyCache === null || delegateDecryptionKeyCache === void 0 ? void 0 : delegateDecryptionKeyCache.set) !== undefined) {
                    const hydratedKeys = yield resolveGrantKeyRecords({
                        agent,
                        grantorDid: targetDid,
                        granteeDid,
                        protocol,
                        protocolPath,
                    });
                    if (hydratedKeys.length > 0) {
                        const mergedKeys = mergeDelegateDecryptionKeys((_a = delegateDecryptionKeyCache.get(cacheKey)) !== null && _a !== void 0 ? _a : [], hydratedKeys);
                        delegateDecryptionKeyCache.set(cacheKey, mergedKeys);
                        const hydratedKey = findCoveringDelegateKey(mergedKeys, targetDid, protocol, protocolPath);
                        if (hydratedKey !== undefined) {
                            return buildProtocolPathSubtreeDecrypter(hydratedKey.derivedPrivateKey);
                        }
                    }
                }
            }
            const audienceDecrypter = yield resolveRoleAudienceDecrypter({
                agent,
                sourceDid: targetDid,
                recipientDid: authorDid,
                granteeDid,
                delegatedGrant,
                recordsWrite,
                delegateDecryptionKeyCache,
                audienceDecryptionKeyCache,
            });
            if (audienceDecrypter !== undefined) {
                return audienceDecrypter;
            }
            throw new Error(`AgentDwnApi: no delivered decryption key covers encrypted record ` +
                `'${recordsWrite.recordId}' for delegate '${granteeDid}'.`);
        }
        if (targetDid !== undefined && targetDid !== authorDid) {
            const audienceDecrypter = yield resolveRoleAudienceDecrypter({
                agent,
                sourceDid: targetDid,
                recipientDid: authorDid,
                delegatedGrant,
                recordsWrite,
                audienceDecryptionKeyCache,
            });
            if (audienceDecrypter !== undefined) {
                return audienceDecrypter;
            }
        }
        return getKeyDecrypter(agent, authorDid);
    });
}
/**
 * Post-processes a DWN reply, auto-decrypting data if encryption is enabled.
 * Delegates to the SDK's Records.decrypt() with the appropriate KeyDecrypter —
 * resolveKeyDecrypter() selects either a delivered delegate key or KMS.
 *
 * @param request - The original DWN request
 * @param reply - The DWN reply to process
 * @param agent - The platform agent
 * @param delegateDecryptionKeyCache - Cache for scope-aware delegate decryption keys
 */
export function maybeDecryptReply(request, reply, agent, delegateDecryptionKeyCache, audienceDecryptionKeyCache) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b, _c;
        if (!('encryption' in request) || !request.encryption) {
            return;
        }
        // `request` here is the encrypted-write variant — `'encryption' in` has
        // eliminated the `{ messageCid }`-only arm of `SendDwnRequest`, leaving
        // the `ProcessDwnRequest<T>` shape which declares the optional
        // `granteeDid` field. Narrow once at the top so neither branch below
        // needs to repeat the cast.
        const encryptedRequest = request;
        const granteeDid = encryptedRequest.granteeDid;
        const delegatedGrant = getDelegatedGrantFromRequest(encryptedRequest);
        // Auto-decrypt RecordsRead replies
        if (isDwnRequest(encryptedRequest, DwnInterface.RecordsRead)) {
            const readReply = reply;
            if (readReply.status.code === 200
                && ((_b = (_a = readReply.entry) === null || _a === void 0 ? void 0 : _a.recordsWrite) === null || _b === void 0 ? void 0 : _b.encryption)
                && ((_c = readReply.entry) === null || _c === void 0 ? void 0 : _c.data)
                && !isEncryptionControlPath(readReply.entry.recordsWrite.descriptor.protocolPath)) {
                const keyDecrypter = yield resolveKeyDecrypter({
                    agent,
                    audienceDecryptionKeyCache,
                    authorDid: encryptedRequest.author,
                    delegatedGrant,
                    delegateDecryptionKeyCache,
                    granteeDid,
                    recordsWrite: readReply.entry.recordsWrite,
                    targetDid: encryptedRequest.target,
                });
                try {
                    readReply.entry.data = yield Records.decrypt(readReply.entry.recordsWrite, keyDecrypter, readReply.entry.data);
                }
                catch (error) {
                    throw new Error(`AgentDwnApi: Failed to decrypt record ` +
                        `'${readReply.entry.recordsWrite.recordId}'. ` +
                        `Original error: ${error.message}`);
                }
            }
        }
        // Auto-decrypt RecordsQuery replies (small records inline as encodedData)
        if (isDwnRequest(encryptedRequest, DwnInterface.RecordsQuery)) {
            const queryReply = reply;
            if (queryReply.status.code === 200 && queryReply.entries) {
                for (const entry of queryReply.entries) {
                    if (entry.encryption && entry.encodedData && !isEncryptionControlPath(entry.descriptor.protocolPath)) {
                        const keyDecrypter = yield resolveKeyDecrypter({
                            agent,
                            audienceDecryptionKeyCache,
                            authorDid: encryptedRequest.author,
                            delegatedGrant,
                            delegateDecryptionKeyCache,
                            granteeDid,
                            recordsWrite: entry,
                            targetDid: encryptedRequest.target,
                        });
                        try {
                            const cipherBytes = Encoder.base64UrlToBytes(entry.encodedData);
                            const cipherStream = DataStream.fromBytes(cipherBytes);
                            const plainStream = yield Records.decrypt(entry, keyDecrypter, cipherStream);
                            const plainBytes = yield DataStream.toBytes(plainStream);
                            entry.encodedData = Encoder.bytesToBase64Url(plainBytes);
                        }
                        catch (error) {
                            throw new Error(`AgentDwnApi: Failed to decrypt record ` +
                                `'${entry.recordId}'. Original error: ${error.message}`);
                        }
                    }
                }
            }
        }
    });
}
function getDelegatedGrantFromRequest(request) {
    const messageParams = request.messageParams;
    return messageParams === null || messageParams === void 0 ? void 0 : messageParams.delegatedGrant;
}
function resolveRoleAudienceDecrypter(params) {
    return __awaiter(this, void 0, void 0, function* () {
        if (params.sourceDid === undefined || params.recordsWrite.encryption === undefined) {
            return undefined;
        }
        const roleAudienceEntries = params.recordsWrite.encryption.keyEncryption.filter((entry) => entry.derivationScheme === ROLE_AUDIENCE_DERIVATION_SCHEME && 'rolePath' in entry);
        for (const entry of roleAudienceEntries) {
            const contextId = getRoleAudienceContextId(entry.rolePath, params.recordsWrite.contextId);
            if (contextId === undefined) {
                continue;
            }
            const cachedKey = getAudienceKeyFromMemoryCache({
                audienceDecryptionKeyCache: params.audienceDecryptionKeyCache,
                sourceDid: params.sourceDid,
                recipientDid: params.recipientDid,
                protocol: entry.protocol,
                contextId,
                rolePath: entry.rolePath,
                keyId: entry.keyId,
            });
            if (cachedKey !== undefined) {
                return buildAudienceContentDecrypter(cachedKey);
            }
            const hydratedKey = yield hydrateAudienceKey({
                agent: params.agent,
                sourceDid: params.sourceDid,
                recipientDid: params.recipientDid,
                granteeDid: params.granteeDid,
                delegatedGrant: params.delegatedGrant,
                delegateDecryptionKeyCache: params.delegateDecryptionKeyCache,
                protocol: entry.protocol,
                contextId,
                rolePath: entry.rolePath,
                keyId: entry.keyId,
            });
            if (hydratedKey !== undefined) {
                putAudienceKeyInMemoryCache({
                    audienceDecryptionKeyCache: params.audienceDecryptionKeyCache,
                    entry: hydratedKey,
                });
                return buildAudienceContentDecrypter(hydratedKey);
            }
        }
        return undefined;
    });
}
function buildAudienceContentDecrypter(entry) {
    return buildFixedPrivateKeyDecrypter({
        derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
        keyId: entry.keyMaterial.keyId,
        privateKeyJwk: entry.keyMaterial.privateKeyJwk,
        publicKeyJwk: entry.keyMaterial.publicKeyJwk,
    });
}
export function resolveAudienceDecryptionKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const cached = getAudienceKeyFromMemoryCache(params);
        if (cached !== undefined) {
            return cached;
        }
        const hydrated = yield hydrateAudienceKey(params);
        if (hydrated !== undefined) {
            putAudienceKeyInMemoryCache({
                audienceDecryptionKeyCache: params.audienceDecryptionKeyCache,
                entry: hydrated,
            });
            return hydrated;
        }
        return undefined;
    });
}
function hydrateAudienceKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const audienceRecord = yield fetchAudienceRecord({
            agent: params.agent,
            authorDid: (_a = params.granteeDid) !== null && _a !== void 0 ? _a : params.recipientDid,
            contextId: params.contextId,
            keyId: params.keyId,
            protocol: params.protocol,
            rolePath: params.rolePath,
            sourceDid: params.sourceDid,
        });
        if (audienceRecord === undefined) {
            return undefined;
        }
        const sealedKey = yield hydrateAudienceKeyFromSeal(params, audienceRecord);
        if (sealedKey !== undefined) {
            return sealedKey;
        }
        const deliveryReadActor = getAudienceDeliveryReadActor(params);
        const deliveryMessages = yield queryAudienceDeliveryMessages(params, deliveryReadActor);
        if (deliveryMessages.length === 0) {
            return undefined;
        }
        const deliveryDecrypters = yield buildAudienceDeliveryDecrypters({
            agent: params.agent,
            recipientDid: params.recipientDid,
            granteeDid: params.granteeDid,
            delegateDecryptionKeyCache: params.delegateDecryptionKeyCache,
            protocol: params.protocol,
            rolePath: params.rolePath,
        });
        if (deliveryDecrypters.length === 0) {
            return undefined;
        }
        return hydrateAudienceKeyFromDeliveries({
            audienceRecord,
            deliveryDecrypters,
            deliveryMessages,
            deliveryReadActor,
            params,
        });
    });
}
function hydrateAudienceKeyFromSeal(params, audienceRecord) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            return yield tryUnsealAudienceKey(Object.assign(Object.assign({}, params), { audiencePayload: audienceRecord.payload }));
        }
        catch (error) {
            logger.log(`AgentDwnApi: skipped audience seal '${audienceRecord.message.recordId}' while resolving role-audience key: ` +
                `${error instanceof Error ? error.message : String(error)}`);
        }
        return undefined;
    });
}
function queryAudienceDeliveryMessages(params, deliveryReadActor) {
    return __awaiter(this, void 0, void 0, function* () {
        const { reply } = yield processDwnRequestWithRemoteFallback(params.agent, {
            author: deliveryReadActor.authorDid,
            granteeDid: deliveryReadActor.granteeDid,
            target: params.sourceDid,
            messageType: DwnInterface.RecordsQuery,
            messageParams: {
                delegatedGrant: deliveryReadActor.delegatedGrant,
                filter: {
                    recipient: params.recipientDid,
                    protocol: params.protocol,
                    protocolPath: ENCRYPTION_CONTROL_DELIVERY_PATH,
                    tags: {
                        protocol: params.protocol,
                        rolePath: params.rolePath,
                        contextId: params.contextId,
                        keyId: params.keyId,
                    },
                },
            },
        }, hasRecordsQueryEntries);
        if (reply.status.code !== 200 || reply.entries === undefined || reply.entries.length === 0) {
            return [];
        }
        return reply.entries;
    });
}
function hydrateAudienceKeyFromDeliveries(input) {
    return __awaiter(this, void 0, void 0, function* () {
        for (const deliveryMessage of input.deliveryMessages) {
            const hydrated = yield hydrateAudienceKeyFromDelivery({
                audienceRecord: input.audienceRecord,
                deliveryDecrypters: input.deliveryDecrypters,
                deliveryMessage,
                deliveryReadActor: input.deliveryReadActor,
                params: input.params,
            });
            if (hydrated !== undefined) {
                return hydrated;
            }
        }
        return undefined;
    });
}
function hydrateAudienceKeyFromDelivery(input) {
    return __awaiter(this, void 0, void 0, function* () {
        const { audienceRecord, deliveryDecrypters, deliveryMessage, deliveryReadActor, params } = input;
        const encryptedData = yield getAudienceDeliveryEncryptedData(params.agent, deliveryReadActor, params.sourceDid, deliveryMessage);
        if (encryptedData === undefined) {
            return undefined;
        }
        for (const decrypter of deliveryDecrypters) {
            try {
                return yield decryptAudienceDelivery({
                    audienceRecord,
                    decrypter,
                    deliveryMessage,
                    deliveryReadActor,
                    encryptedData,
                    params,
                });
            }
            catch (error) {
                logger.log(`AgentDwnApi: skipped audience delivery '${deliveryMessage.recordId}' while resolving role-audience key: ` +
                    `${error instanceof Error ? error.message : String(error)}`);
            }
        }
        return undefined;
    });
}
function decryptAudienceDelivery(input) {
    return __awaiter(this, void 0, void 0, function* () {
        const { audienceRecord, decrypter, deliveryMessage, encryptedData, params } = input;
        const decryptedStream = yield Records.decrypt(deliveryMessage, decrypter, DataStream.fromBytes(encryptedData));
        const payload = Encoder.bytesToObject(yield DataStream.toBytes(decryptedStream));
        yield verifyAudienceKeyPayload({
            agent: params.agent,
            audiencePayload: audienceRecord.payload,
            deliveryMessage,
            deliveryReadActor: input.deliveryReadActor,
            payload,
            sourceDid: params.sourceDid,
            recipientDid: params.recipientDid,
            protocol: params.protocol,
            contextId: params.contextId,
            rolePath: params.rolePath,
            keyId: params.keyId,
        });
        return {
            contextId: payload.contextId,
            keyMaterial: payload.keyMaterial,
            protocol: payload.protocol,
            recipientDid: params.recipientDid,
            rolePath: payload.rolePath,
            sourceDid: params.sourceDid,
        };
    });
}
function getAudienceDeliveryReadActor(params) {
    var _a;
    if (params.granteeDid !== undefined &&
        params.granteeDid !== params.recipientDid &&
        params.delegatedGrant !== undefined) {
        return {
            authorDid: params.recipientDid,
            delegatedGrant: params.delegatedGrant,
            granteeDid: params.granteeDid,
        };
    }
    return { authorDid: (_a = params.granteeDid) !== null && _a !== void 0 ? _a : params.recipientDid };
}
function getAudienceDeliveryEncryptedData(agent, actor, sourceDid, deliveryMessage) {
    return __awaiter(this, void 0, void 0, function* () {
        return readRecordDataWithRemoteFallback({
            agent,
            authorDid: actor.authorDid,
            delegatedGrant: actor.delegatedGrant,
            encodedData: deliveryMessage.encodedData,
            granteeDid: actor.granteeDid,
            recordId: deliveryMessage.recordId,
            targetDid: sourceDid,
        });
    });
}
function buildAudienceDeliveryDecrypters(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const decrypters = [];
        const decrypter = yield buildRecipientProtocolPathDecrypter(Object.assign(Object.assign({}, params), { protocolPath: params.rolePath }));
        if (decrypter !== undefined) {
            decrypters.push(decrypter);
        }
        return decrypters;
    });
}
/**
 * Returns whether the actor can derive the tenant role-path private key that opens audience seals.
 */
export function hasAudienceSealCoverage(params) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const sealingPrivateKey = yield getRecipientProtocolPathPrivateKey({
                agent: params.agent,
                delegateDecryptionKeyCache: params.delegateDecryptionKeyCache,
                derivationPath: getScopeDerivationPath(params.protocol, params.rolePath),
                granteeDid: params.granteeDid,
                protocol: params.protocol,
                protocolPath: params.rolePath,
                recipientDid: params.sourceDid,
            });
            return sealingPrivateKey !== undefined;
        }
        catch (_a) {
            return false;
        }
    });
}
function buildRecipientProtocolPathDecrypter(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const derivationPath = getScopeDerivationPath(params.protocol, params.protocolPath);
        const privateKeyJwk = yield getRecipientProtocolPathPrivateKey(Object.assign(Object.assign({}, params), { derivationPath }));
        if (privateKeyJwk === undefined) {
            return undefined;
        }
        const publicKeyJwk = yield X25519.getPublicKey({ key: privateKeyJwk });
        return buildFixedPrivateKeyDecrypter({
            derivationScheme: KeyDerivationScheme.ProtocolPath,
            keyId: yield Encryption.getKeyId(publicKeyJwk),
            privateKeyJwk,
            publicKeyJwk,
        });
    });
}
function getRecipientProtocolPathPrivateKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b, _c;
        if (params.granteeDid === undefined || params.recipientDid === params.granteeDid) {
            const { keyUri } = yield getEncryptionKeyInfo(params.agent, params.recipientDid);
            const privateKeyBytes = yield params.agent.keyManager.derivePrivateKeyBytes({
                keyUri,
                derivationPath: params.derivationPath,
            });
            return X25519.bytesToPrivateKey({ privateKeyBytes });
        }
        const cacheKey = `ddk~${params.granteeDid}`;
        let delegateKeys = (_b = (_a = params.delegateDecryptionKeyCache) === null || _a === void 0 ? void 0 : _a.get(cacheKey)) !== null && _b !== void 0 ? _b : [];
        let coveringKey = findCoveringDelegateKey(delegateKeys, params.recipientDid, params.protocol, params.protocolPath);
        if (coveringKey === undefined && ((_c = params.delegateDecryptionKeyCache) === null || _c === void 0 ? void 0 : _c.set) !== undefined) {
            const hydratedKeys = yield resolveGrantKeyRecords({
                agent: params.agent,
                grantorDid: params.recipientDid,
                granteeDid: params.granteeDid,
                protocol: params.protocol,
                protocolPath: params.protocolPath,
            });
            delegateKeys = mergeDelegateDecryptionKeys(delegateKeys, hydratedKeys);
            params.delegateDecryptionKeyCache.set(cacheKey, delegateKeys);
            coveringKey = findCoveringDelegateKey(delegateKeys, params.recipientDid, params.protocol, params.protocolPath);
        }
        if (coveringKey === undefined) {
            return undefined;
        }
        const privateKeyBytes = yield Records.derivePrivateKey(coveringKey.derivedPrivateKey, params.derivationPath);
        return X25519.bytesToPrivateKey({ privateKeyBytes });
    });
}
function tryUnsealAudienceKey(params) {
    return __awaiter(this, void 0, void 0, function* () {
        if (params.recipientDid !== params.sourceDid && params.granteeDid === undefined) {
            return undefined;
        }
        const sealingPrivateKey = yield getRecipientProtocolPathPrivateKey({
            agent: params.agent,
            delegateDecryptionKeyCache: params.delegateDecryptionKeyCache,
            derivationPath: getScopeDerivationPath(params.protocol, params.rolePath),
            granteeDid: params.granteeDid,
            protocol: params.protocol,
            protocolPath: params.rolePath,
            recipientDid: params.sourceDid,
        });
        if (sealingPrivateKey === undefined) {
            return undefined;
        }
        const keyMaterial = yield unsealAudienceKey({
            payload: params.audiencePayload,
            sealingPrivateKey,
        });
        return {
            contextId: params.contextId,
            keyMaterial,
            protocol: params.protocol,
            recipientDid: params.recipientDid,
            rolePath: params.rolePath,
            sourceDid: params.sourceDid,
        };
    });
}
function fetchAudienceRecord(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const { reply } = yield processDwnRequestWithRemoteFallback(params.agent, {
            author: params.authorDid,
            target: params.sourceDid,
            messageType: DwnInterface.RecordsQuery,
            messageParams: {
                filter: {
                    protocol: params.protocol,
                    protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
                    tags: {
                        protocol: params.protocol,
                        rolePath: params.rolePath,
                        contextId: params.contextId,
                        keyId: params.keyId,
                    },
                },
            },
        }, hasRecordsQueryEntries);
        if (reply.status.code !== 200 || reply.entries === undefined || reply.entries.length === 0) {
            return undefined;
        }
        for (const entry of reply.entries) {
            const audienceMessage = entry;
            const dataBytes = yield getAudienceRecordData(params.agent, params.authorDid, params.sourceDid, audienceMessage);
            if (dataBytes === undefined) {
                continue;
            }
            const payload = Encoder.bytesToObject(dataBytes);
            if (audiencePayloadMatches(payload, audienceMessage, params)) {
                return { message: audienceMessage, payload };
            }
        }
        return undefined;
    });
}
function getAudienceRecordData(agent, authorDid, sourceDid, audienceMessage) {
    return __awaiter(this, void 0, void 0, function* () {
        return readRecordDataWithRemoteFallback({
            agent,
            authorDid,
            encodedData: audienceMessage.encodedData,
            recordId: audienceMessage.recordId,
            targetDid: sourceDid,
        });
    });
}
function audiencePayloadMatches(payload, audienceMessage, expected) {
    var _a;
    const tags = (_a = audienceMessage.descriptor.tags) !== null && _a !== void 0 ? _a : {};
    return payload.protocol === expected.protocol &&
        payload.rolePath === expected.rolePath &&
        payload.contextId === expected.contextId &&
        payload.keyId === expected.keyId &&
        payload.protocol === tags.protocol &&
        payload.rolePath === tags.rolePath &&
        payload.contextId === tags.contextId &&
        payload.keyId === tags.keyId &&
        isObject(payload.publicKeyJwk) &&
        isObject(payload.sealedPrivateKey);
}
function verifyAudienceKeyPayload(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const { payload, deliveryMessage } = params;
        const tags = (_a = deliveryMessage.descriptor.tags) !== null && _a !== void 0 ? _a : {};
        assertAudienceKeyPayload(payload);
        const keyMaterial = payload.keyMaterial;
        if (deliveryMessage.descriptor.recipient !== params.recipientDid ||
            payload.protocol !== tags.protocol ||
            payload.rolePath !== tags.rolePath ||
            payload.contextId !== tags.contextId ||
            keyMaterial.keyId !== tags.keyId ||
            payload.protocol !== params.protocol ||
            payload.contextId !== params.contextId ||
            payload.rolePath !== params.rolePath ||
            keyMaterial.keyId !== params.keyId) {
            throw new Error('audience delivery payload does not match record tags.');
        }
        yield verifyAudienceKeyMaterial({
            keyMaterial,
            payload: {
                contextId: params.contextId,
                keyId: params.keyId,
                protocol: params.protocol,
                rolePath: params.rolePath,
            },
            publicKeyJwk: params.audiencePayload.publicKeyJwk,
        });
        yield verifyAudienceKeyRecipientAuthority(params);
    });
}
function assertAudienceKeyPayload(payload) {
    if (!isObject(payload) ||
        typeof payload.protocol !== 'string' ||
        typeof payload.contextId !== 'string' ||
        typeof payload.rolePath !== 'string' ||
        typeof payload.keyId !== 'string' ||
        !isRoleAudienceKeyMaterial(payload.keyMaterial)) {
        throw new Error('audience delivery payload is malformed.');
    }
}
function assertGrantKeyPayload(payload) {
    if (!isObject(payload) ||
        typeof payload.grantId !== 'string' ||
        !isObject(payload.scope) ||
        payload.scope.scheme !== KeyDerivationScheme.ProtocolPath ||
        typeof payload.scope.protocol !== 'string' ||
        (payload.scope.protocolPath !== undefined && typeof payload.scope.protocolPath !== 'string') ||
        !isProtocolPathKeyMaterial(payload.keyMaterial)) {
        throw new Error('grantKey payload is malformed.');
    }
}
function isProtocolPathKeyMaterial(value) {
    if (!isX25519KeyMaterial(value) || value.derivationScheme !== KeyDerivationScheme.ProtocolPath) {
        return false;
    }
    return Array.isArray(value.derivationPath) &&
        value.derivationPath.every((segment) => typeof segment === 'string');
}
function isRoleAudienceKeyMaterial(value) {
    return isX25519KeyMaterial(value) &&
        value.derivationScheme === ROLE_AUDIENCE_DERIVATION_SCHEME;
}
function isX25519KeyMaterial(value) {
    return isObject(value) &&
        value.algorithm === KeyAgreementAlgorithm.X25519HkdfSha256A256Kw &&
        typeof value.derivationScheme === 'string' &&
        typeof value.keyId === 'string' &&
        isObject(value.publicKeyJwk) &&
        isObject(value.privateKeyJwk);
}
function verifyAudienceKeyRoleAssignment(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const contextIdPrefix = getRoleContextPrefix(params.rolePath, params.contextId);
        if (contextIdPrefix === undefined && params.rolePath.includes('/')) {
            throw new Error('audience delivery recipient is not an active holder of the referenced role.');
        }
        const { reply } = yield processDwnRequestWithRemoteFallback(params.agent, {
            author: params.deliveryReadActor.authorDid,
            granteeDid: params.deliveryReadActor.granteeDid,
            target: params.sourceDid,
            messageType: DwnInterface.RecordsQuery,
            messageParams: {
                delegatedGrant: params.deliveryReadActor.delegatedGrant,
                filter: Object.assign(Object.assign({}, (contextIdPrefix === undefined ? {} : { contextId: contextIdPrefix })), { recipient: params.recipientDid, protocol: params.protocol, protocolPath: params.rolePath }),
            },
        }, hasRecordsQueryEntries);
        const entries = reply.status.code === 200 ? (_a = reply.entries) !== null && _a !== void 0 ? _a : [] : [];
        const hasRoleRecord = entries.some((entry) => {
            const roleRecord = entry;
            return roleRecord.descriptor.recipient === params.recipientDid &&
                roleRecord.descriptor.protocol === params.protocol &&
                roleRecord.descriptor.protocolPath === params.rolePath &&
                matchesContextIdPrefix(roleRecord.contextId, contextIdPrefix);
        });
        if (!hasRoleRecord) {
            throw new Error('audience delivery recipient is not an active holder of the referenced role.');
        }
    });
}
function verifyAudienceKeyRecipientAuthority(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const tags = (_a = params.deliveryMessage.descriptor.tags) !== null && _a !== void 0 ? _a : {};
        const recipientAuthority = tags.recipientAuthority;
        if (recipientAuthority !== EncryptionControlDeliveryRecipientAuthority.RoleHolder) {
            throw new Error('audience delivery recipient authority is invalid.');
        }
        yield verifyAudienceKeyRoleAssignment(params);
    });
}
function processDwnRequestWithRemoteFallback(agent, request, hasUsableReply) {
    return __awaiter(this, void 0, void 0, function* () {
        return processDwnReadThrough({
            process: agent.processDwnRequest.bind(agent),
            send: agent.sendDwnRequest.bind(agent),
        }, request, hasUsableReply);
    });
}
function hasRecordsQueryEntries(reply) {
    return reply.status.code === 200 && reply.entries !== undefined && reply.entries.length > 0;
}
function hasRecordsReadData(reply) {
    var _a;
    return reply.status.code === 200 && ((_a = reply.entry) === null || _a === void 0 ? void 0 : _a.data) !== undefined;
}
function readRecordDataWithRemoteFallback(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        if (params.encodedData !== undefined) {
            return Encoder.base64UrlToBytes(params.encodedData);
        }
        const { reply } = yield processDwnRequestWithRemoteFallback(params.agent, {
            author: params.authorDid,
            granteeDid: params.granteeDid,
            target: params.targetDid,
            messageType: DwnInterface.RecordsRead,
            messageParams: {
                delegatedGrant: params.delegatedGrant,
                filter: { recordId: params.recordId },
            },
        }, hasRecordsReadData);
        if (reply.status.code !== 200 || ((_a = reply.entry) === null || _a === void 0 ? void 0 : _a.data) === undefined) {
            return undefined;
        }
        return DataStream.toBytes(reply.entry.data);
    });
}
function hasProtocolsQueryDefinition(reply) {
    var _a, _b;
    return reply.status.code === 200 && ((_b = (_a = reply.entries) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.descriptor.definition) !== undefined;
}
function matchesContextIdPrefix(contextId, contextIdPrefix) {
    if (contextIdPrefix === undefined) {
        return true;
    }
    return contextId === contextIdPrefix || (contextId === null || contextId === void 0 ? void 0 : contextId.startsWith(`${contextIdPrefix}/`)) === true;
}
function isPublicKeyJwkEqual(left, right) {
    if (!isObject(left)) {
        return false;
    }
    const rightRecord = right;
    return left.kty === right.kty &&
        left.crv === rightRecord.crv &&
        left.x === rightRecord.x;
}
function isObject(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function getAudienceKeyFromMemoryCache(params) {
    var _a;
    const cacheKey = getAudienceDecryptionKeyCacheKey(params);
    return (_a = params.audienceDecryptionKeyCache) === null || _a === void 0 ? void 0 : _a.get(cacheKey);
}
function putAudienceKeyInMemoryCache(params) {
    var _a, _b;
    const cacheKey = getAudienceDecryptionKeyCacheKey(Object.assign(Object.assign({}, params.entry), { keyId: params.entry.keyMaterial.keyId }));
    (_b = (_a = params.audienceDecryptionKeyCache) === null || _a === void 0 ? void 0 : _a.set) === null || _b === void 0 ? void 0 : _b.call(_a, cacheKey, params.entry);
}
export function getAudienceDecryptionKeyCacheKey(input) {
    return `audience-key~${Encoder.stringToBase64Url(JSON.stringify([
        input.sourceDid,
        input.recipientDid,
        input.protocol,
        input.contextId,
        input.rolePath,
        input.keyId,
    ]))}`;
}
function isGrantKeyEligibleGrant(grant) {
    return isGrantKeyEligibleRecordsScope(grant.scope);
}
function buildGrantKeyPayloads(agent, ownerDid, grant, protocolDefinitions) {
    return __awaiter(this, void 0, void 0, function* () {
        const grantScope = grant.scope;
        let scopes;
        if (grantScope.method === DwnMethodName.Read) {
            const protocolDefinition = grantScope.protocolPath === undefined
                ? undefined
                : yield readProtocolDefinition(agent, ownerDid, ownerDid, grantScope.protocol, protocolDefinitions, 'grantKey coverage');
            scopes = getGrantKeyDeliveryScopes(grantScope, protocolDefinition);
        }
        else {
            const protocolDefinition = yield readProtocolDefinition(agent, ownerDid, ownerDid, grantScope.protocol, protocolDefinitions, 'grantKey coverage');
            scopes = getGrantKeyDeliveryScopes(grantScope, protocolDefinition);
        }
        if (scopes.length === 0) {
            return [];
        }
        const { keyUri } = yield getEncryptionKeyInfo(agent, ownerDid);
        const payloads = [];
        for (const scope of scopes) {
            payloads.push(yield buildGrantKeyPayload(agent, keyUri, grant.id, scope));
        }
        return payloads;
    });
}
function buildGrantKeyPayload(agent, keyUri, grantId, scope) {
    return __awaiter(this, void 0, void 0, function* () {
        const derivationPath = getScopeDerivationPath(scope.protocol, scope.protocolPath);
        const privateKeyBytes = yield agent.keyManager.derivePrivateKeyBytes({
            keyUri,
            derivationPath,
        });
        const privateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes });
        const publicKeyJwk = yield X25519.getPublicKey({ key: privateKeyJwk });
        const keyId = yield Encryption.getKeyId(publicKeyJwk);
        return {
            grantId,
            scope: Object.assign({ scheme: KeyDerivationScheme.ProtocolPath, protocol: scope.protocol }, (scope.protocolPath ? { protocolPath: scope.protocolPath } : {})),
            keyMaterial: {
                algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
                derivationPath,
                derivationScheme: KeyDerivationScheme.ProtocolPath,
                keyId,
                privateKeyJwk,
                publicKeyJwk,
            },
        };
    });
}
function readProtocolDefinition(agent, authorDid, targetDid, protocol, protocolDefinitions, purpose) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        const cachedDefinition = protocolDefinitions.get(protocol);
        if (cachedDefinition !== undefined) {
            return cachedDefinition;
        }
        const { reply } = yield processDwnRequestWithRemoteFallback(agent, {
            author: authorDid,
            target: targetDid,
            messageType: DwnInterface.ProtocolsQuery,
            messageParams: { filter: { protocol } },
        }, hasProtocolsQueryDefinition);
        const definition = (_b = (_a = reply.entries) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.descriptor.definition;
        if (reply.status.code !== 200 || definition === undefined) {
            throw new Error(`AgentDwnApi: unable to resolve protocol definition '${protocol}' for ${purpose}.`);
        }
        protocolDefinitions.set(protocol, definition);
        return definition;
    });
}
function derivePublicKeyFromPrivateKey(privateKey, derivationPath) {
    return __awaiter(this, void 0, void 0, function* () {
        const privateKeyBytes = yield X25519.privateKeyToBytes({ privateKey });
        const derivedPrivateKeyBytes = yield HdKey.derivePrivateKeyBytes(privateKeyBytes, derivationPath);
        const derivedPrivateKeyJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes: derivedPrivateKeyBytes });
        return X25519.getPublicKey({ key: derivedPrivateKeyJwk });
    });
}
function getScopeDerivationPath(protocol, protocolPath) {
    return [
        KeyDerivationScheme.ProtocolPath,
        protocol,
        ...(protocolPath ? protocolPath.split('/') : []),
    ];
}
function findCoveringDelegateKey(allKeys, grantorDid, protocol, protocolPath) {
    if (allKeys === undefined) {
        return undefined;
    }
    const keysForProtocol = allKeys.filter((key) => key.grantorDid === grantorDid && key.protocol === protocol);
    if (protocolPath !== undefined) {
        const exactKey = keysForProtocol.find((key) => key.scope.kind === 'protocolPath' && key.scope.protocolPath === protocolPath);
        if (exactKey !== undefined) {
            return exactKey;
        }
        const ancestorKey = keysForProtocol
            .filter((key) => key.scope.kind === 'protocolPath' &&
            protocolPath.startsWith(key.scope.protocolPath + '/'))
            .sort((a, b) => b.scope.protocolPath.length - a.scope.protocolPath.length)[0];
        if (ancestorKey !== undefined) {
            return ancestorKey;
        }
    }
    return keysForProtocol.find((key) => key.scope.kind === 'protocol');
}
function mergeDelegateDecryptionKeys(existingKeys, newKeys) {
    const merged = new Map();
    for (const key of existingKeys) {
        merged.set(getDelegateDecryptionKeyCacheKey(key), key);
    }
    for (const key of newKeys) {
        merged.set(getDelegateDecryptionKeyCacheKey(key), key);
    }
    return [...merged.values()];
}
function getDelegateDecryptionKeyCacheKey(key) {
    return key.scope.kind === 'protocol'
        ? `${key.grantorDid}~${key.protocol}~protocol`
        : `${key.grantorDid}~${key.protocol}~protocolPath~${key.scope.protocolPath}`;
}
function resolveGrantKeyRecords(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const { reply } = yield processDwnRequestWithRemoteFallback(params.agent, {
            author: params.granteeDid,
            target: params.grantorDid,
            messageType: DwnInterface.RecordsQuery,
            messageParams: {
                filter: {
                    recipient: params.granteeDid,
                    protocol: EncryptionProtocol.uri,
                    protocolPath: EncryptionProtocol.grantKeyPath,
                    tags: { protocol: params.protocol },
                },
            },
        }, hasRecordsQueryEntries);
        if (reply.status.code !== 200 || reply.entries === undefined || reply.entries.length === 0) {
            return [];
        }
        const granteeDecrypter = yield getKeyDecrypter(params.agent, params.granteeDid);
        const resolvedKeys = [];
        for (const entry of reply.entries) {
            const grantKeyMessage = entry;
            if (Message.getAuthor(grantKeyMessage) !== params.grantorDid) {
                continue;
            }
            const encryptedData = yield getGrantKeyEncryptedData(params.agent, params.granteeDid, params.grantorDid, grantKeyMessage);
            if (encryptedData === undefined) {
                continue;
            }
            try {
                const decryptedStream = yield Records.decrypt(grantKeyMessage, granteeDecrypter, DataStream.fromBytes(encryptedData));
                const payload = Encoder.bytesToObject(yield DataStream.toBytes(decryptedStream));
                const grant = yield readPermissionGrant(params.agent, params.granteeDid, params.grantorDid, payload.grantId);
                yield verifyPermissionGrantActive(params.agent, params.granteeDid, params.grantorDid, grant);
                yield verifyGrantKeyPayload({
                    agent: params.agent,
                    payload,
                    grant,
                    grantKeyMessage,
                    grantorDid: params.grantorDid,
                    granteeDid: params.granteeDid,
                    protocol: params.protocol,
                    protocolPath: params.protocolPath,
                });
                resolvedKeys.push({
                    grantorDid: params.grantorDid,
                    protocol: payload.scope.protocol,
                    scope: payload.scope.protocolPath === undefined
                        ? { kind: 'protocol' }
                        : { kind: 'protocolPath', protocolPath: payload.scope.protocolPath },
                    derivedPrivateKey: {
                        rootKeyId: payload.keyMaterial.keyId,
                        keyId: payload.keyMaterial.keyId,
                        derivationScheme: KeyDerivationScheme.ProtocolPath,
                        derivationPath: payload.keyMaterial.derivationPath,
                        derivedPrivateKey: payload.keyMaterial.privateKeyJwk,
                    },
                });
            }
            catch (error) {
                logger.log(`AgentDwnApi: skipped grantKey '${grantKeyMessage.recordId}' while resolving delegate decryption key: ` +
                    `${error instanceof Error ? error.message : String(error)}`);
                continue;
            }
        }
        return resolvedKeys;
    });
}
function getGrantKeyEncryptedData(agent, granteeDid, grantorDid, grantKeyMessage) {
    return __awaiter(this, void 0, void 0, function* () {
        return readRecordDataWithRemoteFallback({
            agent,
            authorDid: granteeDid,
            encodedData: grantKeyMessage.encodedData,
            recordId: grantKeyMessage.recordId,
            targetDid: grantorDid,
        });
    });
}
function readPermissionGrant(agent, granteeDid, grantorDid, grantId) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const { reply } = yield processDwnRequestWithRemoteFallback(agent, {
            author: granteeDid,
            target: grantorDid,
            messageType: DwnInterface.RecordsRead,
            messageParams: { filter: { recordId: grantId } },
        }, hasRecordsReadData);
        if (reply.status.code !== 200 || ((_a = reply.entry) === null || _a === void 0 ? void 0 : _a.recordsWrite) === undefined || reply.entry.data === undefined) {
            throw new Error(`AgentDwnApi: unable to read permission grant '${grantId}'.`);
        }
        const grantData = yield DataStream.toBytes(reply.entry.data);
        return DwnPermissionGrant.parse(Object.assign(Object.assign({}, reply.entry.recordsWrite), { encodedData: Encoder.bytesToBase64Url(grantData) }));
    });
}
function verifyPermissionGrantActive(agent_1, granteeDid_1, grantorDid_1, grant_1) {
    return __awaiter(this, arguments, void 0, function* (agent, granteeDid, grantorDid, grant, label = 'grantKey') {
        const now = Time.getCurrentTimestamp();
        if (now < grant.dateGranted || now >= grant.dateExpires) {
            throw new Error(`${label} references an inactive permission grant.`);
        }
        const revoked = yield agent.permissions.isGrantRevoked({
            author: granteeDid,
            target: grantorDid,
            grantRecordId: grant.id,
        });
        if (revoked) {
            throw new Error(`${label} references a revoked permission grant.`);
        }
    });
}
function verifyGrantKeyPayload(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const { payload, grant, grantKeyMessage } = params;
        const tags = (_a = grantKeyMessage.descriptor.tags) !== null && _a !== void 0 ? _a : {};
        assertGrantKeyPayload(payload);
        if (payload.grantId !== tags.grantId ||
            payload.scope.protocol !== tags.protocol ||
            payload.keyMaterial.keyId !== tags.keyId ||
            payload.scope.protocolPath !== tags.protocolPath) {
            throw new Error('grantKey payload does not match record tags.');
        }
        if (grant.id !== payload.grantId ||
            grant.grantor !== params.grantorDid ||
            grant.grantee !== params.granteeDid ||
            !(yield grantScopeCoversPayload(params.agent, params.granteeDid, params.grantorDid, grant, payload))) {
            throw new Error('grantKey payload is not covered by the referenced grant.');
        }
        if (payload.scope.scheme !== KeyDerivationScheme.ProtocolPath ||
            payload.scope.protocol !== params.protocol ||
            !scopeCoversRecord(payload.scope, params.protocol, params.protocolPath)) {
            throw new Error('grantKey scope does not cover encrypted record.');
        }
        const expectedDerivationPath = getScopeDerivationPath(payload.scope.protocol, payload.scope.protocolPath);
        if (!arrayEquals(payload.keyMaterial.derivationPath, expectedDerivationPath)) {
            throw new Error('grantKey derivationPath does not match scope.');
        }
        const publicKeyId = yield Encryption.getKeyId(payload.keyMaterial.publicKeyJwk);
        const publicKeyFromPrivate = yield X25519.getPublicKey({ key: payload.keyMaterial.privateKeyJwk });
        const privateKeyId = yield Encryption.getKeyId(publicKeyFromPrivate);
        if (payload.keyMaterial.keyId !== publicKeyId || payload.keyMaterial.keyId !== privateKeyId) {
            throw new Error('grantKey keyId does not match delivered key material.');
        }
    });
}
function grantScopeCoversPayload(agent, requesterDid, ownerDid, grant, payload) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!isGrantKeyEligibleGrant(grant)) {
            return false;
        }
        if (grantKeyScopeCoversDeliveredScope({
            grantScope: grant.scope,
            deliveredScope: payload.scope,
        })) {
            return true;
        }
        if (payload.scope.protocolPath === undefined) {
            return false;
        }
        const protocolDefinition = yield readProtocolDefinition(agent, requesterDid, ownerDid, grant.scope.protocol, new Map(), 'grantKey coverage');
        return grantKeyScopeCoversDeliveredScope({
            grantScope: grant.scope,
            deliveredScope: payload.scope,
            protocolDefinition,
        });
    });
}
function scopeCoversRecord(scope, protocol, protocolPath) {
    if (scope.protocol !== protocol) {
        return false;
    }
    if (scope.protocolPath === undefined) {
        return true;
    }
    return protocolPath !== undefined && isBoundaryAwareSubtree(scope.protocolPath, protocolPath);
}
function isBoundaryAwareSubtree(scopePath, candidatePath) {
    return candidatePath === scopePath || candidatePath.startsWith(scopePath + '/');
}
function arrayEquals(a, b) {
    return a.length === b.length && a.every((value, index) => value === b[index]);
}
//# sourceMappingURL=dwn-encryption.js.map