Skip to content

Commit

Permalink
add CONTRACT_INVOKE_RESPONSE_MESSAGE_TYPE
Browse files Browse the repository at this point in the history
  • Loading branch information
volodymyr-basiuk committed Sep 2, 2024
1 parent 82aea54 commit 755b7f7
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 72 deletions.
3 changes: 3 additions & 0 deletions src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
112 changes: 69 additions & 43 deletions src/iden3comm/handlers/contract-request.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -96,9 +91,11 @@ export class ContractRequestHandler
ctx: ContractMessageHandlerOptions
): Promise<BasicMessage | null> {
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);
}
Expand All @@ -107,7 +104,7 @@ export class ContractRequestHandler
private async handleContractInvoke(
message: ContractInvokeRequest,
ctx: ContractMessageHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse> | string> {
): Promise<Map<string, ZeroKnowledgeProofResponse[]>> {
if (message.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid message type for contract invoke request');
}
Expand Down Expand Up @@ -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<string, ZeroKnowledgeProofResponse[]>();
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.'`
Expand All @@ -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<string, ZeroKnowledgeProofResponse>}` - map of transaction hash - ZeroKnowledgeProofResponse
* @param {ContractInvokeRequest} request - ContractInvokeRequest
* @param { Map<string, ZeroKnowledgeProofResponse[]>} responses - map tx hash to array of ZeroKnowledgeProofResponses
* @returns `Promise<ContractInvokeResponse>`
*/
async handleContractInvokeRequest(
did: DID,
request: Uint8Array,
opts: ContractInvokeHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse> | 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<string, ZeroKnowledgeProofResponse[]>
): Promise<ContractInvokeResponse> {
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<string, ZeroKnowledgeProofResponse>}` - map of transaction hash - ZeroKnowledgeProofResponse
*/
async prepareContractInvokeRequestTxData(
async handleContractInvokeRequest(
did: DID,
request: Uint8Array,
opts?: {
challenge?: bigint;
opts: ContractInvokeHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse>> {
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<Map<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>> {
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);
}
}
20 changes: 19 additions & 1 deletion src/iden3comm/types/protocol/contract-request.ts
Original file line number Diff line number Diff line change
@@ -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 & {
Expand All @@ -15,6 +15,24 @@ export type ContractInvokeRequestBody = {
scope: Array<ZeroKnowledgeProofRequest>;
};

/** 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<OnChainZeroKnowledgeProofResponse>;
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;
Expand Down
41 changes: 18 additions & 23 deletions src/storage/blockchain/onchain-zkp-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>> {
if (txData.method_id.replace('0x', '') === OnChainZKPVerifier.SupportedMethodIdV2) {
return this.prepareZKPResponseV2TxData(txData, zkProofResponses);
}
): Promise<Map<number, JsonDocumentObjectValue[]>> {
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<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>();
const response = new Map<number, JsonDocumentObjectValue[]>();
for (const zkProof of zkProofResponses) {
const requestID = zkProof.id;
const inputs = zkProof.pub_signals;
Expand All @@ -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;
Expand All @@ -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<string, ZeroKnowledgeProofResponse>();

const feeData = await provider.getFeeData();
Expand All @@ -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,
Expand Down Expand Up @@ -179,7 +180,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
ethSigner: Signer,
txData: ContractInvokeTransactionData,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<string> {
): Promise<Map<string, ZeroKnowledgeProofResponse[]>> {
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`);
Expand All @@ -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)
Expand All @@ -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 = {
Expand All @@ -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<string, ZeroKnowledgeProofResponse[]>().set(txnHash, zkProofResponses);
}

private async prepareZKPResponseV2TxData(
public async prepareZKPResponseSingleTxData(
txData: ContractInvokeTransactionData,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<Map<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>> {
): Promise<JsonDocumentObjectValue[]> {
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.`
Expand Down Expand Up @@ -355,9 +352,7 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
}

const crossChainProofs = this.packCrossChainProofs(gistUpdateArr, stateUpdateArr);

const response = new Map<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>();
return response.set(zkProofResponses, [payload, crossChainProofs]);
return [payload, crossChainProofs];
}

private packZkpProof(inputs: string[], a: string[], b: string[][], c: string[]): string {
Expand Down
21 changes: 16 additions & 5 deletions src/storage/interfaces/onchain-zkp-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,32 @@ export interface IOnChainZKPVerifier {
* @param {Signer} ethSigner - tx signer
* @param {txData} ContractInvokeTransactionData - transaction data
* @param {ZeroKnowledgeProofResponse[]} zkProofResponses - zkProofResponses
* @returns {Promise<Map<string, ZeroKnowledgeProofResponse>>} - map of transaction hash - ZeroKnowledgeProofResponse
* @returns {Promise<Map<string, ZeroKnowledgeProofResponse[]>>} - map of transaction hash - ZeroKnowledgeProofResponse[]
*/
submitZKPResponseV2(
ethSigner: Signer,
txData: ContractInvokeTransactionData,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<string>;
): Promise<Map<string, ZeroKnowledgeProofResponse[]>>;

/**
* 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<Map<number, JsonDocumentObjectValue[]>>;

/**
* 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<Map<ZeroKnowledgeProofResponse[], JsonDocumentObjectValue[]>>;
): Promise<JsonDocumentObjectValue[]>;
}

0 comments on commit 755b7f7

Please sign in to comment.