Skip to content

Commit

Permalink
Unify Auth and Contract invoke logic (#196)
Browse files Browse the repository at this point in the history
* Unify Auth and Contract invoke logic
  • Loading branch information
Kolezhniuk authored Mar 19, 2024
1 parent 198cfc5 commit ddca455
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 140 deletions.
112 changes: 26 additions & 86 deletions src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ import {
AuthorizationRequestMessage,
AuthorizationResponseMessage,
IPackageManager,
JSONObject,
JWSPackerParams,
ZeroKnowledgeProofRequest,
ZeroKnowledgeProofResponse
ZeroKnowledgeProofRequest
} from '../types';
import { DID } from '@iden3/js-iden3-core';
import { proving } from '@iden3/js-jwz';

import * as uuid from 'uuid';
import { ProofQuery, RevocationStatus, W3CCredential } from '../../verifiable';
import { byteDecoder, byteEncoder, mergeObjects } from '../../utils';
import { getRandomBytes } from '@iden3/js-crypto';
import { ProofQuery } from '../../verifiable';
import { byteDecoder, byteEncoder } from '../../utils';
import { processZeroKnowledgeProofRequests } from './common';
import { CircuitId } from '../../circuits';

/**
* createAuthorizationRequest is a function to create protocol authorization request
Expand Down Expand Up @@ -156,6 +155,12 @@ export interface AuthHandlerOptions {
* @implements implements IAuthHandler interface
*/
export class AuthHandler implements IAuthHandler {
private readonly _supportedCircuits = [
CircuitId.AtomicQueryV3,
CircuitId.AtomicQuerySigV2,
CircuitId.AtomicQueryMTPV2,
CircuitId.LinkedMultiQuery10
];
/**
* Creates an instance of AuthHandler.
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
Expand Down Expand Up @@ -214,98 +219,33 @@ export class AuthHandler implements IAuthHandler {
}
const guid = uuid.v4();

if (!authRequest.from) {
throw new Error('auth request should contain from field');
}

const from = DID.parse(authRequest.from);

const responseScope = await processZeroKnowledgeProofRequests(
did,
authRequest?.body.scope,
from,
this._proofService,
{ ...opts, supportedCircuits: this._supportedCircuits }
);

const authResponse: AuthorizationResponseMessage = {
id: guid,
typ: opts.mediaType,
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE,
thid: authRequest.thid ?? guid,
body: {
message: authRequest?.body?.message,
scope: []
scope: responseScope
},
from: did.string(),
to: authRequest.from
};

const requestScope = authRequest.body.scope;
const combinedQueries = requestScope.reduce((acc, proofReq) => {
const groupId = proofReq.query.groupId as number | undefined;
if (!groupId) {
return acc;
}

const existedData = acc.get(groupId);
if (!existedData) {
const seed = getRandomBytes(12);
const dataView = new DataView(seed.buffer);
const linkNonce = dataView.getUint32(0);
acc.set(groupId, { query: proofReq.query, linkNonce });
return acc;
}

const credentialSubject = mergeObjects(
existedData.query.credentialSubject as JSONObject,
proofReq.query.credentialSubject as JSONObject
);

acc.set(groupId, {
...existedData,
query: {
skipClaimRevocationCheck:
existedData.query.skipClaimRevocationCheck || proofReq.query.skipClaimRevocationCheck,
...(existedData.query as JSONObject),
credentialSubject
}
});

return acc;
}, new Map<number, { query: JSONObject; linkNonce: number }>());

const groupedCredentialsCache = new Map<
number,
{ cred: W3CCredential; revStatus?: RevocationStatus }
>();

for (const proofReq of requestScope) {
const query = proofReq.query;
const groupId = query.groupId as number | undefined;
const combinedQueryData = combinedQueries.get(groupId as number);
if (groupId) {
if (!combinedQueryData) {
throw new Error(`Invalid group id ${query.groupId}`);
}
const combinedQuery = combinedQueryData.query;

if (!groupedCredentialsCache.has(groupId)) {
const credWithRevStatus = await this._proofService.findCredentialByProofQuery(
did,
combinedQueryData.query
);
if (!credWithRevStatus.cred) {
throw new Error(`Credential not found for query ${JSON.stringify(combinedQuery)}`);
}

groupedCredentialsCache.set(groupId, credWithRevStatus);
}
}

const credWithRevStatus = groupedCredentialsCache.get(groupId as number);

const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
proofReq,
did,
{
verifierDid: DID.parse(authRequest.from),
skipRevocation: Boolean(query.skipClaimRevocationCheck),
credential: credWithRevStatus?.cred,
credentialRevocationStatus: credWithRevStatus?.revStatus,
linkNonce: combinedQueryData?.linkNonce ? BigInt(combinedQueryData.linkNonce) : undefined
}
);

authResponse.body.scope.push(zkpRes);
}

const msgBytes = byteEncoder.encode(JSON.stringify(authResponse));

const packerOpts =
Expand Down
140 changes: 140 additions & 0 deletions src/iden3comm/handlers/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { getRandomBytes } from '@iden3/js-crypto';
import {
JSONObject,
JWSPackerParams,
ZeroKnowledgeProofRequest,
ZeroKnowledgeProofResponse
} from '../types';
import { mergeObjects } from '../../utils';
import { RevocationStatus, W3CCredential } from '../../verifiable';
import { DID } from '@iden3/js-iden3-core';
import { IProofService } from '../../proof';
import { CircuitId } from '../../circuits';
import { MediaType } from '../constants';
import { Signer } from 'ethers';

/**
* Groups the ZeroKnowledgeProofRequest objects based on their groupId.
* Returns a Map where the key is the groupId and the value is an object containing the query and linkNonce.
*
* @param requestScope - An array of ZeroKnowledgeProofRequest objects.
* @returns A Map<number, { query: JSONObject; linkNonce: number }> representing the grouped queries.
*/
const getGroupedQueries = (
requestScope: ZeroKnowledgeProofRequest[]
): Map<number, { query: JSONObject; linkNonce: number }> =>
requestScope.reduce((acc, proofReq) => {
const groupId = proofReq.query.groupId as number | undefined;
if (!groupId) {
return acc;
}

const existedData = acc.get(groupId);
if (!existedData) {
const seed = getRandomBytes(12);
const dataView = new DataView(seed.buffer);
const linkNonce = dataView.getUint32(0);
acc.set(groupId, { query: proofReq.query, linkNonce });
return acc;
}

const credentialSubject = mergeObjects(
existedData.query.credentialSubject as JSONObject,
proofReq.query.credentialSubject as JSONObject
);

acc.set(groupId, {
...existedData,
query: {
skipClaimRevocationCheck:
existedData.query.skipClaimRevocationCheck || proofReq.query.skipClaimRevocationCheck,
...(existedData.query as JSONObject),
credentialSubject
}
});

return acc;
}, new Map<number, { query: JSONObject; linkNonce: number }>());


/**
* Processes zero knowledge proof requests.
*
* @param senderIdentifier - The identifier of the sender.
* @param requests - An array of zero knowledge proof requests.
* @param from - The identifier of the sender.
* @param proofService - The proof service.
* @param opts - Additional options for processing the requests.
* @returns A promise that resolves to an array of zero knowledge proof responses.
*/
export const processZeroKnowledgeProofRequests = async (
senderIdentifier: DID,
requests: ZeroKnowledgeProofRequest[] | undefined,
from: DID | undefined,
proofService: IProofService,
opts: {
mediaType?: MediaType;
packerOptions?: JWSPackerParams;
supportedCircuits: CircuitId[];
ethSigner?: Signer;
challenge?: bigint;
}
): Promise<ZeroKnowledgeProofResponse[]> => {
const requestScope = requests ?? [];

const combinedQueries = getGroupedQueries(requestScope);

const groupedCredentialsCache = new Map<
number,
{ cred: W3CCredential; revStatus?: RevocationStatus }
>();

const zkpResponses = [];

for (const proofReq of requestScope) {
if (!opts.supportedCircuits.includes(proofReq.circuitId as CircuitId)) {
throw new Error(`Circuit ${proofReq.circuitId} is not allowed`);
}

const query = proofReq.query;
const groupId = query.groupId as number | undefined;
const combinedQueryData = combinedQueries.get(groupId as number);
if (groupId) {
if (!combinedQueryData) {
throw new Error(`Invalid group id ${query.groupId}`);
}
const combinedQuery = combinedQueryData.query;

if (!groupedCredentialsCache.has(groupId)) {
const credWithRevStatus = await proofService.findCredentialByProofQuery(
senderIdentifier,
combinedQueryData.query
);
if (!credWithRevStatus.cred) {
throw new Error(`Credential not found for query ${JSON.stringify(combinedQuery)}`);
}

groupedCredentialsCache.set(groupId, credWithRevStatus);
}
}

const credWithRevStatus = groupedCredentialsCache.get(groupId as number);

const zkpRes: ZeroKnowledgeProofResponse = await proofService.generateProof(
proofReq,
senderIdentifier,
{
verifierDid: from,
challenge: opts.challenge,
skipRevocation: Boolean(query.skipClaimRevocationCheck),
credential: credWithRevStatus?.cred,
credentialRevocationStatus: credWithRevStatus?.revStatus,
linkNonce: combinedQueryData?.linkNonce ? BigInt(combinedQueryData.linkNonce) : undefined
}
);

zkpResponses.push(zkpRes);
}

return zkpResponses;
};
38 changes: 11 additions & 27 deletions src/iden3comm/handlers/contract-request.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { CircuitId } from '../../circuits/models';
import { IProofService } from '../../proof/proof-service';
import { PROTOCOL_MESSAGE_TYPE } from '../constants';

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

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

/**
* Interface that allows the processing of the contract request
Expand Down Expand Up @@ -56,7 +54,7 @@ export type ContractInvokeHandlerOptions = {
* @implements implements IContractRequestHandler interface
*/
export class ContractRequestHandler implements IContractRequestHandler {
private readonly _allowedCircuits = [
private readonly _supportedCircuits = [
CircuitId.AtomicQueryMTPV2OnChain,
CircuitId.AtomicQuerySigV2OnChain,
CircuitId.AtomicQueryV3OnChain
Expand Down Expand Up @@ -115,39 +113,25 @@ export class ContractRequestHandler implements IContractRequestHandler {
throw new Error("Can't sign transaction. Provide Signer in options.");
}

const zkRequests = [];
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}`);
}

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 query = proofReq.query as ProofQuery;
const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
proofReq,
did,
{
skipRevocation: query.skipClaimRevocationCheck ?? false,
challenge: opts.challenge,
verifierDid: ciRequest.from ? DID.parse(ciRequest.from) : undefined
}
);

zkRequests.push(zkpRes);
}
const verifierDid = ciRequest.from ? DID.parse(ciRequest.from) : undefined;
const zkpResponses = await processZeroKnowledgeProofRequests(
did,
ciRequest?.body?.scope,
verifierDid,
this._proofService,
{ ...opts, supportedCircuits: this._supportedCircuits }
);

return this._zkpVerifier.submitZKPResponse(
opts.ethSigner,
ciRequest.body.transaction_data,
zkRequests
zkpResponses
);
}
}
1 change: 1 addition & 0 deletions src/iden3comm/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './auth';
export * from './fetch';
export * from './contract-request';
export * from './refresh';
export * from './common';
Loading

0 comments on commit ddca455

Please sign in to comment.