diff --git a/packages/cheqd/src/anoncreds/utils/identifiers.ts b/packages/cheqd/src/anoncreds/utils/identifiers.ts index 5368e6b9ee..ac4b58170c 100644 --- a/packages/cheqd/src/anoncreds/utils/identifiers.ts +++ b/packages/cheqd/src/anoncreds/utils/identifiers.ts @@ -1,3 +1,4 @@ +import type { CheqdNetwork } from '@cheqd/sdk' import type { ParsedDid } from '@credo-ts/core' import { TypedArrayEncoder, utils } from '@credo-ts/core' @@ -28,7 +29,7 @@ export const cheqdResourceMetadataRegex = new RegExp( `^did:cheqd:${NETWORK}:${IDENTIFIER}/resources/${IDENTIFIER}/metadata${QUERY}${FRAGMENT}` ) -export type ParsedCheqdDid = ParsedDid & { network: string } +export type ParsedCheqdDid = ParsedDid & { network: `${CheqdNetwork}` } export function parseCheqdDid(didUrl: string): ParsedCheqdDid | null { if (didUrl === '' || !didUrl) return null const sections = didUrl.match(cheqdSdkAnonCredsRegistryIdentifierRegex) @@ -44,7 +45,7 @@ export function parseCheqdDid(didUrl: string): ParsedCheqdDid | null { const parts: ParsedCheqdDid = { did: `did:cheqd:${sections[1]}:${sections[2]}`, method: 'cheqd', - network: sections[1], + network: sections[1] as `${CheqdNetwork}`, id: sections[2], didUrl, } diff --git a/packages/cheqd/src/dids/CheqdDidRegistrar.ts b/packages/cheqd/src/dids/CheqdDidRegistrar.ts index 53df226a42..8c0afd5c12 100644 --- a/packages/cheqd/src/dids/CheqdDidRegistrar.ts +++ b/packages/cheqd/src/dids/CheqdDidRegistrar.ts @@ -7,6 +7,7 @@ import type { DidCreateResult, DidDeactivateResult, DidUpdateResult, + DidUpdateOptions, } from '@credo-ts/core' import { MethodSpecificIdAlgo, createDidVerificationMethod } from '@cheqd/sdk' @@ -26,6 +27,7 @@ import { VerificationMethod, } from '@credo-ts/core' +import { parseCheqdDid } from '../anoncreds/utils/identifiers' import { CheqdLedgerService } from '../ledger' import { @@ -42,14 +44,28 @@ export class CheqdDidRegistrar implements DidRegistrar { const didRepository = agentContext.dependencyManager.resolve(DidRepository) const cheqdLedgerService = agentContext.dependencyManager.resolve(CheqdLedgerService) - const { methodSpecificIdAlgo, network, versionId = utils.uuid() } = options.options - const verificationMethod = options.secret?.verificationMethod let didDocument: DidDocument + const versionId = options.options?.versionId ?? utils.uuid() try { if (options.didDocument && validateSpecCompliantPayload(options.didDocument)) { didDocument = options.didDocument - } else if (verificationMethod) { + + const cheqdDid = parseCheqdDid(options.didDocument.id) + if (!cheqdDid) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Unable to parse cheqd did ${options.didDocument.id}`, + }, + } + } + } else if (options.secret?.verificationMethod) { + const withoutDidDocumentOptions = options as CheqdDidCreateWithoutDidDocumentOptions + const verificationMethod = withoutDidDocumentOptions.secret.verificationMethod + const methodSpecificIdAlgo = withoutDidDocumentOptions.options.methodSpecificIdAlgo const privateKey = verificationMethod.privateKey if (privateKey && !isValidPrivateKey(privateKey, KeyType.Ed25519)) { return { @@ -71,7 +87,7 @@ export class CheqdDidRegistrar implements DidRegistrar { verificationMethod: verificationMethod.type as VerificationMethods, verificationMethodId: verificationMethod.id || 'key-1', methodSpecificIdAlgo: (methodSpecificIdAlgo as MethodSpecificIdAlgo) || MethodSpecificIdAlgo.Uuid, - network: network as CheqdNetwork, + network: withoutDidDocumentOptions.options.network as CheqdNetwork, publicKey: TypedArrayEncoder.toHex(key.publicKey), }) @@ -383,8 +399,10 @@ export class CheqdDidRegistrar implements DidRegistrar { } } -export interface CheqdDidCreateOptions extends DidCreateOptions { +export interface CheqdDidCreateWithoutDidDocumentOptions extends DidCreateOptions { method: 'cheqd' + did?: undefined + didDocument?: undefined options: { network: `${CheqdNetwork}` fee?: DidStdFee @@ -392,12 +410,23 @@ export interface CheqdDidCreateOptions extends DidCreateOptions { methodSpecificIdAlgo?: `${MethodSpecificIdAlgo}` } secret: { - verificationMethod?: IVerificationMethod + verificationMethod: IVerificationMethod } } -export interface CheqdDidUpdateOptions extends DidCreateOptions { +export interface CheqdDidCreateFromDidDocumentOptions extends DidCreateOptions { method: 'cheqd' + did?: undefined + didDocument: DidDocument + options?: { + fee?: DidStdFee + versionId?: string + } +} + +export type CheqdDidCreateOptions = CheqdDidCreateFromDidDocumentOptions | CheqdDidCreateWithoutDidDocumentOptions + +export interface CheqdDidUpdateOptions extends DidUpdateOptions { did: string didDocument: DidDocument options: { diff --git a/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts b/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts index 4e59151298..47454dd326 100644 --- a/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts +++ b/packages/cheqd/tests/cheqd-did-registrar.e2e.test.ts @@ -1,7 +1,16 @@ import type { CheqdDidCreateOptions } from '../src' import type { DidDocument } from '@credo-ts/core' -import { Agent, TypedArrayEncoder } from '@credo-ts/core' +import { + SECURITY_JWS_CONTEXT_URL, + DidDocumentBuilder, + getEd25519VerificationKey2018, + getJsonWebKey2020, + KeyType, + utils, + Agent, + TypedArrayEncoder, +} from '@credo-ts/core' import { generateKeyPairFromSeed } from '@stablelib/ed25519' import { getInMemoryAgentOptions } from '../../core/tests/helpers' @@ -126,4 +135,73 @@ describe('Cheqd DID registrar', () => { const resolvedDocument = await agent.dids.resolve(did) expect(resolvedDocument.didDocumentMetadata.deactivated).toBe(true) }) + + it('should create a did:cheqd did using custom did document containing Ed25519 key', async () => { + const did = `did:cheqd:testnet:${utils.uuid()}` + + const ed25519Key = await agent.wallet.createKey({ + keyType: KeyType.Ed25519, + }) + + const createResult = await agent.dids.create({ + method: 'cheqd', + didDocument: new DidDocumentBuilder(did) + .addContext(SECURITY_JWS_CONTEXT_URL) + .addVerificationMethod( + getEd25519VerificationKey2018({ + key: ed25519Key, + controller: did, + id: `${did}#${ed25519Key.fingerprint}`, + }) + ) + .build(), + }) + + expect(createResult).toMatchObject({ + didState: { + state: 'finished', + }, + }) + + expect(createResult.didState.didDocument?.toJSON()).toMatchObject({ + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'], + verificationMethod: [ + { + controller: did, + type: 'Ed25519VerificationKey2018', + publicKeyBase58: ed25519Key.publicKeyBase58, + }, + ], + }) + }) + + it('should create a did:cheqd did using custom did document containing P256 key', async () => { + const did = `did:cheqd:testnet:${utils.uuid()}` + + const p256Key = await agent.wallet.createKey({ + keyType: KeyType.P256, + }) + + const createResult = await agent.dids.create({ + method: 'cheqd', + didDocument: new DidDocumentBuilder(did) + .addContext(SECURITY_JWS_CONTEXT_URL) + .addVerificationMethod( + getJsonWebKey2020({ + did, + key: p256Key, + verificationMethodId: `${did}#${p256Key.fingerprint}`, + }) + ) + .build(), + }) + + // FIXME: the ES256 signature generated by Credo is invalid for Cheqd + // need to dive deeper into it, but for now adding a failing test so we can fix it in the future + expect(createResult).toMatchObject({ + didState: { + state: 'failed', + }, + }) + }) })