From c3ad20feaf1d5487d439667162e93c22493c417b Mon Sep 17 00:00:00 2001 From: zzcwoshizz Date: Tue, 7 Mar 2023 12:55:55 +0800 Subject: [PATCH] Signing controller (#58) * add ethereum address mapping for ecdsa pair * changeset * 1. add controller sign key for did keyring 2. publish did document default to use controller key 3. vp presentation default to use controller key 4. publish ctype default to use controller key 5. try to use assertionMethod, if it not exist, use controller to sign vc * changeset * test fix --- .changeset/hot-bulldogs-kick.md | 14 ++++++++ .changeset/metal-kings-tell.md | 5 +++ packages/crypto/src/multibase/helpers.ts | 10 ++++-- packages/ctype/src/publish.ts | 6 +--- packages/did/src/did/chain.ts | 6 +--- packages/did/src/did/details.ts | 11 ++++++ packages/did/src/did/index.spec.ts | 2 +- packages/did/src/did/keyring.ts | 2 +- packages/did/src/types.ts | 3 +- packages/keyring/src/keyring.ts | 4 +-- packages/keyring/src/pairs.ts | 26 +++++++++++--- packages/vc/src/credential/vc.ts | 45 +++++++++++++++++++----- packages/vc/src/vp.spec.ts | 14 ++++---- packages/vc/src/vp.ts | 10 ++---- packages/verify/src/didVerify.ts | 6 +++- 15 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 .changeset/hot-bulldogs-kick.md create mode 100644 .changeset/metal-kings-tell.md diff --git a/.changeset/hot-bulldogs-kick.md b/.changeset/hot-bulldogs-kick.md new file mode 100644 index 0000000..2b8560e --- /dev/null +++ b/.changeset/hot-bulldogs-kick.md @@ -0,0 +1,14 @@ +--- +'@zcloak/crypto': minor +'@zcloak/verify': minor +'@zcloak/ctype': minor +'@zcloak/did': minor +'@zcloak/vc': minor +--- + +add controller sign key for did keyring + +1. publish did document default to use controller key +2. vp presentation default to use controller key +3. publish ctype default to use controller key +4. try to use assertionMethod, if it not exist, use controller to sign vc diff --git a/.changeset/metal-kings-tell.md b/.changeset/metal-kings-tell.md new file mode 100644 index 0000000..59066a8 --- /dev/null +++ b/.changeset/metal-kings-tell.md @@ -0,0 +1,5 @@ +--- +'@zcloak/keyring': minor +--- + +add ethereum address mapping for ecdsa pair diff --git a/packages/crypto/src/multibase/helpers.ts b/packages/crypto/src/multibase/helpers.ts index 4b9cc09..a7af7b4 100644 --- a/packages/crypto/src/multibase/helpers.ts +++ b/packages/crypto/src/multibase/helpers.ts @@ -1,6 +1,10 @@ // Copyright 2021-2023 zcloak authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { HexString } from '../types'; + +import { u8aToU8a } from '@polkadot/util'; + interface Coder { decode: (value: string) => Uint8Array; encode: (value: Uint8Array) => string; @@ -14,7 +18,7 @@ interface Config { } type DecodeFn = (value: string) => Uint8Array; -type EncodeFn = (value: Uint8Array) => string; +type EncodeFn = (value: Uint8Array | HexString) => string; type ValidateFn = (value?: unknown) => value is string; @@ -29,8 +33,8 @@ export function createDecode({ coder }: Config, validate: ValidateFn): DecodeFn /** @internal */ export function createEncode({ coder, prefix }: Config): EncodeFn { - return (value: Uint8Array): string => { - const out = coder.encode(value); + return (value: Uint8Array | HexString): string => { + const out = coder.encode(u8aToU8a(value)); return `${prefix}${out}`; }; diff --git a/packages/ctype/src/publish.ts b/packages/ctype/src/publish.ts index 35a9bd1..7958693 100644 --- a/packages/ctype/src/publish.ts +++ b/packages/ctype/src/publish.ts @@ -42,11 +42,7 @@ export async function getPublish(base: BaseCType, publisher: Did): Promise { signedDidDocumentMessage(hashDidDocument(document), document.version || '0') ), decodeMultibase(document.proof[0].signature), - alice.get(alice.getKeyUrl('capabilityInvocation')).publicKey + alice.get(alice.getKeyUrl('controller')).publicKey ) ).toBe(true); }); diff --git a/packages/did/src/did/keyring.ts b/packages/did/src/did/keyring.ts index 69b604e..9a5ccb4 100644 --- a/packages/did/src/did/keyring.ts +++ b/packages/did/src/did/keyring.ts @@ -82,7 +82,7 @@ export abstract class DidKeyring extends DidDetails implements IDidKeyring { public async signWithKey( message: Uint8Array | HexString, - keyOrDidUrl: DidUrl | Exclude + keyOrDidUrl: DidUrl | Exclude = 'controller' ): Promise { const didUrl = isDidUrl(keyOrDidUrl) ? keyOrDidUrl : this.getKeyUrl(keyOrDidUrl); const { type } = this.get(didUrl); diff --git a/packages/did/src/types.ts b/packages/did/src/types.ts index 5895dc0..cc7b233 100644 --- a/packages/did/src/types.ts +++ b/packages/did/src/types.ts @@ -11,6 +11,7 @@ import type { } from '@zcloak/did-resolver/types'; export type DidKeys = + | 'controller' | 'authentication' | 'keyAgreement' | 'assertionMethod' @@ -33,7 +34,7 @@ export type EncryptedData = { export interface KeyRelationship { id: DidUrl; controller: DidUrl[]; - publicKey: Uint8Array; + publicKey: Uint8Array | HexString; type: VerificationMethodType; } diff --git a/packages/keyring/src/keyring.ts b/packages/keyring/src/keyring.ts index 95d6ccc..bc15dce 100644 --- a/packages/keyring/src/keyring.ts +++ b/packages/keyring/src/keyring.ts @@ -144,8 +144,8 @@ export class Keyring implements KeyringInstance { return createPair(keypair, { type }); } - public getPair(publicKey: Uint8Array | HexString): KeyringPair { - return this.#pairs.get(publicKey); + public getPair(publicKeyOrAddress: Uint8Array | HexString): KeyringPair { + return this.#pairs.get(publicKeyOrAddress); } public getPairs(): KeyringPair[] { diff --git a/packages/keyring/src/pairs.ts b/packages/keyring/src/pairs.ts index 3b2dbe8..6f056ec 100644 --- a/packages/keyring/src/pairs.ts +++ b/packages/keyring/src/pairs.ts @@ -4,14 +4,24 @@ import type { HexString } from '@zcloak/crypto/types'; import type { KeyringPair, KeyringPairs } from './types'; -import { isHex, u8aToHex } from '@polkadot/util'; +import { isHex, isU8a, u8aToHex } from '@polkadot/util'; -type KeyringPairMap = Record; +import { ethereumEncode, isEthereumAddress } from '@zcloak/crypto'; + +type KeyringPairMap = Record; + +type EthereumMapping = Record; export class Pairs implements KeyringPairs { readonly #map: KeyringPairMap = {}; + readonly #ethereumMapping: EthereumMapping = {}; public add(pair: KeyringPair): KeyringPair { + // save the ethereum address when `ecdsa` pair + if (pair.type === 'ecdsa') { + this.#ethereumMapping[ethereumEncode(pair.publicKey)] = u8aToHex(pair.publicKey); + } + this.#map[u8aToHex(pair.publicKey)] = pair; return pair; @@ -21,12 +31,20 @@ export class Pairs implements KeyringPairs { return Object.values(this.#map); } - public get(publicKey: HexString | Uint8Array): KeyringPair { + public get(publicKeyOrAddress: HexString | Uint8Array): KeyringPair { + const publicKey = isU8a(publicKeyOrAddress) + ? publicKeyOrAddress + : isEthereumAddress(publicKeyOrAddress) + ? this.#ethereumMapping[publicKeyOrAddress] + : publicKeyOrAddress; + const pair = this.#map[isHex(publicKey) ? publicKey : u8aToHex(publicKey)]; if (!pair) { throw new Error( - `Unable to retrieve keypair '${isHex(publicKey) ? publicKey : u8aToHex(publicKey)}'` + `Unable to retrieve keypair '${ + isHex(publicKeyOrAddress) ? publicKeyOrAddress : u8aToHex(publicKeyOrAddress) + }'` ); } diff --git a/packages/vc/src/credential/vc.ts b/packages/vc/src/credential/vc.ts index d78ca74..c74bf43 100644 --- a/packages/vc/src/credential/vc.ts +++ b/packages/vc/src/credential/vc.ts @@ -2,6 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import type { HexString } from '@polkadot/util/types'; +import type { CType } from '@zcloak/ctype/types'; +import type { Did } from '@zcloak/did'; +import type { DidKeys } from '@zcloak/did/types'; +import type { DidUrl } from '@zcloak/did-resolver/types'; import type { HashType, Proof, @@ -13,8 +17,6 @@ import type { import { assert } from '@polkadot/util'; import { base58Encode } from '@zcloak/crypto'; -import { CType } from '@zcloak/ctype/types'; -import { Did } from '@zcloak/did'; import { DEFAULT_CONTEXT, DEFAULT_VC_VERSION } from '../defaults'; import { calcDigest, DigestPayload } from '../digest'; @@ -89,12 +91,26 @@ export class VerifiableCredentialBuilder { /** * Build to [[PublicVerifiableCredential]] */ - public async build(issuer: Did, isPublic: true): Promise>; + public async build( + issuer: Did, + isPublic: true, + didKey?: Exclude + ): Promise>; + /** * Build to [[PrivateVerifiableCredential]] */ - public async build(issuer: Did, isPublic?: false): Promise>; - public async build(issuer: Did, isPublic?: boolean): Promise> { + public async build( + issuer: Did, + isPublic?: false, + didKey?: Exclude + ): Promise>; + + public async build( + issuer: Did, + isPublic?: boolean, + didKey: Exclude = 'assertionMethod' + ): Promise> { assert(this.raw.checkSubject(), `Subject check failed when use ctype ${this.raw.ctype}`); if ( @@ -126,7 +142,7 @@ export class VerifiableCredentialBuilder { this.digestHashType ); - const proof = await this._signDigest(issuer, digest, this.version); + const proof = await this._signDigest(issuer, digest, this.version, didKey); let vc: VerifiableCredential = { '@context': this['@context'], @@ -218,7 +234,8 @@ export class VerifiableCredentialBuilder { private async _signDigest( did: Did, digest: HexString, - version: VerifiableCredentialVersion + version: VerifiableCredentialVersion, + didKey: Exclude = 'assertionMethod' ): Promise { let message: Uint8Array | HexString; @@ -228,13 +245,23 @@ export class VerifiableCredentialBuilder { message = digest; } - const { id, signature, type: signType } = await did.signWithKey(message, 'assertionMethod'); + let signDidUrl: DidUrl; + + // try to use support didKey, if it not exist, try use controller key + try { + signDidUrl = did.getKeyUrl(didKey); + } catch { + didKey = 'controller'; + signDidUrl = did.getKeyUrl('controller'); + } + + const { id, signature, type: signType } = await did.signWithKey(message, signDidUrl); return { type: signType, created: Date.now(), verificationMethod: id, - proofPurpose: 'assertionMethod', + proofPurpose: didKey, proofValue: base58Encode(signature) }; } diff --git a/packages/vc/src/vp.spec.ts b/packages/vc/src/vp.spec.ts index 487420a..851e1be 100644 --- a/packages/vc/src/vp.spec.ts +++ b/packages/vc/src/vp.spec.ts @@ -140,7 +140,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -176,7 +176,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -216,7 +216,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -250,7 +250,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc1.digest, vc2.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -300,7 +300,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc1.digest, vc2.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -361,7 +361,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc1.digest, vc2.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); @@ -428,7 +428,7 @@ describe('VerifiablePresentation', (): void => { id: vpID([vc1.digest, vc2.digest, vc3.digest], vp.version, DEFAULT_VP_HASH_TYPE).hash, proof: { type: 'EcdsaSecp256k1SignatureEip191', - proofPurpose: 'authentication' + proofPurpose: 'controller' }, hasher: [DEFAULT_VP_HASH_TYPE] }); diff --git a/packages/vc/src/vp.ts b/packages/vc/src/vp.ts index bd665e5..72d6bb1 100644 --- a/packages/vc/src/vp.ts +++ b/packages/vc/src/vp.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { HexString } from '@zcloak/crypto/types'; +import type { Did } from '@zcloak/did'; import type { HashType, Proof, @@ -14,7 +15,6 @@ import type { import { assert, isHex, objectCopy, stringToU8a, u8aConcat, u8aToHex } from '@polkadot/util'; import { base58Encode } from '@zcloak/crypto'; -import { Did } from '@zcloak/did'; import { isSameUri } from '@zcloak/did/utils'; import { DEFAULT_CONTEXT, DEFAULT_VP_HASH_TYPE, DEFAULT_VP_VERSION } from './defaults'; @@ -179,17 +179,13 @@ export class VerifiablePresentationBuilder { message = signedVPMessage(hash, this.version, challenge); } - const { - id, - signature, - type: signType - } = await this.#did.signWithKey(message, 'authentication'); + const { id, signature, type: signType } = await this.#did.signWithKey(message, 'controller'); return { type: signType, created: Date.now(), verificationMethod: id, - proofPurpose: 'authentication', + proofPurpose: 'controller', proofValue: base58Encode(signature), challenge }; diff --git a/packages/verify/src/didVerify.ts b/packages/verify/src/didVerify.ts index c36700d..c95a75e 100644 --- a/packages/verify/src/didVerify.ts +++ b/packages/verify/src/didVerify.ts @@ -11,7 +11,11 @@ import { defaultResolver } from '@zcloak/did-resolver/defaults'; const VERIFIERS: Record< SignatureType, - (message: Uint8Array, signature: HexString | Uint8Array, publicKey: Uint8Array) => boolean + ( + message: Uint8Array, + signature: HexString | Uint8Array, + publicKey: HexString | Uint8Array + ) => boolean > = { EcdsaSecp256k1Signature2019: secp256k1Verify, EcdsaSecp256k1SignatureEip191: secp256k1Verify,