Skip to content

Commit

Permalink
Merge branch 'main' into feature/onchain-non-merklized-credential-con…
Browse files Browse the repository at this point in the history
…verter
  • Loading branch information
ilya-korotya committed Dec 2, 2024
2 parents c7c5bb6 + 335d198 commit 6ca8c21
Show file tree
Hide file tree
Showing 40 changed files with 1,068 additions and 147 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@0xpolygonid/js-sdk",
"version": "1.23.0",
"version": "1.25.0",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand Down
1 change: 1 addition & 0 deletions src/credentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './status/sparse-merkle-tree';
export * from './status/resolver';
export * from './status/agent-revocation';
export * from './status/credential-status-publisher';
export * from './status/did-resolver-revocation';
export * from './credential-wallet';
export * from './rhs';
export * from './utils';
Expand Down
24 changes: 24 additions & 0 deletions src/credentials/status/did-resolver-revocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CredentialStatus, RevocationStatus } from '../../verifiable';
import { CredentialStatusResolveOptions, CredentialStatusResolver } from './resolver';

export class DidDocumentCredentialStatusResolver implements CredentialStatusResolver {
constructor(private readonly didResolverUrl: string) {}
async resolve(
credentialStatus: CredentialStatus,
opts?: CredentialStatusResolveOptions | undefined
): Promise<RevocationStatus> {
if (!opts?.issuerDID) {
throw new Error('IssuerDID is not set in options');
}

const url = `${this.didResolverUrl}/1.0/credential-status/${encodeURIComponent(
opts.issuerDID.string()
)}`;
const resp = await fetch(url, {
method: 'POST',
body: JSON.stringify(credentialStatus)
});
const data = await resp.json();
return data;
}
}
27 changes: 27 additions & 0 deletions src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AcceptProfile } from './types';

const IDEN3_PROTOCOL = 'https://iden3-communication.io/';
/**
* Constants for Iden3 protocol
Expand Down Expand Up @@ -79,5 +81,30 @@ export const SUPPORTED_PUBLIC_KEY_TYPES = {
]
};

export enum ProtocolVersion {
V1 = 'iden3comm/v1'
}

export enum AcceptAuthCircuits {
AuthV2 = 'authV2',
AuthV3 = 'authV3'
}

export enum AcceptJwzAlgorithms {
Groth16 = 'groth16'
}

export enum AcceptJwsAlgorithms {
ES256K = 'ES256K',
ES256KR = 'ES256K-R'
}

export const defaultAcceptProfile: AcceptProfile = {
protocolVersion: ProtocolVersion.V1,
env: MediaType.ZKPMessage,
circuits: [AcceptAuthCircuits.AuthV2],
alg: [AcceptJwzAlgorithms.Groth16]
};

export const DEFAULT_PROOF_VERIFY_DELAY = 1 * 60 * 60 * 1000; // 1 hour
export const DEFAULT_AUTH_VERIFY_DELAY = 5 * 60 * 1000; // 5 minutes
111 changes: 91 additions & 20 deletions src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MediaType } from '../constants';
import { MediaType, ProtocolVersion } from '../constants';
import { IProofService } from '../../proof/proof-service';
import { PROTOCOL_MESSAGE_TYPE } from '../constants';

Expand All @@ -12,43 +12,62 @@ import {
ZeroKnowledgeProofRequest,
JSONObject
} from '../types';
import { DID } from '@iden3/js-iden3-core';
import { DID, getUnixTimestamp } from '@iden3/js-iden3-core';
import { proving } from '@iden3/js-jwz';

import * as uuid from 'uuid';
import { ProofQuery } from '../../verifiable';
import { byteDecoder, byteEncoder } from '../../utils';
import { processZeroKnowledgeProofRequests } from './common';
import { processZeroKnowledgeProofRequests, verifyExpiresTime } from './common';
import { CircuitId } from '../../circuits';
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
import {
AbstractMessageHandler,
BasicHandlerOptions,
IProtocolMessageHandler
} from './message-handler';
import { parseAcceptProfile } from '../utils';

/**
* Options to pass to createAuthorizationRequest function
* @public
*/
export type AuthorizationRequestCreateOptions = {
accept?: string[];
scope?: ZeroKnowledgeProofRequest[];
expires_time?: Date;
};

/**
* createAuthorizationRequest is a function to create protocol authorization request
* @param {string} reason - reason to request proof
* @param {string} sender - sender did
* @param {string} callbackUrl - callback that user should use to send response
* @param {AuthorizationRequestCreateOptions} opts - authorization request options
* @returns `Promise<AuthorizationRequestMessage>`
*/
export function createAuthorizationRequest(
reason: string,
sender: string,
callbackUrl: string
callbackUrl: string,
opts?: AuthorizationRequestCreateOptions
): AuthorizationRequestMessage {
return createAuthorizationRequestWithMessage(reason, '', sender, callbackUrl);
return createAuthorizationRequestWithMessage(reason, '', sender, callbackUrl, opts);
}
/**
* createAuthorizationRequestWithMessage is a function to create protocol authorization request with explicit message to sign
* @param {string} reason - reason to request proof
* @param {string} message - message to sign in the response
* @param {string} sender - sender did
* @param {string} callbackUrl - callback that user should use to send response
* @param {AuthorizationRequestCreateOptions} opts - authorization request options
* @returns `Promise<AuthorizationRequestMessage>`
*/
export function createAuthorizationRequestWithMessage(
reason: string,
message: string,
sender: string,
callbackUrl: string
callbackUrl: string,
opts?: AuthorizationRequestCreateOptions
): AuthorizationRequestMessage {
const uuidv4 = uuid.v4();
const request: AuthorizationRequestMessage = {
Expand All @@ -58,11 +77,14 @@ export function createAuthorizationRequestWithMessage(
typ: MediaType.PlainMessage,
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
body: {
accept: opts?.accept,
reason: reason,
message: message,
callbackUrl: callbackUrl,
scope: []
}
scope: opts?.scope ?? []
},
created_time: getUnixTimestamp(new Date()),
expires_time: opts?.expires_time ? getUnixTimestamp(opts.expires_time) : undefined
};
return request;
}
Expand All @@ -73,10 +95,11 @@ export function createAuthorizationRequestWithMessage(
*
* @public
*/
export type AuthResponseHandlerOptions = StateVerificationOpts & {
// acceptedProofGenerationDelay is the period of time in milliseconds that a generated proof remains valid.
acceptedProofGenerationDelay?: number;
};
export type AuthResponseHandlerOptions = StateVerificationOpts &
BasicHandlerOptions & {
// acceptedProofGenerationDelay is the period of time in milliseconds that a generated proof remains valid.
acceptedProofGenerationDelay?: number;
};

/**
* Interface that allows the processing of the authorization request in the raw format for given identifier
Expand Down Expand Up @@ -154,10 +177,10 @@ export type AuthMessageHandlerOptions = AuthReqOptions | AuthRespOptions;
* @public
* @interface AuthHandlerOptions
*/
export interface AuthHandlerOptions {
export type AuthHandlerOptions = BasicHandlerOptions & {
mediaType: MediaType;
packerOptions?: JWSPackerParams;
}
};

/**
*
Expand Down Expand Up @@ -228,16 +251,20 @@ export class AuthHandler
if (authRequest.type !== PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid message type for authorization request');
}

// override sender did if it's explicitly specified in the auth request
const to = authRequest.to ? DID.parse(authRequest.to) : ctx.senderDid;
const mediaType = ctx.mediaType || MediaType.ZKPMessage;
const guid = uuid.v4();

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

const responseType = PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE;
const mediaType = this.getSupportedMediaTypeByProfile(
ctx,
responseType,
authRequest.body.accept
);
const from = DID.parse(authRequest.from);

const responseScope = await processZeroKnowledgeProofRequests(
Expand All @@ -250,8 +277,8 @@ export class AuthHandler

return {
id: guid,
typ: ctx.mediaType,
type: PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_RESPONSE_MESSAGE_TYPE,
typ: mediaType,
type: responseType,
thid: authRequest.thid ?? guid,
body: {
message: authRequest?.body?.message,
Expand All @@ -275,7 +302,9 @@ export class AuthHandler
authResponse: AuthorizationResponseMessage;
}> {
const authRequest = await this.parseAuthorizationRequest(request);

if (!opts?.allowExpiredMessages) {
verifyExpiresTime(authRequest);
}
if (!opts) {
opts = {
mediaType: MediaType.ZKPMessage
Expand Down Expand Up @@ -408,6 +437,9 @@ export class AuthHandler
request: AuthorizationRequestMessage;
response: AuthorizationResponseMessage;
}> {
if (!opts?.allowExpiredMessages) {
verifyExpiresTime(response);
}
const authResp = (await this.handleAuthResponse(response, {
request,
acceptedStateTransitionDelay: opts?.acceptedStateTransitionDelay,
Expand Down Expand Up @@ -450,4 +482,43 @@ export class AuthHandler
}
}
}

private getSupportedMediaTypeByProfile(
ctx: AuthReqOptions,
responseType: string,
profile?: string[] | undefined
): MediaType {
let mediaType: MediaType;
if (profile?.length) {
const supportedMediaTypes: MediaType[] = [];
for (const acceptProfile of profile) {
// 1. check protocol version
const { protocolVersion, env } = parseAcceptProfile(acceptProfile);
const responseTypeVersion = Number(responseType.split('/').at(-2));
if (
protocolVersion !== ProtocolVersion.V1 ||
(protocolVersion === ProtocolVersion.V1 &&
(responseTypeVersion < 1 || responseTypeVersion >= 2))
) {
continue;
}
// 2. check packer support
if (this._packerMgr.isProfileSupported(env, acceptProfile)) {
supportedMediaTypes.push(env);
}
}

if (!supportedMediaTypes.length) {
throw new Error('no packer with profile which meets `accept` header requirements');
}

mediaType = supportedMediaTypes[0];
if (ctx.mediaType && supportedMediaTypes.includes(ctx.mediaType)) {
mediaType = ctx.mediaType;
}
} else {
mediaType = ctx.mediaType || MediaType.ZKPMessage;
}
return mediaType;
}
}
14 changes: 13 additions & 1 deletion src/iden3comm/handlers/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getRandomBytes } from '@iden3/js-crypto';
import {
BasicMessage,
JsonDocumentObject,
JWSPackerParams,
ZeroKnowledgeProofQuery,
Expand All @@ -8,7 +9,7 @@ import {
} from '../types';
import { mergeObjects } from '../../utils';
import { RevocationStatus, W3CCredential } from '../../verifiable';
import { DID } from '@iden3/js-iden3-core';
import { DID, getUnixTimestamp } from '@iden3/js-iden3-core';
import { IProofService } from '../../proof';
import { CircuitId } from '../../circuits';
import { MediaType } from '../constants';
Expand Down Expand Up @@ -134,3 +135,14 @@ export const processZeroKnowledgeProofRequests = async (

return zkpResponses;
};

/**
* Verifies that the expires_time field of a message is not in the past. Throws an error if it is.
*
* @param message - Basic message to verify.
*/
export const verifyExpiresTime = (message: BasicMessage) => {
if (message?.expires_time && message.expires_time < getUnixTimestamp(new Date())) {
throw new Error('Message expired');
}
};
19 changes: 13 additions & 6 deletions src/iden3comm/handlers/contract-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { IProofService } from '../../proof/proof-service';
import { PROTOCOL_MESSAGE_TYPE } from '../constants';
import { BasicMessage, IPackageManager, ZeroKnowledgeProofResponse } from '../types';
import { ContractInvokeRequest, ContractInvokeResponse } from '../types/protocol/contract-request';
import { DID, ChainIds } from '@iden3/js-iden3-core';
import { DID, ChainIds, getUnixTimestamp } from '@iden3/js-iden3-core';
import { FunctionSignatures, IOnChainZKPVerifier } from '../../storage';
import { Signer } from 'ethers';
import { processZeroKnowledgeProofRequests } from './common';
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
import { processZeroKnowledgeProofRequests, verifyExpiresTime } from './common';
import {
AbstractMessageHandler,
BasicHandlerOptions,
IProtocolMessageHandler
} from './message-handler';

/**
* Interface that allows the processing of the contract request
Expand Down Expand Up @@ -40,7 +44,7 @@ export interface IContractRequestHandler {
}

/** ContractInvokeHandlerOptions represents contract invoke handler options */
export type ContractInvokeHandlerOptions = {
export type ContractInvokeHandlerOptions = BasicHandlerOptions & {
ethSigner: Signer;
challenge?: bigint;
};
Expand Down Expand Up @@ -193,7 +197,8 @@ export class ContractRequestHandler
body: {
transaction_data: request.body.transaction_data,
scope: []
}
},
created_time: getUnixTimestamp(new Date())
};
for (const [txHash, zkpResponses] of txHashToZkpResponseMap) {
for (const zkpResponse of zkpResponses) {
Expand Down Expand Up @@ -222,7 +227,9 @@ export class ContractRequestHandler
opts: ContractInvokeHandlerOptions
): Promise<Map<string, ZeroKnowledgeProofResponse>> {
const ciRequest = await this.parseContractInvokeRequest(request);

if (!opts.allowExpiredMessages) {
verifyExpiresTime(ciRequest);
}
if (ciRequest.body.transaction_data.method_id !== FunctionSignatures.SubmitZKPResponseV1) {
throw new Error(`please use handle method to work with other method ids`);
}
Expand Down
Loading

0 comments on commit 6ca8c21

Please sign in to comment.