From 0d876eb405418937ff7f8d219f4343df02c68ab4 Mon Sep 17 00:00:00 2001 From: Raphael Flechtner Date: Wed, 18 Oct 2023 16:38:07 +0200 Subject: [PATCH] feat: signWithDid --- .../core/src/delegation/DelegationNode.ts | 23 ++---- packages/did/src/Did.signature.ts | 71 ++++++++++++++++++- packages/legacy-credentials/src/Credential.ts | 30 +++----- 3 files changed, 84 insertions(+), 40 deletions(-) diff --git a/packages/core/src/delegation/DelegationNode.ts b/packages/core/src/delegation/DelegationNode.ts index 63af129a6c..b42c87680c 100644 --- a/packages/core/src/delegation/DelegationNode.ts +++ b/packages/core/src/delegation/DelegationNode.ts @@ -265,26 +265,17 @@ export class DelegationNode implements IDelegationNode { delegateDid: DidDocument, signers: SignerInterface[] ): Promise { - const { byDid, verifiableOnChain } = Signers.select - const signer = await Signers.selectSigner( - signers, - verifiableOnChain(), - byDid(delegateDid, { - verificationRelationship: 'authentication', - }) - ) - if (!signer) { - throw new Error( - 'no signer available for on-chain verifiable signatures by an authentication key' - ) - } - const delegateSignature = await signer.sign({ + const { signature, algorithm } = await Did.signWithDid({ data: this.generateHash(), + did: delegateDid, + signers, + purpose: 'authentication', + algorithms: Signers.DID_PALLET_SUPPORTED_ALGORITHMS, // signature is verified on-chain }) return Did.didSignatureToChain({ - signature: delegateSignature, - algorithm: signer.algorithm, + signature, + algorithm, }) } diff --git a/packages/did/src/Did.signature.ts b/packages/did/src/Did.signature.ts index 7191c17916..96e8da0f43 100644 --- a/packages/did/src/Did.signature.ts +++ b/packages/did/src/Did.signature.ts @@ -14,13 +14,14 @@ import type { DidUri, DidUrl, SignatureVerificationRelationship, + SignerInterface, SignResponseData, } from '@kiltprotocol/types' -import { Crypto, SDKErrors } from '@kiltprotocol/utils' +import { Crypto, SDKErrors, Signers } from '@kiltprotocol/utils' import { multibaseKeyToDidKey, parse, validateIdentifier } from './Did.utils.js' -import { dereference } from './DidResolver/DidResolver.js' +import { dereference, resolve } from './DidResolver/DidResolver.js' export type DidSignatureVerificationInput = { message: string | Uint8Array @@ -63,6 +64,72 @@ function verifyDidSignatureDataStructure( validateIdentifier(verificationMethodUrl, 'Url') } +/** + * @param root0 + * @param root0.data + * @param root0.did + * @param root0.signers + * @param root0.purpose + * @param root0.algorithms + */ +export async function signWithDid({ + data, + did, + signers, + purpose = 'authentication', + algorithms = Signers.DID_PALLET_SUPPORTED_ALGORITHMS, +}: { + data: Uint8Array + did: DidDocument | DidUri + signers: readonly SignerInterface[] + purpose?: string + algorithms?: readonly string[] +}): Promise<{ id: DidUrl; algorithm: string; signature: Uint8Array }> { + const didDocument = + typeof did === 'string' ? (await resolve(did)).didDocument : did + if (!didDocument?.id) { + throw new Error('Unable to sign: Signer DID cannot be resolved') + } + if (!didDocument.verificationMethod?.length) { + throw new Error( + 'Unable to sign: No verification methods are associated with the signer DID document. It may be that this DID has been deactivated.' + ) + } + if (typeof purpose === 'string' && !didDocument[purpose]?.length) { + throw new Error( + `Unable to sign: No verification methods associated with the signer DID document match the requested purpose ("${purpose}").` + ) + } + + const { byAlgorithm, byDid } = Signers.select + const signerSelectors = [ + byDid(didDocument, { verificationRelationship: purpose }), + ] + if (Array.isArray(algorithms)) { + signerSelectors.push(byAlgorithm(algorithms)) + } + const signer = await Signers.selectSigner(signers, ...signerSelectors) + if (!signer) { + const eligibleVms: string[] | undefined = purpose + ? didDocument[purpose] + : didDocument.verificationMethod?.map(({ id }) => id) + const msg = `Unable to sign: No signer interface passed to this function fulfills the required criteria (eligible verification methods: [${ + eligibleVms?.join(', ') ?? '' + }]${algorithms ? `, eligible algorithms: [${algorithms.join(', ')}]` : ''}). + Available signers: + ${signers.map(({ id, algorithm }) => `${id} (${algorithm})`).join('\n')} + ` + throw new Error(msg) + } + + const signature = await signer.sign({ data }) + return { + id: signer.id as DidUrl, + algorithm: signer.algorithm, + signature, + } +} + /** * Verify a DID signature given the signer's DID URL (i.e., DID URI + verification method ID). * A signature verification returns false if a migrated and then deleted DID is used. diff --git a/packages/legacy-credentials/src/Credential.ts b/packages/legacy-credentials/src/Credential.ts index 77bee3b872..a101d3a257 100644 --- a/packages/legacy-credentials/src/Credential.ts +++ b/packages/legacy-credentials/src/Credential.ts @@ -21,13 +21,12 @@ import { ConfigService } from '@kiltprotocol/config' import { Attestation, CType } from '@kiltprotocol/core' import { isDidSignature, - resolve, + signWithDid, dereference, signatureFromJson, verifyDidSignature, } from '@kiltprotocol/did' import type { - DidUrl, DidUri, Hash, IAttestation, @@ -507,8 +506,6 @@ function getAttributes(credential: ICredential): Set { return new Set(Object.keys(credential.claim.contents)) } -const { verifiableOnChain, byDid } = Signers.select - /** * Creates a public presentation which can be sent to a verifier. * This presentation is signed. @@ -530,7 +527,7 @@ export async function createPresentation({ didDocument, }: { credential: ICredential - signers: SignerInterface[] + signers: readonly SignerInterface[] selectedAttributes?: string[] challenge?: string didDocument?: DidDocument @@ -548,30 +545,19 @@ export async function createPresentation({ excludedClaimProperties ) - if (!didDocument) { - didDocument = (await resolve(credential.claim.owner)).didDocument - } - if (!didDocument) { - throw new Error('claimer DID cannot be resolved') - } - const signer = await Signers.selectSigner( - signers, - verifiableOnChain(), - byDid(didDocument, { verificationRelationship: 'authentication' }) - ) - if (!signer) { - throw new Error('no suitable signer available') - } - - const signature = await signer?.sign({ + const { signature, id: signerUrl } = await signWithDid({ + did: didDocument ?? credential.claim.owner, data: makeSigningData(presentation, challenge), + signers, + purpose: 'authentication', + algorithms: Signers.DID_PALLET_SUPPORTED_ALGORITHMS, // legacy presentations use Polkadot-compatible signatures exclusively }) return { ...presentation, claimerSignature: { signature: Crypto.u8aToHex(signature), - signerUrl: signer.id as DidUrl, + signerUrl, ...(challenge && { challenge }), }, }