@cheqd/sdk
Version:
A TypeScript SDK built with CosmJS to interact with the cheqd network ledger
948 lines • 57.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DIDModule = exports.setupDidExtension = exports.typeUrlMsgDeactivateDidDocResponse = exports.typeUrlMsgDeactivateDidDoc = exports.typeUrlMsgUpdateDidDocResponse = exports.typeUrlMsgUpdateDidDoc = exports.typeUrlMsgCreateDidDocResponse = exports.typeUrlMsgCreateDidDoc = exports.protobufLiterals = exports.contexts = exports.defaultDidExtensionKey = void 0;
exports.isMsgCreateDidDocEncodeObject = isMsgCreateDidDocEncodeObject;
exports.isMsgUpdateDidDocEncodeObject = isMsgUpdateDidDocEncodeObject;
exports.isMsgDeactivateDidDocEncodeObject = isMsgDeactivateDidDocEncodeObject;
exports.MsgCreateDidDocResponseEncodeObject = MsgCreateDidDocResponseEncodeObject;
exports.MsgUpdateDidDocEncodeObject = MsgUpdateDidDocEncodeObject;
exports.MsgUpdateDidDocResponseEncodeObject = MsgUpdateDidDocResponseEncodeObject;
exports.MsgDeactivateDidDocEncodeObject = MsgDeactivateDidDocEncodeObject;
exports.MsgDeactiveDidDocResponseEncodeObject = MsgDeactiveDidDocResponseEncodeObject;
const stargate_cjs_1 = require("@cosmjs/stargate-cjs");
const _1 = require("./_");
const types_1 = require("../types");
const index_1 = require("@cheqd/ts-proto-cjs/cheqd/did/v2/index");
const uuid_cjs_1 = require("uuid-cjs");
const utils_cjs_1 = require("@cosmjs/utils-cjs");
const utils_1 = require("../utils");
/** Default extension key for DID-related query operations */
exports.defaultDidExtensionKey = 'did';
/**
* Standard W3C and DID-related context URIs used in DID documents.
* These contexts define the semantic meaning of properties in DID documents.
*/
exports.contexts = {
/** W3C DID Core v1 context */
W3CDIDv1: 'https://www.w3.org/ns/did/v1',
/** Ed25519 Signature Suite 2020 context */
W3CSuiteEd255192020: 'https://w3id.org/security/suites/ed25519-2020/v1',
/** Ed25519 Signature Suite 2018 context */
W3CSuiteEd255192018: 'https://w3id.org/security/suites/ed25519-2018/v1',
/** JSON Web Signature Suite 2020 context */
W3CSuiteJws2020: 'https://w3id.org/security/suites/jws-2020/v1',
/** Linked Domains context for domain verification */
LinkedDomainsContext: 'https://identity.foundation/.well-known/did-configuration/v1',
};
/**
* Protobuf message type literals for DID operations.
* Used for consistent message type identification across the module.
*/
exports.protobufLiterals = {
/** Create DID document message type */
MsgCreateDidDoc: 'MsgCreateDidDoc',
/** Create DID document response message type */
MsgCreateDidDocResponse: 'MsgCreateDidDocResponse',
/** Update DID document message type */
MsgUpdateDidDoc: 'MsgUpdateDidDoc',
/** Update DID document response message type */
MsgUpdateDidDocResponse: 'MsgUpdateDidDocResponse',
/** Deactivate DID document message type */
MsgDeactivateDidDoc: 'MsgDeactivateDidDoc',
/** Deactivate DID document response message type */
MsgDeactivateDidDocResponse: 'MsgDeactivateDidDocResponse',
};
/** Type URL for MsgCreateDidDoc messages */
exports.typeUrlMsgCreateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgCreateDidDoc}`;
/** Type URL for MsgCreateDidDocResponse messages */
exports.typeUrlMsgCreateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgCreateDidDocResponse}`;
/** Type URL for MsgUpdateDidDoc messages */
exports.typeUrlMsgUpdateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgUpdateDidDoc}`;
/** Type URL for MsgUpdateDidDocResponse messages */
exports.typeUrlMsgUpdateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgUpdateDidDocResponse}`;
/** Type URL for MsgDeactivateDidDoc messages */
exports.typeUrlMsgDeactivateDidDoc = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgDeactivateDidDoc}`;
/** Type URL for MsgDeactivateDidDocResponse messages */
exports.typeUrlMsgDeactivateDidDocResponse = `/${index_1.protobufPackage}.${exports.protobufLiterals.MsgDeactivateDidDocResponse}`;
/**
* Type guard function to check if an object is a MsgCreateDidDocEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgCreateDidDocEncodeObject
*/
function isMsgCreateDidDocEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgCreateDidDoc;
}
/**
* Type guard function to check if an object is a MsgUpdateDidDocEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgUpdateDidDocEncodeObject
*/
function isMsgUpdateDidDocEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgUpdateDidDoc;
}
/**
* Type guard function to check if an object is a MsgDeactivateDidDocEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgDeactivateDidDocEncodeObject
*/
function isMsgDeactivateDidDocEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgDeactivateDidDoc;
}
/**
* Type guard function to check if an object is a MsgCreateDidDocResponseEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgCreateDidDocResponseEncodeObject
*/
function MsgCreateDidDocResponseEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgCreateDidDocResponse;
}
/**
* Type guard function to check if an object is a MsgUpdateDidDocEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgUpdateDidDocEncodeObject
*/
function MsgUpdateDidDocEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgUpdateDidDoc;
}
/**
* Type guard function to check if an object is a MsgUpdateDidDocResponseEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgUpdateDidDocResponseEncodeObject
*/
function MsgUpdateDidDocResponseEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgUpdateDidDocResponse;
}
/**
* Type guard function to check if an object is a MsgDeactivateDidDocEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgDeactivateDidDocEncodeObject
*/
function MsgDeactivateDidDocEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgDeactivateDidDoc;
}
/**
* Type guard function to check if an object is a MsgDeactivateDidDocResponseEncodeObject.
*
* @param obj - EncodeObject to check
* @returns True if the object is a MsgDeactivateDidDocResponseEncodeObject
*/
function MsgDeactiveDidDocResponseEncodeObject(obj) {
return obj.typeUrl === exports.typeUrlMsgUpdateDidDocResponse;
}
/**
* Sets up the DID extension for the querier client.
* Creates and configures the DID-specific query methods.
*
* @param base - Base QueryClient to extend
* @returns Configured DID extension with query methods
*/
const setupDidExtension = (base) => {
const rpc = (0, stargate_cjs_1.createProtobufRpcClient)(base);
const queryService = new index_1.QueryClientImpl(rpc);
return {
[exports.defaultDidExtensionKey]: {
didDoc: async (id) => {
const { value } = await queryService.DidDoc({ id });
(0, utils_cjs_1.assert)(value);
return value;
},
didDocVersion: async (id, versionId) => {
const { value } = await queryService.DidDocVersion({
id,
version: versionId,
});
(0, utils_cjs_1.assert)(value);
return value;
},
allDidDocVersionsMetadata: async (id, paginationKey) => {
const response = await queryService.AllDidDocVersionsMetadata({
id,
pagination: (0, stargate_cjs_1.createPagination)(paginationKey),
});
return response;
},
},
};
};
exports.setupDidExtension = setupDidExtension;
/**
* DID Module class providing comprehensive DID document management functionality.
* Handles creation, updates, deactivation, and querying of DID documents on the Cheqd blockchain.
*/
class DIDModule extends _1.AbstractCheqdSDKModule {
//@ts-expect-error the underlying type is intentionally wider
static registryTypes = [
[exports.typeUrlMsgCreateDidDoc, index_1.MsgCreateDidDoc],
[exports.typeUrlMsgCreateDidDocResponse, index_1.MsgCreateDidDocResponse],
[exports.typeUrlMsgUpdateDidDoc, index_1.MsgUpdateDidDoc],
[exports.typeUrlMsgUpdateDidDocResponse, index_1.MsgUpdateDidDocResponse],
[exports.typeUrlMsgDeactivateDidDoc, index_1.MsgDeactivateDidDoc],
[exports.typeUrlMsgDeactivateDidDocResponse, index_1.MsgDeactivateDidDocResponse],
];
/** Base denomination for Cheqd network transactions */
static baseMinimalDenom = 'ncheq';
/**
* Standard fee amounts for DID operations.
* These represent the default costs for different DID document operations.
*/
static fees = {
/** Default fee for creating a new DID document */
DefaultCreateDidDocFee: {
amount: '50000000000',
denom: DIDModule.baseMinimalDenom,
},
/** Default fee for updating an existing DID document */
DefaultUpdateDidDocFee: {
amount: '25000000000',
denom: DIDModule.baseMinimalDenom,
},
/** Default fee for deactivating a DID document */
DefaultDeactivateDidDocFee: {
amount: '10000000000',
denom: DIDModule.baseMinimalDenom,
},
};
/** Querier extension setup function for DID operations */
static querierExtensionSetup = exports.setupDidExtension;
/** Querier instance with DID extension capabilities */
querier;
/**
* Constructs a new DID module instance.
*
* @param signer - Signing client for blockchain transactions
* @param querier - Querier client with DID extension for data retrieval
*/
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),
};
}
/**
* Gets the registry types for DID message encoding/decoding.
*
* @returns Iterable of [typeUrl, GeneratedType] pairs for the registry
*/
getRegistryTypes() {
return DIDModule.registryTypes;
}
/**
* Gets the querier extension setup for DID operations.
*
* @returns Query extension setup function for DID functionality
*/
getQuerierExtensionSetup() {
return DIDModule.querierExtensionSetup;
}
/**
* Creates a new DID document transaction on the blockchain.
* Validates the DID payload and authentication before submission.
*
* @param signInputs - Signing inputs or pre-computed signatures for the transaction
* @param didPayload - DID document payload to create
* @param address - Address of the account submitting the transaction
* @param fee - Transaction fee configuration or 'auto' for automatic calculation
* @param memo - Optional transaction memo
* @param versionId - Optional version identifier for the DID document
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to the transaction response
* @throws Error if DID payload is not spec compliant or authentication is invalid
*/
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 = (0, uuid_cjs_1.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 = index_1.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 (types_1.ISignInputs.isSignInput(signInputs)) {
signatures = await this._signer.signCreateDidDocTx(signInputs, payload);
}
else {
signatures = signInputs;
}
const value = {
payload,
signatures,
};
const createDidMsg = {
typeUrl: exports.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);
}
/**
* Updates an existing DID document transaction on the blockchain.
* Validates the updated DID payload and handles key rotation scenarios.
*
* @param signInputs - Signing inputs or pre-computed signatures for the transaction
* @param didPayload - Updated DID document payload
* @param address - Address of the account submitting the transaction
* @param fee - Transaction fee configuration or 'auto' for automatic calculation
* @param memo - Optional transaction memo
* @param versionId - Optional version identifier for the updated DID document
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to the transaction response
* @throws Error if DID payload is not spec compliant or authentication is invalid
*/
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 = (0, uuid_cjs_1.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 = index_1.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 (types_1.ISignInputs.isSignInput(signInputs)) {
signatures = await this._signer.signUpdateDidDocTx(signInputs, payload, externalControllersDocuments, previousDidDocument);
}
else {
signatures = signInputs;
}
const value = {
payload,
signatures,
};
const updateDidMsg = {
typeUrl: exports.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);
}
/**
* Deactivates an existing DID document transaction on the blockchain.
* Validates authentication and creates a deactivation transaction.
*
* @param signInputs - Signing inputs or pre-computed signatures for the transaction
* @param didPayload - DID document payload containing the ID to deactivate
* @param address - Address of the account submitting the transaction
* @param fee - Transaction fee configuration or 'auto' for automatic calculation
* @param memo - Optional transaction memo
* @param versionId - Optional version identifier for the deactivation
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to the transaction response
* @throws Error if DID payload is not spec compliant or authentication is invalid
*/
async deactivateDidDocTx(signInputs, didPayload, address, fee, memo, versionId, context) {
if (!this._signer) {
this._signer = context.sdk.signer;
}
if (!versionId || versionId === '') {
versionId = (0, uuid_cjs_1.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 = index_1.MsgDeactivateDidDocPayload.fromPartial({
id: didPayload.id,
versionId: versionId,
});
let signatures;
if (types_1.ISignInputs.isSignInput(signInputs)) {
signatures = await this._signer.signDeactivateDidDocTx(signInputs, payload, protobufVerificationMethod);
}
else {
signatures = signInputs;
}
const value = {
payload,
signatures,
};
const deactivateDidMsg = {
typeUrl: exports.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);
}
/**
* Queries a DID document by its identifier.
* Retrieves the latest version of the DID document with metadata.
*
* @param id - DID identifier to query
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to the DID document with metadata
*/
async queryDidDoc(id, context) {
if (!this.querier) {
this.querier = context.sdk.querier;
}
const { didDoc, metadata } = await this.querier[exports.defaultDidExtensionKey].didDoc(id);
return {
didDocument: await DIDModule.toSpecCompliantPayload(didDoc),
didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata),
};
}
/**
* Queries a specific version of a DID document by its identifier and version ID.
*
* @param id - DID identifier to query
* @param versionId - Specific version identifier to retrieve
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to the DID document version with metadata
*/
async queryDidDocVersion(id, versionId, context) {
if (!this.querier) {
this.querier = context.sdk.querier;
}
const { didDoc, metadata } = await this.querier[exports.defaultDidExtensionKey].didDocVersion(id, versionId);
return {
didDocument: await DIDModule.toSpecCompliantPayload(didDoc),
didDocumentMetadata: await DIDModule.toSpecCompliantMetadata(metadata),
};
}
/**
* Queries metadata for all versions of a DID document.
* Retrieves version history information for a specific DID.
*
* @param id - DID identifier to query version metadata for
* @param context - Optional SDK context for accessing clients
* @returns Promise resolving to array of version metadata and pagination info
*/
async queryAllDidDocVersionsMetadata(id, context) {
if (!this.querier) {
this.querier = context.sdk.querier;
}
const { versions, pagination } = await this.querier[exports.defaultDidExtensionKey].allDidDocVersionsMetadata(id);
return {
didDocumentVersionsMetadata: await Promise.all(versions.map(async (m) => await DIDModule.toSpecCompliantMetadata(m))),
pagination,
};
}
/**
* Validates a DID document against the Cheqd specification requirements.
* Ensures all required fields are present and verification methods are supported.
*
* @param didDocument - DID document to validate
* @returns Promise resolving to validation result with protobuf conversion or error details
*/
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 types_1.VerificationMethods.Ed255192020:
if (!vm?.publicKeyMultibase)
throw new Error('publicKeyMultibase is required');
return index_1.VerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: types_1.VerificationMethods.Ed255192020,
verificationMaterial: vm.publicKeyMultibase,
});
case types_1.VerificationMethods.JWK:
if (!vm?.publicKeyJwk)
throw new Error('publicKeyJwk is required');
return index_1.VerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: types_1.VerificationMethods.JWK,
verificationMaterial: JSON.stringify(vm.publicKeyJwk),
});
case types_1.VerificationMethods.Ed255192018:
if (!vm?.publicKeyBase58)
throw new Error('publicKeyBase58 is required');
return index_1.VerificationMethod.fromPartial({
id: vm.id,
controller: vm.controller,
verificationMethodType: types_1.VerificationMethods.Ed255192018,
verificationMaterial: vm.publicKeyBase58,
});
default:
throw new Error('Unsupported verificationMethod type');
}
});
const protoService = (0, utils_1.normalizeService)(didDocument);
return {
valid: true,
protobufVerificationMethod: protoVerificationMethod,
protobufService: protoService,
};
}
/**
* Converts a protobuf DID document to a specification-compliant DID document format.
* Handles context inclusion, verification method formatting, and service denormalization.
*
* @param protobufDidDocument - Protobuf DID document to convert
* @returns Promise resolving to a spec-compliant DID document
*/
static async toSpecCompliantPayload(protobufDidDocument) {
const verificationMethod = protobufDidDocument.verificationMethod.map((vm) => {
switch (vm.verificationMethodType) {
case types_1.VerificationMethods.Ed255192020:
if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteEd255192020))
protobufDidDocument.context = [...protobufDidDocument.context, exports.contexts.W3CSuiteEd255192020];
return {
id: vm.id,
type: vm.verificationMethodType,
controller: vm.controller,
publicKeyMultibase: vm.verificationMaterial,
};
case types_1.VerificationMethods.JWK:
if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteJws2020))
protobufDidDocument.context = [...protobufDidDocument.context, exports.contexts.W3CSuiteJws2020];
return {
id: vm.id,
type: vm.verificationMethodType,
controller: vm.controller,
publicKeyJwk: JSON.parse(vm.verificationMaterial),
};
case types_1.VerificationMethods.Ed255192018:
if (!protobufDidDocument.context.includes(exports.contexts.W3CSuiteEd255192018))
protobufDidDocument.context = [...protobufDidDocument.context, exports.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 = (0, utils_1.denormalizeService)(protobufDidDocument);
const context = (function () {
if (protobufDidDocument.context.includes(exports.contexts.W3CDIDv1))
return protobufDidDocument.context;
return [exports.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;
}
/**
* Converts protobuf metadata to specification-compliant DID document metadata format.
* Handles date formatting and optional field normalization.
*
* @param protobufDidDocument - Protobuf metadata to convert
* @returns Promise resolving to spec-compliant DID document metadata
*/
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,
};
}
/**
* Validates that provided signatures match the authentication requirements in a DID document.
* Checks signature count, authentication presence, and controller authorization.
*
* @param didDocument - DID document containing authentication requirements
* @param signatures - Array of signatures to validate against authentication
* @param querier - Optional querier for retrieving external controller documents
* @param externalControllersDidDocuments - Optional pre-loaded external controller documents
* @returns Promise resolving to validation result with error details if invalid
*/
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 = (0, utils_1.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) => (0, utils_1.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 = (0, utils_1.normalizeController)(didDocument);
// 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 = externalController.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[exports.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 ? (0, utils_1.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 };
}
/**
* Validates authentication against signatures for key rotation scenarios.
* Handles validation during DID document updates where keys may have changed.
*
* @param didDocument - Updated DID document to validate
* @param signatures - Array of signatures to validate
* @param querier - Querier for retrieving previous DID document and controllers
* @param previousDidDocument - Optional previous version of the DID document
* @param externalControllersDidDocuments - Optional pre-loaded external controller documents
* @returns Promise resolving to validation result with controller documents and previous document
*/
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 = (0, utils_1.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) => (0, utils_1.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[exports.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 = (0, utils_1.normalizeController)(didDocument);
const previousControllers = (0, utils_1.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((0, utils_1.normalizeAuthentication)(didDocument));
const previousAuthenticationIds = new Set((0, utils_1.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 = (0, utils_1.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 !== vm.publicKeyJwk)))
: [];
// define unique union of signatures required, including key replacement logic
let uniqueUnionSignaturesRequired = new Set();
if (keyRotation && keyReplacement) {
// Combined operation: Both key rotation AND key replacement happening
// Need signatures from:
// 1. All rotated keys (both old and new material for same ID)
// 2. All added keys (new keys being added)
// 3. All removed keys (old keys being removed)
const rotatedKeySignatures = authentication
.filter((a) => rotatedKeys?.find((rk) => a === rk.id))
.map((a) => `${a}(document0)`);
const previousRotatedKeySignatures = previousAuthentication
.filter((a) => rotatedKeys?.find((rk) => a === rk.id))
.map((a) => `${a}(document1)`);
const newKeySignatures = addedKeys
.filter((keyId) => !rotatedKeys?.find((rk) => keyId === rk.id))
.map((keyId) => `${keyId}(document0)`);
const oldKeySignatures = removedKeys
.filter((keyId) => previousAuthentication.includes(keyId))
.map((keyId) => `${keyId}(document1)`);
uniqueUnionSignaturesRequired = new Set([
...rotatedKeySignatures,
...previousRotatedKeySignatures,
...newKeySignatures,
...oldKeySignatures,
]);
}
else if (keyRotation) {
// Key rotation only (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) {
// Key replacement only (different IDs in authentication)
// 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`,
};
// validate signatures - case: all required keys must have signatures provided (check for completely missing keys)
if (!externalController) {
const missingKeys = Array.from(uniqueUnionSignaturesRequiredFrequency.keys()).filter((requiredKey) => !uniqueSignaturesFrequency.has(requiredKey));
if (missingKeys.length > 0) {
return {
valid: false,
error: `authentication does not match signatures: signature from key ${missingKeys[0]} is missing`,
};
}
}
// require querier
if (!querier)
throw new Error('querier is required for external controller validation');
// get external controllers
// Only include rotated controllers if they are external (not the current DID itself)
const externalRotatedControllers = rotatedControllers.filter((c) => c !== didDocument.id);
const externalControllers = controllers?.filter((c) => c !== didDocument.id).concat(externalRotatedControllers);
// get external controllers' documents
const externalControllersDocuments = await Promise.all(externalControllers?.map(async (c) => {
// compute index of external con