From 755b7f74343dbd4d20763a45ca476145d57bfde5 Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Mon, 2 Sep 2024 18:08:22 +0300 Subject: [PATCH] add CONTRACT_INVOKE_RESPONSE_MESSAGE_TYPE --- src/iden3comm/constants.ts | 3 + src/iden3comm/handlers/contract-request.ts | 112 +++++++++++------- .../types/protocol/contract-request.ts | 20 +++- .../blockchain/onchain-zkp-verifier.ts | 41 +++---- .../interfaces/onchain-zkp-verifier.ts | 21 +++- 5 files changed, 125 insertions(+), 72 deletions(-) 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 87ac4fa0..e1dd1a62 100644 --- a/src/iden3comm/handlers/contract-request.ts +++ b/src/iden3comm/handlers/contract-request.ts @@ -1,13 +1,8 @@ import { CircuitId } from '../../circuits/models'; import { IProofService } from '../../proof/proof-service'; import { PROTOCOL_MESSAGE_TYPE } from '../constants'; -import { - BasicMessage, - IPackageManager, - JsonDocumentObjectValue, - ZeroKnowledgeProofResponse -} from '../types'; -import { ContractInvokeRequest } from '../types/protocol/contract-request'; +import { BasicMessage, IPackageManager, ZeroKnowledgeProofResponse } from '../types'; +import { ContractInvokeRequest, ContractInvokeResponse } from '../types/protocol/contract-request'; import { DID, ChainIds } from '@iden3/js-iden3-core'; import { IOnChainZKPVerifier, OnChainZKPVerifier } from '../../storage'; import { Signer } from 'ethers'; @@ -96,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); } @@ -107,7 +104,7 @@ export class ContractRequestHandler private async handleContractInvoke( message: ContractInvokeRequest, ctx: ContractMessageHandlerOptions - ): Promise | string> { + ): Promise> { if (message.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) { throw new Error('Invalid message type for contract invoke request'); } @@ -140,12 +137,18 @@ export class ContractRequestHandler message.body.transaction_data, zkpResponses ); - case OnChainZKPVerifier.SupportedMethodId: - return this._zkpVerifier.submitZKPResponse( + case OnChainZKPVerifier.SupportedMethodId: { + 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 '${OnChainZKPVerifier.SupportedMethodIdV2} and ${OnChainZKPVerifier.SupportedMethodId} are supported.'` @@ -170,63 +173,86 @@ export class ContractRequestHandler } /** - * handle contract invoker request + * creates contract invoke response * @beta - * @param {did} did - sender DID - * @param {ContractInvokeRequest} request - contract invoke request - * @param {ContractInvokeHandlerOptions} opts - handler options - * @returns {Map}` - map of transaction hash - ZeroKnowledgeProofResponse + * @param {ContractInvokeRequest} request - ContractInvokeRequest + * @param { Map} responses - map tx hash to array of ZeroKnowledgeProofResponses + * @returns `Promise` */ - async handleContractInvokeRequest( - did: DID, - request: Uint8Array, - opts: ContractInvokeHandlerOptions - ): Promise | string> { - const ciRequest = await this.parseContractInvokeRequest(request); - - return this.handleContractInvoke(ciRequest, { - senderDid: did, - ethSigner: opts.ethSigner, - challenge: opts.challenge - }); + async createContractInvokeResponse( + request: ContractInvokeRequest, + responses: Map + ): Promise { + const response: ContractInvokeResponse = { + id: request.id, + 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 responses) { + for (const zkpResponse of zkpResponses) { + response.body.scope.push({ + txHash, + ...zkpResponse + }); + } + } + return response; } /** - * prepare contract invoker request transaction data + * handle contract invoker request + * supports only 0xb68967e2 method id * @beta + * @deprecated * @param {did} did - sender DID * @param {ContractInvokeRequest} request - contract invoke request * @param {ContractInvokeHandlerOptions} opts - handler options * @returns {Map}` - map of transaction hash - ZeroKnowledgeProofResponse */ - async prepareContractInvokeRequestTxData( + async handleContractInvokeRequest( did: DID, request: Uint8Array, - opts?: { - challenge?: bigint; + opts: ContractInvokeHandlerOptions + ): Promise> { + const ciRequest = await this.parseContractInvokeRequest(request); + + if (ciRequest.body.transaction_data.method_id !== OnChainZKPVerifier.SupportedMethodId) { + throw new Error(`please use handle method to work with other method ids`); } - ): Promise> { - const message = await this.parseContractInvokeRequest(request); - if (message.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) { + if (ciRequest.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) { throw new Error('Invalid message type for contract invoke request'); } - const { chain_id } = message.body.transaction_data; + 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 = message.from ? DID.parse(message.from) : undefined; + const verifierDid = ciRequest.from ? DID.parse(ciRequest.from) : undefined; const zkpResponses = await processZeroKnowledgeProofRequests( did, - message?.body?.scope, + ciRequest?.body?.scope, verifierDid, this._proofService, - { challenge: opts?.challenge, supportedCircuits: this._supportedCircuits } + { ethSigner, challenge, supportedCircuits: this._supportedCircuits } + ); + return this._zkpVerifier.submitZKPResponse( + ethSigner, + ciRequest.body.transaction_data, + zkpResponses ); - - return this._zkpVerifier.prepareZKPResponseTxData(message.body.transaction_data, zkpResponses); } } 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/storage/blockchain/onchain-zkp-verifier.ts b/src/storage/blockchain/onchain-zkp-verifier.ts index 8a58207a..037a1246 100644 --- a/src/storage/blockchain/onchain-zkp-verifier.ts +++ b/src/storage/blockchain/onchain-zkp-verifier.ts @@ -78,21 +78,18 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { ) {} /** - * {@inheritDoc IOnChainZKPVerifier.prepareZKPResponseTxData} + * {@inheritDoc IOnChainZKPVerifier.prepareZKPResponseSubmitV1TxData} */ - public async prepareZKPResponseTxData( + public async prepareZKPResponseSubmitV1TxData( txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] - ): Promise> { - if (txData.method_id.replace('0x', '') === OnChainZKPVerifier.SupportedMethodIdV2) { - return this.prepareZKPResponseV2TxData(txData, zkProofResponses); - } + ): Promise> { if (txData.method_id.replace('0x', '') !== OnChainZKPVerifier.SupportedMethodId) { throw new Error( `submit doesn't implement requested method id. Only '0x${OnChainZKPVerifier.SupportedMethodId}' is supported.` ); } - const response = new Map(); + const response = new Map(); for (const zkProof of zkProofResponses) { const requestID = zkProof.id; const inputs = zkProof.pub_signals; @@ -108,7 +105,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { zkProof.proof.pi_c.slice(0, 2) ]; - response.set([zkProof], payload); + response.set(requestID, payload); } return response; @@ -134,7 +131,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { const provider = new JsonRpcProvider(chainConfig.url, chainConfig.chainId); ethSigner = ethSigner.connect(provider); - const txDataMap = await this.prepareZKPResponseTxData(txData, zkProofResponses); + const txDataMap = await this.prepareZKPResponseSubmitV1TxData(txData, zkProofResponses); const response = new Map(); const feeData = await provider.getFeeData(); @@ -147,7 +144,11 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { const verifierContract = new Contract(txData.contract_address, abi); - for (const [[zkProof], value] of txDataMap) { + for (const [requestId, value] of txDataMap) { + const zkProof = zkProofResponses.find((i) => i.id == requestId); + if (!zkProof) { + throw new Error(`zkProof not found for request id ${requestId}`); + } const payload = await verifierContract.submitZKPResponse.populateTransaction(...value); const request: TransactionRequest = { to: txData.contract_address, @@ -179,7 +180,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { ethSigner: Signer, txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] - ): Promise { + ): 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`); @@ -195,11 +196,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { const provider = new JsonRpcProvider(chainConfig.url, chainConfig.chainId); ethSigner = ethSigner.connect(provider); - const txRequestMap = await this.prepareZKPResponseV2TxData(txData, zkProofResponses); - const txRequestParams = txRequestMap.get(zkProofResponses); - if (!txRequestParams) { - throw new Error('no transaction args found for requests'); - } + const txDataArgs = await this.prepareZKPResponseSubmitV1TxData(txData, zkProofResponses); const feeData = await provider.getFeeData(); const maxFeePerGas = chainConfig.maxFeePerGas ? BigInt(chainConfig.maxFeePerGas) @@ -210,7 +207,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { const verifierContract = new Contract(txData.contract_address, abi); const txRequestData = await verifierContract.submitZKPResponseV2.populateTransaction( - ...txRequestParams + ...txDataArgs ); const request: TransactionRequest = { @@ -230,13 +227,13 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { const transactionService = new TransactionService(provider); const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request); - return txnHash; + return new Map().set(txnHash, zkProofResponses); } - private async prepareZKPResponseV2TxData( + public async prepareZKPResponseSingleTxData( txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] - ): Promise> { + ): Promise { if (txData.method_id.replace('0x', '') !== OnChainZKPVerifier.SupportedMethodIdV2) { throw new Error( `submit cross chain doesn't implement requested method id. Only '0x${OnChainZKPVerifier.SupportedMethodIdV2}' is supported.` @@ -355,9 +352,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier { } const crossChainProofs = this.packCrossChainProofs(gistUpdateArr, stateUpdateArr); - - const response = new Map(); - return response.set(zkProofResponses, [payload, crossChainProofs]); + return [payload, crossChainProofs]; } private packZkpProof(inputs: string[], a: string[], b: string[][], c: string[]): string { diff --git a/src/storage/interfaces/onchain-zkp-verifier.ts b/src/storage/interfaces/onchain-zkp-verifier.ts index 9f382689..c48cc3cb 100644 --- a/src/storage/interfaces/onchain-zkp-verifier.ts +++ b/src/storage/interfaces/onchain-zkp-verifier.ts @@ -32,21 +32,32 @@ export interface IOnChainZKPVerifier { * @param {Signer} ethSigner - tx signer * @param {txData} ContractInvokeTransactionData - transaction data * @param {ZeroKnowledgeProofResponse[]} zkProofResponses - zkProofResponses - * @returns {Promise>} - map of transaction hash - ZeroKnowledgeProofResponse + * @returns {Promise>} - map of transaction hash - ZeroKnowledgeProofResponse[] */ submitZKPResponseV2( ethSigner: Signer, txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] - ): Promise; + ): Promise>; + + /** + * Returns the Map of request id to transaction data args for the ZKP verifier contract submission. + * For each request id new transaction data is created. + * @param txData + * @param zkProofResponses + */ + prepareZKPResponseSubmitV1TxData( + txData: ContractInvokeTransactionData, + zkProofResponses: ZeroKnowledgeProofResponse[] + ): Promise>; /** - * Returns the Map of ZKP Responses to transaction data args for the ZKP verifier contract submission. + * Returns args for the ZKP verifier contract submission V2 (single tx). * @param txData * @param zkProofResponses */ - prepareZKPResponseTxData( + prepareZKPResponseSingleTxData( txData: ContractInvokeTransactionData, zkProofResponses: ZeroKnowledgeProofResponse[] - ): Promise>; + ): Promise; }