Skip to content

Commit

Permalink
add proposal-request (#208)
Browse files Browse the repository at this point in the history
* add proposal-request
  • Loading branch information
volodymyr-basiuk authored Apr 4, 2024
1 parent 4f2abb9 commit ac073cf
Show file tree
Hide file tree
Showing 8 changed files with 591 additions and 4 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.10.0",
"version": "1.10.1",
"description": "SDK to work with Polygon ID",
"main": "dist/node/cjs/index.js",
"module": "dist/node/esm/index.js",
Expand Down
6 changes: 5 additions & 1 deletion src/iden3comm/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export const PROTOCOL_MESSAGE_TYPE = Object.freeze({
// ContractInvokeRequestMessageType is type for request of contract invoke request
CONTRACT_INVOKE_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'proofs/1.0/contract-invoke-request',
// CredentialOnchainOfferMessageType is type of message with credential onchain offering
CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/1.0/onchain-offer'
CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/1.0/onchain-offer',
// ProposalRequestMessageType is type for proposal-request message
PROPOSAL_REQUEST_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/proposal-request',
// ProposalMessageType is type for proposal message
PROPOSAL_MESSAGE_TYPE: IDEN3_PROTOCOL + 'credentials/0.1/proposal'
});

/**
Expand Down
302 changes: 302 additions & 0 deletions src/iden3comm/handlers/credential-proposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import { PROTOCOL_MESSAGE_TYPE } from '../constants';
import { MediaType } from '../constants';
import {
CredentialOffer,
CredentialsOfferMessage,
IPackageManager,
JSONObject,
PackerParams
} from '../types';

import { DID } from '@iden3/js-iden3-core';
import * as uuid from 'uuid';
import { proving } from '@iden3/js-jwz';
import {
Proposal,
ProposalRequestCredential,
ProposalRequestMessage,
ProposalMessage
} from '../types/protocol/proposal-request';
import { IIdentityWallet } from '../../identity';
import { byteEncoder } from '../../utils';
import { W3CCredential } from '../../verifiable';

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

/**
* @beta
* createProposalRequest is a function to create protocol proposal-request protocol message
* @param {DID} sender - sender did
* @param {DID} receiver - receiver did
* @param {ProposalRequestCreationOptions} opts - creation options
* @returns `Promise<ProposalRequestMessage>`
*/
export function createProposalRequest(
sender: DID,
receiver: DID,
opts: ProposalRequestCreationOptions
): ProposalRequestMessage {
const uuidv4 = uuid.v4();
const request: ProposalRequestMessage = {
id: uuidv4,
thid: uuidv4,
from: sender.string(),
to: receiver.string(),
typ: MediaType.PlainMessage,
type: PROTOCOL_MESSAGE_TYPE.PROPOSAL_REQUEST_MESSAGE_TYPE,
body: opts
};
return request;
}

/**
* @beta
* createProposal is a function to create protocol proposal protocol message
* @param {DID} sender - sender did
* @param {DID} receiver - receiver did
* @param {Proposal[]} proposals - proposals
* @returns `Promise<ProposalRequestMessage>`
*/
export function createProposal(
sender: DID,
receiver: DID,
proposals?: Proposal[]
): ProposalMessage {
const uuidv4 = uuid.v4();
const request: ProposalMessage = {
id: uuidv4,
thid: uuidv4,
from: sender.string(),
to: receiver.string(),
typ: MediaType.PlainMessage,
type: PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE,
body: {
proposals: proposals || []
}
};
return request;
}

/**
* @beta
* Interface that allows the processing of the proposal-request
*
* @interface ICredentialProposalHandler
*/
export interface ICredentialProposalHandler {
/**
* @beta
* unpacks proposal-request
* @param {Uint8Array} request - raw byte message
* @returns `Promise<ProposalRequestMessage>`
*/
parseProposalRequest(request: Uint8Array): Promise<ProposalRequestMessage>;

/**
* @beta
* handle proposal-request
* @param {Uint8Array} request - raw byte message
* @param {ProposalRequestHandlerOptions} opts - handler options
* @returns {Promise<Uint8Array>}` - proposal response message
*/
handleProposalRequest(
request: Uint8Array,
opts?: ProposalRequestHandlerOptions
): Promise<Uint8Array>;

/**
* @beta
* handle proposal protocol message
* @param {ProposalMessage} proposal - proposal message
* @param {ProposalHandlerOptions} opts - options
* @returns `Promise<{
proposal: ProposalMessage;
}>`
*/
handleProposal(
proposal: ProposalMessage,
opts?: ProposalHandlerOptions
): Promise<{
proposal: ProposalMessage;
}>;
}

/** @beta ProposalRequestHandlerOptions represents proposal-request handler options */
export type ProposalRequestHandlerOptions = object;

/** @beta ProposalHandlerOptions represents proposal handler options */
export type ProposalHandlerOptions = {
proposalRequest?: ProposalRequestMessage;
};

/** @beta CredentialProposalHandlerParams represents credential proposal handler params */
export type CredentialProposalHandlerParams = {
agentUrl: string;
proposalResolverFn: (context: string, type: string) => Promise<Proposal>;
packerParams: PackerParams;
};

/**
*
* Allows to process ProposalRequest protocol message
* @beta
* @class CredentialProposalHandler
* @implements implements ICredentialProposalHandler interface
*/
export class CredentialProposalHandler implements ICredentialProposalHandler {
/**
* @beta Creates an instance of CredentialProposalHandler.
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope
* @param {IIdentityWallet} _identityWallet - identity wallet
* @param {CredentialProposalHandlerParams} _params - credential proposal handler params
*
*/

constructor(
private readonly _packerMgr: IPackageManager,
private readonly _identityWallet: IIdentityWallet,
private readonly _params: CredentialProposalHandlerParams
) {}

/**
* @inheritdoc ICredentialProposalHandler#parseProposalRequest
*/
async parseProposalRequest(request: Uint8Array): Promise<ProposalRequestMessage> {
const { unpackedMessage: message } = await this._packerMgr.unpack(request);
const proposalRequest = message as unknown as ProposalRequestMessage;
if (message.type !== PROTOCOL_MESSAGE_TYPE.PROPOSAL_REQUEST_MESSAGE_TYPE) {
throw new Error('Invalid media type');
}
return proposalRequest;
}

/**
* @inheritdoc ICredentialProposalHandler#handleProposalRequest
*/
async handleProposalRequest(
request: Uint8Array,
//eslint-disable-next-line @typescript-eslint/no-unused-vars
opts?: ProposalRequestHandlerOptions
): Promise<Uint8Array> {
if (
this._params.packerParams.mediaType === MediaType.SignedMessage &&
!this._params.packerParams.packerOptions
) {
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`);
}

const proposalRequest = await this.parseProposalRequest(request);

if (!proposalRequest.to) {
throw new Error(`failed request. empty 'to' field`);
}

if (!proposalRequest.from) {
throw new Error(`failed request. empty 'from' field`);
}

if (!proposalRequest.body?.credentials?.length) {
throw new Error(`failed request. no 'credentials' in body`);
}

const senderDID = DID.parse(proposalRequest.from);

let credOfferMessage: CredentialsOfferMessage | undefined = undefined;
let proposalMessage: ProposalMessage | undefined = undefined;
for (let i = 0; i < proposalRequest.body.credentials.length; i++) {
const cred = proposalRequest.body.credentials[i];

// check if there is credentials in the wallet
let credsFromWallet: W3CCredential[] = [];
try {
credsFromWallet = await this._identityWallet.findOwnedCredentialsByDID(senderDID, {
type: cred.type,
context: cred.context
});
} catch (e) {
if ((e as Error).message !== 'no credential satisfied query') {
throw e;
}
}

if (credsFromWallet.length) {
const guid = uuid.v4();
if (!credOfferMessage) {
credOfferMessage = {
id: guid,
typ: this._params.packerParams.mediaType,
type: PROTOCOL_MESSAGE_TYPE.CREDENTIAL_OFFER_MESSAGE_TYPE,
thid: proposalRequest.thid ?? guid,
body: {
url: this._params.agentUrl,
credentials: []
},
from: proposalRequest.to,
to: proposalRequest.from
};
}

credOfferMessage.body.credentials.push(
...credsFromWallet.map<CredentialOffer>((c) => ({
id: c.id,
description: ''
}))
);
continue;
}

// credential not found in the wallet, prepare proposal protocol message
const proposal = await this._params.proposalResolverFn(cred.context, cred.type);
if (!proposal) {
throw new Error(`can't resolve Proposal for type: ${cred.type}, context: ${cred.context}`);
}
if (!proposalMessage) {
const guid = uuid.v4();
proposalMessage = {
id: guid,
typ: this._params.packerParams.mediaType,
type: PROTOCOL_MESSAGE_TYPE.PROPOSAL_MESSAGE_TYPE,
thid: proposalRequest.thid ?? guid,
body: {
proposals: []
},
from: proposalRequest.to,
to: proposalRequest.from
};
}
proposalMessage.body?.proposals.push(proposal);
}

// if there is credentials in the wallet, return offer protocol message, otherwise proposal
const response = byteEncoder.encode(JSON.stringify(proposalMessage ?? credOfferMessage));

const packerOpts =
this._params.packerParams.mediaType === MediaType.SignedMessage
? this._params.packerParams.packerOptions
: {
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg
};

return this._packerMgr.pack(this._params.packerParams.mediaType, response, {
senderDID,
...packerOpts
});
}

/**
* @inheritdoc ICredentialProposalHandler#handleProposal
*/
async handleProposal(proposal: ProposalMessage, opts?: ProposalHandlerOptions) {
if (opts?.proposalRequest && opts.proposalRequest.from !== proposal.to) {
throw new Error(
`sender of the request is not a target of response - expected ${opts.proposalRequest.from}, given ${proposal.to}`
);
}
return { proposal };
}
}
1 change: 1 addition & 0 deletions src/iden3comm/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './contract-request';
export * from './refresh';
export * from './revocation-status';
export * from './common';
export * from './credential-proposal';
1 change: 1 addition & 0 deletions src/iden3comm/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './protocol/messages';
export * from './protocol/proof';
export * from './protocol/revocation';
export * from './protocol/contract-request';
export * from './protocol/proposal-request';

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

/** @beta ProposalRequestMessage is struct the represents proposal-request message */
export type ProposalRequestMessage = BasicMessage & {
body?: ProposalRequestMessageBody;
};

/** @beta ProposalRequestMessageBody is struct the represents body for proposal-request */
export type ProposalRequestMessageBody = {
credentials: ProposalRequestCredential[];
metadata?: { type: string; data?: JSONObject };
did_doc?: JSONObject;
};

/** @beta ProposalMessage is struct the represents proposal message */
export type ProposalMessage = BasicMessage & {
body?: ProposalMessageBody;
};

/** @beta ProposalMessageBody is struct the represents body for proposal message */
export type ProposalMessageBody = {
proposals: Proposal[];
};

/** @beta ProposalRequestCredential is struct the represents proposal request credential */
export type ProposalRequestCredential = {
type: string;
context: string;
};

/** @beta Proposal is struct the represents proposal inside proposal protocol message */
export type Proposal = {
credentials?: ProposalRequestCredential[];
type: string;
url?: string;
expiration?: string;
description?: string;
};
Loading

0 comments on commit ac073cf

Please sign in to comment.