Skip to content

Commit

Permalink
Feature/contract request handler (#136)
Browse files Browse the repository at this point in the history
* add contract request handler

* add onchain zkp verifier

---------

Co-authored-by: vmidyllic <[email protected]>
  • Loading branch information
volodymyr-basiuk and vmidyllic authored Oct 4, 2023
1 parent 16de07e commit 70c0d35
Show file tree
Hide file tree
Showing 12 changed files with 1,071 additions and 3 deletions.
4 changes: 3 additions & 1 deletion src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
// RevocationStatusRequestMessageType is type for request of revocation status
REVOCATION_STATUS_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'revocation/1.0/request-status',
// RevocationStatusResponseMessageType is type for response with a revocation status
REVOCATION_STATUS_RESPONSE_MESSAGE_TYPE: IDEN3_PROTOCOL + 'revocation/1.0/status'
REVOCATION_STATUS_RESPONSE_MESSAGE_TYPE: IDEN3_PROTOCOL + 'revocation/1.0/status',
// ContractInvokeRequestMessageType is type for request of contract invoke request
CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'proofs/1.0/contract-invoke-request'
});

/**
Expand Down
153 changes: 153 additions & 0 deletions src/iden3comm/handlers/contract-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { CircuitId } from '../../circuits/models';
import { IProofService } from '../../proof/proof-service';
import { PROTOCOL_MESSAGE_TYPE } from '../constants';

import { IPackageManager, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../types';

import { ProofQuery } from '../../verifiable';
import { ContractInvokeRequest } from '../types/protocol/contract-request';
import { DID } from '@iden3/js-iden3-core';
import { IOnChainZKPVerifier } from '../../storage';
import { Signer } from 'ethers';

/**
* Interface that allows the processing of the contract request
*
* @beta
* @interface IContractRequestHandler
*/
export interface IContractRequestHandler {
/**
* unpacks contract invoker request
* @beta
* @param {Uint8Array} request - raw byte message
* @returns `Promise<ContractInvokeRequest>`
*/
parseContractInvokeRequest(request: Uint8Array): Promise<ContractInvokeRequest>;

/**
* handle contract invoker request
* @beta
* @param {did} did - sender DID
* @param {Uint8Array} request - raw byte message
* @param {ContractInvokeHandlerOptions} opts - handler options
* @returns {Map<string, ZeroKnowledgeProofResponse>}` - map of transaction hash - ZeroKnowledgeProofResponse
*/
handleContractInvokeRequest(
did: DID,
request: Uint8Array,
opts?: ContractInvokeHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse>>;
}

/** ContractInvokeHandlerOptions represents contract invoke handler options */
export type ContractInvokeHandlerOptions = {
ethSigner: Signer;
challenge?: bigint;
};

/**
*
* Allows to process ContractInvokeRequest protocol message
*
* @beta
* @class ContractRequestHandler
* @implements implements IContractRequestHandler interface
*/
export class ContractRequestHandler implements IContractRequestHandler {
private readonly _allowedCircuits = [
CircuitId.AtomicQueryMTPV2OnChain,
CircuitId.AtomicQuerySigV2OnChain
];

/**
* Creates an instance of ContractRequestHandler.
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
* @param {IProofService} _proofService - proof service to verify zk proofs
* @param {IOnChainZKPVerifier} _zkpVerifier - zkp verifier to submit response
*
*/

constructor(
private readonly _packerMgr: IPackageManager,
private readonly _proofService: IProofService,
private readonly _zkpVerifier: IOnChainZKPVerifier
) {}

/**
* unpacks contract-invoke request
* @beta
* @param {Uint8Array} request - raw byte message
* @returns `Promise<ContractInvokeRequest>`
*/
async parseContractInvokeRequest(request: Uint8Array): Promise<ContractInvokeRequest> {
const { unpackedMessage: message } = await this._packerMgr.unpack(request);
const ciRequest = message as unknown as ContractInvokeRequest;
if (message.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid media type');
}
return ciRequest;
}

/**
* handle contract invoker request
* @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
*/
async handleContractInvokeRequest(
did: DID,
request: Uint8Array,
opts: ContractInvokeHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse>> {
const ciRequest = await this.parseContractInvokeRequest(request);

if (ciRequest.type !== PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid message type for contract invoke request');
}

if (!opts.ethSigner) {
throw new Error("Can't sign transaction. Provide Signer in options.");
}

const zkRequests = [];
for (const proofReq of ciRequest.body.scope) {
if (!this._allowedCircuits.includes(proofReq.circuitId as CircuitId)) {
throw new Error(
`Can't handle circuit ${proofReq.circuitId}. Only onchain circuits are allowed.`
);
}

const zkpReq: ZeroKnowledgeProofRequest = {
id: proofReq.id,
circuitId: proofReq.circuitId as CircuitId,
query: proofReq.query
};

const query = proofReq.query as unknown as ProofQuery;

const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
zkpReq,
did,
{
skipRevocation: query.skipClaimRevocationCheck ?? false,
challenge: opts.challenge
}
);

zkRequests.push(zkpRes);
}

const txData = ciRequest.body.transaction_data;

return this._zkpVerifier.submitZKPResponse(
txData.contract_address,
opts.ethSigner,
txData.chain_id,
zkRequests
);
}
}
1 change: 1 addition & 0 deletions src/iden3comm/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './auth';
export * from './fetch';
export * from './contract-request';
1 change: 1 addition & 0 deletions src/iden3comm/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './protocol/credentials';
export * from './protocol/messages';
export * from './protocol/proof';
export * from './protocol/revocation';
export * from './protocol/contract-request';

export * from './packer';
export * from './packageManager';
25 changes: 25 additions & 0 deletions src/iden3comm/types/protocol/contract-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ZeroKnowledgeProofRequest } from './auth';

/** ContractInvokeRequest represents structure of contract invoke request object */
export type ContractInvokeRequest = {
id: string;
typ: string;
type: string;
thid: string;
body: ContractInvokeRequestBody;
};

/** ContractInvokeRequestBody represents structure of contract invoke request body object */
export type ContractInvokeRequestBody = {
reason: string;
transaction_data: ContractInvokeTransactionData;
scope: Array<ZeroKnowledgeProofRequest>;
};

/** ContractInvokeTransactionData represents structure of contract invoke transaction data object */
export type ContractInvokeTransactionData = {
contract_address: string;
method_id: string;
chain_id: number;
network?: string;
};
1 change: 1 addition & 0 deletions src/storage/blockchain/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './state';
export * from './chainid';
export * from './onchain-zkp-verifier';
77 changes: 77 additions & 0 deletions src/storage/blockchain/onchain-zkp-verifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import abi from './zkp-verifier-abi.json';
import { ethers, Signer } from 'ethers';
import { EthConnectionConfig } from './state';
import { IOnChainZKPVerifier } from '../interfaces/onchain-zkp-verifier';
import { ZeroKnowledgeProofResponse } from '../../iden3comm';

/**
* OnChainZKPVerifier is a class that allows to interact with the OnChainZKPVerifier contract
* and submitZKPResponse.
*
* @beta
* @class OnChainZKPVerifier
*/
export class OnChainZKPVerifier implements IOnChainZKPVerifier {
/**
*
* Creates an instance of OnChainZKPVerifier.
* @beta
* @param {EthConnectionConfig[]} - array of ETH configs
*/

constructor(private readonly _configs: EthConnectionConfig[]) {}

/**
* Submit ZKP Responses to OnChainZKPVerifier contract.
* @beta
* @param {string} address - OnChainZKPVerifier contract address
* @param {Signer} ethSigner - tx signer
* @param {number} chainId - chain Id
* @param {ZeroKnowledgeProofResponse[]} zkProofResponses - zkProofResponses
* @returns {Promise<Map<string, ZeroKnowledgeProofResponse>>} - map of transaction hash - ZeroKnowledgeProofResponse
*/
public async submitZKPResponse(
address: string,
ethSigner: Signer,
chainId: number,
zkProofResponses: ZeroKnowledgeProofResponse[]
): Promise<Map<string, ZeroKnowledgeProofResponse>> {
const chainConfig = this._configs.find((i) => i.chainId == chainId);
if (!chainConfig) {
throw new Error(`config for chain id ${chainId} was not found`);
}
const provider = new ethers.providers.JsonRpcProvider(chainConfig);
const verifierContract: ethers.Contract = new ethers.Contract(address, abi, provider);
ethSigner = ethSigner.connect(provider);
const contract = verifierContract.connect(ethSigner);

const response = new Map<string, ZeroKnowledgeProofResponse>();
for (const zkProof of zkProofResponses) {
const requestID = zkProof.id;
const inputs = zkProof.pub_signals;

const payload = [
requestID,
inputs,
zkProof.proof.pi_a.slice(0, 2),
[
[zkProof.proof.pi_b[0][1], zkProof.proof.pi_b[0][0]],
[zkProof.proof.pi_b[1][1], zkProof.proof.pi_b[1][0]]
],
zkProof.proof.pi_c.slice(0, 2)
];

const tx = await contract.submitZKPResponse(...payload);
const txnReceipt = await tx.wait();
const status: number = txnReceipt.status;
const txnHash: string = txnReceipt.transactionHash;

if (status === 0) {
throw new Error(`transaction: ${txnHash} failed to mined`);
}
response.set(txnHash, zkProof);
}

return response;
}
}
4 changes: 2 additions & 2 deletions src/storage/blockchain/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ export class EthStateStorage implements IStateStorage {
isOldStateGenesis,
proof.proof.pi_a.slice(0, 2),
[
[proof.proof.pi_b[0][1].toString(), proof.proof.pi_b[0][0].toString()],
[proof.proof.pi_b[1][1].toString(), proof.proof.pi_b[1][0].toString()]
[proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]],
[proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]]
],
proof.proof.pi_c.slice(0, 2)
];
Expand Down
Loading

0 comments on commit 70c0d35

Please sign in to comment.