Skip to content

Commit

Permalink
Merge branch 'feat-multiple-vc' of github.com:zCloak-Network/zkid-sdk…
Browse files Browse the repository at this point in the history
… into feat-multiple-vc
  • Loading branch information
王会 committed Aug 26, 2023
2 parents d592a45 + 048d951 commit 4925c7a
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .yarn/releases/yarn-4.0.0-rc.40.cjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/verify/src/vcVerify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ describe('vc verify', (): void => {
credentialSubject: calcRoothash(
fullVC.credentialSubject as AnyJson,
fullVC.hasher[0],
fullVC.version,
fullVC.credentialSubjectNonceMap || {}
).rootHash,
credentialSubjectNonceMap: {},
Expand Down
2 changes: 1 addition & 1 deletion packages/verify/src/vcVerify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function vcVerify(
let rootHash: HexString;

if (isPublicVC(vc)) {
rootHash = calcRoothash(vc.credentialSubject, vc.hasher[0]).rootHash;
rootHash = calcRoothash(vc.credentialSubject, vc.hasher[0], vc.version).rootHash;
} else {
const { credentialSubject, credentialSubjectHashes, credentialSubjectNonceMap, hasher } = vc;

Expand Down
18 changes: 8 additions & 10 deletions protocol/vc/src/credential/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ describe('VerifiableCredential', (): void => {
digestHashType: 'Keccak256'
});

const vc = await vcBuilder.build(issuer);

const vc = await vcBuilder.build(issuer, false);
expect(isPrivateVC(vc)).toBe(true);

expect(vc).toMatchObject({
Expand All @@ -120,7 +119,7 @@ describe('VerifiableCredential', (): void => {
ctype: ctype.$id,
issuanceDate: now,
credentialSubject: CONTENTS,
issuer: issuer.id,
issuer: [issuer.id],
holder: holder.id,
hasher: ['RescuePrime', 'Keccak256'],
proof: [
Expand Down Expand Up @@ -153,7 +152,7 @@ describe('VerifiableCredential', (): void => {
digestHashType: 'Keccak256'
});

const vc = await vcBuilder.build(issuer);
const vc = await vcBuilder.build(issuer, false);

expect(isPrivateVC(vc)).toBe(true);

Expand All @@ -163,7 +162,7 @@ describe('VerifiableCredential', (): void => {
ctype: ctype.$id,
issuanceDate: now,
credentialSubject: CONTENTS,
issuer: issuer.id,
issuer: [issuer.id],
holder: holder.id,
hasher: ['RescuePrime', 'Keccak256'],
proof: [
Expand Down Expand Up @@ -196,17 +195,16 @@ describe('VerifiableCredential', (): void => {
digestHashType: 'Keccak256'
});

const vc = await vcBuilder.build(issuer);

expect(isPrivateVC(vc)).toBe(true);
const vc = await vcBuilder.build(issuer, false,["did:zk:0xFeDE01Ff4402e35c6f6d20De9821d64bDF4Ba563"]);
expect(isPrivateVC(vc)).toBe(false);

expect(vc).toMatchObject({
'@context': DEFAULT_CONTEXT,
version: DEFAULT_VC_VERSION,
ctype: ctype.$id,
issuanceDate: now,
credentialSubject: CONTENTS,
issuer: issuer.id,
issuer: [issuer.id, "did:zk:0xFeDE01Ff4402e35c6f6d20De9821d64bDF4Ba563"],
holder: holder.id,
hasher: ['RescuePrimeOptimized', 'Keccak256'],
proof: [
Expand Down Expand Up @@ -256,7 +254,7 @@ describe('VerifiableCredential', (): void => {
ctype: ctype.$id,
issuanceDate: now,
credentialSubject: CONTENTS,
issuer: issuer.id,
issuer: [issuer.id],
holder: holder.id,
hasher: ['RescuePrime', 'Keccak256'],
proof: [
Expand Down
4 changes: 2 additions & 2 deletions protocol/vc/src/credential/vc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ export class VerifiableCredentialBuilder {
let rootHashResult: RootHashResult;

if (isPublic) {
rootHashResult = calcRoothash(this.raw.contents, this.raw.hashType);
rootHashResult = calcRoothash(this.raw.contents, this.raw.hashType, this.version);
} else {
rootHashResult = calcRoothash(this.raw.contents, this.raw.hashType, {});
rootHashResult = calcRoothash(this.raw.contents, this.raw.hashType, this.version, {});
}

const digestPayload: DigestPayload<VerifiableCredentialVersion> = {
Expand Down
2 changes: 1 addition & 1 deletion protocol/vc/src/digest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('digest', (): void => {
isUser: true
};

const { rootHash } = calcRoothash(input, 'Blake32to1', {
const { rootHash } = calcRoothash(input, 'Blake32to1', '1', {
'0x6b90277e3f4ab97b83b3fc61ecffa4ec063f70d7255233ef5afdf418dcec3b75':
'0x25807968a4c5f3ce2f116c5914991c97e33020408146406a36e4fa4826dd6c7d',
'0x1300000000000000000000000000000000000000000000000000000000000000':
Expand Down
51 changes: 49 additions & 2 deletions protocol/vc/src/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { isBase32, isBase58, isBase64 } from '@zcloak/crypto';
import { isDidUrl } from '@zcloak/did/utils';

import { ALL_HASH_TYPES, ALL_SIG_TYPES, ALL_VP_TYPES } from './defaults';
import { parseDid } from '@zcloak/did-resolver/parseDid';

/**
* @name isHashType
Expand Down Expand Up @@ -61,6 +62,52 @@ export function isProof(input: unknown): input is Proof {
);
}

/**
* @name isAttesterMapping
* @description
* check the `attester` is the same in [[Proof]]
*/
export function isAttesterMapping(issuer: unknown, proof: unknown): boolean {
if (isProof(proof)) {
const issuerInProof = parseDid(proof.verificationMethod).did;
return issuer === issuerInProof;
} else return false;
}

/**
* @name isAttesterProof
* @description
* check the Proof is qualified or not
*/
export function isAttesterProof(issuer: unknown, proof: unknown): boolean {
// version 0 & version 1, only one attester and one proof
if (typeof issuer === 'string' && isArray(proof) && proof.length === 1) {
return isAttesterMapping(issuer, proof);
} else if (isArray(issuer) && isArray(proof) && issuer.length === proof.length) {
let check = true;
for (let i = 0; i < issuer.length; i++) {
check = isAttesterMapping(issuer[i], proof[i]) && check;
}
return check;
} else {
return false;
}
}

export function isAttester(value: unknown, version: unknown): boolean {
if (typeof value === 'string' && (version === '0' || version === '1')) {
return isDidUrl(value)
} else if (isArray(value) && value.length !== 0 && version === '2'){
let check = true;
for (const item of value) {
check = isDidUrl(item) && check;
}
return check;
} else {
return false;
}
}

/**
* @name isRawCredential
* @description
Expand Down Expand Up @@ -118,10 +165,10 @@ export function isVC(input: unknown): input is VerifiableCredential<boolean> {
isString(input.version) &&
isNumber(input.issuanceDate) &&
(isUndefined(input.expirationDate) || isNull(input.expirationDate) || isNumber(input.expirationDate)) &&
isDidUrl(input.issuer) &&
isAttester(input.issuer, input.version) &&
isHex(input.digest) &&
isArray(input.proof) &&
!input.proof.map((p) => isProof(p)).includes(false) &&
isAttesterProof(input.issuer, input.proof) &&
isHex(input.ctype) &&
(isJsonObject(input.credentialSubject) || isHex(input.credentialSubject)) &&
isDidUrl(input.holder) &&
Expand Down
56 changes: 50 additions & 6 deletions protocol/vc/src/rootHash.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright 2021-2023 zcloak authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { initCrypto } from '@zcloak/crypto';
import { HexString } from '@polkadot/util/types';
import { initCrypto, keccak256AsHex } from '@zcloak/crypto';

import { calcRoothash } from './rootHash';
import { encodeAsSol } from './utils';

describe('calcRoothash', (): void => {
beforeAll(async (): Promise<void> => {
Expand All @@ -19,7 +21,7 @@ describe('calcRoothash', (): void => {
isUser: true
};

const { rootHash } = calcRoothash(input, 'RescuePrime', {
const { rootHash } = calcRoothash(input, 'RescuePrime', '1', {
'0x88af5a7ba28c1de54ebd589dea81d30caa3f467646f6d714c0d2604599d63e1e':
'0x357d50aac640931f9976477de30b3b476be4a14ae367b045496670d7a23c457d',
'0x9ad57aefa90d9473f855c14221f330fe959a554b3d86c9d701db11c7559ce107':
Expand All @@ -42,7 +44,7 @@ describe('calcRoothash', (): void => {
types: ['1', '2', '5']
};

const { rootHash } = calcRoothash(input, 'RescuePrime', {
const { rootHash } = calcRoothash(input, 'RescuePrime', '1', {
'0x88af5a7ba28c1de54ebd589dea81d30caa3f467646f6d714c0d2604599d63e1e':
'0x42ecebd0acae94843f906a9fe69e5c672c60d8d688b71aa85948d8e60becf082',
'0x9ad57aefa90d9473f855c14221f330fe959a554b3d86c9d701db11c7559ce107':
Expand All @@ -67,7 +69,7 @@ describe('calcRoothash', (): void => {
types: ['1', '2', '5']
};

const { rootHash } = calcRoothash(input, 'RescuePrime');
const { rootHash } = calcRoothash(input, 'RescuePrime', '1');

expect(rootHash).toEqual('0x9579c93741b6979500bca88dcb104da424c2c3bb0e2fa85f13d76cd00637e46f');
});
Expand All @@ -82,7 +84,7 @@ describe('calcRoothash', (): void => {
isUser: true
};

const { rootHash } = calcRoothash(input, 'Blake32to1', {
const { rootHash } = calcRoothash(input, 'Blake32to1', '1', {
'0x6b90277e3f4ab97b83b3fc61ecffa4ec063f70d7255233ef5afdf418dcec3b75':
'0x25807968a4c5f3ce2f116c5914991c97e33020408146406a36e4fa4826dd6c7d',
'0x1300000000000000000000000000000000000000000000000000000000000000':
Expand All @@ -106,7 +108,7 @@ describe('calcRoothash', (): void => {
isUser: true
};

const { rootHash } = calcRoothash(input, 'RescuePrimeOptimized', {
const { rootHash } = calcRoothash(input, 'RescuePrimeOptimized', '1', {
'0x011eed5f2e2321069b83e2bdd38c790948231cd1d49ed6c4dd09bb0f16b0661c':
'0xfd766e5717ae2ae09abee1268d6a653dc50c2067d9efd665e7b52d8f0f597d2b',
'0x7f8a33bf3f50ca9b84d1dc5561c8f71d48d6256763fd8d0f5e5e902dce5dfb88':
Expand All @@ -120,4 +122,46 @@ describe('calcRoothash', (): void => {
expect(rootHash).toEqual('0x2cbd72a75cf5797fb6893b222a45de60eaa2ddf2e5d435d67e5ed8067efb76e3');
});
});

describe('calcContentAsSolidity', () => {
it('calcRoothash', (): void => {
const input = {
name: 'zCloak',
age: 111,
number_array: [11, 12, 13],
isUser: true,
string_array: ["zCloak", "database"]
};
const values = Object.values(input);
const encodedClaimHashes: HexString[] = values.map((values) => encodeAsSol(values));

expect(encodedClaimHashes).toEqual(["0x28cb5b00333a3266fa3d92f3426ad4ef1d20018b44dd64913578d43438b4051a", "0x39f2babe526038520877fc7c33d81accf578af4a06c5fa6b0d038cae36e12711", "0x8d20824cd86ad1c5673537a4b5d1ee68bd9265b7d357e82bc76483ae879322c2", "0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2", "0x69ab0c29dcf9256886435d72272bceee1f0a0b2cee41865d3b56f9726e7fe0a3",
]);
});
});

describe('calc Roothash with version 2', () => {
it('calcRoothash', (): void => {
const input = {
name: 'zCloak',
age: 111,
number_array: [11, 12, 13],
isUser: true,
string_array: ["zCloak", "database"]
};
const values = Object.values(input);
const encodedClaimHashes: HexString[] = values.map((values) => encodeAsSol(values));

expect(encodedClaimHashes).toEqual(["0x28cb5b00333a3266fa3d92f3426ad4ef1d20018b44dd64913578d43438b4051a", "0x39f2babe526038520877fc7c33d81accf578af4a06c5fa6b0d038cae36e12711", "0x8d20824cd86ad1c5673537a4b5d1ee68bd9265b7d357e82bc76483ae879322c2", "0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2", "0x69ab0c29dcf9256886435d72272bceee1f0a0b2cee41865d3b56f9726e7fe0a3",
]);

const { rootHash } = calcRoothash(input, 'Keccak256', '2', {'0x28cb5b00333a3266fa3d92f3426ad4ef1d20018b44dd64913578d43438b4051a': '0xcd769c703ecfdc6798d56711af8b3a7918973524db6c6c0901209fd396f3a61b',
'0x39f2babe526038520877fc7c33d81accf578af4a06c5fa6b0d038cae36e12711': '0xc5bd9417c2dd820e8c074086acb0e1a785c5f9aa59022ca74606f4f6f32338bb',
'0x8d20824cd86ad1c5673537a4b5d1ee68bd9265b7d357e82bc76483ae879322c2': '0x9895d1010a3c7ae796bfdb2bff52febcfb21d8c71a0fb465a02d8a2a0ad857bd',
'0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2': '0xddb361f768d83f2f8eabd4f51517adb8d92aef33ce627127311bad01bac41710',
'0x69ab0c29dcf9256886435d72272bceee1f0a0b2cee41865d3b56f9726e7fe0a3': '0x51c09e0512047698103565fc9c2aca963eb3af6cf99b9201fc6be1331e4649e5'
});
expect(rootHash).toEqual('0x8e94d639398a25f0484ba8f40feff5e694b084164118af84ffece25c68be4342');
});
});
});
24 changes: 17 additions & 7 deletions protocol/vc/src/rootHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

import type { HexString } from '@zcloak/crypto/types';
import type { AnyJson, HashType } from './types';
import type { AnyJson, HashType, VerifiableCredentialVersion } from './types';

import { assert, bufferToU8a, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';
import { MerkleTree } from 'merkletreejs';

import { randomAsHex } from '@zcloak/crypto';

import { HASHER } from './hasher';
import { rlpEncode } from './utils';
import { encodeAsSol, rlpEncode } from './utils';

export type RootHashResult = {
rootHash: HexString;
Expand Down Expand Up @@ -53,7 +53,6 @@ export function rootHashFromMerkle(
}

const leave = HASHER[hashType](nonceMap ? u8aConcat(encode, nonceMap[encode]) : encode);

leaves.push(leave);
}

Expand All @@ -71,7 +70,7 @@ export function rootHashFromMerkle(
* @description
* calc rootHash with supplied `input` and `hashType`. Returns [[RootHashResult]].
*/
export function calcRoothash(input: AnyJson, hashType: HashType): RootHashResult;
export function calcRoothash(input: AnyJson, hashType: HashType, version: VerifiableCredentialVersion): RootHashResult;
/**
* @name calcRoothash
* @summary calc rootHash from any json.
Expand All @@ -83,16 +82,27 @@ export function calcRoothash(input: AnyJson, hashType: HashType): RootHashResult
export function calcRoothash(
input: AnyJson,
hashType: HashType,
nonceMap: Record<HexString, HexString>
version: VerifiableCredentialVersion,
nonceMap: Record<HexString, HexString>,
): RootHashResult;

export function calcRoothash(
input: AnyJson,
hashType: HashType,
nonceMap?: Record<HexString, HexString>
version: VerifiableCredentialVersion,
nonceMap?: Record<HexString, HexString>,
): RootHashResult {
const values = Object.values(input);
const encoded: HexString[] = values.map((value) => rlpEncode(value, hashType)).map((value) => u8aToHex(value));
let encoded: HexString[] = [];

// if the version is `2` and the hash in merkletree is Keccak256, we assume this vc aims to be used on chain.
if (version === '2' && hashType === 'Keccak256') {
encoded = values.map((values) => encodeAsSol(values));
} else if (version === '0' || version === '1' || version === '2') {
encoded = values.map((value) => rlpEncode(value, hashType)).map((value) => u8aToHex(value));
} else {
throw new Error('The Version is not supported for calc roothash');
}

if (nonceMap) {
for (const encode of encoded) {
Expand Down
27 changes: 27 additions & 0 deletions protocol/vc/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { rlpEncode as rlpEncodeFn } from '@zcloak/crypto';

import { HASHER } from './hasher';

import Web3 from 'web3';

export function rlpEncode(input: NativeType | NativeTypeWithOutNull[], hashType: HashType): Uint8Array {
const result = rlpEncodeFn(input);

Expand All @@ -26,6 +28,31 @@ export function rlpEncode(input: NativeType | NativeTypeWithOutNull[], hashType:
}
}

export function encodeAsSol(input: NativeType | NativeTypeWithOutNull[]): HexString {
const web3 = new Web3() as any;
switch (typeof input) {
case "string":
return web3.utils.soliditySha3({ type: 'string', value: input })

case "number":
if (input % 1 !== 0) {
throw new Error(`Can not encode number with dot`);
}
return web3.utils.soliditySha3({ type: 'int256', value: input });
case "boolean":
return web3.utils.soliditySha3({ type: 'bool', value: input });
case "object":
if (Array.isArray(input) && typeof input[0] == 'string') {
const encodedParams = web3.eth.abi.encodeParameters(['string[]'], [input]);
return web3.utils.keccak256(encodedParams);
} else if ((Array.isArray(input) && typeof input[0] == 'number')) {
return web3.utils.soliditySha3({ type: 'int256[]', value: input });
} else throw new Error(`This object can not be encoded ${input}`);
default:
throw new Error(`Can not encode this type: ${input}`)
}
}

export function signedVCMessage(digest: HexString, version: VerifiableCredentialVersion): Uint8Array {
return u8aConcat(stringToU8a('CredentialVersionedDigest'), numberToU8a(Number(version), 16), digest);
}
Expand Down
Loading

0 comments on commit 4925c7a

Please sign in to comment.