UNPKG

@cheqd/sdk

Version:

A TypeScript SDK built with CosmJS to interact with the cheqd network ledger

769 lines 42.5 kB
import { createPagination, createProtobufRpcClient } from '@cosmjs/stargate'; import { AbstractCheqdSDKModule } from './_.js'; import { ISignInputs, VerificationMethods, } from '../types.js'; import { MsgCreateDidDoc, MsgCreateDidDocPayload, MsgCreateDidDocResponse, MsgDeactivateDidDoc, MsgDeactivateDidDocPayload, MsgDeactivateDidDocResponse, MsgUpdateDidDoc, MsgUpdateDidDocPayload, MsgUpdateDidDocResponse, protobufPackage, QueryClientImpl, VerificationMethod, } from '@cheqd/ts-proto/cheqd/did/v2/index.js'; import { v4 } from 'uuid'; import { assert } from '@cosmjs/utils'; import { denormalizeService, normalizeAuthentication, normalizeController, normalizeService } from '../utils.js'; export const defaultDidExtensionKey = 'did'; export const contexts = { W3CDIDv1: 'https://www.w3.org/ns/did/v1', W3CSuiteEd255192020: 'https://w3id.org/security/suites/ed25519-2020/v1', W3CSuiteEd255192018: 'https://w3id.org/security/suites/ed25519-2018/v1', W3CSuiteJws2020: 'https://w3id.org/security/suites/jws-2020/v1', LinkedDomainsContext: 'https://identity.foundation/.well-known/did-configuration/v1', }; export const protobufLiterals = { MsgCreateDidDoc: 'MsgCreateDidDoc', MsgCreateDidDocResponse: 'MsgCreateDidDocResponse', MsgUpdateDidDoc: 'MsgUpdateDidDoc', MsgUpdateDidDocResponse: 'MsgUpdateDidDocResponse', MsgDeactivateDidDoc: 'MsgDeactivateDidDoc', MsgDeactivateDidDocResponse: 'MsgDeactivateDidDocResponse', }; export const typeUrlMsgCreateDidDoc = `/${protobufPackage}.${protobufLiterals.MsgCreateDidDoc}`; export const typeUrlMsgCreateDidDocResponse = `/${protobufPackage}.${protobufLiterals.MsgCreateDidDocResponse}`; export const typeUrlMsgUpdateDidDoc = `/${protobufPackage}.${protobufLiterals.MsgUpdateDidDoc}`; export const typeUrlMsgUpdateDidDocResponse = `/${protobufPackage}.${protobufLiterals.MsgUpdateDidDocResponse}`; export const typeUrlMsgDeactivateDidDoc = `/${protobufPackage}.${protobufLiterals.MsgDeactivateDidDoc}`; export const typeUrlMsgDeactivateDidDocResponse = `/${protobufPackage}.${protobufLiterals.MsgDeactivateDidDocResponse}`; export function isMsgCreateDidDocEncodeObject(obj) { return obj.typeUrl === typeUrlMsgCreateDidDoc; } export function isMsgUpdateDidDocEncodeObject(obj) { return obj.typeUrl === typeUrlMsgUpdateDidDoc; } export function isMsgDeactivateDidDocEncodeObject(obj) { return obj.typeUrl === typeUrlMsgDeactivateDidDoc; } export function MsgCreateDidDocResponseEncodeObject(obj) { return obj.typeUrl === typeUrlMsgCreateDidDocResponse; } export function MsgUpdateDidDocEncodeObject(obj) { return obj.typeUrl === typeUrlMsgUpdateDidDoc; } export function MsgUpdateDidDocResponseEncodeObject(obj) { return obj.typeUrl === typeUrlMsgUpdateDidDocResponse; } export function MsgDeactivateDidDocEncodeObject(obj) { return obj.typeUrl === typeUrlMsgDeactivateDidDoc; } export function MsgDeactiveDidDocResponseEncodeObject(obj) { return obj.typeUrl === typeUrlMsgUpdateDidDocResponse; } export const setupDidExtension = (base) => { const rpc = createProtobufRpcClient(base); const queryService = new QueryClientImpl(rpc); return { [defaultDidExtensionKey]: { didDoc: async (id) => { const { value } = await queryService.DidDoc({ id }); assert(value); return value; }, didDocVersion: async (id, versionId) => { const { value } = await queryService.DidDocVersion({ id, version: versionId }); assert(value); return value; }, allDidDocVersionsMetadata: async (id, paginationKey) => { const response = await queryService.AllDidDocVersionsMetadata({ id, pagination: createPagination(paginationKey), }); return response; }, }, }; }; export class DIDModule extends AbstractCheqdSDKModule { // @ts-expect-error underlying type `GeneratedType` is intentionally wider static registryTypes = [ [typeUrlMsgCreateDidDoc, MsgCreateDidDoc], [typeUrlMsgCreateDidDocResponse, MsgCreateDidDocResponse], [typeUrlMsgUpdateDidDoc, MsgUpdateDidDoc], [typeUrlMsgUpdateDidDocResponse, MsgUpdateDidDocResponse], [typeUrlMsgDeactivateDidDoc, MsgDeactivateDidDoc], [typeUrlMsgDeactivateDidDocResponse, MsgDeactivateDidDocResponse], ]; static baseMinimalDenom = 'ncheq'; static fees = { DefaultCreateDidDocFee: { amount: '50000000000', denom: DIDModule.baseMinimalDenom }, DefaultUpdateDidDocFee: { amount: '25000000000', denom: DIDModule.baseMinimalDenom }, DefaultDeactivateDidDocFee: { amount: '10000000000', denom: DIDModule.baseMinimalDenom }, }; static querierExtensionSetup = setupDidExtension; querier; constructor(signer, querier) { super(signer, querier); this.querier = querier; this.methods = { createDidDocTx: this.createDidDocTx.bind(this), updateDidDocTx: this.updateDidDocTx.bind(this), deactivateDidDocTx: this.deactivateDidDocTx.bind(this), queryDidDoc: this.queryDidDoc.bind(this), queryDidDocVersion: this.queryDidDocVersion.bind(this), queryAllDidDocVersionsMetadata: this.queryAllDidDocVersionsMetadata.bind(this), }; } getRegistryTypes() { return DIDModule.registryTypes; } getQuerierExtensionSetup() { return DIDModule.querierExtensionSetup; } async createDidDocTx(signInputs, didPayload, address, fee, memo, versionId, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!this.querier) { this.querier = context.sdk.querier; } if (!versionId || versionId === '') { versionId = v4(); } const { valid, error, protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError } = await DIDModule.validateAuthenticationAgainstSignatures(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = MsgCreateDidDocPayload.fromPartial({ context: didPayload?.['@context'], id: didPayload.id, controller: didPayload.controller, verificationMethod: protobufVerificationMethod, authentication: didPayload.authentication, assertionMethod: didPayload.assertionMethod, capabilityInvocation: didPayload.capabilityInvocation, capabilityDelegation: didPayload.capabilityDelegation, keyAgreement: didPayload.keyAgreement, service: protobufService, alsoKnownAs: didPayload.alsoKnownAs, versionId: versionId, }); let signatures; if (ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signCreateDidDocTx(signInputs, payload); } else { signatures = signInputs; } const value = { payload, signatures, }; const createDidMsg = { typeUrl: typeUrlMsgCreateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await DIDModule.generateCreateDidDocFees(address); } return this._signer.signAndBroadcast(address, [createDidMsg], fee, memo); } async updateDidDocTx(signInputs, didPayload, address, fee, memo, versionId, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!this.querier) { this.querier = context.sdk.querier; } if (!versionId || versionId === '') { versionId = v4(); } const { valid, error, protobufVerificationMethod, protobufService } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError, externalControllersDocuments, previousDidDocument, } = await DIDModule.validateAuthenticationAgainstSignaturesKeyRotation(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = MsgUpdateDidDocPayload.fromPartial({ context: didPayload?.['@context'], id: didPayload.id, controller: didPayload.controller, verificationMethod: protobufVerificationMethod, authentication: didPayload.authentication, assertionMethod: didPayload.assertionMethod, capabilityInvocation: didPayload.capabilityInvocation, capabilityDelegation: didPayload.capabilityDelegation, keyAgreement: didPayload.keyAgreement, service: protobufService, alsoKnownAs: didPayload.alsoKnownAs, versionId: versionId, }); let signatures; if (ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signUpdateDidDocTx(signInputs, payload, externalControllersDocuments, previousDidDocument); } else { signatures = signInputs; } const value = { payload, signatures, }; const updateDidMsg = { typeUrl: typeUrlMsgUpdateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await DIDModule.generateUpdateDidDocFees(address); } return this._signer.signAndBroadcast(address, [updateDidMsg], fee, memo); } async deactivateDidDocTx(signInputs, didPayload, address, fee, memo, versionId, context) { if (!this._signer) { this._signer = context.sdk.signer; } if (!versionId || versionId === '') { versionId = v4(); } const { valid, error, protobufVerificationMethod } = await DIDModule.validateSpecCompliantPayload(didPayload); if (!valid) { throw new Error(`DID payload is not spec compliant: ${error}`); } const { valid: authenticationValid, error: authenticationError } = await DIDModule.validateAuthenticationAgainstSignatures(didPayload, signInputs, this.querier); if (!authenticationValid) { throw new Error(`DID authentication is not valid: ${authenticationError}`); } const payload = MsgDeactivateDidDocPayload.fromPartial({ id: didPayload.id, versionId: versionId, }); let signatures; if (ISignInputs.isSignInput(signInputs)) { signatures = await this._signer.signDeactivateDidDocTx(signInputs, payload, protobufVerificationMethod); } else { signatures = signInputs; } const value = { payload, signatures, }; const deactivateDidMsg = { typeUrl: typeUrlMsgDeactivateDidDoc, value, }; if (address === '') { address = (await context.sdk.options.wallet.getAccounts())[0].address; } if (!fee) { fee = await DIDModule.generateDeactivateDidDocFees(address); } return this._signer.signAndBroadcast(address, [deactivateDidMsg], fee, memo); } async queryDidDoc(id, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { didDoc, metadata } = await this.querier[defaultDidExtensionKey].didDoc(id); return { didDocument: await DIDModule.toSpecCompliantPayload(didDoc), didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata), }; } async queryDidDocVersion(id, versionId, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { didDoc, metadata } = await this.querier[defaultDidExtensionKey].didDocVersion(id, versionId); return { didDocument: await DIDModule.toSpecCompliantPayload(didDoc), didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata), }; } async queryAllDidDocVersionsMetadata(id, context) { if (!this.querier) { this.querier = context.sdk.querier; } const { versions, pagination } = await this.querier[defaultDidExtensionKey].allDidDocVersionsMetadata(id); return { didDocumentVersionsMetadata: await Promise.all(versions.map(async (m) => await DIDModule.toSpecCompliantMetadata(m))), pagination, }; } static async validateSpecCompliantPayload(didDocument) { // id is required, validated on both compile and runtime if (!didDocument?.id) return { valid: false, error: 'id is required' }; // verificationMethod is required if (!didDocument?.verificationMethod) return { valid: false, error: 'verificationMethod is required' }; // verificationMethod must be an array if (!Array.isArray(didDocument?.verificationMethod)) return { valid: false, error: 'verificationMethod must be an array' }; // verificationMethod types must be supported const protoVerificationMethod = didDocument.verificationMethod.map((vm) => { switch (vm?.type) { case VerificationMethods.Ed255192020: if (!vm?.publicKeyMultibase) throw new Error('publicKeyMultibase is required'); return VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: VerificationMethods.Ed255192020, verificationMaterial: vm.publicKeyMultibase, }); case VerificationMethods.JWK: if (!vm?.publicKeyJwk) throw new Error('publicKeyJwk is required'); return VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: VerificationMethods.JWK, verificationMaterial: JSON.stringify(vm.publicKeyJwk), }); case VerificationMethods.Ed255192018: if (!vm?.publicKeyBase58) throw new Error('publicKeyBase58 is required'); return VerificationMethod.fromPartial({ id: vm.id, controller: vm.controller, verificationMethodType: VerificationMethods.Ed255192018, verificationMaterial: vm.publicKeyBase58, }); default: throw new Error('Unsupported verificationMethod type'); } }); const protoService = normalizeService(didDocument); return { valid: true, protobufVerificationMethod: protoVerificationMethod, protobufService: protoService, }; } static async toSpecCompliantPayload(protobufDidDocument) { const verificationMethod = protobufDidDocument.verificationMethod.map((vm) => { switch (vm.verificationMethodType) { case VerificationMethods.Ed255192020: if (!protobufDidDocument.context.includes(contexts.W3CSuiteEd255192020)) protobufDidDocument.context = [...protobufDidDocument.context, contexts.W3CSuiteEd255192020]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyMultibase: vm.verificationMaterial, }; case VerificationMethods.JWK: if (!protobufDidDocument.context.includes(contexts.W3CSuiteJws2020)) protobufDidDocument.context = [...protobufDidDocument.context, contexts.W3CSuiteJws2020]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyJwk: JSON.parse(vm.verificationMaterial), }; case VerificationMethods.Ed255192018: if (!protobufDidDocument.context.includes(contexts.W3CSuiteEd255192018)) protobufDidDocument.context = [...protobufDidDocument.context, contexts.W3CSuiteEd255192018]; return { id: vm.id, type: vm.verificationMethodType, controller: vm.controller, publicKeyBase58: vm.verificationMaterial, }; default: throw new Error('Unsupported verificationMethod type'); // should never happen } }); const service = denormalizeService(protobufDidDocument); const context = (function () { if (protobufDidDocument.context.includes(contexts.W3CDIDv1)) return protobufDidDocument.context; return [contexts.W3CDIDv1, ...protobufDidDocument.context]; })(); const assertionMethod = protobufDidDocument.assertionMethod.map((am) => { try { // Check if the assertionMethod is a DID URL if (!am.startsWith('did:cheqd:')) { // Parse once if it's a stringified JSON const parsedAm = JSON.parse(am); if (typeof parsedAm === 'string') { // Parse again only if necessary return JSON.parse(parsedAm); } return parsedAm; } return am; } catch (error) { throw new Error(`Unsupported assertionMethod type: ${am}`); } }); const specCompliant = { '@context': context, id: protobufDidDocument.id, controller: protobufDidDocument.controller, verificationMethod: verificationMethod, authentication: protobufDidDocument.authentication, assertionMethod: assertionMethod, capabilityInvocation: protobufDidDocument.capabilityInvocation, capabilityDelegation: protobufDidDocument.capabilityDelegation, keyAgreement: protobufDidDocument.keyAgreement, service: service, alsoKnownAs: protobufDidDocument.alsoKnownAs, }; if (!protobufDidDocument.authentication?.length) delete specCompliant.authentication; if (!protobufDidDocument.assertionMethod?.length) delete specCompliant.assertionMethod; if (!protobufDidDocument.capabilityInvocation?.length) delete specCompliant.capabilityInvocation; if (!protobufDidDocument.capabilityDelegation?.length) delete specCompliant.capabilityDelegation; if (!protobufDidDocument.keyAgreement?.length) delete specCompliant.keyAgreement; if (!protobufDidDocument.service?.length) delete specCompliant.service; if (!protobufDidDocument.alsoKnownAs?.length) delete specCompliant.alsoKnownAs; return specCompliant; } static async toSpecCompliantMetadata(protobufDidDocument) { return { created: protobufDidDocument.created?.toISOString(), updated: protobufDidDocument.updated?.toISOString(), deactivated: protobufDidDocument.deactivated, versionId: protobufDidDocument.versionId, nextVersionId: protobufDidDocument?.nextVersionId, previousVersionId: protobufDidDocument?.previousVersionId, }; } static async validateAuthenticationAgainstSignatures(didDocument, signatures, querier, externalControllersDidDocuments) { // validate signatures - case: no signatures if (!signatures || !signatures.length) return { valid: false, error: 'signatures are required' }; // validate authentication - case: no authentication when at least one verificationMethod if ((!didDocument.authentication || !didDocument.authentication.length) && didDocument.verificationMethod?.length) return { valid: false, error: 'authentication is required' }; const normalizedAuthentication = normalizeAuthentication(didDocument); // define unique authentication const uniqueAuthentication = new Set(normalizedAuthentication); // validate authentication - case: authentication contains duplicates if (uniqueAuthentication.size < normalizedAuthentication.length) return { valid: false, error: `authentication contains duplicate key references: duplicate key reference ${Array.from(uniqueAuthentication).find((a) => normalizeAuthentication(didDocument).filter((aa) => aa === a).length > 1)}`, }; // define unique signatures - shallow, only verificationMethodId, no signature const uniqueSignatures = new Set(signatures.map((s) => s.verificationMethodId)); // validate signatures - case: signatures contain duplicates if (uniqueSignatures.size < signatures.length) return { valid: false, error: `signatures contain duplicates: duplicate signature for key reference ${Array.from(uniqueSignatures).find((s) => signatures.filter((ss) => ss.verificationMethodId === s).length > 1)}`, }; // validate authentication - case: authentication contains invalid key references if (!Array.from(uniqueAuthentication).every((a) => didDocument.verificationMethod?.some((vm) => vm.id === a))) return { valid: false, error: `authentication contains invalid key references: invalid key reference ${Array.from(uniqueAuthentication).find((a) => !didDocument.verificationMethod?.some((vm) => vm.id === a))}`, }; // define whether external controller or not const externalController = normalizeController(didDocument).some((c) => c !== didDocument.id); // validate authentication - case: authentication matches signatures, unique, if no external controller if (!Array.from(uniqueAuthentication).every((a) => uniqueSignatures.has(a)) && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueAuthentication).find((a) => !uniqueSignatures.has(a))} is missing`, }; // validate signatures - case: authentication matches signatures, unique, excessive signatures, no external controller if (!Array.from(uniqueSignatures).every((s) => uniqueAuthentication.has(s)) && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignatures).find((s) => !uniqueAuthentication.has(s))} is not required`, }; // return, if no external controller if (!externalController) return { valid: true }; // require querier if (!querier) throw new Error('querier is required for external controller validation'); // get external controllers const externalControllers = normalizeController(didDocument).filter((c) => c !== didDocument.id); // get external controllers' documents const externalControllersDocuments = await Promise.all(externalControllers?.map(async (c) => { // compute index of external controller's document, if provided const externalControllerDocumentIndex = externalControllersDidDocuments?.findIndex((d) => d.id === c); // get external controller's document, if provided if (externalControllerDocumentIndex !== undefined && externalControllerDocumentIndex !== -1) return externalControllersDidDocuments?.[externalControllerDocumentIndex]; // fetch external controller's document const protobufDocument = await querier[defaultDidExtensionKey].didDoc(c); // throw, if not found if (!protobufDocument || !protobufDocument.didDoc) throw new Error(`Document for controller ${c} not found`); // convert to spec compliant payload return await DIDModule.toSpecCompliantPayload(protobufDocument.didDoc); })); // define unique required signatures const uniqueRequiredSignatures = new Set(externalControllersDocuments.concat(didDocument).flatMap((d) => (d ? normalizeAuthentication(d) : []))); // validate authentication - case: authentication matches signatures, unique, if external controller if (!Array.from(uniqueRequiredSignatures).every((a) => uniqueSignatures.has(a))) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueRequiredSignatures).find((a) => !uniqueSignatures.has(a))} is missing`, }; // validate authentication - case: authentication matches signatures, unique, excessive signatures, if external controller if (uniqueRequiredSignatures.size < uniqueSignatures.size) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignatures).find((s) => !uniqueRequiredSignatures.has(s))} is not required`, }; // return valid return { valid: true }; } static async validateAuthenticationAgainstSignaturesKeyRotation(didDocument, signatures, querier, previousDidDocument, externalControllersDidDocuments) { // validate signatures - case: no signatures if (!signatures || !signatures.length) return { valid: false, error: 'signatures are required' }; // validate authentication - case: no authentication when at least one verificationMethod if ((!didDocument.authentication || !didDocument.authentication.length) && didDocument.verificationMethod?.length) return { valid: false, error: 'authentication is required' }; // define unique authentication const authentication = normalizeAuthentication(didDocument); const uniqueAuthentication = new Set(authentication); // validate authentication - case: authentication contains duplicates if (uniqueAuthentication.size < authentication.length) return { valid: false, error: `authentication contains duplicate key references: duplicate key reference ${Array.from(uniqueAuthentication).find((a) => normalizeAuthentication(didDocument).filter((aa) => aa === a).length > 1)}`, }; // define unique signatures const uniqueSignatures = new Set(signatures.map((s) => s.verificationMethodId)); // validate authentication - case: authentication contains invalid key references if (!Array.from(uniqueAuthentication).every((a) => didDocument.verificationMethod?.some((vm) => vm.id === a))) return { valid: false, error: `authentication contains invalid key references: invalid key reference ${Array.from(uniqueAuthentication).find((a) => !didDocument.verificationMethod?.some((vm) => vm.id === a))}`, }; // lookup previous document if (!previousDidDocument) { // get previous document const previousDocument = await querier[defaultDidExtensionKey].didDoc(didDocument.id); // throw, if not found if (!previousDocument || !previousDocument.didDoc) throw new Error('Previous did document not found'); previousDidDocument = await DIDModule.toSpecCompliantPayload(previousDocument.didDoc); } const controllers = normalizeController(didDocument); const previousControllers = normalizeController(previousDidDocument); // define whether external controller or not const externalController = controllers.concat(previousControllers).some((c) => c !== didDocument.id); // define whether key rotation or not (same ID, different material) const keyRotation = !!didDocument.verificationMethod?.some((vm) => previousDidDocument?.verificationMethod?.some((pvm) => pvm.id === vm.id && (pvm.publicKeyBase58 !== vm.publicKeyBase58 || pvm.publicKeyMultibase !== vm.publicKeyMultibase || pvm.publicKeyJwk?.x !== vm.publicKeyJwk?.x))); // define whether key replacement or not (different IDs in authentication) const currentAuthenticationIds = new Set(normalizeAuthentication(didDocument)); const previousAuthenticationIds = new Set(normalizeAuthentication(previousDidDocument)); const removedKeys = Array.from(previousAuthenticationIds).filter((id) => !currentAuthenticationIds.has(id)); const addedKeys = Array.from(currentAuthenticationIds).filter((id) => !previousAuthenticationIds.has(id)); const keyReplacement = removedKeys.length > 0 && addedKeys.length > 0; // define controller rotation const controllerRotation = !controllers.every((c) => previousControllers.includes(c)) || !previousControllers.every((c) => controllers.includes(c)); // define rotated controllers const rotatedControllers = controllerRotation ? previousControllers.filter((c) => !controllers.includes(c)) : []; // define unique union of authentication const previousAuthentication = normalizeAuthentication(previousDidDocument); const uniqueUnionAuthentication = new Set([...uniqueAuthentication, ...previousAuthentication]); // validate authentication - case: authentication matches signatures, unique, if no external controller, no key rotation, no key replacement if (!Array.from(uniqueUnionAuthentication).every((a) => uniqueSignatures.has(a)) && !externalController && !keyRotation && !keyReplacement) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueAuthentication).find((a) => !uniqueSignatures.has(a))} is missing`, }; // define rotated keys const rotatedKeys = keyRotation ? didDocument.verificationMethod?.filter((vm) => previousDidDocument?.verificationMethod?.some((pvm) => pvm.id === vm.id && (pvm.publicKeyBase58 !== vm.publicKeyBase58 || pvm.publicKeyMultibase !== vm.publicKeyMultibase || pvm.publicKeyJwk?.x !== vm.publicKeyJwk?.x))) : []; // define unique union of signatures required, including key replacement logic let uniqueUnionSignaturesRequired = new Set(); if (keyRotation) { // Existing key rotation logic (same ID, different material) uniqueUnionSignaturesRequired = new Set([ ...authentication.filter((a) => rotatedKeys?.find((rk) => a === rk.id)).map((a) => `${a}(document0)`), ...previousAuthentication.map((a) => `${a}(document1)`), ]); } else if (keyReplacement) { // For key replacement, we need signatures from: // 1. The new keys (from current document) // 2. The old keys that are being replaced (from previous document) const newKeySignatures = addedKeys.map((keyId) => `${keyId}(document0)`); const oldKeySignatures = removedKeys .filter((keyId) => previousAuthentication.includes(keyId)) // Only if they were in authentication .map((keyId) => `${keyId}(document1)`); uniqueUnionSignaturesRequired = new Set([...newKeySignatures, ...oldKeySignatures]); } else { // No rotation or replacement uniqueUnionSignaturesRequired = new Set([...authentication.map((a) => `${a}(document0)`)]); } // define frequency of unique union of signatures required const uniqueUnionSignaturesRequiredFrequency = new Map([...uniqueUnionSignaturesRequired].map((s) => [s.replace(new RegExp(/\(document\d+\)/), ''), 0])); // count frequency of unique union of signatures required uniqueUnionSignaturesRequired.forEach((s) => { // define key const key = s.replace(new RegExp(/\(document\d+\)/), ''); // increment frequency uniqueUnionSignaturesRequiredFrequency.set(key, uniqueUnionSignaturesRequiredFrequency.get(key) + 1); }); // define frequency of signatures provided const uniqueSignaturesFrequency = new Map(signatures.map((s) => [s.verificationMethodId, 0])); // count frequency of signatures provided signatures.forEach((s) => { // increment frequency uniqueSignaturesFrequency.set(s.verificationMethodId, uniqueSignaturesFrequency.get(s.verificationMethodId) + 1); }); // validate signatures - case: authentication matches signatures, unique, excessive signatures, no external controller if (Array.from(uniqueSignaturesFrequency).filter(([k, f]) => uniqueUnionSignaturesRequiredFrequency.get(k) === undefined || (uniqueUnionSignaturesRequiredFrequency.get(k) && uniqueUnionSignaturesRequiredFrequency.get(k) < f)).length && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignaturesFrequency).find(([k, f]) => uniqueUnionSignaturesRequiredFrequency.get(k) === undefined || uniqueUnionSignaturesRequiredFrequency.get(k) < f)?.[0]} is not required`, }; // validate signatures - case: authentication matches signatures, unique, missing signatures, no external controller if (Array.from(uniqueSignaturesFrequency).filter(([k, f]) => uniqueUnionSignaturesRequiredFrequency.get(k) && uniqueUnionSignaturesRequiredFrequency.get(k) > f).length && !externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignaturesFrequency).find(([k, f]) => uniqueUnionSignaturesRequiredFrequency.get(k) > f)?.[0]} is missing`, }; // return, if no external controller if (!externalController) return { valid: true }; // require querier if (!querier) throw new Error('querier is required for external controller validation'); // get external controllers const externalControllers = controllers?.filter((c) => c !== didDocument.id).concat(rotatedControllers); // get external controllers' documents const externalControllersDocuments = await Promise.all(externalControllers?.map(async (c) => { // compute index of external controller's document, if provided const externalControllerDocumentIndex = externalControllersDidDocuments?.findIndex((d) => d.id === c); // get external controller's document, if provided if (externalControllerDocumentIndex !== undefined && externalControllerDocumentIndex !== -1) return externalControllersDidDocuments[externalControllerDocumentIndex]; // fetch external controller's document const protobufDocument = await querier[defaultDidExtensionKey].didDoc(c); // throw, if not found if (!protobufDocument || !protobufDocument.didDoc) throw new Error(`Document for controller ${c} not found`); // convert to spec compliant payload return await DIDModule.toSpecCompliantPayload(protobufDocument.didDoc); })); // define unique required signatures, delimited, with external controllers const uniqueUnionSignaturesRequiredWithExternalControllers = new Set([ ...uniqueUnionSignaturesRequired, ...externalControllersDocuments .flatMap((d) => normalizeAuthentication(d)) .map((a) => `${a}(document${externalControllersDocuments.findIndex((d) => normalizeAuthentication(d).includes(a))})`), ]); // add rotated controller keys to unique required signatures, if any if (controllerRotation) { // walk through rotated controllers rotatedControllers.forEach((c) => { // get rotated controller's document index const rotatedControllerDocumentIndex = externalControllersDocuments.findIndex((d) => d?.id === c); // early return, if no document if (rotatedControllerDocumentIndex === -1) return; // get rotated controller's document const rotatedControllerDocument = externalControllersDocuments[rotatedControllerDocumentIndex]; // add rotated controller's authentication to unique required signatures rotatedControllerDocument.authentication?.forEach((a) => uniqueUnionSignaturesRequiredWithExternalControllers.add(`${a}(document${rotatedControllerDocumentIndex})`)); }); } // define frequency of unique union of signatures required, with external controllers const uniqueUnionSignaturesRequiredWithExternalControllersFrequency = new Map([...uniqueUnionSignaturesRequiredWithExternalControllers].map((s) => [ s.replace(new RegExp(/\(document\d+\)/), ''), 0, ])); // count frequency of unique union of signatures required, with external controllers uniqueUnionSignaturesRequiredWithExternalControllers.forEach((s) => { // define key const key = s.replace(new RegExp(/\(document\d+\)/), ''); // increment frequency uniqueUnionSignaturesRequiredWithExternalControllersFrequency.set(key, uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(key) + 1); }); // define frequency of signatures provided, with external controllers const uniqueSignaturesFrequencyWithExternalControllers = new Map(signatures.map((s) => [s.verificationMethodId, 0])); // count frequency of signatures provided, with external controllers signatures.forEach((s) => { // increment frequency uniqueSignaturesFrequencyWithExternalControllers.set(s.verificationMethodId, uniqueSignaturesFrequencyWithExternalControllers.get(s.verificationMethodId) + 1); }); // validate signatures - case: authentication matches signatures, unique, excessive signatures, with external controllers if (Array.from(uniqueSignaturesFrequencyWithExternalControllers).filter(([k, f]) => uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) === undefined || (uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) && uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) < f)).length && externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignaturesFrequencyWithExternalControllers).find(([k, f]) => uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) === undefined || uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) < f)?.[0]} is not required`, }; // validate signatures - case: authentication matches signatures, unique, missing signatures, with external controllers if (Array.from(uniqueSignaturesFrequencyWithExternalControllers).filter(([k, f]) => uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) && uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) > f).length && externalController) return { valid: false, error: `authentication does not match signatures: signature from key ${Array.from(uniqueSignaturesFrequencyWithExternalControllers).find(([k, f]) => uniqueUnionSignaturesRequiredWithExternalControllersFrequency.get(k) > f)?.[0]} is missing`, }; // return valid return { valid: true, previousDidDocument, externalControllersDocuments }; } static async generateCreateDidDocFees(feePayer, granter) { return { amount: [DIDModule.fees.DefaultCreateDidDocFee], gas: '360000', payer: feePayer, granter: granter, }; } static async generateUpdateDidDocFees(feePayer, granter) { return { amount: [DIDModule.fees.DefaultUpdateDidDocFee], gas: '360000', payer: feePayer, granter: granter, }; } static async generateDeactivateDidDocFees(feePayer, granter) { return { amount: [DIDModule.fees.DefaultDeactivateDidDocFee], gas: '360000', payer: feePayer, granter: granter, }; } } //# sourceMappingURL=did.js.map