From 3457b7c30c04191cacb3cf1a7ff0df0cf669ce94 Mon Sep 17 00:00:00 2001 From: zzcwoshizz Date: Fri, 10 Feb 2023 17:28:50 +0800 Subject: [PATCH] upgrade vc version to v1 1. add issuance date param whencalc digest. 2. When there are multiple versions of vc, there are different build logics. The difference between v1 and v2 is that the digest is constructed differently 3. vcVerify function can pass `version`(VerifiableCredentialVersion) for verification. --- .changeset/bright-gifts-flash.md | 10 +++++ packages/vc/src/credential/vc.ts | 22 ++++++---- packages/vc/src/defaults.ts | 2 +- packages/vc/src/digest.spec.ts | 36 ++++++++-------- packages/vc/src/digest.ts | 55 +++++++++++++++++------- packages/vc/src/types.ts | 2 +- packages/verify/src/digestVerify.spec.ts | 8 ++++ packages/verify/src/digestVerify.ts | 13 +++--- packages/verify/src/vcVerify.ts | 6 ++- 9 files changed, 104 insertions(+), 50 deletions(-) create mode 100644 .changeset/bright-gifts-flash.md diff --git a/.changeset/bright-gifts-flash.md b/.changeset/bright-gifts-flash.md new file mode 100644 index 0000000..c20a652 --- /dev/null +++ b/.changeset/bright-gifts-flash.md @@ -0,0 +1,10 @@ +--- +'@zcloak/verify': minor +'@zcloak/vc': minor +--- + +upgrade vc version to v1 + +1. add issuance date param whencalc digest. +2. When there are multiple versions of vc, there are different build logics. The difference between v1 and v2 is that the digest is constructed differently +3. vcVerify function can pass `version`(VerifiableCredentialVersion) for verification. diff --git a/packages/vc/src/credential/vc.ts b/packages/vc/src/credential/vc.ts index 2a15dd4..aba8cae 100644 --- a/packages/vc/src/credential/vc.ts +++ b/packages/vc/src/credential/vc.ts @@ -16,7 +16,7 @@ import { CType } from '@zcloak/ctype/types'; import { Did } from '@zcloak/did'; import { DEFAULT_CONTEXT, DEFAULT_VC_VERSION } from '../defaults'; -import { calcDigest } from '../digest'; +import { calcDigest, DigestPayload } from '../digest'; import { isRawCredential } from '../is'; import { calcRoothash, RootHashResult } from '../rootHash'; import { keyTypeToSignatureType } from '../utils'; @@ -106,16 +106,22 @@ export class VerifiableCredentialBuilder { rootHashResult = calcRoothash(this.raw.contents, this.raw.hashType, {}); } + const digestPayload: DigestPayload = { + rootHash: rootHashResult.rootHash, + expirationDate: this.expirationDate || undefined, + holder: this.raw.owner, + ctype: this.raw.ctype.$id + }; + + if (this.version) { + (digestPayload as DigestPayload<'1'>).issuanceDate = this.issuanceDate; + } + const { digest, type: digestHashType } = calcDigest( - { - rootHash: rootHashResult.rootHash, - expirationDate: this.expirationDate || undefined, - holder: this.raw.owner, - ctype: this.raw.ctype.$id - }, + this.version, + digestPayload, this.digestHashType ); - const { id, signature, type: keyType } = await issuer.signWithKey(digest, 'assertionMethod'); const proof: Proof = { diff --git a/packages/vc/src/defaults.ts b/packages/vc/src/defaults.ts index b117f3c..53f9e86 100644 --- a/packages/vc/src/defaults.ts +++ b/packages/vc/src/defaults.ts @@ -9,7 +9,7 @@ import type { VerifiablePresentationVersion } from './types'; -export const DEFAULT_VC_VERSION: VerifiableCredentialVersion = '0'; +export const DEFAULT_VC_VERSION: VerifiableCredentialVersion = '1'; export const DEFAULT_VP_VERSION: VerifiablePresentationVersion = '0'; diff --git a/packages/vc/src/digest.spec.ts b/packages/vc/src/digest.spec.ts index b2f9f76..b272366 100644 --- a/packages/vc/src/digest.spec.ts +++ b/packages/vc/src/digest.spec.ts @@ -3,7 +3,7 @@ import { initCrypto } from '@zcloak/crypto'; -import { calcDigest, DigestPayload } from './digest'; +import { calcDigest, DigestPayloadV0 } from './digest'; import { calcRoothash } from './rootHash'; describe('digest', (): void => { @@ -13,27 +13,27 @@ describe('digest', (): void => { describe('digest use keccak256', (): void => { it('digest without expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload)).toEqual({ + expect(calcDigest('0', payload)).toEqual({ type: 'Keccak256', digest: '0x358c172298da91c7736df58b30ddc87fcec1ff13f85bcfd60f0ef4d54a12c419' }); }); it('digest include expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', expirationDate: 1668167309925, ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload)).toEqual({ + expect(calcDigest('0', payload)).toEqual({ type: 'Keccak256', digest: '0xafe8c82e9581af9917f20d72d997ece3ed6edad6fc25498c5a2f61324be79a2a' }); @@ -58,7 +58,7 @@ describe('digest', (): void => { }); expect( - calcDigest({ + calcDigest('0', { rootHash, holder: 'did:zk:0x4042F3631656227d92452C9561889677c48f2C4c', ctype: '0x7d31ec6cceb9d313ab35dafa9058c88e758f30887362c2fab3c845902f9ccb31' @@ -72,27 +72,27 @@ describe('digest', (): void => { describe('digest use Blake3', (): void => { it('digest without expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Blake3')).toEqual({ + expect(calcDigest('0', payload, 'Blake3')).toEqual({ type: 'Blake3', digest: '0x95a9fe24ced5800caf0b31558700ed96d8c896cc9ecc877adc5059577f98c4fd' }); }); it('digest include expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', expirationDate: 1668167309925, ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Blake3')).toEqual({ + expect(calcDigest('0', payload, 'Blake3')).toEqual({ type: 'Blake3', digest: '0xd6648d4aa8054975e0e7bc1b012d9dd95c052269c32a082eaeda25c021e18765' }); @@ -101,27 +101,27 @@ describe('digest', (): void => { describe('digest use Blake2', (): void => { it('digest without expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Blake2')).toEqual({ + expect(calcDigest('0', payload, 'Blake2')).toEqual({ type: 'Blake2', digest: '0x895723d7b51f60fb3a4d7c408600cdbdfd077899a23f3009a8968348b4cf49cc' }); }); it('digest include expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', expirationDate: 1668167309925, ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Blake2')).toEqual({ + expect(calcDigest('0', payload, 'Blake2')).toEqual({ type: 'Blake2', digest: '0xbd54e9609a47ccef94111f870b4fd32c9388cbe5105a57e8f67e84c9d3edd335' }); @@ -130,27 +130,27 @@ describe('digest', (): void => { describe('digest use Sha256', (): void => { it('digest without expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Sha256')).toEqual({ + expect(calcDigest('0', payload, 'Sha256')).toEqual({ type: 'Sha256', digest: '0x8f35a8411fdb42cdca44ac8666b370e0de1233f5a56267183e939e9659a4ba80' }); }); it('digest include expirationDate', (): void => { - const payload: DigestPayload = { + const payload: DigestPayloadV0 = { rootHash: '0x0a86d6642395770ac481f8a215b08a881e9e0229de2686d498893dec0572649a', holder: 'did:zk:0x082d674c00e27fBaAAE123a85f5024A1DD702e51', expirationDate: 1668167309925, ctype: '0xd50f5298fda74ff0b46be740e602fa5ce0bc2a48fc5ddfbbae3c0678f59b5b97' }; - expect(calcDigest(payload, 'Sha256')).toEqual({ + expect(calcDigest('0', payload, 'Sha256')).toEqual({ type: 'Sha256', digest: '0x5b6d9f4d1f188494b1a32ba5995d797ba573efe40291774e92dd77cc3bc7dfa6' }); diff --git a/packages/vc/src/digest.ts b/packages/vc/src/digest.ts index 9d98e0b..7d538b0 100644 --- a/packages/vc/src/digest.ts +++ b/packages/vc/src/digest.ts @@ -3,7 +3,7 @@ import type { HexString } from '@zcloak/crypto/types'; import type { DidUrl } from '@zcloak/did-resolver/types'; -import type { HashType } from './types'; +import type { HashType, VerifiableCredentialVersion } from './types'; import { numberToU8a, stringToU8a, u8aConcat, u8aToHex } from '@polkadot/util'; @@ -12,7 +12,7 @@ import { HASHER } from './hasher'; export type DigestResult = { digest: HexString; type: HashType }; -export type DigestPayload = { +export interface DigestPayloadV0 { /** * rootHash of credential subject */ @@ -29,31 +29,56 @@ export type DigestPayload = { * ctype hash */ ctype: HexString; -}; +} + +export interface DigestPayloadV1 extends DigestPayloadV0 { + /** + * @since `v1` + * issuance date + */ + issuanceDate: number; +} + +export type DigestPayload = Version extends '0' + ? DigestPayloadV0 + : DigestPayloadV1; /** * @name calcDigest - * @summary calc credential digest + * @summary calc credential digest with version * @description - * 1. it will encode by ctype, expirationDate, rootHash, holder + * 1. it will encode by ctype, expirationDate, rootHash, holder, ?issuanceDate * 2. generate hash value use provide [[hashType]] * @example * ```typescript * import { calcDigest } from '@zcloak/vc'; * - * calcDigest({ rootHash: '0x...', holder: 'did:zk:...', ctype: '0x...' }); // { digest: '0x...', type: 'Keccak256' } + * calcDigest('1', { rootHash: '0x...', holder: 'did:zk:...', ctype: '0x...' }); // { digest: '0x...', type: 'Keccak256' } * ``` */ -export function calcDigest( - payload: DigestPayload, +export function calcDigest( + version: Version, + payload: DigestPayload, hashType: HashType = DEFAULT_DIGEST_HASH_TYPE -): DigestResult { - const encoded = u8aConcat( - payload.rootHash, - stringToU8a(payload.holder), - numberToU8a(payload.expirationDate), - payload.ctype - ); +) { + let encoded: Uint8Array; + + if (version === '0') { + encoded = u8aConcat( + payload.rootHash, + stringToU8a(payload.holder), + numberToU8a(payload.expirationDate || 0), + payload.ctype + ); + } else { + encoded = u8aConcat( + payload.rootHash, + stringToU8a(payload.holder), + numberToU8a((payload as DigestPayload<'1'>).issuanceDate), + numberToU8a(payload.expirationDate || 0), + payload.ctype + ); + } return { type: hashType, diff --git a/packages/vc/src/types.ts b/packages/vc/src/types.ts index 2130d87..f6da968 100644 --- a/packages/vc/src/types.ts +++ b/packages/vc/src/types.ts @@ -31,7 +31,7 @@ export type ProofType = SignatureType; export type VerifiablePresentationType = 'VP' | 'VP_Digest' | 'VP_SelectiveDisclosure'; -export type VerifiableCredentialVersion = '0'; +export type VerifiableCredentialVersion = '0' | '1'; export type VerifiablePresentationVersion = '0'; diff --git a/packages/verify/src/digestVerify.spec.ts b/packages/verify/src/digestVerify.spec.ts index 6a5031e..09144b7 100644 --- a/packages/verify/src/digestVerify.spec.ts +++ b/packages/verify/src/digestVerify.spec.ts @@ -20,6 +20,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -36,6 +37,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -55,6 +57,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -71,6 +74,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -90,6 +94,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -106,6 +111,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -125,6 +131,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, @@ -141,6 +148,7 @@ describe('verify digest', (): void => { expect( digestVerify( + '0', expectedDigest, { rootHash, diff --git a/packages/verify/src/digestVerify.ts b/packages/verify/src/digestVerify.ts index 7a71a15..3c07c3e 100644 --- a/packages/verify/src/digestVerify.ts +++ b/packages/verify/src/digestVerify.ts @@ -4,8 +4,10 @@ import type { HexString } from '@zcloak/crypto/types'; import type { DigestPayload } from '@zcloak/vc'; +import { u8aEq } from '@polkadot/util'; + import { calcDigest } from '@zcloak/vc'; -import { HashType } from '@zcloak/vc/types'; +import { HashType, VerifiableCredentialVersion } from '@zcloak/vc/types'; /** * @name verifyDigest @@ -27,12 +29,13 @@ import { HashType } from '@zcloak/vc/types'; * digestVerify(expectedDigest, payload, 'Keccak256'); // true * ``` */ -export function digestVerify( +export function digestVerify( + version: Version, digestIn: HexString, - payload: DigestPayload, + payload: DigestPayload, hashType?: HashType ): boolean { - const { digest } = calcDigest(payload, hashType); + const digest: HexString = calcDigest(version, payload, hashType).digest; - return digestIn === digest; + return u8aEq(digest, digestIn); } diff --git a/packages/verify/src/vcVerify.ts b/packages/verify/src/vcVerify.ts index b56eef0..a063926 100644 --- a/packages/verify/src/vcVerify.ts +++ b/packages/verify/src/vcVerify.ts @@ -24,19 +24,21 @@ async function verifyShared( ): Promise { assert(isVC(vc), 'input `vc` is not a VerifiableCredential'); - const { ctype, digest, expirationDate, hasher, holder, proof } = vc; + const { ctype, digest, expirationDate, hasher, holder, issuanceDate, proof, version } = vc; if (expirationDate && expirationDate < Date.now()) { return false; } const digestValid = digestVerify( + version, digest, { rootHash, holder, expirationDate, - ctype + ctype, + issuanceDate: version === '0' ? undefined : issuanceDate }, hasher[1] );