From 2390adddeaaf426ca39bea60c48da129b589ab67 Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:46:41 +0300 Subject: [PATCH] support profiles (#105) * support profiles * support profiles * modify IPFS tests * MInor fix * Rollback rename * make findCredential private --------- Co-authored-by: Dimasik Kolezhniuk --- .github/workflows/ci.yaml | 1 + README.md | 2 + package-lock.json | 8 +- package.json | 2 +- src/credentials/status/on-chain-revocation.ts | 13 +- src/iden3comm/handlers/auth.ts | 248 ++++++------------ src/iden3comm/handlers/fetch.ts | 120 +++++---- src/iden3comm/packers/jws.ts | 17 +- src/iden3comm/packers/zkp.ts | 17 +- src/iden3comm/types/packer.ts | 19 +- src/iden3comm/types/protocol/auth.ts | 6 +- src/iden3comm/types/protocol/credentials.ts | 16 +- src/identity/common.ts | 15 +- src/identity/identity-wallet.ts | 101 ++++++- src/proof/proof-service.ts | 145 ++++++---- src/storage/entities/identity.ts | 8 +- src/storage/interfaces/identity.ts | 4 +- src/storage/shared/identity-storage.ts | 20 +- src/verifiable/presentation.ts | 5 +- src/verifiable/proof.ts | 1 + tests/handlers/auth.test.ts | 75 +----- tests/handlers/fetch.test.ts | 16 +- tests/iden3comm/mock/proving.ts | 1 - tests/proofs/common.ts | 42 --- tests/proofs/mtp-onchain.test.ts | 19 +- tests/proofs/mtp.test.ts | 25 +- tests/proofs/sig-onchain.test.ts | 13 +- tests/proofs/sig.test.ts | 36 +-- tests/rhs/rhs.test.ts | 2 +- 29 files changed, 466 insertions(+), 531 deletions(-) delete mode 100644 tests/proofs/common.ts diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 389d9664..6c897223 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,6 +54,7 @@ jobs: - name: Run Tests env: + IPFS_URL: ${{ secrets.IPFS_URL }} WALLET_KEY: ${{ secrets.WALLET_KEY }} RPC_URL: ${{ secrets.RPC_URL }} RHS_URL: ${{ secrets.RHS_URL }} diff --git a/README.md b/README.md index ea5a987c..3e007341 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ To run them, please set following variables: export WALLET_KEY="...key in hex format" export RPC_URL="...url to polygon network rpc node" export RHS_URL="..reverse hash service url" +export IPFS_URL="url for ipfs" + ``` And place actual circuits to `test/proofs/testdata` diff --git a/package-lock.json b/package-lock.json index 5f952572..33117865 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@iden3/js-crypto": "1.0.0", "@iden3/js-iden3-core": "1.0.0", - "@iden3/js-jsonld-merklization": "1.0.0", + "@iden3/js-jsonld-merklization": "1.0.1", "@iden3/js-jwz": "1.0.0", "@iden3/js-merkletree": "1.0.0", "@lumeweb/js-sha3-browser": "^0.8.1", @@ -1404,9 +1404,9 @@ } }, "node_modules/@iden3/js-jsonld-merklization": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@iden3/js-jsonld-merklization/-/js-jsonld-merklization-1.0.0.tgz", - "integrity": "sha512-7SiMrgw8+ReRZw9jO6EhLzv34iUMbCAp2chYUn9Hw0Ia4DEBVyWIkooY07QcPKSNqx5BolmR8juRhCdP1daomw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@iden3/js-jsonld-merklization/-/js-jsonld-merklization-1.0.1.tgz", + "integrity": "sha512-Nk5Msc+K5BBUnPJHlDiD+z5VKboB6scG5BIKZTC4AH4tNf67XPfR/zky0WEmJ8TxslQZb9Cbl3DZ3lkw5Z9akA==", "dependencies": { "@iden3/js-crypto": "1.0.0", "@iden3/js-merkletree": "1.0.0", diff --git a/package.json b/package.json index 04b49e7f..22f0c394 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "dependencies": { "@iden3/js-crypto": "1.0.0", "@iden3/js-iden3-core": "1.0.0", - "@iden3/js-jsonld-merklization": "1.0.0", + "@iden3/js-jsonld-merklization": "1.0.1", "@iden3/js-jwz": "1.0.0", "@iden3/js-merkletree": "1.0.0", "@lumeweb/js-sha3-browser": "^0.8.1", diff --git a/src/credentials/status/on-chain-revocation.ts b/src/credentials/status/on-chain-revocation.ts index 89a2d09f..6853e47d 100644 --- a/src/credentials/status/on-chain-revocation.ts +++ b/src/credentials/status/on-chain-revocation.ts @@ -66,10 +66,13 @@ export class OnChainResolver implements CredentialStatusResolver { issuer: string; } { const url = new URL(id); - if (!url.searchParams.has('contractAddress')) { + const contractId = url.searchParams.get('contractAddress'); + const revocationNonceParam = url.searchParams.get('revocationNonce'); + + if (!contractId) { throw new Error('contractAddress not found'); } - if (!url.searchParams.has('revocationNonce')) { + if (!revocationNonceParam) { throw new Error('revocationNonce not found'); } @@ -77,10 +80,10 @@ export class OnChainResolver implements CredentialStatusResolver { if (!issuer) { throw new Error('issuer not found in credentialStatus id'); } + // TODO (illia-korotia): after merging core v2 need to parse contract address from did if `contractAddress` is not present in id as param - const contractId = url.searchParams.get('contractAddress'); - const revocationNonce = parseInt(url.searchParams.get('revocationNonce')!, 10); - const parts = contractId!.split(':'); + const revocationNonce = parseInt(revocationNonceParam, 10); + const parts = contractId.split(':'); if (parts.length != 2) { throw new Error('invalid contract address'); } diff --git a/src/iden3comm/handlers/auth.ts b/src/iden3comm/handlers/auth.ts index a4092f9c..30e74656 100644 --- a/src/iden3comm/handlers/auth.ts +++ b/src/iden3comm/handlers/auth.ts @@ -5,10 +5,9 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { AuthorizationRequestMessage, - AuthorizationRequestMessageBody, AuthorizationResponseMessage, IPackageManager, - PackerParams, + JWSPackerParams, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../types'; @@ -16,21 +15,9 @@ import { DID } from '@iden3/js-iden3-core'; import { proving } from '@iden3/js-jwz'; import * as uuid from 'uuid'; -import { ICredentialWallet } from '../../credentials'; -import { W3CCredential } from '../../verifiable'; +import { ProofQuery } from '../../verifiable'; import { byteDecoder, byteEncoder } from '../../utils'; -/** - * ZKP request and credential that satisfies the zkp query conditions - * - * @export - * @interface ZKPRequestWithCredential - */ -export interface ZKPRequestWithCredential { - req: ZeroKnowledgeProofRequest; - credential: W3CCredential; - credentialSubjectProfileNonce: number; -} /** * Interface that allows the processing of the authorization request in the raw format for given identifier * @@ -39,29 +26,6 @@ export interface ZKPRequestWithCredential { * @interface IAuthHandler */ export interface IAuthHandler { - /** - * Handle authorization request protocol message - * - * @param {DID} id - identifier that will handle request - * @param {Uint8Array} request - request payload - * @returns `Promise<{ - * token: string; - * authRequest: AuthorizationRequestMessage; - * authResponse: AuthorizationResponseMessage; - * }>` - */ - handleAuthorizationRequestForGenesisDID(options: { - did: DID; - request: Uint8Array; - packer: { - mediaType: MediaType; - } & PackerParams; - }): Promise<{ - token: string; - authRequest: AuthorizationRequestMessage; - authResponse: AuthorizationResponseMessage; - }>; - /** * unpacks authorization request * @export @@ -72,30 +36,40 @@ export interface IAuthHandler { parseAuthorizationRequest(request: Uint8Array): Promise; /** - * Generates zero-knowledge proofs for given requests and credentials + * unpacks authorization request * @export * @beta - * @param {DID} userGenesisDID - user genesis identifier for which user holds key pair. - * @param {number} authProfileNonce - profile nonce that will be used for authorization. - * @param {AuthorizationRequestMessage} authRequest - authorization request, protocol message. - * @param {ZKPRequestWithCredential[]} zkpRequestsWithCreds - zero knowledge proof request with chosen credential to use. + * @param {did} did - sender DID + * @param {Uint8Array} request - raw byte message * @returns `Promise<{ - * token: string; - * authRequest: AuthorizationRequestMessage; - * authResponse: AuthorizationResponseMessage; - * }>}` + token: string; + authRequest: AuthorizationRequestMessage; + authResponse: AuthorizationResponseMessage; + }>` */ - generateAuthorizationResponse( - userGenesisDID: DID, - authProfileNonce: number, - authRequest: AuthorizationRequestMessage, - zkpRequestsWithCreds: ZKPRequestWithCredential[] + handleAuthorizationRequest( + did: DID, + request: Uint8Array, + opts?: AuthHandlerOptions ): Promise<{ token: string; authRequest: AuthorizationRequestMessage; authResponse: AuthorizationResponseMessage; }>; } +/** + * + * Options to pass to auth handler + * + * @export + * @beta + * @interface AuthHandlerOptions + */ +export interface AuthHandlerOptions { + mediaType: MediaType; + packerOptions?: JWSPackerParams; +} + /** * * Allows to process AuthorizationRequest protocol message and produce JWZ response. @@ -111,95 +85,13 @@ export class AuthHandler implements IAuthHandler { * Creates an instance of AuthHandler. * @param {IPackageManager} _packerMgr - package manager to unpack message envelope * @param {IProofService} _proofService - proof service to verify zk proofs - * @param {ICredentialWallet} _credentialWallet - wallet to search credentials * */ constructor( private readonly _packerMgr: IPackageManager, - private readonly _proofService: IProofService, - private readonly _credentialWallet: ICredentialWallet + private readonly _proofService: IProofService ) {} - /** - * Handles only messages with authorization/v1.0/request type - * Generates all requested proofs and wraps authorization response message to JWZ token - * works when profiles are not supported - * @param {DID} did - an identity that will process the request - * @param {Uint8Array} request - raw request - * @returns `Promise<{token: string; authRequest: AuthorizationRequestMessage; authResponse: AuthorizationResponseMessage;}>` JWZ token, parsed request and response - */ - async handleAuthorizationRequestForGenesisDID(options: { - did: DID; - request: Uint8Array; - packer: { - mediaType: MediaType; - } & PackerParams; - }): Promise<{ - token: string; - authRequest: AuthorizationRequestMessage; - authResponse: AuthorizationResponseMessage; - }> { - const { - did, - request, - packer: { mediaType, ...packerParams } - } = options; - - const { unpackedMessage: message } = await this._packerMgr.unpack(request); - const authRequest = message as unknown as AuthorizationRequestMessage; - if (message.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) { - throw new Error('Invalid media type'); - } - const authRequestBody = message.body as unknown as AuthorizationRequestMessageBody; - - const guid = uuid.v4(); - const authResponse: AuthorizationResponseMessage = { - id: guid, - typ: mediaType, - type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE, - thid: message.thid ?? guid, - body: { - did_doc: undefined, // slipped for now, todo: get did doc for id - message: authRequestBody.message, - scope: [] - }, - from: options.did.string(), - to: message.from - }; - - for (const proofReq of authRequestBody.scope) { - const zkpReq: ZeroKnowledgeProofRequest = { - id: proofReq.id, - circuitId: proofReq.circuitId as CircuitId, - query: proofReq.query - }; - - const creds = await this._credentialWallet.findByQuery(proofReq.query); - - const credsForGenesisDID = await this._credentialWallet.filterByCredentialSubject(creds, did); - if (credsForGenesisDID.length == 0) { - throw new Error(`no credential were issued on the given id ${did.string()}`); - } - const { cred } = await this._credentialWallet.findNonRevokedCredential(credsForGenesisDID); - - const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof( - zkpReq, - did, - cred - ); - - authResponse.body.scope.push(zkpRes); - } - const msgBytes = byteEncoder.encode(JSON.stringify(authResponse)); - const token = byteDecoder.decode( - await this._packerMgr.pack(mediaType, msgBytes, { - senderDID: did, - ...packerParams - }) - ); - return { authRequest, authResponse, token }; - } - /** * unpacks authorization request * @export @@ -217,65 +109,97 @@ export class AuthHandler implements IAuthHandler { } /** - * Generates zero-knowledge proofs for given requests and credentials + * unpacks authorization request and packs authorization response * @export * @beta - * @param {DID} userGenesisDID - user genesis identifier for which user holds key pair. - * @param {number} authProfileNonce - profile nonce that will be used for authorization. - * @param {AuthorizationRequestMessage} authRequest - authorization request, protocol message. - * @param {ZKPRequestWithCredential[]} zkpRequestsWithCreds - zero knowledge proof request with chosen credential to use. + * @param {did} did - sender DID + * @param {Uint8Array} request - raw byte message * @returns `Promise<{ - * token: string; - * authRequest: AuthorizationRequestMessage; - * authResponse: AuthorizationResponseMessage; - * }>}` + token: string; + authRequest: AuthorizationRequestMessage; + authResponse: AuthorizationResponseMessage; + }>` */ - async generateAuthorizationResponse( - userGenesisDID: DID, - authProfileNonce: number, - authRequest: AuthorizationRequestMessage, - zkpRequestsWithCreds?: ZKPRequestWithCredential[] + async handleAuthorizationRequest( + did: DID, + request: Uint8Array, + opts?: AuthHandlerOptions ): Promise<{ token: string; authRequest: AuthorizationRequestMessage; authResponse: AuthorizationResponseMessage; }> { + const authRequest = await this.parseAuthorizationRequest(request); + + if (authRequest.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) { + throw new Error('Invalid message type for authorization request'); + } + + if (!opts) { + opts = { + mediaType: MediaType.ZKPMessage + }; + } + + if (opts.mediaType === MediaType.SignedMessage && !opts.packerOptions) { + throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`); + } + + if (authRequest.to) { + // override sender did if it's explicitly specified in the auth request + did = DID.parse(authRequest.to); + } const guid = uuid.v4(); + const authResponse: AuthorizationResponseMessage = { id: guid, - typ: MediaType.ZKPMessage, + typ: opts.mediaType, type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE, thid: authRequest.thid ?? guid, body: { message: authRequest?.body?.message, scope: [] }, - from: userGenesisDID.string(), + from: did.string(), to: authRequest.from }; - for (const r of zkpRequestsWithCreds || []) { + for (const proofReq of authRequest.body.scope) { + const zkpReq: ZeroKnowledgeProofRequest = { + id: proofReq.id, + circuitId: proofReq.circuitId as CircuitId, + query: proofReq.query + }; + + const query = proofReq.query as unknown as ProofQuery; + const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof( - r.req, - userGenesisDID, - r.credential, + zkpReq, + did, { - authProfileNonce: authProfileNonce, - credentialSubjectProfileNonce: r.credentialSubjectProfileNonce, - skipRevocation: false + skipRevocation: query.skipClaimRevocationCheck ?? false } ); authResponse.body.scope.push(zkpRes); } + const msgBytes = byteEncoder.encode(JSON.stringify(authResponse)); + + const packerOpts = + opts.mediaType === MediaType.SignedMessage + ? opts.packerOptions + : { + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + }; + const token = byteDecoder.decode( - await this._packerMgr.pack(MediaType.ZKPMessage, msgBytes, { - senderDID: userGenesisDID, - profileNonce: authProfileNonce, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + await this._packerMgr.pack(opts.mediaType, msgBytes, { + senderDID: did, + ...packerOpts }) ); + return { authRequest, authResponse, token }; } } diff --git a/src/iden3comm/handlers/fetch.ts b/src/iden3comm/handlers/fetch.ts index a39fb19b..7f546441 100644 --- a/src/iden3comm/handlers/fetch.ts +++ b/src/iden3comm/handlers/fetch.ts @@ -4,14 +4,30 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { CredentialsOfferMessage, IPackageManager, - MessageFetchRequestMessage, - PackerParams + JWSPackerParams, + MessageFetchRequestMessage } from '../types'; -import { DID } from '@iden3/js-iden3-core'; import * as uuid from 'uuid'; import { W3CCredential } from '../../verifiable'; import { byteDecoder, byteEncoder } from '../../utils'; +import { proving } from '@iden3/js-jwz'; + +/** + * + * Options to pass to fetch handler + * + * @export + * @beta + * @interface FetchHandlerOptions + */ +export interface FetchHandlerOptions { + mediaType: MediaType; + packerOptions?: JWSPackerParams; + headers?: { + [key: string]: string; + }; +} /** * Interface that allows the processing of the credential offer in the raw format for given identifier @@ -22,26 +38,18 @@ import { byteDecoder, byteEncoder } from '../../utils'; */ export interface IFetchHandler { /** - * Handle credential offer request protocol message - * - *@param {({ - * did: DID; identifier that will handle offer - * offer: Uint8Array; offer - raw offer message - * profileNonce?: number; nonce of the did to which credential has been offered - * packerOpts: { - * mediaType: MediaType; - * } & PackerParams; packer options how to pack message - * })} options how to fetch credential - * @returns `Promise` + * unpacks authorization request + * @export + * @beta + * @param {Uint8Array} offer - raw byte message + * @param {FetchHandlerOptions} opts - FetchHandlerOptions + * @returns `Promise<{ + token: string; + authRequest: AuthorizationRequestMessage; + authResponse: AuthorizationResponseMessage; + }>` */ - handleCredentialOffer(options: { - did: DID; - offer: Uint8Array; - profileNonce?: number; - packer: { - mediaType: MediaType; - } & PackerParams; - }): Promise; + handleCredentialOffer(offer: Uint8Array, opts?: FetchHandlerOptions): Promise; } /** * @@ -63,33 +71,26 @@ export class FetchHandler implements IFetchHandler { /** * Handles only messages with credentials/1.0/offer type * - * @param {({ - * did: DID; identifier that will handle offer + * @param { * offer: Uint8Array; offer - raw offer message - * profileNonce?: number; nonce of the did to which credential has been offered - * packer: { - * mediaType: MediaType; - * } & PackerParams; packer options how to pack message - * })} options how to fetch credential + * opts + * }) options how to fetch credential * @returns `Promise` */ - async handleCredentialOffer(options: { - did: DID; - offer: Uint8Array; - profileNonce?: number; - packer: { - mediaType: MediaType; - } & PackerParams; - headers?: { - [key: string]: string; - }; - }): Promise { - // each credential info in the offer we need to fetch - const { - did, - offer, - packer: { mediaType, ...packerParams } - } = options; + async handleCredentialOffer( + offer: Uint8Array, + opts?: FetchHandlerOptions + ): Promise { + if (!opts) { + opts = { + mediaType: MediaType.ZKPMessage + }; + } + + if (opts.mediaType === MediaType.SignedMessage && !opts.packerOptions) { + throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`); + } + const { unpackedMessage: message } = await this._packerMgr.unpack(offer); const offerMessage = message as unknown as CredentialsOfferMessage; if (message.type !== PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE) { @@ -97,25 +98,36 @@ export class FetchHandler implements IFetchHandler { } const credentials: W3CCredential[] = []; - for (let index = 0; index < (offerMessage?.body?.credentials?.length ?? 0); index++) { - const credentialInfo = offerMessage?.body?.credentials[index]; + for (let index = 0; index < offerMessage.body.credentials.length; index++) { + const credentialInfo = offerMessage.body.credentials[index]; const guid = uuid.v4(); const fetchRequest: MessageFetchRequestMessage = { id: guid, - typ: mediaType, + typ: opts.mediaType, type: PROTOCOL_MESSAGE_TYPE.CREDENTIAL_FETCH_REQUEST_MESSAGE_TYPE, thid: offerMessage.thid ?? guid, body: { - id: credentialInfo?.id || '' + id: credentialInfo.id }, - from: did.string(), + from: offerMessage.to, to: offerMessage.from }; const msgBytes = byteEncoder.encode(JSON.stringify(fetchRequest)); + + const packerOpts = + opts.mediaType === MediaType.SignedMessage + ? opts.packerOptions + : { + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + }; + const token = byteDecoder.decode( - await this._packerMgr.pack(mediaType, msgBytes, { senderDID: did, ...packerParams }) + await this._packerMgr.pack(opts.mediaType, msgBytes, { + senderDID: offerMessage.to, + ...packerOpts + }) ); let message: { body: { credential: W3CCredential } }; try { @@ -125,7 +137,8 @@ export class FetchHandler implements IFetchHandler { const resp = await fetch(offerMessage.body.url, { method: 'post', headers: { - 'Content-Type': 'application/x-www-form-urlencoded' + 'Content-Type': 'application/x-www-form-urlencoded', + ...(opts.headers ?? {}) }, body: token }); @@ -142,7 +155,6 @@ export class FetchHandler implements IFetchHandler { ); } } - return credentials; } } diff --git a/src/iden3comm/packers/jws.ts b/src/iden3comm/packers/jws.ts index 965ee266..9f6fdcc8 100644 --- a/src/iden3comm/packers/jws.ts +++ b/src/iden3comm/packers/jws.ts @@ -1,10 +1,10 @@ -import { BasicMessage, IPacker, PackerParams } from '../types'; +import { BasicMessage, IPacker, JWSPackerParams } from '../types'; import { MediaType, SUPPORTED_PUBLIC_KEY_TYPES } from '../constants'; import { extractPublicKeyBytes, resolveVerificationMethods } from '../utils/did'; import { keyPath, KMS } from '../../kms/'; -import { Signer, verifyJWS } from 'did-jwt'; -import { DIDDocument, Resolvable, VerificationMethod, parse } from 'did-resolver'; +import { verifyJWS } from 'did-jwt'; +import { DIDDocument, Resolvable, parse } from 'did-resolver'; import { byteDecoder, byteEncoder, @@ -12,7 +12,6 @@ import { decodeBase64url, encodeBase64url } from '../../utils'; -export type SignerFn = (vm: VerificationMethod, data: Uint8Array) => Signer; /** * Packer that can pack message to JWZ token, @@ -38,15 +37,7 @@ export class JWSPacker implements IPacker { * @param {PackerParams} params - sender id and proving alg are required * @returns `Promise` */ - async pack( - payload: Uint8Array, - params: PackerParams & { - alg: string; - kid?: string; - didDocument?: DIDDocument; - signer?: SignerFn; - } - ): Promise { + async pack(payload: Uint8Array, params: JWSPackerParams): Promise { if (!params.alg) { throw new Error('Missing algorithm'); } diff --git a/src/iden3comm/packers/zkp.ts b/src/iden3comm/packers/zkp.ts index e3cde6cc..e1d8c10f 100644 --- a/src/iden3comm/packers/zkp.ts +++ b/src/iden3comm/packers/zkp.ts @@ -43,17 +43,11 @@ export class DataPrepareHandlerFunc { * * @param {Uint8Array} hash - challenge that will be signed * @param {DID} did - did of identity that will prepare inputs - * @param {Number} profileNonce - nonce for profile (genesis id must be 0) * @param {CircuitId} circuitId - circuit id * @returns `Promise` */ - prepare( - hash: Uint8Array, - did: DID, - profileNonce: number, - circuitId: CircuitId - ): Promise { - return this.dataPrepareFunc(hash, did, profileNonce, circuitId); + prepare(hash: Uint8Array, did: DID, circuitId: CircuitId): Promise { + return this.dataPrepareFunc(hash, did, circuitId); } } @@ -121,12 +115,7 @@ export class ZKPPacker implements IPacker { provingMethod, byteDecoder.decode(payload), (hash: Uint8Array, circuitId: string) => { - return provingParams?.dataPreparer?.prepare( - hash, - params.senderDID, - params.profileNonce, - circuitId as CircuitId - ); + return provingParams?.dataPreparer?.prepare(hash, params.senderDID, circuitId as CircuitId); } ); token.setHeader(Header.Type, MediaType.ZKPMessage); diff --git a/src/iden3comm/types/packer.ts b/src/iden3comm/types/packer.ts index 6fac60c0..d99ab71c 100644 --- a/src/iden3comm/types/packer.ts +++ b/src/iden3comm/types/packer.ts @@ -3,7 +3,8 @@ import { DataPrepareHandlerFunc, VerificationHandlerFunc } from '../packers'; import { ProvingMethodAlg } from '@iden3/js-jwz'; import { CircuitId } from '../../circuits'; import { MediaType } from '../constants'; - +import { DIDDocument, VerificationMethod } from 'did-resolver'; +import { Signer } from 'did-jwt'; /** * Protocol message type */ @@ -46,6 +47,21 @@ export type ZKPPackerParams = PackerParams & { provingMethodAlg: ProvingMethodAlg; }; +/** + * SignerFn Is function to sign data with a verification method + */ +export type SignerFn = (vm: VerificationMethod, data: Uint8Array) => Signer; + +/** + * JWSPackerParams are parameters for JWS packer + */ +export type JWSPackerParams = PackerParams & { + alg: string; + kid?: string; + didDocument?: DIDDocument; + signer?: SignerFn; +}; + /** * parameters for plain packer */ @@ -56,7 +72,6 @@ export type PlainPackerParams = PackerParams; export type AuthDataPrepareFunc = ( hash: Uint8Array, did: DID, - profileNonce: number, circuitId: CircuitId ) => Promise; diff --git a/src/iden3comm/types/protocol/auth.ts b/src/iden3comm/types/protocol/auth.ts index 1560e526..f07023f0 100644 --- a/src/iden3comm/types/protocol/auth.ts +++ b/src/iden3comm/types/protocol/auth.ts @@ -23,11 +23,11 @@ export type AuthorizationMessageResponseBody = { /** AuthorizationRequestMessage is struct the represents iden3message authorization request */ export type AuthorizationRequestMessage = { id: string; - typ?: MediaType; + typ: MediaType; type: ProtocolMessage; thid?: string; - body?: AuthorizationRequestMessageBody; - from?: string; + body: AuthorizationRequestMessageBody; + from: string; to?: string; }; diff --git a/src/iden3comm/types/protocol/credentials.ts b/src/iden3comm/types/protocol/credentials.ts index 5718bc2d..b7055446 100644 --- a/src/iden3comm/types/protocol/credentials.ts +++ b/src/iden3comm/types/protocol/credentials.ts @@ -12,23 +12,23 @@ export type CredentialIssuanceRequestMessageBody = { /** CredentialIssuanceRequestMessage represent Iden3message for credential request */ export type CredentialIssuanceRequestMessage = { id: string; - typ?: MediaType; + typ: MediaType; type: ProtocolMessage; thid?: string; - body?: CredentialIssuanceRequestMessageBody; - from?: string; - to?: string; + body: CredentialIssuanceRequestMessageBody; + from: string; + to: string; }; /** CredentialsOfferMessage represent Iden3message for credential offer */ export type CredentialsOfferMessage = { id: string; - typ?: MediaType; + typ: MediaType; type: ProtocolMessage; thid?: string; - body?: CredentialsOfferMessageBody; - from?: string; - to?: string; + body: CredentialsOfferMessageBody; + from: string; + to: string; }; /** CredentialsOfferMessageBody is struct the represents offer message */ diff --git a/src/identity/common.ts b/src/identity/common.ts index 0cbc9f9b..5992585b 100644 --- a/src/identity/common.ts +++ b/src/identity/common.ts @@ -1,4 +1,4 @@ -import { IdPosition, MerklizedRootPosition } from '@iden3/js-iden3-core'; +import { DID, Id, IdPosition, MerklizedRootPosition } from '@iden3/js-iden3-core'; import { SchemaMetadata } from '../schema-processor'; import { SubjectPosition } from '../verifiable'; @@ -40,3 +40,16 @@ export const defineMerklizedRootPosition = ( return MerklizedRootPosition.Index; }; + +/** + * Returns profile DID based on did and profile nonce + * + * @param {DID} [did] - did from which profile will be derived + * @param {number} [profileNonce] - profile nonce + * @returns {DID} + */ +export const generateProfileDID = (did: DID, profileNonce?: number): DID => { + const id = DID.idFromDID(did); + const profile = Id.profileId(id, BigInt(profileNonce ?? 0)); + return DID.parseFromId(profile); +}; diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index fa5cb5ef..92262dfa 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -15,7 +15,7 @@ import { import { Hex, poseidon, PublicKey, Signature } from '@iden3/js-crypto'; import { hashElems, ZERO_HASH } from '@iden3/js-merkletree'; -import { subjectPositionIndex } from './common'; +import { generateProfileDID, subjectPositionIndex } from './common'; import * as uuid from 'uuid'; import { JSONSchema, Parser, CoreClaimOptions } from '../schema-processor'; import { IDataStorage } from '../storage/interfaces/data-storage'; @@ -31,7 +31,8 @@ import { Iden3SparseMerkleTreeProof, ProofType, IssuerData, - CredentialStatusType + CredentialStatusType, + ProofQuery } from '../verifiable'; import { CredentialRequest, ICredentialWallet } from '../credentials'; import { pushHashesToRHS, TreesModel } from '../credentials/rhs'; @@ -39,6 +40,7 @@ import { TreeState } from '../circuits'; import { byteEncoder } from '../utils'; import { Options, Path, getDocumentLoader } from '@iden3/js-jsonld-merklization'; import { sha256js } from 'cross-sha256'; +import { Profile } from '../storage'; /** * DID creation options @@ -232,6 +234,41 @@ export interface IIdentityWallet { * @returns `{Promise}` */ getCoreClaimFromCredential(credential: W3CCredential): Promise; + + /** + * + * gets profile identity by genesis identifiers + * + * @param {string} did - genesis identifier from which profile has been derived + * @returns `{Promise}` + */ + getProfilesByDID(did: DID): Promise; + + /** + * + * gets profile nonce by it's id. if profile is genesis identifier - 0 is returned + * + * @param {string} did - profile that has been derived or genesis identity + * @returns `{Promise<{nonce:number, genesisIdentifier: DID}>}` + */ + getGenesisDIDMetadata(did: DID): Promise<{ nonce: number; genesisDID: DID }>; + + /** + * + * find all credentials that belong to any profile or genesis identity for the given did + * + * @param {string} did - profile that has been derived or genesis identity + * @returns `{Promise}` + */ + findOwnedCredentialsByDID(did: DID, query: ProofQuery): Promise; + /** + * + * gets profile identity by verifier + * + * @param {string} verifier - identifier of the verifier + * @returns `{Promise}` + */ + getProfileByVerifier(verifier: string): Promise; } /** @@ -379,10 +416,10 @@ export class IdentityWallet implements IIdentityWallet { credential.proof = [mtpProof]; await this._storage.identity.saveIdentity({ - identifier: did.string(), + did: did.string(), state: currentState, - published: false, - genesis: true + isStatePublished: false, + isStateGenesis: true }); await this._credentialWallet.save(credential); @@ -392,10 +429,25 @@ export class IdentityWallet implements IIdentityWallet { credential }; } + /** {@inheritDoc IIdentityWallet.getGenesisDIDMetadata} */ + async getGenesisDIDMetadata(did: DID): Promise<{ nonce: number; genesisDID: DID }> { + // check if it is a genesis identity + const identity = await this._storage.identity.getIdentity(did.string()); + + if (identity) { + return { nonce: 0, genesisDID: DID.parse(identity.did) }; + } + const profile = await this._storage.identity.getProfileById(did.string()); + + if (!profile) { + throw new Error('profile or identity not found'); + } + return { nonce: profile.nonce, genesisDID: DID.parse(profile.genesisIdentifier) }; + } /** {@inheritDoc IIdentityWallet.createProfile} */ async createProfile(did: DID, nonce: number, verifier: string): Promise { - const id = DID.idFromDID(did); + const profileDID = generateProfileDID(did, nonce); const identityProfiles = await this._storage.identity.getProfilesByGenesisIdentifier( did.string() @@ -408,9 +460,6 @@ export class IdentityWallet implements IIdentityWallet { throw new Error('profile with given nonce or verifier already exists'); } - const profile = Id.profileId(id, BigInt(nonce)); - const profileDID = DID.parseFromId(profile); - await this._storage.identity.saveProfile({ id: profileDID.string(), nonce, @@ -420,12 +469,25 @@ export class IdentityWallet implements IIdentityWallet { return profileDID; } + /** + * + * gets profile identity by genesis identifiers + * + * @param {string} genesisIdentifier - genesis identifier from which profile has been derived + * @returns `{Promise}` + */ + async getProfilesByDID(did: DID): Promise { + return this._storage.identity.getProfilesByGenesisIdentifier(did.string()); + } /** {@inheritDoc IIdentityWallet.generateKey} */ async generateKey(keyType: KmsKeyType): Promise { const key = await this._kms.createKeyFromSeed(keyType, getRandomBytes(32)); return key; } + async getProfileByVerifier(verifier: string): Promise { + return this._storage.identity.getProfileByVerifier(verifier); + } /** {@inheritDoc IIdentityWallet.getDIDTreeModel} */ async getDIDTreeModel(did: DID): Promise { const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( @@ -824,4 +886,25 @@ export class IdentityWallet implements IIdentityWallet { return coreClaim; } + + async findOwnedCredentialsByDID(did: DID, query: ProofQuery): Promise { + const credentials = await this._credentialWallet.findByQuery(query); + if (!credentials.length) { + throw new Error(`no credential satisfied query`); + } + + const { genesisDID } = await this.getGenesisDIDMetadata(did); + + const profiles = await this.getProfilesByDID(genesisDID); + + return credentials.filter((cred) => { + const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject + return ( + credentialSubjectId == genesisDID.string() || + profiles.some((p) => { + return p.id === credentialSubjectId; + }) + ); + }); + } } diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 67fd1fed..80120333 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -72,12 +72,18 @@ export interface QueryWithFieldName { } export interface ProofGenerationOptions { - authProfileNonce: number; - credentialSubjectProfileNonce: number; skipRevocation: boolean; challenge?: bigint; + credential?: W3CCredential; } +export interface DIDProfileMetadata { + authProfileNonce: number; + credentialSubjectProfileNonce: number; +} + +type InputsParams = ProofGenerationOptions & DIDProfileMetadata; + export interface IProofService { /** * Verification of zkp proof for given circuit id @@ -101,7 +107,6 @@ export interface IProofService { generateProof( proofReq: ZeroKnowledgeProofRequest, identifier: DID, - credential: W3CCredential, opts?: ProofGenerationOptions ): Promise; @@ -114,12 +119,7 @@ export interface IProofService { * @param {CircuitId} circuitId - circuit id for authentication * @returns `Promise` */ - generateAuthV2Inputs( - hash: Uint8Array, - did: DID, - profileNonce: number, - circuitId: CircuitId - ): Promise; + generateAuthV2Inputs(hash: Uint8Array, did: DID, circuitId: CircuitId): Promise; /** * state verification function @@ -188,25 +188,38 @@ export class ProofService implements IProofService { async generateProof( proofReq: ZeroKnowledgeProofRequest, identifier: DID, - credential: W3CCredential, opts?: ProofGenerationOptions ): Promise { if (!opts) { opts = { - authProfileNonce: 0, - credentialSubjectProfileNonce: 0, skipRevocation: false, challenge: 0n }; } + + // find credential + + const credential = + opts.credential ?? (await this.findCredential(identifier, proofReq.query)); + + const { nonce: authProfileNonce, genesisDID } = + await this._identityWallet.getGenesisDIDMetadata(identifier); + const preparedCredential: PreparedCredential = await this.getPreparedCredential(credential); - const { inputs, vp } = await this.generateInputs( - preparedCredential, - identifier, - proofReq, - opts - ); + const subjectDID = DID.parse(preparedCredential.credential.credentialSubject['id'] as string); + + const { nonce: credentialSubjectProfileNonce, genesisDID: subjectGenesisDID } = + await this._identityWallet.getGenesisDIDMetadata(subjectDID); + + if (subjectGenesisDID.string() !== genesisDID.string()) { + throw new Error('subject and auth profiles are not derived from the same did'); + } + const { inputs, vp } = await this.generateInputs(preparedCredential, genesisDID, proofReq, { + ...opts, + authProfileNonce, + credentialSubjectProfileNonce + }); const { proof, pub_signals } = await this._prover.generate( inputs, @@ -278,12 +291,11 @@ export class ProofService implements IProofService { } private async getPreparedCredential(credential: W3CCredential): Promise { - const { cred: nonRevokedCred, revStatus } = - await this._credentialWallet.findNonRevokedCredential([credential]); + const revStatus = await this._credentialWallet.getRevocationStatusFromCredential(credential); - const credCoreClaim = await this._identityWallet.getCoreClaimFromCredential(nonRevokedCred); + const credCoreClaim = await this._identityWallet.getCoreClaimFromCredential(credential); - return { credential: nonRevokedCred, revStatus, credentialCoreClaim: credCoreClaim }; + return { credential, revStatus, credentialCoreClaim: credCoreClaim }; } private async prepareAuthBJJCredential( @@ -318,7 +330,7 @@ export class ProofService implements IProofService { preparedCredential: PreparedCredential, identifier: DID, proofReq: ZeroKnowledgeProofRequest, - opts: ProofGenerationOptions + params: InputsParams ): Promise<{ inputs: Uint8Array; vp?: object }> { let generateInputFn; switch (proofReq.circuitId) { @@ -338,14 +350,14 @@ export class ProofService implements IProofService { throw new Error(`circuit with id ${proofReq.circuitId} is not supported by issuer`); } - return generateInputFn(preparedCredential, identifier, proofReq, opts); + return generateInputFn(preparedCredential, identifier, proofReq, params); } private async generateMTPV2Inputs( preparedCredential: PreparedCredential, identifier: DID, proofReq: ZeroKnowledgeProofRequest, - opts: ProofGenerationOptions + params: InputsParams ): Promise<{ inputs: Uint8Array; vp?: object }> { const circuitClaimData = await this.newCircuitClaimData( preparedCredential.credential, @@ -369,9 +381,9 @@ export class ProofService implements IProofService { nonRevProof: circuitClaimData.nonRevProof }; circuitInputs.currentTimeStamp = getUnixTimestamp(new Date()); - circuitInputs.claimSubjectProfileNonce = BigInt(opts.credentialSubjectProfileNonce); - circuitInputs.profileNonce = BigInt(opts.authProfileNonce); - circuitInputs.skipClaimRevocationCheck = opts.skipRevocation; + circuitInputs.claimSubjectProfileNonce = BigInt(params.credentialSubjectProfileNonce); + circuitInputs.profileNonce = BigInt(params.authProfileNonce); + circuitInputs.skipClaimRevocationCheck = params.skipRevocation; return { inputs: circuitInputs.inputsMarshal(), vp }; } @@ -380,7 +392,7 @@ export class ProofService implements IProofService { preparedCredential: PreparedCredential, identifier: DID, proofReq: ZeroKnowledgeProofRequest, - opts: ProofGenerationOptions + params: InputsParams ): Promise<{ inputs: Uint8Array; vp?: object }> { const circuitClaimData = await this.newCircuitClaimData( preparedCredential.credential, @@ -416,14 +428,16 @@ export class ProofService implements IProofService { circuitInputs.authClaim = authClaimData.claim; circuitInputs.authClaimIncMtp = authClaimData.proof; circuitInputs.authClaimNonRevMtp = authPrepared.nonRevProof.proof; - const challenge = opts.challenge!; + if (!params.challenge) { + throw new Error('challenge must be provided for onchain circuits'); + } const signature = await this._identityWallet.signChallenge( - challenge, + params.challenge, authPrepared.authCredential ); circuitInputs.signature = signature; - circuitInputs.challenge = challenge; + circuitInputs.challenge = params.challenge; const { query, vp } = await this.toCircuitsQuery( proofReq.query, @@ -439,9 +453,9 @@ export class ProofService implements IProofService { nonRevProof: circuitClaimData.nonRevProof }; circuitInputs.currentTimeStamp = getUnixTimestamp(new Date()); - circuitInputs.claimSubjectProfileNonce = BigInt(opts.credentialSubjectProfileNonce); - circuitInputs.profileNonce = BigInt(opts.authProfileNonce); - circuitInputs.skipClaimRevocationCheck = opts.skipRevocation; + circuitInputs.claimSubjectProfileNonce = BigInt(params.credentialSubjectProfileNonce); + circuitInputs.profileNonce = BigInt(params.authProfileNonce); + circuitInputs.skipClaimRevocationCheck = params.skipRevocation; return { inputs: circuitInputs.inputsMarshal(), vp }; } @@ -450,7 +464,7 @@ export class ProofService implements IProofService { preparedCredential: PreparedCredential, identifier: DID, proofReq: ZeroKnowledgeProofRequest, - opts: ProofGenerationOptions + params: InputsParams ): Promise<{ inputs: Uint8Array; vp?: object }> { const circuitClaimData = await this.newCircuitClaimData( preparedCredential.credential, @@ -468,9 +482,9 @@ export class ProofService implements IProofService { nonRevProof: circuitClaimData.nonRevProof }; circuitInputs.requestID = BigInt(proofReq.id); - circuitInputs.claimSubjectProfileNonce = BigInt(opts.credentialSubjectProfileNonce); - circuitInputs.profileNonce = BigInt(opts.authProfileNonce); - circuitInputs.skipClaimRevocationCheck = opts.skipRevocation; + circuitInputs.claimSubjectProfileNonce = BigInt(params.credentialSubjectProfileNonce); + circuitInputs.profileNonce = BigInt(params.authProfileNonce); + circuitInputs.skipClaimRevocationCheck = params.skipRevocation; const { query, vp } = await this.toCircuitsQuery( proofReq.query, preparedCredential.credential, @@ -486,7 +500,7 @@ export class ProofService implements IProofService { preparedCredential: PreparedCredential, identifier: DID, proofReq: ZeroKnowledgeProofRequest, - opts: ProofGenerationOptions + params: InputsParams ): Promise<{ inputs: Uint8Array; vp?: object }> { const circuitClaimData = await this.newCircuitClaimData( preparedCredential.credential, @@ -511,9 +525,9 @@ export class ProofService implements IProofService { nonRevProof: circuitClaimData.nonRevProof }; circuitInputs.requestID = BigInt(proofReq.id); - circuitInputs.claimSubjectProfileNonce = BigInt(opts.credentialSubjectProfileNonce); - circuitInputs.profileNonce = BigInt(opts.authProfileNonce); - circuitInputs.skipClaimRevocationCheck = opts.skipRevocation; + circuitInputs.claimSubjectProfileNonce = BigInt(params.credentialSubjectProfileNonce); + circuitInputs.profileNonce = BigInt(params.authProfileNonce); + circuitInputs.skipClaimRevocationCheck = params.skipRevocation; const { query, vp } = await this.toCircuitsQuery( proofReq.query, preparedCredential.credential, @@ -540,14 +554,17 @@ export class ProofService implements IProofService { circuitInputs.authClaimIncMtp = authClaimData.proof; circuitInputs.authClaimNonRevMtp = authPrepared.nonRevProof.proof; - const challenge = opts.challenge!; + if (!params.challenge) { + throw new Error('challenge must be provided for onchain circuits'); + } + const signature = await this._identityWallet.signChallenge( - challenge, + params.challenge, authPrepared.authCredential ); circuitInputs.signature = signature; - circuitInputs.challenge = challenge; + circuitInputs.challenge = params.challenge; return { inputs: circuitInputs.inputsMarshal(), vp }; } @@ -688,9 +705,10 @@ export class ProofService implements IProofService { parsedQuery.query.valueProof.path = pathKey; parsedQuery.query.valueProof.mtp = proof; const mtEntry = await mtValue?.mtEntry(); - if (mtEntry) { - parsedQuery.query.valueProof.value = mtEntry; + if (!mtEntry) { + throw new Error(`can't merklize credential: no merkle tree entry found`); } + parsedQuery.query.valueProof.value = mtEntry; if (merklizedPosition == MerklizedRootPosition.Index) { parsedQuery.query.slotIndex = 2; // value data slot a @@ -700,7 +718,7 @@ export class ProofService implements IProofService { if (!parsedQuery.fieldName) { const resultQuery = parsedQuery.query; resultQuery.operator = QueryOperators.$eq; - resultQuery.values = [mtEntry!]; + resultQuery.values = [mtEntry]; return { query: resultQuery }; } if (parsedQuery.isSelectiveDisclosure) { @@ -713,7 +731,7 @@ export class ProofService implements IProofService { ); const resultQuery = parsedQuery.query; resultQuery.operator = QueryOperators.$eq; - resultQuery.values = [mtEntry!]; + resultQuery.values = [mtEntry]; return { query: resultQuery, vp }; } if (parsedQuery.rawValue === null || parsedQuery.rawValue === undefined) { @@ -832,15 +850,19 @@ export class ProofService implements IProofService { async generateAuthV2Inputs( hash: Uint8Array, did: DID, - profileNonce: number, circuitId: CircuitId ): Promise { if (circuitId !== CircuitId.AuthV2) { throw new Error('CircuitId is not supported'); } + + const { nonce: authProfileNonce, genesisDID } = + await this._identityWallet.getGenesisDIDMetadata(did); + + // // todo: check if bigint is correct const challenge = BytesHelper.bytesToInt(hash.reverse()); - const authPrepared = await this.prepareAuthBJJCredential(did); + const authPrepared = await this.prepareAuthBJJCredential(genesisDID); const authClaimData = await this.newCircuitClaimData( authPrepared.authCredential, @@ -851,7 +873,7 @@ export class ProofService implements IProofService { challenge, authPrepared.authCredential ); - const id = DID.idFromDID(did); + const id = DID.idFromDID(genesisDID); const stateProof = await this._stateStorage.getGISTProof(id.bigInt()); const gistProof = toGISTProof(stateProof); @@ -859,7 +881,7 @@ export class ProofService implements IProofService { const authInputs = new AuthV2Inputs(); authInputs.genesisID = id; - authInputs.profileNonce = BigInt(profileNonce); + authInputs.profileNonce = BigInt(authProfileNonce); authInputs.authClaim = authClaimData.claim; authInputs.authClaimIncMtp = authClaimData.proof; authInputs.authClaimNonRevMtp = authPrepared.nonRevProof.proof; @@ -894,6 +916,21 @@ export class ProofService implements IProofService { return true; } + + private async findCredential(did: DID, query: ProofQuery): Promise { + const credentials = await this._identityWallet.findOwnedCredentialsByDID(did, query); + + if (!credentials.length) { + throw new Error(`no credentials belong to did ot its profiles`); + } + + // For EQ / IN / NIN / LT / GT operations selective if credential satisfies query - we can get any. + // TODO: choose credential for selective credentials + const credential = query.skipClaimRevocationCheck + ? credentials[0] + : (await this._credentialWallet.findNonRevokedCredential(credentials)).cred; + return credential; + } } // BJJSignatureFromHexString converts hex to babyjub.Signature export const bJJSignatureFromHexString = async (sigHex: string): Promise => { diff --git a/src/storage/entities/identity.ts b/src/storage/entities/identity.ts index 8911e051..2b9f3505 100644 --- a/src/storage/entities/identity.ts +++ b/src/storage/entities/identity.ts @@ -2,10 +2,10 @@ import { Hash } from '@iden3/js-merkletree'; /** Identity structure that can be used for identity storage */ export type Identity = { - identifier: string; - state: Hash; - published: boolean; - genesis: boolean; + did: string; + state?: Hash; + isStatePublished?: boolean; + isStateGenesis?: boolean; }; /** Profile structure that can be used for profiles storage */ diff --git a/src/storage/interfaces/identity.ts b/src/storage/interfaces/identity.ts index f748ba94..e3187d5f 100644 --- a/src/storage/interfaces/identity.ts +++ b/src/storage/interfaces/identity.ts @@ -46,14 +46,14 @@ export interface IIdentityStorage { * @param {string} verifier - verifier to which profile has been shared * @returns `{Promise}` */ - getProfileByVerifier(verifier: string): Promise; + getProfileByVerifier(verifier: string): Promise; /** * gets profile by identifier * * @param {string} identifier - profile id * @returns `{Promise}` */ - getProfileById(identifier: string): Promise; + getProfileById(identifier: string): Promise; /** * * gets profile identity by genesis identifiers diff --git a/src/storage/shared/identity-storage.ts b/src/storage/shared/identity-storage.ts index 73645c7f..871c4490 100644 --- a/src/storage/shared/identity-storage.ts +++ b/src/storage/shared/identity-storage.ts @@ -46,20 +46,12 @@ export class IdentityStorage implements IIdentityStorage { } } - async getProfileByVerifier(verifier: string): Promise { - const profile = await this._profileDataSource.get(verifier, 'verifier'); - if (!profile) { - throw new Error('profile not found'); - } - return profile; + async getProfileByVerifier(verifier: string): Promise { + return this._profileDataSource.get(verifier, 'verifier'); } - async getProfileById(profileId: string): Promise { - const profile = await this._profileDataSource.get(profileId); - if (!profile) { - throw new Error('profile not found'); - } - return profile; + async getProfileById(profileId: string): Promise { + return this._profileDataSource.get(profileId); } async getProfilesByGenesisIdentifier(genesisIdentifier: string): Promise { @@ -73,10 +65,10 @@ export class IdentityStorage implements IIdentityStorage { } async saveIdentity(identity: Identity): Promise { - return this._identityDataSource.save(identity.identifier, identity, 'identifier'); + return this._identityDataSource.save(identity.did, identity, 'did'); } async getIdentity(identifier: string): Promise { - return this._identityDataSource.get(identifier, 'identifier'); + return this._identityDataSource.get(identifier, 'did'); } } diff --git a/src/verifiable/presentation.ts b/src/verifiable/presentation.ts index e20e4106..399229aa 100644 --- a/src/verifiable/presentation.ts +++ b/src/verifiable/presentation.ts @@ -101,5 +101,8 @@ export const verifiablePresentationFromCred = async ( const vp = createVerifiablePresentation(contextURL, contextType, field, rawValue); - return { vp, mzValue: value!, dataType, hasher }; + if (!value) { + throw new Error(`can't merklize verifiable presentation`); + } + return { vp, mzValue: value, dataType, hasher }; }; diff --git a/src/verifiable/proof.ts b/src/verifiable/proof.ts index 2035c8fa..8583b058 100644 --- a/src/verifiable/proof.ts +++ b/src/verifiable/proof.ts @@ -121,6 +121,7 @@ export interface ProofQuery { credentialSubjectId?: string; context?: string; type?: string; + skipClaimRevocationCheck?: boolean; } /** diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 61bf5727..f728da5c 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -8,7 +8,6 @@ import { CredentialStorage, IAuthHandler, IdentityWallet, - ZKPRequestWithCredential, byteEncoder } from '../../src'; import { BjjProvider, KMS, KmsKeyType } from '../../src/kms'; @@ -55,6 +54,8 @@ describe('auth', () => { let authHandler: IAuthHandler; let packageMgr: IPackageManager; const rhsUrl = process.env.RHS_URL as string; + const ipfsNodeURL = process.env.IPFS_URL as string; + const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); @@ -177,17 +178,17 @@ describe('auth', () => { idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); packageMgr = await getPackageMgr( await circuitStorage.loadCircuitData(CircuitId.AuthV2), proofService.generateAuthV2Inputs.bind(proofService), proofService.verifyState.bind(proofService) ); - authHandler = new AuthHandler(packageMgr, proofService, credWallet); + authHandler = new AuthHandler(packageMgr, proofService); }); - it('request-response flow genesis', async () => { + it('request-response flow identity (not profile)', async () => { const { did: userDID, credential: cred } = await idWallet.createIdentity({ method: DidMethod.Iden3, blockchain: Blockchain.Polygon, @@ -271,15 +272,7 @@ describe('auth', () => { console.log(JSON.stringify(issuerCred)); const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const authRes = await authHandler.handleAuthorizationRequestForGenesisDID({ - did: userDID, - request: msgBytes, - packer: { - mediaType: MediaType.ZKPMessage, - profileNonce: 0, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg.toString() - } - }); + const authRes = await authHandler.handleAuthorizationRequest(userDID, msgBytes); const tokenStr = authRes.token; console.log(tokenStr); @@ -368,58 +361,16 @@ describe('auth', () => { const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const authR = await authHandler.parseAuthorizationRequest(msgBytes); - - // let's find cred for each request. - const reqCreds: ZKPRequestWithCredential[] = []; - - for (let index = 0; index < authR.body!.scope.length; index++) { - const zkpReq = authR.body!.scope[index]; - - const credsToChooseForZKPReq = await credWallet.findByQuery(zkpReq.query); - - // filter credentials for subjects that are profiles of identity - - // 1g 2g - // 1.1p Pas 1.2p Age 2.1p Pas 2.2p Age - - const profiles = await dataStorage.identity.getProfilesByGenesisIdentifier(userDID.string()); - // 1.1p Pas 1.2p Age - - // finds all credentials that belongs to genesis identity or profiles derived from it - const credsThatBelongToGenesisIdOrItsProfiles = credsToChooseForZKPReq.filter((cred) => { - const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject - return ( - credentialSubjectId == userDID.string() || - profiles.some((p) => { - return p.id === credentialSubjectId; - }) - ); - }); - - // you can show user credential that can be used for request - const chosenCredByUser = credsThatBelongToGenesisIdOrItsProfiles[0]; - - // get profile nonce that was used as a part of subject in the credential - const credentialSubjectProfileNonce = - chosenCredByUser.credentialSubject['id'] === userDID.string() - ? 0 - : profiles.find((p) => { - return p.id === chosenCredByUser.credentialSubject['id']; - })!.nonce; - reqCreds.push({ req: zkpReq, credential: chosenCredByUser, credentialSubjectProfileNonce }); - } - // you can create new profile here for auth or if you want to login with genesis set to 0. - const authProfileNonce = 100; + const authR = await authHandler.parseAuthorizationRequest(msgBytes); + + const authProfile = await idWallet.getProfileByVerifier(authR.from); + const authProfileDID = authProfile + ? DID.parse(authProfile.id) + : await idWallet.createProfile(userDID, 100, authR.from); - const resp = await authHandler.generateAuthorizationResponse( - userDID, - authProfileNonce, - authR, - reqCreds - ); + const resp = await authHandler.handleAuthorizationRequest(authProfileDID, msgBytes); console.log(resp); }); diff --git a/tests/handlers/fetch.test.ts b/tests/handlers/fetch.test.ts index a3727be7..2158db90 100644 --- a/tests/handlers/fetch.test.ts +++ b/tests/handlers/fetch.test.ts @@ -36,7 +36,6 @@ import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; import { assert, expect } from 'chai'; import fetchMock from '@gr2m/fetch-mock'; import { after } from 'mocha'; -import { proving } from '@iden3/js-jwz'; describe('fetch', () => { let idWallet: IdentityWallet; @@ -47,6 +46,7 @@ describe('fetch', () => { let packageMgr: IPackageManager; const rhsUrl = process.env.RHS_URL as string; const agentUrl = 'https://testagent.com/'; + const mockedToken = 'jwz token to fetch credential'; const mockStateStorage: IStateStorage = { @@ -186,7 +186,7 @@ describe('fetch', () => { fetchMock.restore(); }); - it('fetch credential', async () => { + it('fetch credential issued to genesis did', async () => { const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); @@ -225,19 +225,13 @@ describe('fetch', () => { url: agentUrl, credentials: [{ id: 'https://credentialId', description: 'kyc age credentials' }] } as CredentialsOfferMessageBody, - from: issuerDID.string() + from: issuerDID.string(), + to: userDID.string() }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const res = await fetchHandler.handleCredentialOffer({ - did: userDID, - offer: msgBytes, - packer: { - mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg - } - }); + const res = await fetchHandler.handleCredentialOffer(msgBytes); await credWallet.saveAll(res); diff --git a/tests/iden3comm/mock/proving.ts b/tests/iden3comm/mock/proving.ts index 25b3cfa3..ecef6061 100644 --- a/tests/iden3comm/mock/proving.ts +++ b/tests/iden3comm/mock/proving.ts @@ -48,7 +48,6 @@ export class ProvingMethodGroth16Authv2 implements ProvingMethod { export const mockPrepareAuthInputs = ( hash: Uint8Array, //eslint-disable-line @typescript-eslint/no-unused-vars did: DID, //eslint-disable-line @typescript-eslint/no-unused-vars - profileNonce: number, //eslint-disable-line @typescript-eslint/no-unused-vars circuitID: CircuitId //eslint-disable-line @typescript-eslint/no-unused-vars ): Promise => { const challenge = newBigIntFromBytes(hash); diff --git a/tests/proofs/common.ts b/tests/proofs/common.ts deleted file mode 100644 index 1cbacc51..00000000 --- a/tests/proofs/common.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from 'chai'; -import { CircuitId, IProofService, W3CCredential, ZeroKnowledgeProofRequest } from '../../src'; -import { DID } from '@iden3/js-iden3-core'; - -export async function checkVerifiablePresentation( - type: string, - userDID: DID, - cred: W3CCredential, - proofService: IProofService, - circuitId: CircuitId -) { - const vpProofReq: ZeroKnowledgeProofRequest = { - id: 1, - circuitId, - optional: false, - query: { - allowedIssuers: ['*'], - type, - context: - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld', - credentialSubject: { - documentType: {} - } - } - }; - const { vp: vp2 } = await proofService.generateProof(vpProofReq, userDID, cred); - expect(vp2).to.deep.equal({ - '@context': ['https://www.w3.org/2018/credentials/v1'], - '@type': 'VerifiablePresentation', - verifiableCredential: { - '@context': [ - 'https://www.w3.org/2018/credentials/v1', - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld' - ], - '@type': ['VerifiableCredential', 'KYCAgeCredential'], - credentialSubject: { - '@type': 'KYCAgeCredential', - documentType: 99 - } - } - }); -} diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts index b254cb5d..0bde57ef 100644 --- a/tests/proofs/mtp-onchain.test.ts +++ b/tests/proofs/mtp-onchain.test.ts @@ -30,7 +30,6 @@ import { ZeroKnowledgeProofRequest } from '../../src/iden3comm'; import { CircuitData } from '../../src/storage/entities/circuitData'; import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; import { expect } from 'chai'; -import { checkVerifiablePresentation } from './common'; describe('mtp onchain proofs', () => { let idWallet: IdentityWallet; @@ -236,21 +235,11 @@ describe('mtp onchain proofs', () => { } }; - const creds = await credWallet.findByQuery(proofReq.query); - expect(creds.length).to.not.equal(0); - - const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); - expect(creds.length).to.equal(1); - - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID, { + challenge: BigInt(2), + skipRevocation: false + }); console.log(proof); expect(vp).to.be.undefined; - await checkVerifiablePresentation( - claimReq.type, - userDID, - credsForMyUserDID[0], - proofService, - CircuitId.AtomicQueryMTPV2OnChain - ); }); }); diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts index e6958fd7..85d4a19b 100644 --- a/tests/proofs/mtp.test.ts +++ b/tests/proofs/mtp.test.ts @@ -25,7 +25,6 @@ import { ZeroKnowledgeProofRequest } from '../../src/iden3comm'; import { CircuitData } from '../../src/storage/entities/circuitData'; import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; import { expect } from 'chai'; -import { checkVerifiablePresentation } from './common'; import { CredentialStatusResolverRegistry } from '../../src/credentials'; import { RHSResolver } from '../../src/credentials'; @@ -233,19 +232,9 @@ describe('mtp proofs', () => { const creds = await credWallet.findByQuery(proofReq.query); expect(creds.length).to.not.equal(0); - const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); - expect(creds.length).to.equal(1); - - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID); console.log(proof); expect(vp).to.be.undefined; - await checkVerifiablePresentation( - claimReq.type, - userDID, - credsForMyUserDID[0], - proofService, - CircuitId.AtomicQueryMTPV2 - ); }); it('mtpv2-merklized', async () => { @@ -348,15 +337,11 @@ describe('mtp proofs', () => { const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); expect(creds.length).to.equal(1); - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID, { + credential: credsForMyUserDID[0], + skipRevocation: false + }); console.log(proof); expect(vp).to.be.undefined; - await checkVerifiablePresentation( - claimReq.type, - userDID, - credsForMyUserDID[0], - proofService, - CircuitId.AtomicQueryMTPV2 - ); }); }); diff --git a/tests/proofs/sig-onchain.test.ts b/tests/proofs/sig-onchain.test.ts index 553a47c7..ca306fad 100644 --- a/tests/proofs/sig-onchain.test.ts +++ b/tests/proofs/sig-onchain.test.ts @@ -28,7 +28,6 @@ import { ZeroKnowledgeProofRequest } from '../../src/iden3comm'; import { CircuitData } from '../../src/storage/entities/circuitData'; import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; import { expect } from 'chai'; -import { checkVerifiablePresentation } from './common'; describe('sig onchain proofs', () => { let idWallet: IdentityWallet; @@ -191,19 +190,13 @@ describe('sig onchain proofs', () => { expect(creds.length).to.not.equal(0); const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); - expect(creds.length).to.equal(1); + expect(credsForMyUserDID.length).to.equal(1); - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID ,{challenge: BigInt(2), + skipRevocation:false}); console.log(proof); expect(vp).to.be.undefined; - await checkVerifiablePresentation( - claimReq.type, - userDID, - credsForMyUserDID[0], - proofService, - CircuitId.AtomicQuerySigV2OnChain - ); }); }); diff --git a/tests/proofs/sig.test.ts b/tests/proofs/sig.test.ts index 79d672c5..90cb2459 100644 --- a/tests/proofs/sig.test.ts +++ b/tests/proofs/sig.test.ts @@ -33,6 +33,8 @@ describe('sig proofs', () => { let dataStorage: IDataStorage; let proofService: ProofService; const rhsUrl = process.env.RHS_URL as string; + const ipfsNodeURL = process.env.IPFS_URL as string; + let userDID: DID; let issuerDID: DID; let circuitStorage: CircuitStorage; @@ -123,7 +125,7 @@ describe('sig proofs', () => { idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); @@ -189,7 +191,7 @@ describe('sig proofs', () => { const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); expect(credsForMyUserDID.length).to.equal(1); - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID); expect(proof).not.to.be.undefined; expect(vp).to.be.undefined; @@ -238,7 +240,10 @@ describe('sig proofs', () => { const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); expect(credsForMyUserDID.length).to.equal(1); - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID, { + credential: credsForMyUserDID[0], + skipRevocation: false + }); expect(vp).to.be.undefined; expect(proof).not.to.be.undefined; @@ -287,7 +292,10 @@ describe('sig proofs', () => { const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); expect(credsForMyUserDID.length).to.equal(1); - const { proof, vp } = await proofService.generateProof(proofReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(proofReq, userDID, { + credential: credsForMyUserDID[0], + skipRevocation: true + }); expect(proof).not.to.be.undefined; expect(vp).to.be.undefined; }); @@ -335,7 +343,7 @@ describe('sig proofs', () => { } }; const issuerCred = await idWallet.issueCredential(issuerDID, claimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(issuerCred); @@ -343,14 +351,7 @@ describe('sig proofs', () => { const creds = await credWallet.findByQuery(req.body.scope[0].query); expect(creds.length).to.not.equal(0); - const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID); - expect(credsForMyUserDID.length).to.equal(1); - - const { proof, vp } = await proofService.generateProof( - req.body.scope[0], - userDID, - credsForMyUserDID[0] - ); + const { proof, vp } = await proofService.generateProof(req.body.scope[0], userDID); expect(proof).not.to.be.undefined; expect(vp).to.be.undefined; }); @@ -389,7 +390,7 @@ describe('sig proofs', () => { } }; const issuedCred = await idWallet.issueCredential(issuerDID, claimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(issuedCred); @@ -428,7 +429,7 @@ describe('sig proofs', () => { }; const deliveryCred = await idWallet.issueCredential(issuerDID, deliveryClaimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(deliveryCred); @@ -455,7 +456,7 @@ describe('sig proofs', () => { circuitId: 'credentialAtomicQuerySigV2', query }; - const { proof, vp } = await proofService.generateProof(vpReq, userDID, credsForMyUserDID[0]); + const { proof, vp } = await proofService.generateProof(vpReq, userDID); expect(proof).not.to.be.undefined; expect(vp).to.deep.equal({ @@ -483,8 +484,7 @@ describe('sig proofs', () => { }; const { proof: deliveryProof, vp: deliveryVP } = await proofService.generateProof( deliveryVPReq, - userDID, - credsFromWallet[0] + userDID ); expect(deliveryProof).not.to.be.undefined; diff --git a/tests/rhs/rhs.test.ts b/tests/rhs/rhs.test.ts index 2855782c..f4b13132 100644 --- a/tests/rhs/rhs.test.ts +++ b/tests/rhs/rhs.test.ts @@ -171,7 +171,7 @@ describe('rhs', () => { return rhsResolver .getStatus(credRHSStatus, issuerDID) - .then(function (m) { + .then(function () { throw new Error('was not supposed to succeed'); }) .catch((m) => {