From 3fbc3333ef760df5d8927d09f3f6c5264b78726d Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Thu, 20 Jul 2023 23:32:21 +0300 Subject: [PATCH 1/6] support profiles --- src/iden3comm/handlers/auth.ts | 292 ++++++++++++++------------------ src/iden3comm/handlers/fetch.ts | 117 ++++++++----- src/iden3comm/packers/jws.ts | 17 +- src/iden3comm/packers/zkp.ts | 2 +- src/iden3comm/types/packer.ts | 18 +- src/identity/common.ts | 15 +- src/identity/identity-wallet.ts | 47 ++++- src/proof/proof-service.ts | 7 +- src/verifiable/presentation.ts | 2 +- src/verifiable/proof.ts | 1 + tests/handlers/auth.test.ts | 136 +++++++++++---- tests/handlers/fetch.test.ts | 152 ++++++++++++++++- 12 files changed, 532 insertions(+), 274 deletions(-) diff --git a/src/iden3comm/handlers/auth.ts b/src/iden3comm/handlers/auth.ts index a4092f9c..68c916cc 100644 --- a/src/iden3comm/handlers/auth.ts +++ b/src/iden3comm/handlers/auth.ts @@ -5,32 +5,21 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { AuthorizationRequestMessage, - AuthorizationRequestMessageBody, AuthorizationResponseMessage, IPackageManager, - PackerParams, + JWSPackerParams, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../types'; import { DID } from '@iden3/js-iden3-core'; -import { proving } from '@iden3/js-jwz'; +import { ProvingMethodAlg, proving } from '@iden3/js-jwz'; import * as uuid from 'uuid'; import { ICredentialWallet } from '../../credentials'; -import { W3CCredential } from '../../verifiable'; +import { ProofQuery, W3CCredential } from '../../verifiable'; import { byteDecoder, byteEncoder } from '../../utils'; +import { IIdentityWallet, generateProfileDID } from '../../identity'; -/** - * 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 +28,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 +38,39 @@ 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; }>; } + +interface AuthHandlerOptions { + mediaType: MediaType; + packerOptions: + | { + profileNonce: number; + provingMethodAlg: ProvingMethodAlg; + } + | JWSPackerParams; + credential?: W3CCredential; +} + /** * * Allows to process AuthorizationRequest protocol message and produce JWZ response. @@ -112,170 +87,161 @@ export class AuthHandler implements IAuthHandler { * @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 + * @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities + * * */ constructor( private readonly _packerMgr: IPackageManager, private readonly _proofService: IProofService, - private readonly _credentialWallet: ICredentialWallet + private readonly _credentialWallet: ICredentialWallet, + private readonly _identityWallet: IIdentityWallet ) {} /** - * 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 + * unpacks authorization request + * @export + * @beta + * @param {Uint8Array} request - raw byte message + * @returns `Promise` */ - async handleAuthorizationRequestForGenesisDID(options: { - did: DID; - request: Uint8Array; - packer: { - mediaType: MediaType; - } & PackerParams; - }): Promise<{ + async parseAuthorizationRequest(request: Uint8Array): Promise { + 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'); + } + return authRequest; + } + + async handleAuthorizationRequest( + did: DID, + request: Uint8Array, + opts?: AuthHandlerOptions + ): Promise<{ token: string; authRequest: AuthorizationRequestMessage; authResponse: AuthorizationResponseMessage; }> { - const { - did, - request, - packer: { mediaType, ...packerParams } - } = options; + const authRequest = await this.parseAuthorizationRequest(request); - 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'); + if (authRequest.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) { + throw new Error('Invalid message type for authorization request'); + } + + if (!opts) { + const zkpPackerOpts = { + profileNonce: 0, + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg, + alg: '' + }; + + opts = { + packerOptions: zkpPackerOpts, + mediaType: MediaType.ZKPMessage + }; } - const authRequestBody = message.body as unknown as AuthorizationRequestMessageBody; const guid = uuid.v4(); + const senderDID = + opts!.mediaType === MediaType.SignedMessage + ? did.string() + : generateProfileDID(did, opts!.packerOptions!.profileNonce).string(); const authResponse: AuthorizationResponseMessage = { id: guid, - typ: mediaType, + typ: opts!.mediaType, type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE, - thid: message.thid ?? guid, + thid: authRequest.thid ?? guid, body: { - did_doc: undefined, // slipped for now, todo: get did doc for id - message: authRequestBody.message, + message: authRequest?.body?.message, scope: [] }, - from: options.did.string(), - to: message.from + from: senderDID, + to: authRequest.from }; - for (const proofReq of authRequestBody.scope) { + for (const proofReq of authRequest.body!.scope) { const zkpReq: ZeroKnowledgeProofRequest = { id: proofReq.id, circuitId: proofReq.circuitId as CircuitId, query: proofReq.query }; - const creds = await this._credentialWallet.findByQuery(proofReq.query); + const query = proofReq.query as unknown as ProofQuery; - 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()}`); + if (opts!.credential) { + proofReq.query.claimId = opts!.credential.id; } - const { cred } = await this._credentialWallet.findNonRevokedCredential(credsForGenesisDID); + + const { credential, nonce } = await this.findCredentialForDID(did, query); const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof( zkpReq, did, - cred + credential, + { + credentialSubjectProfileNonce: nonce, + skipRevocation: query.skipClaimRevocationCheck ?? false, + authProfileNonce: opts!.packerOptions!.profileNonce + } ); authResponse.body.scope.push(zkpRes); } + const msgBytes = byteEncoder.encode(JSON.stringify(authResponse)); const token = byteDecoder.decode( - await this._packerMgr.pack(mediaType, msgBytes, { + await this._packerMgr.pack(opts!.mediaType, msgBytes, { senderDID: did, - ...packerParams + ...opts!.packerOptions }) ); return { authRequest, authResponse, token }; } - - /** - * unpacks authorization request - * @export - * @beta - * @param {Uint8Array} request - raw byte message - * @returns `Promise` - */ - async parseAuthorizationRequest(request: Uint8Array): Promise { - 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'); + async findCredentialForDID( + did: DID, + query: ProofQuery + ): Promise<{ credential: W3CCredential; nonce: number }> { + const creds = await this._credentialWallet.findByQuery(query); + if (!creds.length) { + throw new Error(`no credential satisfied query`); } - return authRequest; - } - /** - * Generates zero-knowledge proofs for given requests and credentials - * @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. - * @returns `Promise<{ - * token: string; - * authRequest: AuthorizationRequestMessage; - * authResponse: AuthorizationResponseMessage; - * }>}` - */ - async generateAuthorizationResponse( - userGenesisDID: DID, - authProfileNonce: number, - authRequest: AuthorizationRequestMessage, - zkpRequestsWithCreds?: ZKPRequestWithCredential[] - ): Promise<{ - token: string; - authRequest: AuthorizationRequestMessage; - authResponse: AuthorizationResponseMessage; - }> { - const guid = uuid.v4(); - const authResponse: AuthorizationResponseMessage = { - id: guid, - typ: MediaType.ZKPMessage, - type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE, - thid: authRequest.thid ?? guid, - body: { - message: authRequest?.body?.message, - scope: [] - }, - from: userGenesisDID.string(), - to: authRequest.from - }; + const profiles = await this._identityWallet.getProfilesByDID(did); - for (const r of zkpRequestsWithCreds || []) { - const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof( - r.req, - userGenesisDID, - r.credential, - { - authProfileNonce: authProfileNonce, - credentialSubjectProfileNonce: r.credentialSubjectProfileNonce, - skipRevocation: false - } + const ownedCreds = creds.filter((cred) => { + const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject + return ( + credentialSubjectId == did.string() || + profiles.some((p) => { + return p.id === credentialSubjectId; + }) ); + }); - authResponse.body.scope.push(zkpRes); + if (!ownedCreds.length) { + throw new Error(`no credentials belong to did ot its profiles`); } - const msgBytes = byteEncoder.encode(JSON.stringify(authResponse)); - const token = byteDecoder.decode( - await this._packerMgr.pack(MediaType.ZKPMessage, msgBytes, { - senderDID: userGenesisDID, - profileNonce: authProfileNonce, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg - }) - ); - return { authRequest, authResponse, token }; + + // For EQ / IN / NIN / LT / GT operations selective if credential satisfies query - we can get any. + // TODO: choose credential for selective credentials + const cred = query.skipClaimRevocationCheck + ? ownedCreds[0] + : (await this._credentialWallet.findNonRevokedCredential(ownedCreds)).cred; + + // get profile nonce that was used as a part of subject in the credential + + let subjectDID = cred.credentialSubject['id']; + if (!subjectDID) { + subjectDID = cred.issuer; // self credential + } + const credentialSubjectProfileNonce = + subjectDID === did.string() + ? 0 + : profiles.find((p) => { + return p.id === subjectDID; + })!.nonce; + + return { credential: cred, nonce: credentialSubjectProfileNonce }; } } diff --git a/src/iden3comm/handlers/fetch.ts b/src/iden3comm/handlers/fetch.ts index a39fb19b..97612d76 100644 --- a/src/iden3comm/handlers/fetch.ts +++ b/src/iden3comm/handlers/fetch.ts @@ -4,6 +4,7 @@ import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { CredentialsOfferMessage, IPackageManager, + JWSPackerParams, MessageFetchRequestMessage, PackerParams } from '../types'; @@ -12,6 +13,23 @@ import { DID } from '@iden3/js-iden3-core'; import * as uuid from 'uuid'; import { W3CCredential } from '../../verifiable'; import { byteDecoder, byteEncoder } from '../../utils'; +import { ProvingMethodAlg, proving } from '@iden3/js-jwz'; +import { ICredentialWallet } from '../../credentials'; +import { IIdentityWallet } from '../../identity'; + +interface FetchHandlerOptions { + mediaType: MediaType; + packerOptions: + | { + profileNonce: number; + provingMethodAlg: ProvingMethodAlg; + } + | JWSPackerParams; + did?: DID; + headers?: { + [key: string]: string; + }; +} /** * Interface that allows the processing of the credential offer in the raw format for given identifier @@ -22,26 +40,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; } /** * @@ -57,39 +67,41 @@ export class FetchHandler implements IFetchHandler { /** * Creates an instance of AuthHandler. * @param {IPackageManager} _packerMgr - package manager to unpack message envelope + * @param {ICredentialWallet} _credentialWallet - wallet to search credentials + * @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities */ - constructor(private readonly _packerMgr: IPackageManager) {} + constructor( + private readonly _packerMgr: IPackageManager, + private readonly _identityWallet: IIdentityWallet, + private readonly _credentialWallet: ICredentialWallet + ) {} /** * 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) { + const zkpPackerOpts = { + profileNonce: 0, + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg, + alg: '' + }; + + opts = { + packerOptions: zkpPackerOpts, + mediaType: MediaType.ZKPMessage + }; + } + 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) { @@ -103,19 +115,29 @@ export class FetchHandler implements IFetchHandler { 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 || '' }, - from: did.string(), + from: opts.did ? opts.did.string() : offerMessage.to, to: offerMessage.from }; const msgBytes = byteEncoder.encode(JSON.stringify(fetchRequest)); + + // check if offer is for profile we need to find its nonce + // if it opts did is set, then + opts.packerOptions.profileNonce = opts.did + ? 0 + : this._identityWallet.getProfileNonce(DID.parse(offerMessage.to!)); + const token = byteDecoder.decode( - await this._packerMgr.pack(mediaType, msgBytes, { senderDID: did, ...packerParams }) + await this._packerMgr.pack(opts.mediaType, msgBytes, { + senderDID: offerMessage.to, + ...opts.packerOptions + }) ); let message: { body: { credential: W3CCredential } }; try { @@ -143,6 +165,7 @@ export class FetchHandler implements IFetchHandler { } } + this._credentialWallet.saveAll(credentials); 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..6a9754bd 100644 --- a/src/iden3comm/packers/zkp.ts +++ b/src/iden3comm/packers/zkp.ts @@ -124,7 +124,7 @@ export class ZKPPacker implements IPacker { return provingParams?.dataPreparer?.prepare( hash, params.senderDID, - params.profileNonce, + params.profileNonce!, circuitId as CircuitId ); } diff --git a/src/iden3comm/types/packer.ts b/src/iden3comm/types/packer.ts index 6fac60c0..8cc67ce7 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 */ 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..5a52a37b 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'; @@ -39,6 +39,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 +233,24 @@ 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} profile - profile has been derived + * @returns `{Promise}` + */ + getProfileNonce(profile: DID): Promise; } /** @@ -392,10 +411,21 @@ export class IdentityWallet implements IIdentityWallet { credential }; } + /** {@inheritDoc IIdentityWallet.getProfileNonce} */ + async getProfileNonce(profile: DID): Promise { + // check if it is a genesis identity + + const identity = await this._storage.identity.getIdentity(profile.string()); + + if (identity) { + return 0; + } + return (await this._storage.identity.getProfileById(profile.string())).nonce; + } /** {@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 +438,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,6 +447,16 @@ 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)); diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 67fd1fed..732c6bb4 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -278,12 +278,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: credential, revStatus, credentialCoreClaim: credCoreClaim }; } private async prepareAuthBJJCredential( diff --git a/src/verifiable/presentation.ts b/src/verifiable/presentation.ts index e20e4106..2d46e7b5 100644 --- a/src/verifiable/presentation.ts +++ b/src/verifiable/presentation.ts @@ -83,7 +83,7 @@ export const verifiablePresentationFromCred = async ( }> => { const mz = await w3cCred.merklize(opts); - const request = requestObj as { [key: string]: unknown }; + const request = requestObj as unknown as { [key: string]: unknown }; const contextType = stringByPath(request, 'type'); 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..2aea7228 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'; @@ -184,7 +183,7 @@ describe('auth', () => { proofService.generateAuthV2Inputs.bind(proofService), proofService.verifyState.bind(proofService) ); - authHandler = new AuthHandler(packageMgr, proofService, credWallet); + authHandler = new AuthHandler(packageMgr, proofService, credWallet, idWallet); }); it('request-response flow genesis', async () => { @@ -271,15 +270,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); @@ -288,7 +279,7 @@ describe('auth', () => { expect(token).to.be.a('object'); }); - it('request-response flow profiles', async () => { + it('request-response flow profiles explicit credential', async () => { const { did: userDID } = await idWallet.createIdentity({ method: DidMethod.Iden3, blockchain: Blockchain.Polygon, @@ -370,9 +361,9 @@ describe('auth', () => { const authR = await authHandler.parseAuthorizationRequest(msgBytes); - // let's find cred for each request. - const reqCreds: ZKPRequestWithCredential[] = []; + let cred: W3CCredential; + // let's find cred for each request. for (let index = 0; index < authR.body!.scope.length; index++) { const zkpReq = authR.body!.scope[index]; @@ -398,28 +389,113 @@ describe('auth', () => { }); // 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 }); + cred = credsThatBelongToGenesisIdOrItsProfiles[0]; } // you can create new profile here for auth or if you want to login with genesis set to 0. const authProfileNonce = 100; - const resp = await authHandler.generateAuthorizationResponse( - userDID, - authProfileNonce, - authR, - reqCreds - ); + const resp = await authHandler.handleAuthorizationRequest(userDID, msgBytes, { + mediaType: MediaType.ZKPMessage, + packerOptions: { + profileNonce: authProfileNonce, + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + }, + credential: cred! + }); + + console.log(resp); + }); + it('request-response flow profiles implicit credential', async () => { + const { did: userDID } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + const profileDID = await idWallet.createProfile(userDID, 50, 'test verifier'); + + const { did: issuerDID } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + + const claimReq: CredentialRequest = { + credentialSchema: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v2.json', + type: 'KYCAgeCredential', + credentialSubject: { + id: profileDID.string(), + birthday: 19960424, + documentType: 99 + }, + expiration: 1693526400, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }; + const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); + + await credWallet.save(issuerCred); + + const proofReq: ZeroKnowledgeProofRequest = { + id: 1, + circuitId: CircuitId.AtomicQuerySigV2, + optional: false, + query: { + allowedIssuers: ['*'], + type: claimReq.type, + context: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld', + credentialSubject: { + documentType: { + $eq: 99 + } + } + } + }; + + const authReqBody: AuthorizationRequestMessageBody = { + callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123', + reason: 'reason', + message: 'message', + did_doc: {}, + scope: [proofReq as ZeroKnowledgeProofRequest] + }; + + const id = uuid.v4(); + const authReq: AuthorizationRequestMessage = { + id, + typ: MediaType.PlainMessage, + type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE, + thid: id, + body: authReqBody, + from: DID.idFromDID(issuerDID).string() + }; + + const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); + + const authProfileNonce = 100; + + const resp = await authHandler.handleAuthorizationRequest(userDID, msgBytes, { + mediaType: MediaType.ZKPMessage, + packerOptions: { + profileNonce: authProfileNonce, + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + } + }); console.log(resp); }); diff --git a/tests/handlers/fetch.test.ts b/tests/handlers/fetch.test.ts index a3727be7..0fcaddab 100644 --- a/tests/handlers/fetch.test.ts +++ b/tests/handlers/fetch.test.ts @@ -33,7 +33,7 @@ import { import * as uuid from 'uuid'; import { byteEncoder } from '../../src/utils'; import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; -import { assert, expect } from 'chai'; +import { assert, expect, use } from 'chai'; import fetchMock from '@gr2m/fetch-mock'; import { after } from 'mocha'; import { proving } from '@iden3/js-jwz'; @@ -47,6 +47,8 @@ describe('fetch', () => { let packageMgr: IPackageManager; const rhsUrl = process.env.RHS_URL as string; const agentUrl = 'https://testagent.com/'; + const agentUrlForProfile = 'https://testagentprofile.com/'; + const mockedToken = 'jwz token to fetch credential'; const mockStateStorage: IStateStorage = { @@ -145,6 +147,71 @@ describe('fetch', () => { "typ": "application/iden3comm-plain-json", "type": "https://iden3-communication.io/credentials/1.0/issuance-response" }`; + const mockerCredResponseForProfile = `{ + "body": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld", + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v100.json-ld" + ], + "credentialSchema": { + "id": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v100.json", + "type": "JsonSchemaValidator2018" + }, + "credentialStatus": { + "id": "http://localhost:8001/api/v1/identities/did%3Aiden3%3AtSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/revocation/status/3701011735", + "revocationNonce": 3701011735, + "type": "SparseMerkleTreeProof" + }, + "credentialSubject": { + "birthday": 19960424, + "documentType": 99, + "id": "did:polygon:mumbai:wwYSEgcJxHAwyjKbBmVbm7w1p33Xb3e1BbDCmHuxh", + "type": "KYCAgeCredential" + }, + "expirationDate": "2361-03-21T21:14:48+02:00", + "id": "http://localhost:8001/api/v1/identities/did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/d04fcbf8-b373-11ed-88d2-de17148ce1ce", + "issuanceDate": "2023-02-23T14:15:51.58546+02:00", + "issuer": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", + "proof": [ + { + "coreClaim": "06ce4f021d1d9fe3b5dd115882f469ce2a000000000000000000000000000000021253a8d5867185af98ef6bb512c021fc5f099e3ac82f9b924b353128e20c005cff21a8096a19e3198035035c815a2c4d2384ea6b9b2c4a6cc3b9aebff2dc1d000000000000000000000000000000000000000000000000000000000000000017f598dc00000000281cdcdf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "issuerData": { + "authCoreClaim": "cca3371a6cb1b715004407e325bd993c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ccf39129a7759649ab1a70538602ca651f76abc1e9b7b7b84db2faa48037a0bd55ec8cdd16d2989f0a5c9824b578c914bd0fd10746bec83075773931d6cb1290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "credentialStatus": { + "id": "http://localhost:8001/api/v1/identities/did%3Aiden3%3AtSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/revocation/status/0", + "revocationNonce": 0, + "type": "SparseMerkleTreeProof" + }, + "id": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", + "mtp": { + "existence": true, + "siblings": [ + "307532286953684850322725598023921449547946667992416711686417617300288340824" + ] + }, + "state": { + "claimsTreeRoot": "188a0b752a0add6ab1b4639b2529999de16cffb42a251074b67551b838404330", + "value": "e74265e2e3c054db09bab873642d9675727538a7010f4dbd8250eed06ca54100" + } + }, + "signature": "62affc0893c76c1e9e279ea0a5b7a48d0fc186d6133303fcd924ae5d3cc0b39fd6791fe0903a80501707f272724ee889f134c64035e40a956fd0cf1c3e4baa02", + "type": "BJJSignature2021" + } + ], + "type": [ + "VerifiableCredential", + "KYCAgeCredential" + ] + } + }, + "from": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", + "id": "30e37e90-2242-4a36-b475-799047d60481", + "to": "did:polygon:mumbai:wwYSEgcJxHAwyjKbBmVbm7w1p33Xb3e1BbDCmHuxh, + "typ": "application/iden3comm-plain-json", + "type": "https://iden3-communication.io/credentials/1.0/issuance-response" +}`; beforeEach(async () => { const memoryKeyStore = new InMemoryPrivateKeyStore(); @@ -178,15 +245,16 @@ describe('fetch', () => { return { unpackedMessage: msg, unpackedMediaType: PROTOCOL_CONSTANTS.MediaType.PlainMessage }; }; packageMgr.pack = async (): Promise => byteEncoder.encode(mockedToken); - fetchHandler = new FetchHandler(packageMgr); + fetchHandler = new FetchHandler(packageMgr, idWallet, credWallet); fetchMock.post(agentUrl, JSON.parse(mockedCredResponse)); + fetchMock.post(agentUrlForProfile, JSON.parse(mockedCredResponse)); }); after(() => { 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,16 +293,84 @@ 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(msgBytes, { + did: userDID, + mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, + packerOptions: { + profileNonce: 0, + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + } + }); + + await credWallet.saveAll(res); + + expect(res).to.be.a('array'); + expect(res).to.have.length(1); + assert.deepEqual( + res[0], + (JSON.parse(mockedCredResponse) as CredentialIssuanceMessage).body?.credential + ); + }); + + it('fetch credential issued to profile', async () => { + const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); + const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); + + const { did: userDID, credential: cred } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + expect(cred).not.to.be.undefined; + const profileDID = await idWallet.createProfile(userDID, 50, 'test verifier'); + expect(profileDID).not.to.be.undefined; + console.log(profileDID); + + const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + + expect(issuerAuthCredential).not.to.be.undefined; + + const id = uuid.v4(); + const authReq: CredentialsOfferMessage = { + id, + typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, + type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE, + thid: id, + body: { + url: agentUrlForProfile, + credentials: [{ id: 'https://credentialId', description: 'kyc age credentials' }] + } as CredentialsOfferMessageBody, + from: issuerDID.string(), + to: profileDID.string() }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const res = await fetchHandler.handleCredentialOffer({ + const res = await fetchHandler.handleCredentialOffer(msgBytes, { did: userDID, - offer: msgBytes, - packer: { - mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, + mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, + packerOptions: { + profileNonce: 0, provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg } }); From 23a89d7ed433cd485feac0459bc65fa7d8994320 Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Fri, 21 Jul 2023 14:08:25 +0300 Subject: [PATCH 2/6] support profiles --- src/credentials/status/on-chain-revocation.ts | 13 +- src/iden3comm/handlers/auth.ts | 139 ++++++---------- src/iden3comm/handlers/fetch.ts | 71 ++++----- src/iden3comm/packers/zkp.ts | 17 +- src/iden3comm/types/packer.ts | 1 - src/iden3comm/types/protocol/auth.ts | 6 +- src/iden3comm/types/protocol/credentials.ts | 16 +- src/identity/identity-wallet.ts | 72 +++++++-- src/proof/proof-service.ts | 140 +++++++++++------ src/storage/entities/identity.ts | 8 +- src/storage/interfaces/identity.ts | 4 +- src/storage/shared/identity-storage.ts | 20 +-- src/verifiable/presentation.ts | 7 +- tests/handlers/auth.test.ts | 145 ++--------------- tests/handlers/fetch.test.ts | 148 +----------------- 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 | 26 ++- tests/rhs/rhs.test.ts | 2 +- 22 files changed, 303 insertions(+), 632 deletions(-) delete mode 100644 tests/proofs/common.ts 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 68c916cc..53467cfc 100644 --- a/src/iden3comm/handlers/auth.ts +++ b/src/iden3comm/handlers/auth.ts @@ -12,13 +12,11 @@ import { ZeroKnowledgeProofResponse } from '../types'; import { DID } from '@iden3/js-iden3-core'; -import { ProvingMethodAlg, proving } from '@iden3/js-jwz'; +import { proving } from '@iden3/js-jwz'; import * as uuid from 'uuid'; -import { ICredentialWallet } from '../../credentials'; -import { ProofQuery, W3CCredential } from '../../verifiable'; +import { ProofQuery } from '../../verifiable'; import { byteDecoder, byteEncoder } from '../../utils'; -import { IIdentityWallet, generateProfileDID } from '../../identity'; /** * Interface that allows the processing of the authorization request in the raw format for given identifier @@ -59,16 +57,17 @@ export interface IAuthHandler { authResponse: AuthorizationResponseMessage; }>; } - -interface AuthHandlerOptions { +/** + * + * Options to pass to auth handler + * + * @export + * @beta + * @interface AuthHandlerOptions + */ +export interface AuthHandlerOptions { mediaType: MediaType; - packerOptions: - | { - profileNonce: number; - provingMethodAlg: ProvingMethodAlg; - } - | JWSPackerParams; - credential?: W3CCredential; + packerOptions?: JWSPackerParams; } /** @@ -86,16 +85,12 @@ 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 - * @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities * * */ constructor( private readonly _packerMgr: IPackageManager, - private readonly _proofService: IProofService, - private readonly _credentialWallet: ICredentialWallet, - private readonly _identityWallet: IIdentityWallet + private readonly _proofService: IProofService ) {} /** @@ -114,6 +109,18 @@ export class AuthHandler implements IAuthHandler { return authRequest; } + /** + * unpacks authorization request and packs authorization response + * @export + * @beta + * @param {did} did - sender DID + * @param {Uint8Array} request - raw byte message + * @returns `Promise<{ + token: string; + authRequest: AuthorizationRequestMessage; + authResponse: AuthorizationResponseMessage; + }>` + */ async handleAuthorizationRequest( did: DID, request: Uint8Array, @@ -130,37 +137,35 @@ export class AuthHandler implements IAuthHandler { } if (!opts) { - const zkpPackerOpts = { - profileNonce: 0, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg, - alg: '' - }; - opts = { - packerOptions: zkpPackerOpts, 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 senderDID = - opts!.mediaType === MediaType.SignedMessage - ? did.string() - : generateProfileDID(did, opts!.packerOptions!.profileNonce).string(); + const authResponse: AuthorizationResponseMessage = { id: guid, - typ: opts!.mediaType, + typ: opts.mediaType, type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE, thid: authRequest.thid ?? guid, body: { message: authRequest?.body?.message, scope: [] }, - from: senderDID, + from: did.string(), to: authRequest.from }; - for (const proofReq of authRequest.body!.scope) { + for (const proofReq of authRequest.body.scope) { const zkpReq: ZeroKnowledgeProofRequest = { id: proofReq.id, circuitId: proofReq.circuitId as CircuitId, @@ -169,20 +174,11 @@ export class AuthHandler implements IAuthHandler { const query = proofReq.query as unknown as ProofQuery; - if (opts!.credential) { - proofReq.query.claimId = opts!.credential.id; - } - - const { credential, nonce } = await this.findCredentialForDID(did, query); - const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof( zkpReq, did, - credential, { - credentialSubjectProfileNonce: nonce, - skipRevocation: query.skipClaimRevocationCheck ?? false, - authProfileNonce: opts!.packerOptions!.profileNonce + skipRevocation: query.skipClaimRevocationCheck ?? false } ); @@ -190,58 +186,21 @@ export class AuthHandler implements IAuthHandler { } 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(opts!.mediaType, msgBytes, { + await this._packerMgr.pack(opts.mediaType, msgBytes, { senderDID: did, - ...opts!.packerOptions + ...packerOpts }) ); - return { authRequest, authResponse, token }; - } - async findCredentialForDID( - did: DID, - query: ProofQuery - ): Promise<{ credential: W3CCredential; nonce: number }> { - const creds = await this._credentialWallet.findByQuery(query); - if (!creds.length) { - throw new Error(`no credential satisfied query`); - } - - const profiles = await this._identityWallet.getProfilesByDID(did); - - const ownedCreds = creds.filter((cred) => { - const credentialSubjectId = cred.credentialSubject['id'] as string; // credential subject - return ( - credentialSubjectId == did.string() || - profiles.some((p) => { - return p.id === credentialSubjectId; - }) - ); - }); - - if (!ownedCreds.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 cred = query.skipClaimRevocationCheck - ? ownedCreds[0] - : (await this._credentialWallet.findNonRevokedCredential(ownedCreds)).cred; - - // get profile nonce that was used as a part of subject in the credential - let subjectDID = cred.credentialSubject['id']; - if (!subjectDID) { - subjectDID = cred.issuer; // self credential - } - const credentialSubjectProfileNonce = - subjectDID === did.string() - ? 0 - : profiles.find((p) => { - return p.id === subjectDID; - })!.nonce; - - return { credential: cred, nonce: credentialSubjectProfileNonce }; + return { authRequest, authResponse, token }; } } diff --git a/src/iden3comm/handlers/fetch.ts b/src/iden3comm/handlers/fetch.ts index 97612d76..7f546441 100644 --- a/src/iden3comm/handlers/fetch.ts +++ b/src/iden3comm/handlers/fetch.ts @@ -5,27 +5,25 @@ import { CredentialsOfferMessage, IPackageManager, JWSPackerParams, - MessageFetchRequestMessage, - PackerParams + 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 { ProvingMethodAlg, proving } from '@iden3/js-jwz'; -import { ICredentialWallet } from '../../credentials'; -import { IIdentityWallet } from '../../identity'; +import { proving } from '@iden3/js-jwz'; -interface FetchHandlerOptions { +/** + * + * Options to pass to fetch handler + * + * @export + * @beta + * @interface FetchHandlerOptions + */ +export interface FetchHandlerOptions { mediaType: MediaType; - packerOptions: - | { - profileNonce: number; - provingMethodAlg: ProvingMethodAlg; - } - | JWSPackerParams; - did?: DID; + packerOptions?: JWSPackerParams; headers?: { [key: string]: string; }; @@ -67,14 +65,8 @@ export class FetchHandler implements IFetchHandler { /** * Creates an instance of AuthHandler. * @param {IPackageManager} _packerMgr - package manager to unpack message envelope - * @param {ICredentialWallet} _credentialWallet - wallet to search credentials - * @param {IIdentityWallet} _identityWallet - wallet to search profiles and identities */ - constructor( - private readonly _packerMgr: IPackageManager, - private readonly _identityWallet: IIdentityWallet, - private readonly _credentialWallet: ICredentialWallet - ) {} + constructor(private readonly _packerMgr: IPackageManager) {} /** * Handles only messages with credentials/1.0/offer type @@ -90,18 +82,15 @@ export class FetchHandler implements IFetchHandler { opts?: FetchHandlerOptions ): Promise { if (!opts) { - const zkpPackerOpts = { - profileNonce: 0, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg, - alg: '' - }; - opts = { - packerOptions: zkpPackerOpts, 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) { @@ -109,8 +98,8 @@ 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 = { @@ -119,24 +108,25 @@ export class FetchHandler implements IFetchHandler { type: PROTOCOL_MESSAGE_TYPE.CREDENTIAL_FETCH_REQUEST_MESSAGE_TYPE, thid: offerMessage.thid ?? guid, body: { - id: credentialInfo?.id || '' + id: credentialInfo.id }, - from: opts.did ? opts.did.string() : offerMessage.to, + from: offerMessage.to, to: offerMessage.from }; const msgBytes = byteEncoder.encode(JSON.stringify(fetchRequest)); - // check if offer is for profile we need to find its nonce - // if it opts did is set, then - opts.packerOptions.profileNonce = opts.did - ? 0 - : this._identityWallet.getProfileNonce(DID.parse(offerMessage.to!)); + const packerOpts = + opts.mediaType === MediaType.SignedMessage + ? opts.packerOptions + : { + provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg + }; const token = byteDecoder.decode( await this._packerMgr.pack(opts.mediaType, msgBytes, { senderDID: offerMessage.to, - ...opts.packerOptions + ...packerOpts }) ); let message: { body: { credential: W3CCredential } }; @@ -147,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 }); @@ -164,8 +155,6 @@ export class FetchHandler implements IFetchHandler { ); } } - - this._credentialWallet.saveAll(credentials); return credentials; } } diff --git a/src/iden3comm/packers/zkp.ts b/src/iden3comm/packers/zkp.ts index 6a9754bd..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 8cc67ce7..d99ab71c 100644 --- a/src/iden3comm/types/packer.ts +++ b/src/iden3comm/types/packer.ts @@ -72,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/identity-wallet.ts b/src/identity/identity-wallet.ts index 5a52a37b..92262dfa 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -31,7 +31,8 @@ import { Iden3SparseMerkleTreeProof, ProofType, IssuerData, - CredentialStatusType + CredentialStatusType, + ProofQuery } from '../verifiable'; import { CredentialRequest, ICredentialWallet } from '../credentials'; import { pushHashesToRHS, TreesModel } from '../credentials/rhs'; @@ -247,10 +248,27 @@ export interface IIdentityWallet { * * gets profile nonce by it's id. if profile is genesis identifier - 0 is returned * - * @param {string} profile - profile has been derived - * @returns `{Promise}` + * @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}` */ - getProfileNonce(profile: DID): 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; } /** @@ -398,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); @@ -411,16 +429,20 @@ export class IdentityWallet implements IIdentityWallet { credential }; } - /** {@inheritDoc IIdentityWallet.getProfileNonce} */ - async getProfileNonce(profile: DID): Promise { + /** {@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(profile.string()); + const identity = await this._storage.identity.getIdentity(did.string()); if (identity) { - return 0; + return { nonce: 0, genesisDID: DID.parse(identity.did) }; } - return (await this._storage.identity.getProfileById(profile.string())).nonce; + 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} */ @@ -463,6 +485,9 @@ export class IdentityWallet implements IIdentityWallet { 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( @@ -861,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 732c6bb4..e428f959 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.findCredentialToProve(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, @@ -282,7 +295,7 @@ export class ProofService implements IProofService { const credCoreClaim = await this._identityWallet.getCoreClaimFromCredential(credential); - return { credential: credential, revStatus, credentialCoreClaim: credCoreClaim }; + return { credential, revStatus, credentialCoreClaim: credCoreClaim }; } private async prepareAuthBJJCredential( @@ -317,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) { @@ -337,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, @@ -368,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 }; } @@ -379,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, @@ -415,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, @@ -438,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 }; } @@ -449,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, @@ -467,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, @@ -485,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, @@ -510,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, @@ -539,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 }; } @@ -687,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 @@ -699,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) { @@ -712,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) { @@ -831,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, @@ -850,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); @@ -858,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; @@ -893,6 +916,21 @@ export class ProofService implements IProofService { return true; } + + async findCredentialToProve(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 2d46e7b5..399229aa 100644 --- a/src/verifiable/presentation.ts +++ b/src/verifiable/presentation.ts @@ -83,7 +83,7 @@ export const verifiablePresentationFromCred = async ( }> => { const mz = await w3cCred.merklize(opts); - const request = requestObj as unknown as { [key: string]: unknown }; + const request = requestObj as { [key: string]: unknown }; const contextType = stringByPath(request, 'type'); @@ -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/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 2aea7228..165c3882 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -183,10 +183,10 @@ describe('auth', () => { proofService.generateAuthV2Inputs.bind(proofService), proofService.verifyState.bind(proofService) ); - authHandler = new AuthHandler(packageMgr, proofService, credWallet, idWallet); + 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, @@ -279,7 +279,7 @@ describe('auth', () => { expect(token).to.be.a('object'); }); - it('request-response flow profiles explicit credential', async () => { + it('request-response flow profiles', async () => { const { did: userDID } = await idWallet.createIdentity({ method: DidMethod.Iden3, blockchain: Blockchain.Polygon, @@ -359,143 +359,16 @@ describe('auth', () => { const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const authR = await authHandler.parseAuthorizationRequest(msgBytes); - - let cred: W3CCredential; - - // let's find cred for each request. - 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 - cred = credsThatBelongToGenesisIdOrItsProfiles[0]; - } - // you can create new profile here for auth or if you want to login with genesis set to 0. - const authProfileNonce = 100; - - const resp = await authHandler.handleAuthorizationRequest(userDID, msgBytes, { - mediaType: MediaType.ZKPMessage, - packerOptions: { - profileNonce: authProfileNonce, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg - }, - credential: cred! - }); - - console.log(resp); - }); - it('request-response flow profiles implicit credential', async () => { - const { did: userDID } = await idWallet.createIdentity({ - method: DidMethod.Iden3, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, - seed: seedPhrase, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: rhsUrl - } - }); - const profileDID = await idWallet.createProfile(userDID, 50, 'test verifier'); - - const { did: issuerDID } = await idWallet.createIdentity({ - method: DidMethod.Iden3, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, - seed: seedPhraseIssuer, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: rhsUrl - } - }); - - const claimReq: CredentialRequest = { - credentialSchema: - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v2.json', - type: 'KYCAgeCredential', - credentialSubject: { - id: profileDID.string(), - birthday: 19960424, - documentType: 99 - }, - expiration: 1693526400, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: rhsUrl - } - }; - const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); - - await credWallet.save(issuerCred); - - const proofReq: ZeroKnowledgeProofRequest = { - id: 1, - circuitId: CircuitId.AtomicQuerySigV2, - optional: false, - query: { - allowedIssuers: ['*'], - type: claimReq.type, - context: - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld', - credentialSubject: { - documentType: { - $eq: 99 - } - } - } - }; - - const authReqBody: AuthorizationRequestMessageBody = { - callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123', - reason: 'reason', - message: 'message', - did_doc: {}, - scope: [proofReq as ZeroKnowledgeProofRequest] - }; - - const id = uuid.v4(); - const authReq: AuthorizationRequestMessage = { - id, - typ: MediaType.PlainMessage, - type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE, - thid: id, - body: authReqBody, - from: DID.idFromDID(issuerDID).string() - }; - - const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); + const authR = await authHandler.parseAuthorizationRequest(msgBytes); - const authProfileNonce = 100; + 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.handleAuthorizationRequest(userDID, msgBytes, { - mediaType: MediaType.ZKPMessage, - packerOptions: { - profileNonce: authProfileNonce, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg - } - }); + 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 0fcaddab..2158db90 100644 --- a/tests/handlers/fetch.test.ts +++ b/tests/handlers/fetch.test.ts @@ -33,10 +33,9 @@ import { import * as uuid from 'uuid'; import { byteEncoder } from '../../src/utils'; import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core'; -import { assert, expect, use } from 'chai'; +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,7 +46,6 @@ describe('fetch', () => { let packageMgr: IPackageManager; const rhsUrl = process.env.RHS_URL as string; const agentUrl = 'https://testagent.com/'; - const agentUrlForProfile = 'https://testagentprofile.com/'; const mockedToken = 'jwz token to fetch credential'; @@ -147,71 +145,6 @@ describe('fetch', () => { "typ": "application/iden3comm-plain-json", "type": "https://iden3-communication.io/credentials/1.0/issuance-response" }`; - const mockerCredResponseForProfile = `{ - "body": { - "credential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld", - "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v100.json-ld" - ], - "credentialSchema": { - "id": "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v100.json", - "type": "JsonSchemaValidator2018" - }, - "credentialStatus": { - "id": "http://localhost:8001/api/v1/identities/did%3Aiden3%3AtSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/revocation/status/3701011735", - "revocationNonce": 3701011735, - "type": "SparseMerkleTreeProof" - }, - "credentialSubject": { - "birthday": 19960424, - "documentType": 99, - "id": "did:polygon:mumbai:wwYSEgcJxHAwyjKbBmVbm7w1p33Xb3e1BbDCmHuxh", - "type": "KYCAgeCredential" - }, - "expirationDate": "2361-03-21T21:14:48+02:00", - "id": "http://localhost:8001/api/v1/identities/did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/d04fcbf8-b373-11ed-88d2-de17148ce1ce", - "issuanceDate": "2023-02-23T14:15:51.58546+02:00", - "issuer": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", - "proof": [ - { - "coreClaim": "06ce4f021d1d9fe3b5dd115882f469ce2a000000000000000000000000000000021253a8d5867185af98ef6bb512c021fc5f099e3ac82f9b924b353128e20c005cff21a8096a19e3198035035c815a2c4d2384ea6b9b2c4a6cc3b9aebff2dc1d000000000000000000000000000000000000000000000000000000000000000017f598dc00000000281cdcdf0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "issuerData": { - "authCoreClaim": "cca3371a6cb1b715004407e325bd993c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ccf39129a7759649ab1a70538602ca651f76abc1e9b7b7b84db2faa48037a0bd55ec8cdd16d2989f0a5c9824b578c914bd0fd10746bec83075773931d6cb1290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "credentialStatus": { - "id": "http://localhost:8001/api/v1/identities/did%3Aiden3%3AtSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb/claims/revocation/status/0", - "revocationNonce": 0, - "type": "SparseMerkleTreeProof" - }, - "id": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", - "mtp": { - "existence": true, - "siblings": [ - "307532286953684850322725598023921449547946667992416711686417617300288340824" - ] - }, - "state": { - "claimsTreeRoot": "188a0b752a0add6ab1b4639b2529999de16cffb42a251074b67551b838404330", - "value": "e74265e2e3c054db09bab873642d9675727538a7010f4dbd8250eed06ca54100" - } - }, - "signature": "62affc0893c76c1e9e279ea0a5b7a48d0fc186d6133303fcd924ae5d3cc0b39fd6791fe0903a80501707f272724ee889f134c64035e40a956fd0cf1c3e4baa02", - "type": "BJJSignature2021" - } - ], - "type": [ - "VerifiableCredential", - "KYCAgeCredential" - ] - } - }, - "from": "did:iden3:tSsTSJY6g9yUc54FFH6yhx2ymZNtsuTAD9p3avWCb", - "id": "30e37e90-2242-4a36-b475-799047d60481", - "to": "did:polygon:mumbai:wwYSEgcJxHAwyjKbBmVbm7w1p33Xb3e1BbDCmHuxh, - "typ": "application/iden3comm-plain-json", - "type": "https://iden3-communication.io/credentials/1.0/issuance-response" -}`; beforeEach(async () => { const memoryKeyStore = new InMemoryPrivateKeyStore(); @@ -245,9 +178,8 @@ describe('fetch', () => { return { unpackedMessage: msg, unpackedMediaType: PROTOCOL_CONSTANTS.MediaType.PlainMessage }; }; packageMgr.pack = async (): Promise => byteEncoder.encode(mockedToken); - fetchHandler = new FetchHandler(packageMgr, idWallet, credWallet); + fetchHandler = new FetchHandler(packageMgr); fetchMock.post(agentUrl, JSON.parse(mockedCredResponse)); - fetchMock.post(agentUrlForProfile, JSON.parse(mockedCredResponse)); }); after(() => { @@ -299,81 +231,7 @@ describe('fetch', () => { const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const res = await fetchHandler.handleCredentialOffer(msgBytes, { - did: userDID, - mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, - packerOptions: { - profileNonce: 0, - provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg - } - }); - - await credWallet.saveAll(res); - - expect(res).to.be.a('array'); - expect(res).to.have.length(1); - assert.deepEqual( - res[0], - (JSON.parse(mockedCredResponse) as CredentialIssuanceMessage).body?.credential - ); - }); - - it('fetch credential issued to profile', async () => { - const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed'); - const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser'); - - const { did: userDID, credential: cred } = await idWallet.createIdentity({ - method: DidMethod.Iden3, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, - seed: seedPhrase, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: rhsUrl - } - }); - expect(cred).not.to.be.undefined; - const profileDID = await idWallet.createProfile(userDID, 50, 'test verifier'); - expect(profileDID).not.to.be.undefined; - console.log(profileDID); - - const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ - method: DidMethod.Iden3, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, - seed: seedPhraseIssuer, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: rhsUrl - } - }); - - expect(issuerAuthCredential).not.to.be.undefined; - - const id = uuid.v4(); - const authReq: CredentialsOfferMessage = { - id, - typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, - type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE, - thid: id, - body: { - url: agentUrlForProfile, - credentials: [{ id: 'https://credentialId', description: 'kyc age credentials' }] - } as CredentialsOfferMessageBody, - from: issuerDID.string(), - to: profileDID.string() - }; - - const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - - const res = await fetchHandler.handleCredentialOffer(msgBytes, { - did: userDID, - mediaType: PROTOCOL_CONSTANTS.MediaType.ZKPMessage, - packerOptions: { - profileNonce: 0, - 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..0b2d3bed 100644 --- a/tests/proofs/sig.test.ts +++ b/tests/proofs/sig.test.ts @@ -189,7 +189,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 +238,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 +290,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; }); @@ -343,14 +349,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; }); @@ -455,7 +454,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 +482,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) => { From 81463770e5ef2770c7de26eb6da5a7d02e19ae46 Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:00:57 +0300 Subject: [PATCH 3/6] modify IPFS tests --- .github/workflows/ci.yaml | 1 + README.md | 2 ++ package-lock.json | 8 ++++---- package.json | 2 +- tests/handlers/auth.test.ts | 4 +++- tests/proofs/sig.test.ts | 10 ++++++---- 6 files changed, 17 insertions(+), 10 deletions(-) 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/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 165c3882..f728da5c 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -54,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'); @@ -176,7 +178,7 @@ 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), diff --git a/tests/proofs/sig.test.ts b/tests/proofs/sig.test.ts index 0b2d3bed..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'); @@ -341,7 +343,7 @@ describe('sig proofs', () => { } }; const issuerCred = await idWallet.issueCredential(issuerDID, claimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(issuerCred); @@ -388,7 +390,7 @@ describe('sig proofs', () => { } }; const issuedCred = await idWallet.issueCredential(issuerDID, claimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(issuedCred); @@ -427,7 +429,7 @@ describe('sig proofs', () => { }; const deliveryCred = await idWallet.issueCredential(issuerDID, deliveryClaimReq, { - ipfsGatewayURL: 'https://ipfs.io' + ipfsNodeURL }); await credWallet.save(deliveryCred); From 4cfdf4b00d5fd97420485657675b05d8cee5b91e Mon Sep 17 00:00:00 2001 From: Dimasik Kolezhniuk Date: Fri, 21 Jul 2023 15:23:06 +0200 Subject: [PATCH 4/6] MInor fix --- src/iden3comm/handlers/auth.ts | 1 - src/proof/proof-service.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/iden3comm/handlers/auth.ts b/src/iden3comm/handlers/auth.ts index 53467cfc..30e74656 100644 --- a/src/iden3comm/handlers/auth.ts +++ b/src/iden3comm/handlers/auth.ts @@ -86,7 +86,6 @@ export class AuthHandler implements IAuthHandler { * @param {IPackageManager} _packerMgr - package manager to unpack message envelope * @param {IProofService} _proofService - proof service to verify zk proofs * - * */ constructor( private readonly _packerMgr: IPackageManager, diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index e428f959..0e23f497 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -200,7 +200,7 @@ export class ProofService implements IProofService { // find credential const credential = - opts.credential ?? (await this.findCredentialToProve(identifier, proofReq.query)); + opts.credential ?? (await this.getNonRevokedCredential(identifier, proofReq.query)); const { nonce: authProfileNonce, genesisDID } = await this._identityWallet.getGenesisDIDMetadata(identifier); @@ -917,7 +917,7 @@ export class ProofService implements IProofService { return true; } - async findCredentialToProve(did: DID, query: ProofQuery): Promise { + async getNonRevokedCredential(did: DID, query: ProofQuery): Promise { const credentials = await this._identityWallet.findOwnedCredentialsByDID(did, query); if (!credentials.length) { From 022dbeca5e12bdcb6a2969c1a8b2d04d6cf1d1a2 Mon Sep 17 00:00:00 2001 From: Dimasik Kolezhniuk Date: Fri, 21 Jul 2023 15:38:01 +0200 Subject: [PATCH 5/6] Rollback rename --- src/proof/proof-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 0e23f497..eeb01895 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -200,7 +200,7 @@ export class ProofService implements IProofService { // find credential const credential = - opts.credential ?? (await this.getNonRevokedCredential(identifier, proofReq.query)); + opts.credential ?? (await this.findCredential(identifier, proofReq.query)); const { nonce: authProfileNonce, genesisDID } = await this._identityWallet.getGenesisDIDMetadata(identifier); @@ -917,7 +917,7 @@ export class ProofService implements IProofService { return true; } - async getNonRevokedCredential(did: DID, query: ProofQuery): Promise { + async findCredential(did: DID, query: ProofQuery): Promise { const credentials = await this._identityWallet.findOwnedCredentialsByDID(did, query); if (!credentials.length) { From e83db23d1a048f327af60ea3b6ca4b3da87c94ff Mon Sep 17 00:00:00 2001 From: Dimasik Kolezhniuk Date: Fri, 21 Jul 2023 15:38:18 +0200 Subject: [PATCH 6/6] make findCredential private --- src/proof/proof-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index eeb01895..80120333 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -917,7 +917,7 @@ export class ProofService implements IProofService { return true; } - async findCredential(did: DID, query: ProofQuery): Promise { + private async findCredential(did: DID, query: ProofQuery): Promise { const credentials = await this._identityWallet.findOwnedCredentialsByDID(did, query); if (!credentials.length) {