Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Directives with message store #269

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions flow.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<mxfile host="65bd71144e">
<diagram id="xrEWAPjFNV7tEtzuz-PX" name="Page-1">
<mxGraphModel dx="867" dy="826" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="Auth-Req&lt;br&gt;directive" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="320" y="50" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="No credential" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="160" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="4" value="Cred exist" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="440" y="150" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="5" value="End" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;" parent="1" vertex="1">
<mxGeometry x="440" y="290" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="6" value="store directive" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="240" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="7" value="protocol erro msg?" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontStyle=1;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="320" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="9" value="take infor from trusted registry" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="480" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="11" value="handle error" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="400" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="12" value="create proposal request" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="555" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="13" value="search directive" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="200" y="640" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="14" value="" style="endArrow=none;html=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="40" y="760" as="sourcePoint"/>
<mxPoint x="750" y="760" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="17" value="" style="edgeStyle=none;html=1;" edge="1" parent="1" source="15" target="16">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="15" value="Auth with empty scope" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="910" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="16" value="payment contract&lt;br&gt;verifierPayment" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="450" y="910" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="18" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="70" y="920" width="30" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
getRpcProvider: () => {
return {};
},
publishStateGeneric: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
Expand Down
23 changes: 18 additions & 5 deletions src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { byteDecoder, byteEncoder } from '../../utils';
import { processZeroKnowledgeProofRequests } from './common';
import { CircuitId } from '../../circuits';
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
import { IIden3MessageStorage } from '../../storage';

/**
* createAuthorizationRequest is a function to create protocol authorization request
Expand Down Expand Up @@ -186,12 +187,24 @@ export class AuthHandler
*/
constructor(
private readonly _packerMgr: IPackageManager,
private readonly _proofService: IProofService
private readonly _proofService: IProofService,
private readonly _opts?: {
messageStorage: IIden3MessageStorage;
}
) {
super();
}

handle(message: BasicMessage, ctx: AuthMessageHandlerOptions): Promise<BasicMessage | null> {
async handle(
message: BasicMessage,
ctx: AuthMessageHandlerOptions
): Promise<BasicMessage | null> {
if (this._opts?.messageStorage) {
await this.processMessage(message, {
messageStorage: this._opts.messageStorage
});
}

switch (message.type) {
case PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE:
return this.handleAuthRequest(
Expand Down Expand Up @@ -286,7 +299,7 @@ export class AuthHandler
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`);
}

const authResponse = await this.handleAuthRequest(authRequest, {
const authResponse = await this.handle(authRequest, {
senderDid: did,
mediaType: opts.mediaType
});
Expand All @@ -307,7 +320,7 @@ export class AuthHandler
})
);

return { authRequest, authResponse, token };
return { authRequest, authResponse: authResponse as AuthorizationResponseMessage, token };
}

private async handleAuthResponse(
Expand Down Expand Up @@ -408,7 +421,7 @@ export class AuthHandler
request: AuthorizationRequestMessage;
response: AuthorizationResponseMessage;
}> {
const authResp = (await this.handleAuthResponse(response, {
const authResp = (await this.handle(response, {
request,
acceptedStateTransitionDelay: opts?.acceptedStateTransitionDelay,
acceptedProofGenerationDelay: opts?.acceptedProofGenerationDelay
Expand Down
9 changes: 7 additions & 2 deletions src/iden3comm/handlers/contract-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { FunctionSignatures, IOnChainZKPVerifier } from '../../storage';
import { FunctionSignatures, IIden3MessageStorage, IOnChainZKPVerifier } from '../../storage';
import { Signer } from 'ethers';
import { processZeroKnowledgeProofRequests } from './common';
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
Expand Down Expand Up @@ -81,7 +81,8 @@ export class ContractRequestHandler
constructor(
private readonly _packerMgr: IPackageManager,
private readonly _proofService: IProofService,
private readonly _zkpVerifier: IOnChainZKPVerifier
private readonly _zkpVerifier: IOnChainZKPVerifier,
private readonly _opts?: { messageStorage: IIden3MessageStorage }
) {
super();
}
Expand All @@ -90,6 +91,7 @@ export class ContractRequestHandler
message: BasicMessage,
ctx: ContractMessageHandlerOptions
): Promise<BasicMessage | null> {
await this.processMessage(message, { messageStorage: this._opts?.messageStorage });
switch (message.type) {
case PROTOCOL_MESSAGE_TYPE.CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: {
const ciMessage = message as ContractInvokeRequest;
Expand Down Expand Up @@ -184,6 +186,9 @@ export class ContractRequestHandler
request: ContractInvokeRequest,
txHashToZkpResponseMap: Map<string, ZeroKnowledgeProofResponse[]>
): Promise<ContractInvokeResponse> {
if (!request.to) {
throw new Error('Invalid contract invoke request');
}
const contractInvokeResponse: ContractInvokeResponse = {
id: request.id,
thid: request.thid,
Expand Down
151 changes: 146 additions & 5 deletions src/iden3comm/handlers/credential-proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
BasicMessage,
CredentialOffer,
CredentialsOfferMessage,
Iden3DirectiveType,
IPackageManager,
JsonDocumentObject,
PackerParams
Expand All @@ -18,15 +19,17 @@ import {
ProposalMessage
} from '../types/protocol/proposal-request';
import { IIdentityWallet } from '../../identity';
import { byteEncoder } from '../../utils';
import { byteDecoder, byteEncoder } from '../../utils';
import { W3CCredential } from '../../verifiable';
import { AbstractMessageHandler, IProtocolMessageHandler } from './message-handler';
import { IIden3MessageStorage } from '../../storage';

/** @beta ProposalRequestCreationOptions represents proposal-request creation options */
export type ProposalRequestCreationOptions = {
credentials: ProposalRequestCredential[];
metadata?: { type: string; data?: JsonDocumentObject };
metadata?: { type: string; data?: JsonDocumentObject | JsonDocumentObject[] };
did_doc?: JsonDocumentObject;
thid?: string;
};

/**
Expand All @@ -45,7 +48,7 @@ export function createProposalRequest(
const uuidv4 = uuid.v4();
const request: ProposalRequestMessage = {
id: uuidv4,
thid: uuidv4,
thid: opts.thid ?? uuidv4,
from: sender.string(),
to: receiver.string(),
typ: MediaType.PlainMessage,
Expand Down Expand Up @@ -98,6 +101,20 @@ export interface ICredentialProposalHandler {
*/
parseProposalRequest(request: Uint8Array): Promise<ProposalRequestMessage>;

/**
* @beta
* creates proposal-request
* @param {ProposalRequestCreationOptions} params - creation options
* @returns `Promise<ProposalRequestMessage>`
*/
createProposalRequestPacked(
params: {
thid: string;
sender: DID;
receiver: DID;
} & ProposalRequestCreationOptions
): Promise<{ request: ProposalRequestMessage; token: string }>;

/**
* @beta
* handle proposal-request
Expand Down Expand Up @@ -138,8 +155,13 @@ export type ProposalHandlerOptions = {
/** @beta CredentialProposalHandlerParams represents credential proposal handler params */
export type CredentialProposalHandlerParams = {
agentUrl: string;
proposalResolverFn: (context: string, type: string) => Promise<Proposal>;
proposalResolverFn: (
context: string,
type: string,
request?: ProposalRequestMessage
) => Promise<Proposal>;
packerParams: PackerParams;
messageStorage?: IIden3MessageStorage;
};

/**
Expand Down Expand Up @@ -169,6 +191,121 @@ export class CredentialProposalHandler
super();
}

/**
* @inheritdoc ICredentialProposalHandler#createProposalRequest
*/
async createProposalRequestPacked(
params: {
thid: string;
sender: DID;
receiver: DID;
} & ProposalRequestCreationOptions
): Promise<{ request: ProposalRequestMessage; token: string }> {
if (!this._params.messageStorage) {
throw new Error('metadata storage is required');
}

const thid = uuid.v4();

const messages: BasicMessage[] = (
await this._params.messageStorage.getMessagesByThreadId(params.thid, 'pending')
).map((message) => JSON.parse(message.jsonString));

if (!messages.length) {
throw new Error('no pending messages found');
// const request = createProposalRequest(params.sender, params.receiver, {
// credentials: params.credentials,
// metadata: params.metadata,
// did_doc: params.did_doc,
// thid
// });
// const msgBytes = byteEncoder.encode(JSON.stringify(request));
// const token = byteDecoder.decode(
// await this._packerMgr.pack(MediaType.PlainMessage, msgBytes, {
// senderDID: params.sender,
// ...this._params.packerParams
// })
// );
// return { request, token };
}

//TODO: handle multiple messages
if (messages.length > 1) {
throw new Error('multiple messages are not supported');
}

const directives = (messages[0].attachments ?? [])
.filter(
(attachment) =>
attachment?.data?.type === 'Iden3Directives' && attachment?.data?.directives.length
)
.flatMap((attachment) => attachment.data.directives ?? [])
.filter(
(directive) => directive.purpose === PROTOCOL_MESSAGE_TYPE.PROPOSAL_REQUEST_MESSAGE_TYPE
);

const credentialsToRequest: ProposalRequestCredential[] = [...params.credentials];
// let metadata = {...params.metadata};
//TODO: how to merge metadata?

const result = directives.reduce<{
metadata: {
type: string;
data: JsonDocumentObject[];
};
credentialsToRequest: ProposalRequestCredential[];
}>(
(acc, directive) => {
if (directive.type !== Iden3DirectiveType.TransparentPaymentDirective) {
return acc;
}
const directiveCredentials: ProposalRequestCredential[] = directive.credentials ?? [];
acc.credentialsToRequest = [...acc.credentialsToRequest, ...directiveCredentials];
delete directive.purpose;
acc.metadata.data = [...acc.metadata.data, directive];
return acc;
},
{
metadata: {
type: 'Iden3Metadata',
data: []
},
credentialsToRequest
}
);

const msg = createProposalRequest(params.sender, params.receiver, {
credentials: result.credentialsToRequest,
metadata: result.metadata,
did_doc: params.did_doc,
thid
});

// todo: is it correct and right place to save the message before sending it?
//
await this._params.messageStorage.save(msg.id, {
id: msg.id,
thid: msg.thid,
createdAt: new Date().toISOString(),
status: 'pending',
correlationId: messages[0].id,
type: msg.type,
correlationThId: messages[0].thid,
jsonString: JSON.stringify(msg)
});

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

const token = byteDecoder.decode(
await this._packerMgr.pack(messages[0]?.typ ?? MediaType.PlainMessage, msgBytes, {
senderDID: params.sender,
...this._params.packerParams
})
);

return { request: msg, token };
}

public async handle(
message: BasicMessage,
context: ProposalRequestHandlerOptions
Expand Down Expand Up @@ -266,7 +403,11 @@ export class CredentialProposalHandler
}

// credential not found in the wallet, prepare proposal protocol message
const proposal = await this._params.proposalResolverFn(cred.context, cred.type);
const proposal = await this._params.proposalResolverFn(
cred.context,
cred.type,
proposalRequest
);
if (!proposal) {
throw new Error(`can't resolve Proposal for type: ${cred.type}, context: ${cred.context}`);
}
Expand Down
Loading
Loading