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

Suppoort attachments in the protocol #299

Open
wants to merge 4 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
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.27.0",
"version": "1.28.0",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand Down
4 changes: 3 additions & 1 deletion src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
`${DIDCOMM_PROTOCOL}discover-features/2.0/queries` as const,
// DiscoveryProtocolDiscloseMessageType is type for didcomm discovery protocol disclose
DISCOVERY_PROTOCOL_DISCLOSE_MESSAGE_TYPE:
`${DIDCOMM_PROTOCOL}discover-features/2.0/disclose` as const
`${DIDCOMM_PROTOCOL}discover-features/2.0/disclose` as const,
TRANSPARENT_PAYMENT_INSTRUCTION_MESSAGE_TYPE:
`${IDEN3_PROTOCOL}credentials/0.1/transparent-payment-instruction` as const
});

/**
Expand Down
6 changes: 5 additions & 1 deletion src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
IPackageManager,
JWSPackerParams,
ZeroKnowledgeProofRequest,
JSONObject
JSONObject,
Attachment
} from '../types';
import { DID, getUnixTimestamp } from '@iden3/js-iden3-core';
import { proving } from '@iden3/js-jwz';
Expand All @@ -35,6 +36,7 @@ export type AuthorizationRequestCreateOptions = {
accept?: string[];
scope?: ZeroKnowledgeProofRequest[];
expires_time?: Date;
attachments?: Attachment[];
};

/**
Expand Down Expand Up @@ -86,6 +88,8 @@ export function createAuthorizationRequestWithMessage(
created_time: getUnixTimestamp(new Date()),
expires_time: opts?.expires_time ? getUnixTimestamp(opts.expires_time) : undefined
};

opts?.attachments && (request.attachments = opts.attachments);
return request;
}

Expand Down
33 changes: 29 additions & 4 deletions src/iden3comm/handlers/credential-proposal.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { PROTOCOL_MESSAGE_TYPE, MediaType } from '../constants';
import {
Attachment,
BasicMessage,
CredentialOffer,
CredentialsOfferMessage,
DIDDocument,
IPackageManager,
PackerParams
PackerParams,
TransparentPaymentData,
TransparentPaymentInstructionMessage
} from '../types';

import { DID, getUnixTimestamp } from '@iden3/js-iden3-core';
Expand All @@ -26,12 +29,12 @@ import {
IProtocolMessageHandler
} from './message-handler';
import { verifyExpiresTime } from './common';

/** @beta ProposalRequestCreationOptions represents proposal-request creation options */
export type ProposalRequestCreationOptions = {
credentials: ProposalRequestCredential[];
did_doc?: DIDDocument;
expires_time?: Date;
attachments?: Attachment[];
};

/** @beta ProposalCreationOptions represents proposal creation options */
Expand Down Expand Up @@ -64,6 +67,7 @@ export function createProposalRequest(
created_time: getUnixTimestamp(new Date()),
expires_time: opts?.expires_time ? getUnixTimestamp(opts.expires_time) : undefined
};
opts.attachments?.length && (request.attachments = opts.attachments);
return request;
}

Expand Down Expand Up @@ -153,7 +157,11 @@ export type ProposalHandlerOptions = BasicHandlerOptions & {
/** @beta CredentialProposalHandlerParams represents credential proposal handler params */
export type CredentialProposalHandlerParams = {
agentUrl: string;
proposalResolverFn: (context: string, type: string) => Promise<Proposal>;
proposalResolverFn: (
context: string,
type: string,
opts?: { paymentInfo?: TransparentPaymentData }
) => Promise<Proposal>;
packerParams: PackerParams;
};

Expand Down Expand Up @@ -231,6 +239,18 @@ export class CredentialProposalHandler
let credOfferMessage: CredentialsOfferMessage | undefined = undefined;
let proposalMessage: ProposalMessage | undefined = undefined;

const paymentInstructionsMessages: TransparentPaymentInstructionMessage[] = (
proposalRequest.attachments ?? []
)
.flatMap((a) => a.data.json as TransparentPaymentInstructionMessage)
.filter(
(m) =>
m &&
m.body?.goal_code === PROTOCOL_MESSAGE_TYPE.PROPOSAL_REQUEST_MESSAGE_TYPE &&
m.to === proposalRequest.to && // issuer
m.body.did === proposalRequest.from // user
);

for (let i = 0; i < proposalRequest.body.credentials.length; i++) {
const cred = proposalRequest.body.credentials[i];

Expand Down Expand Up @@ -280,8 +300,13 @@ export class CredentialProposalHandler
continue;
}

const paymentInfo = paymentInstructionsMessages.find((m) =>
m.body.credentials.find((c) => c.type === cred.type && c.context === cred.context)
);
// 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, {
paymentInfo: paymentInfo?.body?.paymentData
});
if (!proposal) {
throw new Error(`can't resolve Proposal for type: ${cred.type}, context: ${cred.context}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/iden3comm/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export * from './protocol/proposal-request';
export * from './protocol/payment';
export * from './protocol/accept-profile';
export * from './protocol/discovery-protocol';

export * from './protocol/attachment';
export * from './protocol/transparent-payment';
export * from './packer';
export * from './models';
export * from './packageManager';
8 changes: 7 additions & 1 deletion src/iden3comm/types/packer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CircuitId } from '../../circuits';
import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../constants';
import { DIDDocument, VerificationMethod } from 'did-resolver';
import { StateVerificationOpts } from './models';
import { Attachment } from './protocol/attachment';

/**
* Protocol message type
Expand Down Expand Up @@ -51,14 +52,19 @@ export type BasicMessage = {
to?: string;
created_time?: number;
expires_time?: number;
attachments?: Attachment[];
};

/**
* Basic message with all possible fields required
*/
export type RequiredBasicMessage = Omit<Required<BasicMessage>, 'created_time' | 'expires_time'> & {
export type RequiredBasicMessage = Omit<
Required<BasicMessage>,
'created_time' | 'expires_time' | 'attachments'
> & {
created_time?: number;
expires_time?: number;
attachments?: Attachment[];
};

/**
Expand Down
13 changes: 13 additions & 0 deletions src/iden3comm/types/protocol/attachment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MediaType } from '../../constants';

export type Attachment = {
id: string;
description?: string;
media_type: MediaType;
data: AttachData;
};

export type AttachData = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
json: any;
};
3 changes: 1 addition & 2 deletions src/iden3comm/types/protocol/proposal-request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BasicMessage, DIDDocument, JsonDocumentObject } from '../';
import { BasicMessage, DIDDocument } from '../';
import { PROTOCOL_MESSAGE_TYPE } from '../../constants';

/** @beta ProposalRequestMessage is struct the represents proposal-request message */
Expand All @@ -10,7 +10,6 @@ export type ProposalRequestMessage = BasicMessage & {
/** @beta ProposalRequestMessageBody is struct the represents body for proposal-request */
export type ProposalRequestMessageBody = {
credentials: ProposalRequestCredential[];
metadata?: { type: string; data?: JsonDocumentObject };
did_doc?: DIDDocument;
};

Expand Down
32 changes: 32 additions & 0 deletions src/iden3comm/types/protocol/transparent-payment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// package protocol

import { PROTOCOL_MESSAGE_TYPE } from '../../constants';
import { RequiredBasicMessage } from '../packer';

export type TransparentPaymentInstructionMessage = RequiredBasicMessage & {
body: TransparentPaymentInstructionMessageBody;
type: typeof PROTOCOL_MESSAGE_TYPE.TRANSPARENT_PAYMENT_INSTRUCTION_MESSAGE_TYPE;
};

export type TransparentPaymentInstructionMessageBody = {
goal_code: string;
did?: string;
credentials: TransparentCredential[];
paymentData: TransparentPaymentData;
};

export type TransparentCredential = {
context: string;
type: string;
};

export type TransparentPaymentData = {
type: string;
signature: string;
recipient: string;
amount: string;
token?: string;
expiration: number;
nonce: number;
metadata?: string;
};
138 changes: 138 additions & 0 deletions tests/iden3comm/attachment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
Attachment,
AuthorizationRequestMessage,
byteEncoder,
createAuthorizationRequest,
createProposalRequest,
CredentialProposalHandler,
ICredentialWallet,
IDataStorage,
IdentityWallet,
IPackageManager,
KMS,
Proposal,
ProposalRequestMessage,
TransparentPaymentData,
TransparentPaymentInstructionMessage
} from '../../src';
import * as uuid from 'uuid';
import { getRandomBytes } from '@iden3/js-crypto';
import { Blockchain, buildDIDType, DID, DidMethod, Id, NetworkId } from '@iden3/js-iden3-core';
import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../../src/iden3comm/constants';
import { expect } from 'chai';

describe('Attachments', () => {
const didType = buildDIDType(DidMethod.Iden3, Blockchain.Polygon, NetworkId.Amoy);

const cred = {
context: 'https://schema.iden3.io/transparent-payment/1.0.0/schema.json',
type: 'TransparentCredential'
};

const paymentData = {
type: 'TransparentPaymentData',
signature: '0x0000000000000000000000000000000000000000000000000000000000000000',
recipient: '0x0000000000000000000000000000000000000000',
amount: '100',
token: 'USDT',
expiration: 1716153600,
nonce: 1,
metadata: '0x'
};

const [verifierDid, userDid, issuerDid] = [
getRandomBytes(27),
getRandomBytes(27),
getRandomBytes(27)
].map((seed) => DID.parseFromId(new Id(didType, seed)).string());

const verifierFlow = () => {
const id = uuid.v4();
const transparentPaymentInstructionMessage: TransparentPaymentInstructionMessage = {
id,
thid: id,
from: verifierDid,
to: issuerDid,
typ: MediaType.PlainMessage,
type: PROTOCOL_MESSAGE_TYPE.TRANSPARENT_PAYMENT_INSTRUCTION_MESSAGE_TYPE,
body: {
goal_code: PROTOCOL_MESSAGE_TYPE.PROPOSAL_REQUEST_MESSAGE_TYPE,
did: userDid,
credentials: [cred],
paymentData
}
};

const attachment: Attachment = {
id: id,
media_type: MediaType.PlainMessage,
data: {
json: transparentPaymentInstructionMessage
}
};
// verifier creates auth request with attachment
const authRequest = createAuthorizationRequest(
'transparent payment',
verifierDid,
'http://verifier.com/callback',
{
attachments: [attachment]
}
);

return authRequest;
};

const userFlow = (authRequest: AuthorizationRequestMessage): ProposalRequestMessage => {
// checks no credential
// creates cred proposal request
return createProposalRequest(DID.parse(userDid), DID.parse(issuerDid), {
credentials: [cred],
attachments: authRequest.attachments
});
};

const issuerFlow = async (proposalRequest: ProposalRequestMessage) => {
const credentialProposalHandler = new CredentialProposalHandler(
{
pack: () => null,
unpack: () => ({ unpackedMessage: proposalRequest })
} as unknown as IPackageManager,
new IdentityWallet(
new KMS(),
{
states: {
getRpcProvider: () => null
}
} as unknown as IDataStorage,
{
findByQuery: () => Promise.reject(new Error('no credential satisfied query'))
} as unknown as ICredentialWallet
),
{
agentUrl: 'http://issuer.com',
packerParams: {},
proposalResolverFn: (
context: string,
type: string,
opts: { paymentInfo?: TransparentPaymentData } | undefined
) => {
expect(opts?.paymentInfo).to.be.deep.equal(paymentData);

return Promise.resolve({} as Proposal);
}
}
);

return credentialProposalHandler.handleProposalRequest(
byteEncoder.encode(JSON.stringify(proposalRequest)),
{}
);
};

it('verifier transparent payment instruction flow', async () => {
const authRequest = verifierFlow();
const proposalRequest = userFlow(authRequest);
await issuerFlow(proposalRequest);
});
});
Loading