diff --git a/package-lock.json b/package-lock.json index 5e0b4632..5998e6e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.17.8", + "version": "1.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@0xpolygonid/js-sdk", - "version": "1.17.8", + "version": "1.18.0", "license": "MIT or Apache-2.0", "dependencies": { "@noble/curves": "^1.4.0", diff --git a/package.json b/package.json index a5b72f4a..576f28a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xpolygonid/js-sdk", - "version": "1.17.8", + "version": "1.18.0", "description": "SDK to work with Polygon ID", "main": "dist/node/cjs/index.js", "module": "dist/node/esm/index.js", diff --git a/src/circuits/atomic-query-mtp-v2-on-chain.ts b/src/circuits/atomic-query-mtp-v2-on-chain.ts index 8ff07df2..4d0216c5 100644 --- a/src/circuits/atomic-query-mtp-v2-on-chain.ts +++ b/src/circuits/atomic-query-mtp-v2-on-chain.ts @@ -7,6 +7,8 @@ import { bigIntArrayToStringArray, existenceToInt, getNodeAuxValue, + IStateInfoPubSignals, + StatesInfo, prepareCircuitArrayValues, prepareSiblingsStr } from './common'; @@ -240,7 +242,7 @@ interface atomicQueryMTPV2OnChainCircuitInputs { * @class AtomicQueryMTPV2OnChainPubSignals * @extends {BaseConfig} */ -export class AtomicQueryMTPV2OnChainPubSignals extends BaseConfig { +export class AtomicQueryMTPV2OnChainPubSignals extends BaseConfig implements IStateInfoPubSignals { requestID!: bigint; userID!: Id; issuerID!: Id; @@ -322,4 +324,15 @@ export class AtomicQueryMTPV2OnChainPubSignals extends BaseConfig { return this; } + + /** {@inheritDoc IStateInfoPubSignals.getStatesInfo} */ + getStatesInfo(): StatesInfo { + return { + states: [ + { id: this.issuerID, state: this.issuerClaimIdenState }, + { id: this.issuerID, state: this.issuerClaimNonRevState } + ], + gists: [{ id: this.userID, root: this.gistRoot }] + }; + } } diff --git a/src/circuits/atomic-query-sig-v2-on-chain.ts b/src/circuits/atomic-query-sig-v2-on-chain.ts index a3d901d6..c91c17a3 100644 --- a/src/circuits/atomic-query-sig-v2-on-chain.ts +++ b/src/circuits/atomic-query-sig-v2-on-chain.ts @@ -7,8 +7,10 @@ import { bigIntArrayToStringArray, existenceToInt, getNodeAuxValue, + IStateInfoPubSignals, prepareCircuitArrayValues, - prepareSiblingsStr + prepareSiblingsStr, + StatesInfo } from './common'; import { byteDecoder, byteEncoder } from '../utils'; @@ -312,7 +314,7 @@ export class AtomicQuerySigV2OnChainCircuitInputs { * @class AtomicQuerySigV2OnChainPubSignals * @extends {BaseConfig} */ -export class AtomicQuerySigV2OnChainPubSignals extends BaseConfig { +export class AtomicQuerySigV2OnChainPubSignals extends BaseConfig implements IStateInfoPubSignals { requestID!: bigint; userID!: Id; issuerID!: Id; @@ -397,4 +399,15 @@ export class AtomicQuerySigV2OnChainPubSignals extends BaseConfig { return this; } + + /** {@inheritDoc IStateInfoPubSignals.getStatesInfo} */ + getStatesInfo(): StatesInfo { + return { + states: [ + { id: this.issuerID, state: this.issuerAuthState }, + { id: this.issuerID, state: this.issuerClaimNonRevState } + ], + gists: [{ id: this.userID, root: this.gistRoot }] + }; + } } diff --git a/src/circuits/atomic-query-v3-on-chain.ts b/src/circuits/atomic-query-v3-on-chain.ts index 3898d35d..9a6ba1ad 100644 --- a/src/circuits/atomic-query-v3-on-chain.ts +++ b/src/circuits/atomic-query-v3-on-chain.ts @@ -4,7 +4,9 @@ import { bigIntArrayToStringArray, prepareSiblingsStr, getNodeAuxValue, - prepareCircuitArrayValues + prepareCircuitArrayValues, + IStateInfoPubSignals, + StatesInfo } from './common'; import { BJJSignatureProof, CircuitError, GISTProof, Query, TreeState, ValueProof } from './models'; import { Hash, Proof, ZERO_HASH } from '@iden3/js-merkletree'; @@ -420,7 +422,7 @@ interface AtomicQueryV3OnChainCircuitInputs { * @beta * AtomicQueryV3OnChainPubSignals public inputs */ -export class AtomicQueryV3OnChainPubSignals extends BaseConfig { +export class AtomicQueryV3OnChainPubSignals extends BaseConfig implements IStateInfoPubSignals { requestID!: bigint; userID!: Id; issuerID!: Id; @@ -515,4 +517,15 @@ export class AtomicQueryV3OnChainPubSignals extends BaseConfig { return this; } + + /** {@inheritDoc IStateInfoPubSignals.getStatesInfo} */ + getStatesInfo(): StatesInfo { + return { + states: [ + { id: this.issuerID, state: this.issuerState }, + { id: this.issuerID, state: this.issuerClaimNonRevState } + ], + gists: [{ id: this.userID, root: this.gistRoot }] + }; + } } diff --git a/src/circuits/common.ts b/src/circuits/common.ts index 57c10dd4..7b7eab5c 100644 --- a/src/circuits/common.ts +++ b/src/circuits/common.ts @@ -1,6 +1,7 @@ import { Hex } from '@iden3/js-crypto'; import { Hash, ZERO_HASH, Proof, swapEndianness } from '@iden3/js-merkletree'; import { TreeState } from './models'; +import { Id } from '@iden3/js-iden3-core'; export const defaultMTLevels = 40; // max MT levels, default value for identity circuits export const defaultValueArraySize = 64; // max value array size, default value for identity circuits @@ -226,3 +227,35 @@ export function getProperties(obj: object): object { } return result; } + +/** + * states info from pub signals + * + * @public + * @type StatesInfo + */ +export type StatesInfo = { + states: { + id: Id; + state: Hash; + }[]; + gists: { + id: Id; + root: Hash; + }[]; +}; + +/** + * state pub signals + * + * @public + * @interface IStatePubSignals + */ +export interface IStateInfoPubSignals { + /** + * return object with state params + * + * @returns {OnChainStateInfo} + */ + getStatesInfo(): StatesInfo; +} diff --git a/src/iden3comm/constants.ts b/src/iden3comm/constants.ts index e755af68..a2d4e1c2 100644 --- a/src/iden3comm/constants.ts +++ b/src/iden3comm/constants.ts @@ -34,6 +34,9 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({ // ContractInvokeRequestMessageType is type for request of contract invoke request CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: `${IDEN3_PROTOCOL}proofs/1.0/contract-invoke-request` as const, + // ContractInvokeResponseMessageType is type for response of contract invoke request + CONTRACT_INVOKE_RESPONSE_MESSAGE_TYPE: + `${IDEN3_PROTOCOL}proofs/1.0/contract-invoke-response` as const, // CredentialOnchainOfferMessageType is type of message with credential onchain offering CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE: `${IDEN3_PROTOCOL}credentials/1.0/onchain-offer` as const, // ProposalRequestMessageType is type for proposal-request message diff --git a/src/iden3comm/handlers/contract-request.ts b/src/iden3comm/handlers/contract-request.ts index d9959938..a00afff3 100644 --- a/src/iden3comm/handlers/contract-request.ts +++ b/src/iden3comm/handlers/contract-request.ts @@ -2,9 +2,9 @@ import { CircuitId } from '../../circuits/models'; import { IProofService } from '../../proof/proof-service'; import { PROTOCOL_MESSAGE_TYPE } from '../constants'; import { BasicMessage, IPackageManager, ZeroKnowledgeProofResponse } from '../types'; -import { ContractInvokeRequest } from '../types/protocol/contract-request'; +import { ContractInvokeRequest, ContractInvokeResponse } from '../types/protocol/contract-request'; import { DID, ChainIds } from '@iden3/js-iden3-core'; -import { IOnChainZKPVerifier } from '../../storage'; +import { FunctionSignatures, IOnChainZKPVerifier } from '../../storage'; import { Signer } from 'ethers'; import { processZeroKnowledgeProofRequests } from './common'; import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler'; @@ -17,7 +17,7 @@ import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handl */ export interface IContractRequestHandler { /** - * unpacks contract invoker request + * unpacks contract invoke request * @beta * @param {Uint8Array} request - raw byte message * @returns `Promise` @@ -25,7 +25,7 @@ export interface IContractRequestHandler { parseContractInvokeRequest(request: Uint8Array): Promise; /** - * handle contract invoker request + * handle contract invoke request * @beta * @param {did} did - sender DID * @param {Uint8Array} request - raw byte message @@ -91,9 +91,11 @@ export class ContractRequestHandler ctx: ContractMessageHandlerOptions ): Promise { switch (message.type) { - case PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: - await this.handleContractInvoke(message as ContractInvokeRequest, ctx); - return null; + case PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: { + const ciMessage = message as ContractInvokeRequest; + const txHashResponsesMap = await this.handleContractInvoke(ciMessage, ctx); + return this.createContractInvokeResponse(ciMessage, txHashResponsesMap); + } default: return super.handle(message, ctx); } @@ -102,7 +104,7 @@ export class ContractRequestHandler private async handleContractInvoke( message: ContractInvokeRequest, ctx: ContractMessageHandlerOptions - ): Promise> { + ): Promise> { if (message.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) { throw new Error('Invalid message type for contract invoke request'); } @@ -127,11 +129,31 @@ export class ContractRequestHandler { ethSigner, challenge, supportedCircuits: this._supportedCircuits } ); - return this._zkpVerifier.submitZKPResponse( - ethSigner, - message.body.transaction_data, - zkpResponses - ); + const methodId = message.body.transaction_data.method_id.replace('0x', ''); + switch (methodId) { + case FunctionSignatures.SumbitZKPResponseV2: + return this._zkpVerifier.submitZKPResponseV2( + ethSigner, + message.body.transaction_data, + zkpResponses + ); + case FunctionSignatures.SumbitZKPResponseV1: { + const txHashZkpResponseMap = await this._zkpVerifier.submitZKPResponse( + ethSigner, + message.body.transaction_data, + zkpResponses + ); + const response = new Map(); + for (const [txHash, zkpResponse] of txHashZkpResponseMap) { + response.set(txHash, [zkpResponse]); + } + return response; + } + default: + throw new Error( + `Not supported method id. Only '${FunctionSignatures.SumbitZKPResponseV1} and ${FunctionSignatures.SumbitZKPResponseV2} are supported.'` + ); + } } /** @@ -151,8 +173,45 @@ export class ContractRequestHandler } /** - * handle contract invoker request + * creates contract invoke response + * @private + * @beta + * @param {ContractInvokeRequest} request - ContractInvokeRequest + * @param { Map} responses - map tx hash to array of ZeroKnowledgeProofResponses + * @returns `Promise` + */ + private async createContractInvokeResponse( + request: ContractInvokeRequest, + txHashToZkpResponseMap: Map + ): Promise { + const contractInvokeResponse: ContractInvokeResponse = { + id: request.id, + thid: request.thid, + type: PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_RESPONSE_MESSAGE_TYPE, + from: request.to, + to: request.from, + body: { + transaction_data: request.body.transaction_data, + reason: request.body.reason, + scope: [] + } + }; + for (const [txHash, zkpResponses] of txHashToZkpResponseMap) { + for (const zkpResponse of zkpResponses) { + contractInvokeResponse.body.scope.push({ + txHash, + ...zkpResponse + }); + } + } + return contractInvokeResponse; + } + + /** + * handle contract invoke request + * supports only 0xb68967e2 method id * @beta + * @deprecated * @param {did} did - sender DID * @param {ContractInvokeRequest} request - contract invoke request * @param {ContractInvokeHandlerOptions} opts - handler options @@ -165,10 +224,37 @@ export class ContractRequestHandler ): Promise> { const ciRequest = await this.parseContractInvokeRequest(request); - return this.handleContractInvoke(ciRequest, { - senderDid: did, - ethSigner: opts.ethSigner, - challenge: opts.challenge - }); + if (ciRequest.body.transaction_data.method_id !== FunctionSignatures.SumbitZKPResponseV1) { + throw new Error(`please use handle method to work with other method ids`); + } + + if (ciRequest.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) { + throw new Error('Invalid message type for contract invoke request'); + } + + const { ethSigner, challenge } = opts; + if (!ethSigner) { + throw new Error("Can't sign transaction. Provide Signer in options."); + } + + const { chain_id } = ciRequest.body.transaction_data; + const networkFlag = Object.keys(ChainIds).find((key) => ChainIds[key] === chain_id); + + if (!networkFlag) { + throw new Error(`Invalid chain id ${chain_id}`); + } + const verifierDid = ciRequest.from ? DID.parse(ciRequest.from) : undefined; + const zkpResponses = await processZeroKnowledgeProofRequests( + did, + ciRequest?.body?.scope, + verifierDid, + this._proofService, + { ethSigner, challenge, supportedCircuits: this._supportedCircuits } + ); + return this._zkpVerifier.submitZKPResponse( + ethSigner, + ciRequest.body.transaction_data, + zkpResponses + ); } } diff --git a/src/iden3comm/types/protocol/auth.ts b/src/iden3comm/types/protocol/auth.ts index 30e3e4b9..6f787cdb 100644 --- a/src/iden3comm/types/protocol/auth.ts +++ b/src/iden3comm/types/protocol/auth.ts @@ -48,5 +48,16 @@ export type ZeroKnowledgeProofRequest = { export type ZeroKnowledgeProofResponse = { id: number; circuitId: string; - vp?: object; + vp?: VerifiablePresentation; } & ZKProof; + +/** VerifiablePresentation represents structure of Verifiable Presentation */ +export type VerifiablePresentation = { + '@context': string | (string | object)[]; + '@type': string; + verifiableCredential: { + '@context': string | string[]; + '@type': string | string[]; + credentialSubject: JsonDocumentObject; + }; +}; diff --git a/src/iden3comm/types/protocol/contract-request.ts b/src/iden3comm/types/protocol/contract-request.ts index 5c1a230d..588c37d5 100644 --- a/src/iden3comm/types/protocol/contract-request.ts +++ b/src/iden3comm/types/protocol/contract-request.ts @@ -1,6 +1,6 @@ import { PROTOCOL_MESSAGE_TYPE } from '../../constants'; import { BasicMessage } from '../packer'; -import { ZeroKnowledgeProofRequest } from './auth'; +import { ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from './auth'; /** ContractInvokeRequest represents structure of contract invoke request object */ export type ContractInvokeRequest = BasicMessage & { @@ -15,6 +15,24 @@ export type ContractInvokeRequestBody = { scope: Array; }; +/** ContractInvokeResponse represents structure of contract invoke response object */ +export type ContractInvokeResponse = BasicMessage & { + body: ContractInvokeResponseBody; + type: typeof PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_RESPONSE_MESSAGE_TYPE; +}; + +/** ContractInvokeResponseBody represents structure of contract invoke response body object */ +export type ContractInvokeResponseBody = { + scope: Array; + transaction_data: ContractInvokeTransactionData; + reason: string; +}; + +/** OnChainZeroKnowledgeProofResponse represents structure of onchain zero knowledge proof response */ +export type OnChainZeroKnowledgeProofResponse = ZeroKnowledgeProofResponse & { + txHash: string; +}; + /** ContractInvokeTransactionData represents structure of contract invoke transaction data object */ export type ContractInvokeTransactionData = { contract_address: string; diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 3bb8bcbd..91ec1719 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -40,6 +40,7 @@ import { ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse, PROTOCOL_CONSTANTS, + VerifiablePresentation, JsonDocumentObject } from '../iden3comm'; import { cacheLoader } from '../schema-processor'; @@ -220,7 +221,7 @@ export class ProofService implements IProofService { const verifyContext: VerifyContext = { pubSignals: proofResp.pub_signals, query: opts.query, - verifiablePresentation: proofResp.vp as JSON, + verifiablePresentation: proofResp.vp, sender: opts.sender, challenge: BigInt(proofResp.id), opts: opts.opts, @@ -340,7 +341,7 @@ export class ProofService implements IProofService { ); const sdQueries = queriesMetadata.filter((q) => q.operator === Operators.SD); - let vp: object | undefined; + let vp: VerifiablePresentation | undefined; if (sdQueries.length) { vp = createVerifiablePresentation( context, diff --git a/src/proof/verifiers/pub-signals-verifier.ts b/src/proof/verifiers/pub-signals-verifier.ts index e8565f6e..29b932f6 100644 --- a/src/proof/verifiers/pub-signals-verifier.ts +++ b/src/proof/verifiers/pub-signals-verifier.ts @@ -32,7 +32,12 @@ import { parseQueriesMetadata, QueryMetadata } from '../common'; import { Operators } from '../../circuits'; import { calculateQueryHashV3 } from './query-hash'; import { JsonLd } from 'jsonld/jsonld-spec'; -import { PROTOCOL_CONSTANTS, JSONObject, JsonDocumentObject } from '../../iden3comm'; +import { + PROTOCOL_CONSTANTS, + JSONObject, + VerifiablePresentation, + JsonDocumentObject +} from '../../iden3comm'; /** * Verify Context - params for pub signal verification @@ -41,7 +46,7 @@ import { PROTOCOL_CONSTANTS, JSONObject, JsonDocumentObject } from '../../iden3c export type VerifyContext = { pubSignals: string[]; query: ProofQuery; - verifiablePresentation?: JSON; + verifiablePresentation?: VerifiablePresentation; sender: string; challenge: bigint; opts?: VerifyOpts; @@ -521,7 +526,7 @@ export class PubSignalsVerifier { query: ProofQuery, outs: ClaimOutputs, opts: VerifyOpts | undefined, - verifiablePresentation: JSON | undefined + verifiablePresentation: VerifiablePresentation | undefined ) { if (!query.type) { throw new Error(`proof query type is undefined`); diff --git a/src/proof/verifiers/query.ts b/src/proof/verifiers/query.ts index 5afb9cb9..4fb2751f 100644 --- a/src/proof/verifiers/query.ts +++ b/src/proof/verifiers/query.ts @@ -8,6 +8,7 @@ import { calculateCoreSchemaHash, ProofQuery, VerifiableConstants } from '../../ import { QueryMetadata } from '../common'; import { circuitValidator } from '../provers'; import { JsonLd } from 'jsonld/jsonld-spec'; +import { VerifiablePresentation } from '../../iden3comm'; /** * Options to verify state @@ -189,7 +190,7 @@ export async function validateOperators(cq: QueryMetadata, outputs: ClaimOutputs export async function validateDisclosureV2Circuit( cq: QueryMetadata, outputs: ClaimOutputs, - verifiablePresentation?: JSON, + verifiablePresentation?: VerifiablePresentation, ldLoader?: DocumentLoader ) { const bi = await fieldValueFromVerifiablePresentation( @@ -215,7 +216,7 @@ export async function validateDisclosureV2Circuit( export async function validateDisclosureNativeSDSupport( cq: QueryMetadata, outputs: ClaimOutputs, - verifiablePresentation?: JSON, + verifiablePresentation?: VerifiablePresentation, ldLoader?: DocumentLoader ) { const bi = await fieldValueFromVerifiablePresentation( @@ -250,7 +251,7 @@ export async function validateEmptyCredentialSubjectNoopNativeSupport(outputs: C export const fieldValueFromVerifiablePresentation = async ( fieldName: string, - verifiablePresentation?: JSON, + verifiablePresentation?: VerifiablePresentation, ldLoader?: DocumentLoader ): Promise => { if (!verifiablePresentation) { diff --git a/src/storage/blockchain/abi/ZkpVerifier.json b/src/storage/blockchain/abi/ZkpVerifier.json index b8559527..d16bee80 100644 --- a/src/storage/blockchain/abi/ZkpVerifier.json +++ b/src/storage/blockchain/abi/ZkpVerifier.json @@ -1,268 +1,789 @@ [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [], - "name": "REQUESTS_RETURN_LIMIT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "requestId", - "type": "uint64" - } - ], - "name": "getZKPRequest", - "outputs": [ - { - "components": [ - { - "internalType": "string", - "name": "metadata", - "type": "string" - }, - { - "internalType": "contract ICircuitValidator", - "name": "validator", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IZKPVerifier.ZKPRequest", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "startIndex", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "length", - "type": "uint256" - } - ], - "name": "getZKPRequests", - "outputs": [ - { - "components": [ - { - "internalType": "string", - "name": "metadata", - "type": "string" - }, - { - "internalType": "contract ICircuitValidator", - "name": "validator", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IZKPVerifier.ZKPRequest[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getZKPRequestsCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "name": "proofs", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "requestId", - "type": "uint64" - } - ], - "name": "requestIdExists", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "requestId", - "type": "uint64" - }, - { - "components": [ - { - "internalType": "string", - "name": "metadata", - "type": "string" - }, - { - "internalType": "contract ICircuitValidator", - "name": "validator", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct IZKPVerifier.ZKPRequest", - "name": "request", - "type": "tuple" - } - ], - "name": "setZKPRequest", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "requestId", - "type": "uint64" - }, - { - "internalType": "uint256[]", - "name": "inputs", - "type": "uint256[]" - }, - { - "internalType": "uint256[2]", - "name": "a", - "type": "uint256[2]" - }, - { - "internalType": "uint256[2][2]", - "name": "b", - "type": "uint256[2][2]" - }, - { - "internalType": "uint256[2]", - "name": "c", - "type": "uint256[2]" - } - ], - "name": "submitZKPResponse", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + }, + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "linkID", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "requestIdToCompare", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "linkIdToCompare", + "type": "uint256" + } + ], + "name": "LinkedProofError", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "indexed": true, + "internalType": "address", + "name": "requestOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "validator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "ZKPRequestSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "name": "ZKPResponseSubmitted", + "type": "event" + }, + { + "inputs": [], + "name": "REQUESTS_RETURN_LIMIT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + } + ], + "name": "addValidatorToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "disableZKPRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "enableZKPRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "getProofStatus", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "isVerified", + "type": "bool" + }, + { + "internalType": "string", + "name": "validatorVersion", + "type": "string" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "blockTimestamp", + "type": "uint256" + } + ], + "internalType": "struct IZKPVerifier.ProofStatus", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "getProofStorageField", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "getRequestOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "getZKPRequest", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IZKPVerifier.ZKPRequest", + "name": "zkpRequest", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "startIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "getZKPRequests", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IZKPVerifier.ZKPRequest[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getZKPRequestsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IStateCrossChain", + "name": "stateCrossChain", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "isProofVerified", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + } + ], + "name": "isWhitelistedValidator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "isZKPRequestEnabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + } + ], + "name": "removeValidatorFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + } + ], + "name": "requestIdExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "address", + "name": "requestOwner", + "type": "address" + } + ], + "name": "setRequestOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "string", + "name": "metadata", + "type": "string" + }, + { + "internalType": "contract ICircuitValidator", + "name": "validator", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct IZKPVerifier.ZKPRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "setZKPRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "uint256[]", + "name": "inputs", + "type": "uint256[]" + }, + { + "internalType": "uint256[2]", + "name": "a", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2][2]", + "name": "b", + "type": "uint256[2][2]" + }, + { + "internalType": "uint256[2]", + "name": "c", + "type": "uint256[2]" + } + ], + "name": "submitZKPResponse", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "zkProof", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct ZKPResponse[]", + "name": "responses", + "type": "tuple[]" + }, + { + "internalType": "bytes", + "name": "crossChainProof", + "type": "bytes" + } + ], + "name": "submitZKPResponseV2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint64[]", + "name": "requestIds", + "type": "uint64[]" + } + ], + "name": "verifyLinkedProofs", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "requestId", + "type": "uint64" + }, + { + "internalType": "uint256[]", + "name": "inputs", + "type": "uint256[]" + }, + { + "internalType": "uint256[2]", + "name": "a", + "type": "uint256[2]" + }, + { + "internalType": "uint256[2][2]", + "name": "b", + "type": "uint256[2][2]" + }, + { + "internalType": "uint256[2]", + "name": "c", + "type": "uint256[2]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "verifyZKPResponse", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "uint256", + "name": "inputValue", + "type": "uint256" + } + ], + "internalType": "struct ICircuitValidator.KeyToInputValue[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/src/storage/blockchain/onchain-zkp-verifier.ts b/src/storage/blockchain/onchain-zkp-verifier.ts index 0449c28b..a873d646 100644 --- a/src/storage/blockchain/onchain-zkp-verifier.ts +++ b/src/storage/blockchain/onchain-zkp-verifier.ts @@ -1,9 +1,47 @@ -import { JsonRpcProvider, Signer, Contract, TransactionRequest } from 'ethers'; +import { JsonRpcProvider, Signer, Contract, TransactionRequest, ethers } from 'ethers'; import { EthConnectionConfig } from './state'; import { IOnChainZKPVerifier } from '../interfaces/onchain-zkp-verifier'; -import { ContractInvokeTransactionData, ZeroKnowledgeProofResponse } from '../../iden3comm'; +import { + ContractInvokeTransactionData, + JsonDocumentObjectValue, + ZeroKnowledgeProofResponse +} from '../../iden3comm'; import abi from './abi/ZkpVerifier.json'; import { TransactionService } from '../../blockchain'; +import { DID } from '@iden3/js-iden3-core'; +import { + AtomicQueryMTPV2OnChainPubSignals, + AtomicQuerySigV2OnChainPubSignals, + AtomicQueryV3OnChainPubSignals, + CircuitId, + StatesInfo +} from '../../circuits'; +import { byteEncoder, DIDDocumentSignature, resolveDidDocument } from '../../utils'; +import { GlobalStateUpdate, IdentityStateUpdate } from '../entities/state'; +import { poseidon } from '@iden3/js-crypto'; +import { Hash } from '@iden3/js-merkletree'; + +const maxGasLimit = 10000000n; + +/** + * Supported function signature for SubmitZKPResponse + */ +export enum FunctionSignatures { + /** + * solidity identifier for function signature: + * function submitZKPResponse(uint64 requestId, uint256[] calldata inputs, + * uint256[2] calldata a, uint256[2][2] calldata b, uint256[2] calldata c) public + */ + SumbitZKPResponseV1 = 'b68967e2', + //function submitZKPResponseV2(tuple[](uint64 requestId,bytes zkProof,bytes data),bytes crossChainProof) + SumbitZKPResponseV2 = 'ade09fcd' +} +/** + * OnChainZKPVerifierOptions represents OnChainZKPVerifier options + */ +export type OnChainZKPVerifierOptions = { + didResolverUrl?: string; +}; /** * OnChainZKPVerifier is a class that allows to interact with the OnChainZKPVerifier contract @@ -14,11 +52,18 @@ import { TransactionService } from '../../blockchain'; */ export class OnChainZKPVerifier implements IOnChainZKPVerifier { /** - * solidity identifier for function signature: - * function submitZKPResponse(uint64 requestId, uint256[] calldata inputs, - * uint256[2] calldata a, uint256[2][2] calldata b, uint256[2] calldata c) public + * supported circuits */ - private readonly _supportedMethodId = 'b68967e2'; + private readonly _supportedCircuits = [ + CircuitId.AtomicQueryMTPV2OnChain, + CircuitId.AtomicQuerySigV2OnChain, + CircuitId.AtomicQueryV3OnChain + ]; + + /** + * abi coder to encode/decode structures to solidity bytes + */ + private readonly _abiCoder = new ethers.AbiCoder(); /** * * Creates an instance of OnChainZKPVerifier. @@ -26,15 +71,42 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { * @param {EthConnectionConfig[]} - array of ETH configs */ - constructor(private readonly _configs: EthConnectionConfig[]) {} + constructor( + private readonly _configs: EthConnectionConfig[], + private readonly _opts?: OnChainZKPVerifierOptions + ) {} /** - * Submit ZKP Responses to OnChainZKPVerifier contract. - * @beta - * @param {Signer} ethSigner - tx signer - * @param {txData} ContractInvokeTransactionData - transaction data - * @param {ZeroKnowledgeProofResponse[]} zkProofResponses - zkProofResponses - * @returns {Promise>} - map of transaction hash - ZeroKnowledgeProofResponse + * {@inheritDoc IOnChainZKPVerifier.prepareTxArgsSubmitV1} + */ + public async prepareTxArgsSubmitV1( + txData: ContractInvokeTransactionData, + zkProofResponse: ZeroKnowledgeProofResponse + ): Promise { + if (txData.method_id.replace('0x', '') !== FunctionSignatures.SumbitZKPResponseV1) { + throw new Error( + `prepareTxArgsSubmitV1 function doesn't implement requested method id. Only '0x${FunctionSignatures.SumbitZKPResponseV1}' is supported.` + ); + } + const requestID = zkProofResponse.id; + const inputs = zkProofResponse.pub_signals; + + const payload = [ + requestID, + inputs, + zkProofResponse.proof.pi_a.slice(0, 2), + [ + [zkProofResponse.proof.pi_b[0][1], zkProofResponse.proof.pi_b[0][0]], + [zkProofResponse.proof.pi_b[1][1], zkProofResponse.proof.pi_b[1][0]] + ], + zkProofResponse.proof.pi_c.slice(0, 2) + ]; + + return payload; + } + + /** + * {@inheritDoc IOnChainZKPVerifier.submitZKPResponse} */ public async submitZKPResponse( ethSigner: Signer, @@ -45,23 +117,136 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { if (!chainConfig) { throw new Error(`config for chain id ${txData.chain_id} was not found`); } - if (txData.method_id.replace('0x', '') !== this._supportedMethodId) { + if (txData.method_id.replace('0x', '') !== FunctionSignatures.SumbitZKPResponseV1) { throw new Error( - `submit doesn't implement requested method id. Only '0x${this._supportedMethodId}' is supported.` + `submitZKPResponse function doesn't implement requested method id. Only '0x${FunctionSignatures.SumbitZKPResponseV1}' is supported.` ); } const provider = new JsonRpcProvider(chainConfig.url, chainConfig.chainId); - const verifierContract = new Contract(txData.contract_address, abi, provider); ethSigner = ethSigner.connect(provider); - const contract = verifierContract.connect(ethSigner) as Contract; - const response = new Map(); + + const feeData = await provider.getFeeData(); + const maxFeePerGas = chainConfig.maxFeePerGas + ? BigInt(chainConfig.maxFeePerGas) + : feeData.maxFeePerGas; + const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas + ? BigInt(chainConfig.maxPriorityFeePerGas) + : feeData.maxPriorityFeePerGas; + + const verifierContract = new Contract(txData.contract_address, abi); + + for (const zkProofResponse of zkProofResponses) { + const txArgs = await this.prepareTxArgsSubmitV1(txData, zkProofResponse); + const payload = await verifierContract.submitZKPResponse.populateTransaction(...txArgs); + const request: TransactionRequest = { + to: txData.contract_address, + data: payload.data, + maxFeePerGas, + maxPriorityFeePerGas + }; + + let gasLimit; + try { + gasLimit = await ethSigner.estimateGas(request); + } catch (e) { + gasLimit = maxGasLimit; + } + request.gasLimit = gasLimit; + + const transactionService = new TransactionService(provider); + const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request); + response.set(txnHash, zkProofResponse); + } + + return response; + } + + /** + * {@inheritDoc IOnChainZKPVerifier.submitZKPResponseV2} + */ + public async submitZKPResponseV2( + ethSigner: Signer, + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ): Promise> { + const chainConfig = this._configs.find((i) => i.chainId == txData.chain_id); + if (!chainConfig) { + throw new Error(`config for chain id ${txData.chain_id} was not found`); + } + if (txData.method_id.replace('0x', '') !== FunctionSignatures.SumbitZKPResponseV2) { + throw new Error( + `submitZKPResponseV2 function doesn't implement requested method id. Only '0x${FunctionSignatures.SumbitZKPResponseV2}' is supported.` + ); + } + if (!this._opts?.didResolverUrl) { + throw new Error(`did resolver url required for crosschain verification`); + } + const provider = new JsonRpcProvider(chainConfig.url, chainConfig.chainId); + ethSigner = ethSigner.connect(provider); + + const txDataArgs = await this.prepareTxArgsSubmitV2(txData, zkProofResponses); + const feeData = await provider.getFeeData(); + const maxFeePerGas = chainConfig.maxFeePerGas + ? BigInt(chainConfig.maxFeePerGas) + : feeData.maxFeePerGas; + const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas + ? BigInt(chainConfig.maxPriorityFeePerGas) + : feeData.maxPriorityFeePerGas; + + const verifierContract = new Contract(txData.contract_address, abi); + const txRequestData = await verifierContract.submitZKPResponseV2.populateTransaction( + ...txDataArgs + ); + + const request: TransactionRequest = { + to: txData.contract_address, + data: txRequestData.data, + maxFeePerGas, + maxPriorityFeePerGas + }; + + let gasLimit; + try { + gasLimit = await ethSigner.estimateGas(request); + } catch (e) { + gasLimit = maxGasLimit; + } + request.gasLimit = gasLimit; + + const transactionService = new TransactionService(provider); + const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request); + return new Map().set(txnHash, zkProofResponses); + } + + public async prepareTxArgsSubmitV2( + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ): Promise { + if (txData.method_id.replace('0x', '') !== FunctionSignatures.SumbitZKPResponseV2) { + throw new Error( + `submit cross chain doesn't implement requested method id. Only '0x${FunctionSignatures.SumbitZKPResponseV2}' is supported.` + ); + } + if (!this._opts?.didResolverUrl) { + throw new Error(`did resolver url required for crosschain verification`); + } + const gistUpdateArr = []; + const stateUpdateArr = []; + const payload = []; + // Resolved gists and states to avoid duplicate requests + const gistUpdateResolutionsPending: string[] = []; + const stateUpdateResolutionsPending: string[] = []; + for (const zkProof of zkProofResponses) { const requestID = zkProof.id; const inputs = zkProof.pub_signals; - const payload = [ - requestID, + if (!this._supportedCircuits.includes(zkProof.circuitId as CircuitId)) { + throw new Error(`Circuit ${zkProof.circuitId} not supported by OnChainZKPVerifier`); + } + + const zkProofEncoded = this.packZkpProof( inputs, zkProof.proof.pi_a.slice(0, 2), [ @@ -69,32 +254,234 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { [zkProof.proof.pi_b[1][1], zkProof.proof.pi_b[1][0]] ], zkProof.proof.pi_c.slice(0, 2) - ]; + ); - const feeData = await provider.getFeeData(); - const maxFeePerGas = chainConfig.maxFeePerGas - ? BigInt(chainConfig.maxFeePerGas) - : feeData.maxFeePerGas; - const maxPriorityFeePerGas = chainConfig.maxPriorityFeePerGas - ? BigInt(chainConfig.maxPriorityFeePerGas) - : feeData.maxPriorityFeePerGas; + const stateInfo = this.getOnChainGistRootStatePubSignals( + zkProof.circuitId as + | CircuitId.AtomicQueryMTPV2OnChain + | CircuitId.AtomicQuerySigV2OnChain + | CircuitId.AtomicQueryV3OnChain, + zkProof.pub_signals + ); - const gasLimit = await contract.submitZKPResponse.estimateGas(...payload); - const txData = await contract.submitZKPResponse.populateTransaction(...payload); + const gistUpdateResolutions = []; + for (const gist of stateInfo.gists) { + const gistResolutionPending = gistUpdateResolutionsPending.find( + (g) => g == JSON.stringify(gist) + ); - const request: TransactionRequest = { - to: txData.to, - data: txData.data, - gasLimit, - maxFeePerGas, - maxPriorityFeePerGas - }; + if (gistResolutionPending) { + continue; + } + gistUpdateResolutionsPending.push(JSON.stringify(gist)); - const transactionService = new TransactionService(provider); - const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request); - response.set(txnHash, zkProof); + gistUpdateResolutions.push( + this.resolveDidDocumentEip712MessageAndSignature( + DID.parseFromId(gist.id), + this._opts.didResolverUrl, + { gist: gist.root } + ) + ); + } + + const stateUpdateResolutions = []; + for (const state of stateInfo.states) { + const stateResolutionPending = stateUpdateResolutionsPending.find( + (s) => s == JSON.stringify(state) + ); + + if (stateResolutionPending) { + continue; + } + stateUpdateResolutionsPending.push(JSON.stringify(state)); + + stateUpdateResolutions.push( + this.resolveDidDocumentEip712MessageAndSignature( + DID.parseFromId(state.id), + this._opts.didResolverUrl, + { + state: state.state + } + ) + ); + } + + if (gistUpdateResolutions.length > 0) { + gistUpdateArr.push(...((await Promise.all(gistUpdateResolutions)) as GlobalStateUpdate[])); + } + if (stateUpdateResolutions.length > 0) { + stateUpdateArr.push( + ...((await Promise.all(stateUpdateResolutions)) as IdentityStateUpdate[]) + ); + } + + const metadataArr: { key: string; value: Uint8Array }[] = []; + if (zkProof.vp) { + for (const key in zkProof.vp.verifiableCredential.credentialSubject) { + if (key === '@type') { + continue; + } + const metadataValue = poseidon.hashBytes( + byteEncoder.encode( + JSON.stringify(zkProof.vp.verifiableCredential.credentialSubject[key]) + ) + ); + const bytesValue = byteEncoder.encode(metadataValue.toString()); + metadataArr.push({ + key, + value: bytesValue + }); + } + } + + const metadata = this.packMetadatas(metadataArr); + payload.push({ + requestId: requestID, + zkProof: zkProofEncoded, + data: metadata + }); } - return response; + const crossChainProofs = this.packCrossChainProofs(gistUpdateArr, stateUpdateArr); + return [payload, crossChainProofs]; + } + + private packZkpProof(inputs: string[], a: string[], b: string[][], c: string[]): string { + return this._abiCoder.encode( + ['uint256[] inputs', 'uint256[2]', 'uint256[2][2]', 'uint256[2]'], + [inputs, a, b, c] + ); + } + + private packCrossChainProofs( + gistUpdateArr: GlobalStateUpdate[], + stateUpdateArr: IdentityStateUpdate[] + ) { + const proofs = []; + + for (const globalStateUpdate of gistUpdateArr) { + proofs.push({ + proofType: 'globalStateProof', + proof: this.packGlobalStateMsg(globalStateUpdate) + }); + } + for (const stateUpdate of stateUpdateArr) { + proofs.push({ + proofType: 'stateProof', + proof: this.packIdentityStateMsg(stateUpdate) + }); + } + return this._abiCoder.encode( + ['tuple(' + 'string proofType,' + 'bytes proof' + ')[]'], + [proofs] + ); + } + + private packGlobalStateMsg(msg: GlobalStateUpdate): string { + return this._abiCoder.encode( + [ + 'tuple(' + + 'tuple(' + + 'uint256 timestamp,' + + 'bytes2 idType,' + + 'uint256 root,' + + 'uint256 replacedAtTimestamp' + + ') globalStateMsg,' + + 'bytes signature,' + + ')' + ], + [msg] + ); + } + + private packIdentityStateMsg(msg: IdentityStateUpdate): string { + return this._abiCoder.encode( + [ + 'tuple(' + + 'tuple(' + + 'uint256 timestamp,' + + 'uint256 id,' + + 'uint256 state,' + + 'uint256 replacedAtTimestamp' + + ') idStateMsg,' + + 'bytes signature,' + + ')' + ], + [msg] + ); + } + + private packMetadatas( + metas: { + key: string; + value: Uint8Array; + }[] + ): string { + return this._abiCoder.encode(['tuple(' + 'string key,' + 'bytes value' + ')[]'], [metas]); + } + + private getOnChainGistRootStatePubSignals( + onChainCircuitId: + | CircuitId.AtomicQueryMTPV2OnChain + | CircuitId.AtomicQuerySigV2OnChain + | CircuitId.AtomicQueryV3OnChain, + inputs: string[] + ): StatesInfo { + let atomicQueryPubSignals; + switch (onChainCircuitId) { + case CircuitId.AtomicQueryMTPV2OnChain: + atomicQueryPubSignals = new AtomicQueryMTPV2OnChainPubSignals(); + break; + case CircuitId.AtomicQuerySigV2OnChain: + atomicQueryPubSignals = new AtomicQuerySigV2OnChainPubSignals(); + break; + case CircuitId.AtomicQueryV3OnChain: + atomicQueryPubSignals = new AtomicQueryV3OnChainPubSignals(); + break; + } + const encodedInputs = byteEncoder.encode(JSON.stringify(inputs)); + atomicQueryPubSignals.pubSignalsUnmarshal(encodedInputs); + return atomicQueryPubSignals.getStatesInfo(); + } + + private async resolveDidDocumentEip712MessageAndSignature( + did: DID, + resolverUrl: string, + opts?: { + state?: Hash; + gist?: Hash; + } + ) { + const didDoc = await resolveDidDocument(did, resolverUrl, { + ...opts, + signature: DIDDocumentSignature.EthereumEip712Signature2021 + }); + if (!didDoc.didResolutionMetadata.proof?.length) { + throw new Error('No proof found in resolved DID document'); + } + const message = didDoc.didResolutionMetadata.proof[0].eip712.message; + const signature = didDoc.didResolutionMetadata.proof[0].proofValue; + const isGistRequest = opts?.gist && !opts.state; + if (isGistRequest) { + return { + globalStateMsg: { + timestamp: message.timestamp, + idType: message.idType, + root: message.root, + replacedAtTimestamp: message.replacedAtTimestamp + }, + signature + }; + } + + return { + idStateMsg: { + timestamp: message.timestamp, + id: message.id, + state: message.state, + replacedAtTimestamp: message.replacedAtTimestamp + }, + signature + }; } } diff --git a/src/storage/entities/state.ts b/src/storage/entities/state.ts index 0b743151..a6661d25 100644 --- a/src/storage/entities/state.ts +++ b/src/storage/entities/state.ts @@ -45,3 +45,51 @@ export interface RootInfo { createdAtBlock: bigint; replacedAtBlock: bigint; } + +/** + * identity state message + * + * @public + * @interface IdentityStateMsg + */ +export interface IdentityStateMsg { + timestamp: number; + id: bigint; + state: bigint; + replacedAtTimestamp: number; +} + +/** + * global state message + * + * @public + * @interface GlobalStateMsg + */ +export interface GlobalStateMsg { + timestamp: number; + idType: string; + root: bigint; + replacedAtTimestamp: number; +} + +/** + * identity state update + * + * @public + * @interface IdentityStateUpdate + */ +export interface IdentityStateUpdate { + idStateMsg: IdentityStateMsg; + signature: string; +} + +/** + * global state update + * + * @public + * @interface GlobalStateUpdate + */ +export interface GlobalStateUpdate { + globalStateMsg: GlobalStateMsg; + signature: string; +} diff --git a/src/storage/interfaces/onchain-zkp-verifier.ts b/src/storage/interfaces/onchain-zkp-verifier.ts index dba233c2..22769649 100644 --- a/src/storage/interfaces/onchain-zkp-verifier.ts +++ b/src/storage/interfaces/onchain-zkp-verifier.ts @@ -1,5 +1,9 @@ import { Signer } from 'ethers'; -import { ContractInvokeTransactionData, ZeroKnowledgeProofResponse } from '../../iden3comm'; +import { + ContractInvokeTransactionData, + JsonDocumentObjectValue, + ZeroKnowledgeProofResponse +} from '../../iden3comm'; /** * Interface that defines methods for ZKP verifier @@ -21,4 +25,38 @@ export interface IOnChainZKPVerifier { txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] ): Promise>; + + /** + * Submit ZKP Response V2 to OnChainZKPVerifier contract. + * @beta + * @param {Signer} ethSigner - tx signer + * @param {txData} ContractInvokeTransactionData - transaction data + * @param {ZeroKnowledgeProofResponse[]} zkProofResponses - zkProofResponses + * @returns {Promise>} - map of transaction hash - ZeroKnowledgeProofResponse[] + */ + submitZKPResponseV2( + ethSigner: Signer, + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ): Promise>; + + /** + * Returns tx args for the ZKP verifier contract submission (singe tx args for each response). + * @param txData + * @param zkProofResponse + */ + prepareTxArgsSubmitV1( + txData: ContractInvokeTransactionData, + zkProofResponse: ZeroKnowledgeProofResponse + ): Promise; + + /** + * Returns args for the ZKP verifier contract submission V2 (single tx args for an array of responses). + * @param txData + * @param zkProofResponses + */ + prepareTxArgsSubmitV2( + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ): Promise; } diff --git a/src/utils/did-helper.ts b/src/utils/did-helper.ts index 2bb3eb9f..058094ee 100644 --- a/src/utils/did-helper.ts +++ b/src/utils/did-helper.ts @@ -1,10 +1,17 @@ import { Hex } from '@iden3/js-crypto'; import { Id, buildDIDType, genesisFromEthAddress, DID } from '@iden3/js-iden3-core'; import { Hash } from '@iden3/js-merkletree'; -import { DIDResolutionResult, VerificationMethod } from 'did-resolver'; +import { DIDResolutionResult, VerificationMethod, DIDResolutionMetadata } from 'did-resolver'; import { keccak256 } from 'js-sha3'; import { hexToBytes } from './encoding'; +/** + * Supported DID Document Signatures + */ +export enum DIDDocumentSignature { + EthereumEip712Signature2021 = 'EthereumEip712Signature2021' +} + /** * Checks if state is genesis state * @@ -86,6 +93,53 @@ export const resolveDIDDocumentAuth = async ( ); }; +function emptyStateDID(did: DID) { + const id = DID.idFromDID(did); + const didType = buildDIDType( + DID.methodFromId(id), + DID.blockchainFromId(id), + DID.networkIdFromId(id) + ); + const identifier = Id.idGenesisFromIdenState(didType, 0n); + const emptyDID = DID.parseFromId(identifier); + + return emptyDID; +} + +export const resolveDidDocument = async ( + did: DID, + resolverUrl: string, + opts?: { + state?: Hash; + gist?: Hash; + signature?: DIDDocumentSignature; + } +): Promise => { + let didString = did.string().replace(/:/g, '%3A'); + // for gist resolve we have to `hide` user did (look into resolver implementation) + const isGistRequest = opts?.gist && !opts.state; + if (isGistRequest) { + didString = emptyStateDID(did).string(); + } + let url = `${resolverUrl}/1.0/identifiers/${didString}`; + + if (opts?.signature) { + url += `?signature=${opts.signature}`; + } + + if (opts?.state) { + url += `${url.includes('?') ? '&' : '?'}state=${opts.state.hex()}`; + } + + if (opts?.gist) { + url += `${url.includes('?') ? '&' : '?'}gist=${opts.gist.hex()}`; + } + const resp = await fetch(url); + const data = await resp.json(); + + return data; +}; + export const buildDIDFromEthPubKey = (didType: Uint8Array, pubKeyEth: string): DID => { // Use Keccak-256 hash function to get public key hash const hashOfPublicKey = keccak256(hexToBytes(pubKeyEth)); diff --git a/src/verifiable/presentation.ts b/src/verifiable/presentation.ts index 6496d9e9..b207b0ce 100644 --- a/src/verifiable/presentation.ts +++ b/src/verifiable/presentation.ts @@ -2,7 +2,7 @@ import { VerifiableConstants } from './constants'; import { Options, Path } from '@iden3/js-jsonld-merklization'; import { W3CCredential } from './credential'; import { QueryMetadata } from '../proof'; -import { JsonDocumentObject } from '../iden3comm'; +import { VerifiablePresentation, JsonDocumentObject } from '../iden3comm'; export const stringByPath = (obj: { [key: string]: unknown }, path: string): string => { const parts = path.split('.'); @@ -50,7 +50,7 @@ export const createVerifiablePresentation = ( tp: string, credential: W3CCredential, queries: QueryMetadata[] -): object => { +): VerifiablePresentation => { const baseContext = [VerifiableConstants.JSONLD_SCHEMA.W3C_CREDENTIAL_2018]; const ldContext = baseContext[0] === context ? baseContext : [...baseContext, context]; diff --git a/tests/handlers/contract-request.test.ts b/tests/handlers/contract-request.test.ts index 9fbe6d29..a45d4ca0 100644 --- a/tests/handlers/contract-request.test.ts +++ b/tests/handlers/contract-request.test.ts @@ -12,8 +12,6 @@ import { defaultEthConnectionConfig, hexToBytes } from '../../src'; -import { BjjProvider, KMS, KmsKeyType } from '../../src/kms'; -import { InMemoryPrivateKeyStore } from '../../src/kms/store'; import { IDataStorage, IStateStorage, IOnChainZKPVerifier } from '../../src/storage/interfaces'; import { InMemoryDataSource, InMemoryMerkleTreeStorage } from '../../src/storage/memory'; import { CredentialRequest, CredentialWallet } from '../../src/credentials'; @@ -55,7 +53,7 @@ import { expect } from 'chai'; import { CredentialStatusResolverRegistry } from '../../src/credentials'; import { RHSResolver } from '../../src/credentials'; import { ethers, JsonRpcProvider, Signer } from 'ethers'; -import { RPC_URL } from '../helpers'; +import { registerKeyProvidersInMemoryKMS, RPC_URL } from '../helpers'; describe('contract-request', () => { let idWallet: IdentityWallet; @@ -122,6 +120,24 @@ describe('contract-request', () => { const response = new Map(); response.set('txhash1', zkProofResponses[0]); return response; + }, + + submitZKPResponseV2: async ( + signer: Signer, + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ) => { + const response = new Map(); + response.set('txhash1', zkProofResponses); + return response; + }, + + prepareTxArgsSubmitV1: async () => { + return []; + }, + + prepareTxArgsSubmitV2: async () => { + return []; } }; @@ -170,10 +186,7 @@ describe('contract-request', () => { }; beforeEach(async () => { - const memoryKeyStore = new InMemoryPrivateKeyStore(); - const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore); - const kms = new KMS(); - kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider); + const kms = registerKeyProvidersInMemoryKMS(); dataStorage = { credential: new CredentialStorage(new InMemoryDataSource()), identity: new IdentityStorage( @@ -270,7 +283,7 @@ describe('contract-request', () => { const transactionData: ContractInvokeTransactionData = { contract_address: '0x134b1be34911e39a8397ec6289782989729807a4', - method_id: '123', + method_id: 'b68967e2', chain_id: 80001 }; @@ -302,7 +315,7 @@ describe('contract-request', () => { options ); - expect(ciResponse.has('txhash1')).to.be.true; + expect((ciResponse as Map).has('txhash1')).to.be.true; }); // SKIPPED : integration test @@ -312,10 +325,7 @@ describe('contract-request', () => { stateEthConfig.contractAddress = '0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124'; stateEthConfig.chainId = 80002; - const memoryKeyStore = new InMemoryPrivateKeyStore(); - const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore); - const kms = new KMS(); - kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider); + const kms = registerKeyProvidersInMemoryKMS(); dataStorage = { credential: new CredentialStorage(new InMemoryDataSource()), identity: new IdentityStorage( @@ -453,9 +463,12 @@ describe('contract-request', () => { ); expect(ciResponse).not.be.undefined; - expect((ciResponse.values().next().value as ZeroKnowledgeProofResponse).id).to.be.equal( - proofReq.id - ); + expect( + ( + (ciResponse as Map).values().next() + .value as ZeroKnowledgeProofResponse + ).id + ).to.be.equal(proofReq.id); }); // V3 integration test it.skip('contract request flow V3 - integration test', async () => { @@ -463,10 +476,7 @@ describe('contract-request', () => { stateEthConfig.url = rpcUrl; stateEthConfig.contractAddress = '0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124'; - const memoryKeyStore = new InMemoryPrivateKeyStore(); - const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore); - const kms = new KMS(); - kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider); + const kms = registerKeyProvidersInMemoryKMS(); dataStorage = { credential: new CredentialStorage(new InMemoryDataSource()), identity: new IdentityStorage( @@ -627,8 +637,361 @@ describe('contract-request', () => { ); expect(ciResponse).not.be.undefined; - expect((ciResponse.values().next().value as ZeroKnowledgeProofResponse).id).to.be.equal( - proofReqs[0].id + expect( + ( + (ciResponse as Map).values().next() + .value as ZeroKnowledgeProofResponse + ).id + ).to.be.equal(proofReqs[0].id); + }); + + // cross chain integration test + it.skip('cross chain contract request flow - integration test', async () => { + const privadoTestRpcUrl = '<>'; // issuer RPC URL - privato test + const privadoTestStateContract = '0x975556428F077dB5877Ea2474D783D6C69233742'; + const amoyVerifierRpcUrl = '<>'; // verifier RPC URL - amoy + const erc20Verifier = '0x74030e4c5d53ef381A889C01f0bBd3B8336F4a4a'; + + const issuerStateEthConfig = defaultEthConnectionConfig; + issuerStateEthConfig.url = privadoTestRpcUrl; + issuerStateEthConfig.contractAddress = privadoTestStateContract; // privado test state contract + + const kms = registerKeyProvidersInMemoryKMS(); + dataStorage = { + credential: new CredentialStorage(new InMemoryDataSource()), + identity: new IdentityStorage( + new InMemoryDataSource(), + new InMemoryDataSource() + ), + mt: new InMemoryMerkleTreeStorage(40), + states: new EthStateStorage(issuerStateEthConfig) + }; + const circuitStorage = new FSCircuitStorage({ + dirname: path.join(__dirname, '../proofs/testdata') + }); + + const resolvers = new CredentialStatusResolverRegistry(); + resolvers.register( + CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + new RHSResolver(dataStorage.states) + ); + credWallet = new CredentialWallet(dataStorage, resolvers); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); + + proofService = new ProofService(idWallet, credWallet, circuitStorage, dataStorage.states, { + ipfsNodeURL + }); + packageMgr = await getPackageMgr( + await circuitStorage.loadCircuitData(CircuitId.AuthV2), + proofService.generateAuthV2Inputs.bind(proofService), + proofService.verifyState.bind(proofService) + ); + + const { did: userDID, credential: cred } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Privado, + networkId: NetworkId.Main, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + + expect(cred).not.to.be.undefined; + + const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Privado, + networkId: NetworkId.Test, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + expect(issuerAuthCredential).not.to.be.undefined; + + const claimReq: CredentialRequest = { + credentialSchema: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v3.json', + type: 'KYCAgeCredential', + credentialSubject: { + id: userDID.string(), + birthday: 19960424, + documentType: 99 + }, + expiration: 2793526400, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }; + const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); + + await credWallet.save(issuerCred); + + const proofReqs: ZeroKnowledgeProofRequest[] = [ + { + id: 4, // 2 - mtp, 4 - sig + circuitId: CircuitId.AtomicQuerySigV2OnChain, + 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: { + birthday: { + $lt: 20020101 + } + } + } + } + ]; + + const conf = defaultEthConnectionConfig; + conf.contractAddress = erc20Verifier; + conf.url = amoyVerifierRpcUrl; + conf.chainId = 80002; // amoy chain id + + const zkpVerifier = new OnChainZKPVerifier([conf], { + didResolverUrl: 'https://resolver-dev.privado.id' + }); + contractRequestHandler = new ContractRequestHandler(packageMgr, proofService, zkpVerifier); + + const transactionData: ContractInvokeTransactionData = { + contract_address: erc20Verifier, + method_id: 'fd41d8d4', + chain_id: conf.chainId + }; + + const ciRequestBody: ContractInvokeRequestBody = { + reason: 'reason', + transaction_data: transactionData, + scope: [...proofReqs] + }; + + const id = uuid.v4(); + const ciRequest: ContractInvokeRequest = { + id, + typ: MediaType.PlainMessage, + type: PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE, + thid: id, + body: ciRequestBody + }; + + const ethSigner = new ethers.Wallet(walletKey); + + const challenge = BytesHelper.bytesToInt(hexToBytes(ethSigner.address)); + + const options: ContractInvokeHandlerOptions = { + ethSigner, + challenge + }; + const msgBytes = byteEncoder.encode(JSON.stringify(ciRequest)); + const ciResponse = await contractRequestHandler.handleContractInvokeRequest( + userDID, + msgBytes, + options + ); + + expect(ciResponse).not.be.undefined; + expect( + ( + (ciResponse as Map).values().next() + .value as ZeroKnowledgeProofResponse + ).id + ).to.be.equal(proofReqs[0].id); + }); + + it.skip('contract request flow V3 sig `email-verified` transak req - integration test', async () => { + const privadoTestRpcUrl = '<>'; // issuer RPC URL - privato test + const privadoMainRpcUrl = '<>'; + const privadoTestStateContract = '0x975556428F077dB5877Ea2474D783D6C69233742'; + const amoyVerifierRpcUrl = '<>'; // verifier RPC URL - amoy + const verifierAddress = '0xE31725a735dd00eB0cc8aaf6b6eAB898f1BA9A69'; + const amoyStateAddress = '0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124'; + + const issuerStateEthConfig = { + ...defaultEthConnectionConfig, + url: privadoTestRpcUrl, + contractAddress: privadoTestStateContract, + chainId: 21001 + }; + + const userStateEthConfig = { + ...defaultEthConnectionConfig, + url: privadoMainRpcUrl, + contractAddress: privadoTestStateContract, + chainId: 21000 + }; + + const amoyStateEthConfig = { + ...defaultEthConnectionConfig, + url: amoyVerifierRpcUrl, + contractAddress: amoyStateAddress, + chainId: 80002 + }; + + const kms = registerKeyProvidersInMemoryKMS(); + dataStorage = { + credential: new CredentialStorage(new InMemoryDataSource()), + identity: new IdentityStorage( + new InMemoryDataSource(), + new InMemoryDataSource() + ), + mt: new InMemoryMerkleTreeStorage(40), + states: new EthStateStorage([issuerStateEthConfig, userStateEthConfig, amoyStateEthConfig]) + }; + const circuitStorage = new FSCircuitStorage({ + dirname: path.join(__dirname, '../proofs/testdata') + }); + + const resolvers = new CredentialStatusResolverRegistry(); + resolvers.register( + CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + new RHSResolver(dataStorage.states) ); + credWallet = new CredentialWallet(dataStorage, resolvers); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); + + proofService = new ProofService(idWallet, credWallet, circuitStorage, dataStorage.states, { + ipfsGatewayURL: 'https://ipfs.io' + }); + packageMgr = await getPackageMgr( + await circuitStorage.loadCircuitData(CircuitId.AuthV2), + proofService.generateAuthV2Inputs.bind(proofService), + proofService.verifyState.bind(proofService) + ); + + const { did: userDID, credential: cred } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Privado, + networkId: NetworkId.Main, + seed: seedPhrase, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + + expect(cred).not.to.be.undefined; + + const { did: issuerDID, credential: issuerAuthCredential } = await idWallet.createIdentity({ + method: DidMethod.Iden3, + blockchain: Blockchain.Privado, + networkId: NetworkId.Test, + seed: seedPhraseIssuer, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }); + expect(issuerAuthCredential).not.to.be.undefined; + + const profileDID = await idWallet.createProfile(userDID, 777, issuerDID.string()); + + const claimReq: CredentialRequest = { + credentialSchema: 'ipfs://QmYgooZFeXYi1QQm6iUpiEteMJ822pUSuxonXUpqNgFVnQ', + type: 'IndividualKYC', + credentialSubject: { + id: profileDID.string(), + 'full-name': 'full-name', + country: 'USA', + state: 'Florida', + city: 'homeland', + street: 'groove', + 'zip-code': '123', + phone: '333', + email: 'me-eme-e@gmail.com', + 'email-verified': true + }, + expiration: 2793526400, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: rhsUrl + } + }; + const issuerCred = await idWallet.issueCredential(issuerDID, claimReq, { + ipfsGatewayURL: 'https://ipfs.io' + }); + + await credWallet.save(issuerCred); + + const proofReqs: ZeroKnowledgeProofRequest[] = [ + { + id: 1, + circuitId: CircuitId.AtomicQueryV3OnChain, + optional: false, + query: { + allowedIssuers: ['*'], + type: claimReq.type, + proofType: ProofType.BJJSignature, + context: 'ipfs://Qmdhuf9fhqzweDa1TgoajDEj7Te7p28eeeZVfiioAjUC15', + credentialSubject: { + 'email-verified': { + $eq: true + } + } + }, + params: { + nullifierSessionId: '8372131' + } + } + ]; + + const zkpVerifier = new OnChainZKPVerifier([amoyStateEthConfig], { + didResolverUrl: 'https://resolver-dev.privado.id' + }); + contractRequestHandler = new ContractRequestHandler(packageMgr, proofService, zkpVerifier); + + const transactionData: ContractInvokeTransactionData = { + contract_address: verifierAddress, + method_id: 'fd41d8d4', + chain_id: amoyStateEthConfig.chainId + }; + + const verifierDid = 'did:iden3:polygon:amoy:x6x5sor7zpy1YGS4yjcmnzQSC7FZC7q7DPgNMT79q'; + + const ciRequestBody: ContractInvokeRequestBody = { + reason: 'reason', + transaction_data: transactionData, + scope: proofReqs + }; + + const id = uuid.v4(); + const ciRequest: ContractInvokeRequest = { + id, + typ: MediaType.PlainMessage, + type: PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE, + thid: id, + body: ciRequestBody, + from: verifierDid + }; + + console.log(JSON.stringify(ciRequest)); + const ethSigner = new ethers.Wallet(walletKey); + + const challenge = BytesHelper.bytesToInt(hexToBytes(ethSigner.address)); + + const options: ContractInvokeHandlerOptions = { + ethSigner, + challenge + }; + const msgBytes = byteEncoder.encode(JSON.stringify(ciRequest)); + const ciResponse = await contractRequestHandler.handleContractInvokeRequest( + userDID, + msgBytes, + options + ); + + expect(ciResponse).not.be.undefined; + expect( + ( + (ciResponse as Map).values().next() + .value as ZeroKnowledgeProofResponse + ).id + ).to.be.equal(proofReqs[0].id); }); });