diff --git a/examples/integration-scripts/credentials.test.ts b/examples/integration-scripts/credentials.test.ts index 4f87c6ce..3ae1f6d4 100644 --- a/examples/integration-scripts/credentials.test.ts +++ b/examples/integration-scripts/credentials.test.ts @@ -44,6 +44,10 @@ let holderAid: Aid; let verifierAid: Aid; let legalEntityAid: Aid; +let applySaid: string; +let offerSaid: string; +let agreeSaid: string; + beforeAll(async () => { [issuerClient, holderClient, verifierClient, legalEntityClient] = await getOrCreateClients(4); @@ -251,14 +255,13 @@ test('single signature credentials', async () => { ); const grantNotification = holderNotifications[0]; // should only have one notification right now - const [admit, sigs, aend] = await holderClient - .ipex() - .admit( - holderAid.name, - '', - grantNotification.a.d!, - createTimestamp() - ); + const [admit, sigs, aend] = await holderClient.ipex().admit({ + senderName: holderAid.name, + message: '', + grantSaid: grantNotification.a.d!, + recipient: issuerAid.prefix, + datetime: createTimestamp(), + }); const op = await holderClient .ipex() .submitAdmit(holderAid.name, admit, sigs, aend, [issuerAid.prefix]); @@ -289,7 +292,107 @@ test('single signature credentials', async () => { assert(holderCredential.atc !== undefined); }); - await step('holder IPEX present', async () => { + await step('verifier IPEX apply', async () => { + const [apply, sigs, _] = await verifierClient.ipex().apply({ + senderName: verifierAid.name, + schemaSaid: QVI_SCHEMA_SAID, + attributes: { LEI: '5493001KJTIIGC8Y1R17' }, + recipient: holderAid.prefix, + datetime: createTimestamp(), + }); + + const op = await verifierClient + .ipex() + .submitApply(verifierAid.name, apply, sigs, [holderAid.prefix]); + await waitOperation(verifierClient, op); + }); + + await step('holder IPEX apply receive and offer', async () => { + const holderNotifications = await waitForNotifications( + holderClient, + '/exn/ipex/apply' + ); + + const holderApplyNote = holderNotifications[0]; + assert(holderApplyNote.a.d); + + const apply = await holderClient.exchanges().get(holderApplyNote.a.d); + applySaid = apply.exn.d; + + let filter: { [x: string]: any } = { '-s': apply.exn.a.s }; + for (const key in apply.exn.a.a) { + filter[`-a-${key}`] = apply.exn.a.a[key]; + } + + const matchingCreds = await holderClient.credentials().list({ filter }); + expect(matchingCreds).toHaveLength(1); + + await markAndRemoveNotification(holderClient, holderNotifications[0]); + + const [offer, sigs, end] = await holderClient.ipex().offer({ + senderName: holderAid.name, + recipient: verifierAid.prefix, + acdc: new Serder(matchingCreds[0].sad), + applySaid: applySaid, + datetime: createTimestamp(), + }); + + const op = await holderClient + .ipex() + .submitOffer(holderAid.name, offer, sigs, end, [ + verifierAid.prefix, + ]); + await waitOperation(holderClient, op); + }); + + await step('verifier receive offer and agree', async () => { + const verifierNotifications = await waitForNotifications( + verifierClient, + '/exn/ipex/offer' + ); + + const verifierOfferNote = verifierNotifications[0]; + assert(verifierOfferNote.a.d); + + const offer = await verifierClient + .exchanges() + .get(verifierOfferNote.a.d); + offerSaid = offer.exn.d; + + expect(offer.exn.p).toBe(applySaid); + expect(offer.exn.e.acdc.a.LEI).toBe('5493001KJTIIGC8Y1R17'); + + await markAndRemoveNotification(verifierClient, verifierOfferNote); + + const [agree, sigs, _] = await verifierClient.ipex().agree({ + senderName: verifierAid.name, + recipient: holderAid.prefix, + offerSaid: offerSaid, + datetime: createTimestamp(), + }); + + const op = await verifierClient + .ipex() + .submitAgree(verifierAid.name, agree, sigs, [holderAid.prefix]); + await waitOperation(verifierClient, op); + }); + + await step('holder IPEX receive agree and grant/present', async () => { + const holderNotifications = await waitForNotifications( + holderClient, + '/exn/ipex/agree' + ); + + const holderAgreeNote = holderNotifications[0]; + assert(holderAgreeNote.a.d); + + const agree = await holderClient.exchanges().get(holderAgreeNote.a.d); + agreeSaid = agree.exn.d; + + expect(agree.exn.p).toBe(offerSaid); + + await markAndRemoveNotification(holderClient, holderAgreeNote); + const holderCredential = await holderClient .credentials() .get(qviCredentialId); @@ -303,6 +406,7 @@ test('single signature credentials', async () => { acdcAttachment: holderCredential.atc, ancAttachment: holderCredential.ancatc, issAttachment: holderCredential.issAtc, + agreeSaid: agreeSaid, datetime: createTimestamp(), }); @@ -321,15 +425,18 @@ test('single signature credentials', async () => { ); const verifierGrantNote = verifierNotifications[0]; + assert(verifierGrantNote.a.d); - const [admit3, sigs3, aend3] = await verifierClient - .ipex() - .admit( - verifierAid.name, - '', - verifierGrantNote.a.d!, - createTimestamp() - ); + const grant = await holderClient.exchanges().get(verifierGrantNote.a.d); + expect(grant.exn.p).toBe(agreeSaid); + + const [admit3, sigs3, aend3] = await verifierClient.ipex().admit({ + senderName: verifierAid.name, + message: '', + grantSaid: verifierGrantNote.a.d!, + recipient: holderAid.prefix, + datetime: createTimestamp(), + }); const op = await verifierClient .ipex() @@ -354,6 +461,7 @@ test('single signature credentials', async () => { holderClient, '/exn/ipex/admit' ); + await markAndRemoveNotification(holderClient, holderNotifications[0]); }); @@ -445,14 +553,13 @@ test('single signature credentials', async () => { ); const grantNotification = notifications[0]; - const [admit, sigs, aend] = await legalEntityClient - .ipex() - .admit( - legalEntityAid.name, - '', - grantNotification.a.d!, - createTimestamp() - ); + const [admit, sigs, aend] = await legalEntityClient.ipex().admit({ + senderName: legalEntityAid.name, + message: '', + grantSaid: grantNotification.a.d!, + recipient: holderAid.prefix, + datetime: createTimestamp(), + }); const op = await legalEntityClient .ipex() diff --git a/examples/integration-scripts/multisig-holder.test.ts b/examples/integration-scripts/multisig-holder.test.ts index 93162e54..8d1939af 100644 --- a/examples/integration-scripts/multisig-holder.test.ts +++ b/examples/integration-scripts/multisig-holder.test.ts @@ -510,9 +510,13 @@ async function multisigAdmitCredential( let mHab = await client.identifiers().get(memberAlias); let gHab = await client.identifiers().get(groupName); - const [admit, sigs, end] = await client - .ipex() - .admit(groupName, '', grantSaid, TIME); + const [admit, sigs, end] = await client.ipex().admit({ + senderName: groupName, + message: '', + grantSaid: grantSaid, + recipient: issuerPrefix, + datetime: TIME, + }); let op = await client .ipex() diff --git a/examples/integration-scripts/multisig.test.ts b/examples/integration-scripts/multisig.test.ts index 2b5dd14c..723ac04b 100644 --- a/examples/integration-scripts/multisig.test.ts +++ b/examples/integration-scripts/multisig.test.ts @@ -1079,9 +1079,12 @@ test('multisig', async function run() { console.log('Holder received exchange message with the grant message'); res = await client4.exchanges().get(msgSaid); - const [admit, asigs, aend] = await client4 - .ipex() - .admit('holder', '', res.exn.d); + const [admit, asigs, aend] = await client4.ipex().admit({ + senderName: 'holder', + message: '', + grantSaid: res.exn.d, + recipient: m['prefix'], + }); op4 = await client4 .ipex() diff --git a/examples/integration-scripts/singlesig-vlei-issuance.test.ts b/examples/integration-scripts/singlesig-vlei-issuance.test.ts index 1d87d522..eb4c9bbe 100644 --- a/examples/integration-scripts/singlesig-vlei-issuance.test.ts +++ b/examples/integration-scripts/singlesig-vlei-issuance.test.ts @@ -527,9 +527,13 @@ async function sendAdmitMessage( assert.equal(notifications.length, 1); const grantNotification = notifications[0]; - const [admit, sigs, aend] = await senderClient - .ipex() - .admit(senderAid.name, '', grantNotification.a.d!, createTimestamp()); + const [admit, sigs, aend] = await senderClient.ipex().admit({ + senderName: senderAid.name, + message: '', + grantSaid: grantNotification.a.d!, + recipient: recipientAid.prefix, + datetime: createTimestamp(), + }); let op = await senderClient .ipex() diff --git a/examples/integration-scripts/utils/multisig-utils.ts b/examples/integration-scripts/utils/multisig-utils.ts index 6cc876ae..df6fbd59 100644 --- a/examples/integration-scripts/utils/multisig-utils.ts +++ b/examples/integration-scripts/utils/multisig-utils.ts @@ -157,9 +157,13 @@ export async function admitMultisig( '/exn/ipex/grant' ); - const [admit, sigs, end] = await client - .ipex() - .admit(multisigAID.name, '', grantMsgSaid, timestamp); + const [admit, sigs, end] = await client.ipex().admit({ + senderName: multisigAID.name, + message: '', + grantSaid: grantMsgSaid, + recipient: recipientAID.prefix, + datetime: timestamp, + }); await client .ipex() diff --git a/examples/integration-scripts/utils/test-util.ts b/examples/integration-scripts/utils/test-util.ts index d2485296..f22b1950 100644 --- a/examples/integration-scripts/utils/test-util.ts +++ b/examples/integration-scripts/utils/test-util.ts @@ -42,9 +42,12 @@ export async function admitSinglesig( '/exn/ipex/grant' ); - const [admit, sigs, aend] = await client - .ipex() - .admit(aidName, '', grantMsgSaid); + const [admit, sigs, aend] = await client.ipex().admit({ + senderName: aidName, + message: '', + grantSaid: grantMsgSaid, + recipient: recipientAid.prefix, + }); await client .ipex() diff --git a/src/keri/app/credentialing.ts b/src/keri/app/credentialing.ts index 36ce5eb0..a13ddba3 100644 --- a/src/keri/app/credentialing.ts +++ b/src/keri/app/credentialing.ts @@ -98,6 +98,85 @@ export interface RevokeCredentialResult { op: Operation; } +export interface IpexApplyArgs { + /** + * Alias for the IPEX sender AID + */ + senderName: string; + + /** + * Prefix of the IPEX recipient AID + */ + recipient: string; + + /** + * Message to send + */ + message?: string; + + /** + * SAID of schema to apply for + */ + schemaSaid: string; + + /** + * Optional attributes for selective disclosure + */ + attributes?: Record; + datetime?: string; +} + +export interface IpexOfferArgs { + /** + * Alias for the IPEX sender AID + */ + senderName: string; + + /** + * Prefix of the IPEX recipient AID + */ + recipient: string; + + /** + * Message to send + */ + message?: string; + + /** + * ACDC to offer + */ + acdc: Serder; + + /** + * Optional qb64 SAID of apply message this offer is responding to + */ + applySaid?: string; + datetime?: string; +} + +export interface IpexAgreeArgs { + /** + * Alias for the IPEX sender AID + */ + senderName: string; + + /** + * Prefix of the IPEX recipient AID + */ + recipient: string; + + /** + * Message to send + */ + message?: string; + + /** + * qb64 SAID of offer message this agree is responding to + */ + offerSaid: string; + datetime?: string; +} + export interface IpexGrantArgs { /** * Alias for the IPEX sender AID @@ -117,7 +196,7 @@ export interface IpexGrantArgs { /** * qb64 SAID of agree message this grant is responding to */ - agree?: string; + agreeSaid?: string; datetime?: string; acdc: Serder; acdcAttachment?: string; @@ -127,6 +206,29 @@ export interface IpexGrantArgs { ancAttachment?: string; } +export interface IpexAdmitArgs { + /** + * Alias for the IPEX sender AID + */ + senderName: string; + + /** + * Prefix of the IPEX recipient AID + */ + recipient: string; + + /** + * Message to send + */ + message?: string; + + /** + * qb64 SAID of agree message this admit is responding to + */ + grantSaid: string; + datetime?: string; +} + /** * Credentials */ @@ -735,6 +837,139 @@ export class Ipex { this.client = client; } + /** + * Create an IPEX apply EXN message + */ + async apply(args: IpexApplyArgs): Promise<[Serder, string[], string]> { + const hab = await this.client.identifiers().get(args.senderName); + const data = { + m: args.message ?? '', + s: args.schemaSaid, + a: args.attributes ?? {}, + }; + + return this.client + .exchanges() + .createExchangeMessage( + hab, + '/ipex/apply', + data, + {}, + args.recipient, + args.datetime, + undefined + ); + } + + async submitApply( + name: string, + exn: Serder, + sigs: string[], + recp: string[] + ): Promise { + const body = { + exn: exn.ked, + sigs, + rec: recp, + }; + + const response = await this.client.fetch( + `/identifiers/${name}/ipex/apply`, + 'POST', + body + ); + + return response.json(); + } + + /** + * Create an IPEX offer EXN message + */ + async offer(args: IpexOfferArgs): Promise<[Serder, string[], string]> { + const hab = await this.client.identifiers().get(args.senderName); + const data = { + m: args.message ?? '', + }; + + return this.client + .exchanges() + .createExchangeMessage( + hab, + '/ipex/offer', + data, + { acdc: [args.acdc, undefined] }, + args.recipient, + args.datetime, + args.applySaid + ); + } + + async submitOffer( + name: string, + exn: Serder, + sigs: string[], + atc: string, + recp: string[] + ): Promise { + const body = { + exn: exn.ked, + sigs, + atc, + rec: recp, + }; + + const response = await this.client.fetch( + `/identifiers/${name}/ipex/offer`, + 'POST', + body + ); + + return response.json(); + } + + /** + * Create an IPEX agree EXN message + */ + async agree(args: IpexAgreeArgs): Promise<[Serder, string[], string]> { + const hab = await this.client.identifiers().get(args.senderName); + const data = { + m: args.message ?? '', + }; + + return this.client + .exchanges() + .createExchangeMessage( + hab, + '/ipex/agree', + data, + {}, + args.recipient, + args.datetime, + args.offerSaid + ); + } + + async submitAgree( + name: string, + exn: Serder, + sigs: string[], + recp: string[] + ): Promise { + const body = { + exn: exn.ked, + sigs, + rec: recp, + }; + + const response = await this.client.fetch( + `/identifiers/${name}/ipex/agree`, + 'POST', + body + ); + + return response.json(); + } + /** * Create an IPEX grant EXN message */ @@ -742,7 +977,6 @@ export class Ipex { const hab = await this.client.identifiers().get(args.senderName); const data = { m: args.message ?? '', - i: args.recipient, }; let atc = args.ancAttachment; @@ -778,7 +1012,7 @@ export class Ipex { embeds, args.recipient, args.datetime, - args.agree + args.agreeSaid ); } @@ -807,22 +1041,11 @@ export class Ipex { /** * Create an IPEX admit EXN message - * @async - * @param {string} name Name or alias of the identifier - * @param {string} message accompany human readable description of the credential being admitted - * @param {string} grant qb64 SAID of grant message this admit is responding to - * @param {string} datetime Optional datetime to set for the credential - * @returns {Promise<[Serder, string[], string]>} A promise to the long-running operation */ - async admit( - name: string, - message: string, - grant: string, - datetime?: string - ): Promise<[Serder, string[], string]> { - const hab = await this.client.identifiers().get(name); + async admit(args: IpexAdmitArgs): Promise<[Serder, string[], string]> { + const hab = await this.client.identifiers().get(args.senderName); const data: any = { - m: message, + m: args.message, }; return this.client @@ -832,9 +1055,9 @@ export class Ipex { '/ipex/admit', data, {}, - '', - datetime, - grant + args.recipient, + args.datetime, + args.grantSaid ); } diff --git a/test/app/credentialing.test.ts b/test/app/credentialing.test.ts index 53d64243..33aa0d1e 100644 --- a/test/app/credentialing.test.ts +++ b/test/app/credentialing.test.ts @@ -372,7 +372,7 @@ describe('Credentialing', () => { }); describe('Ipex', () => { - it('should do all the IPEX things', async () => { + it('IPEX - grant-admit flow initiated by discloser', async () => { await libsodium.ready; const bran = '0123456789abcdefghijk'; const client = new SignifyClient(url, bran, Tier.low, boot_url); @@ -501,29 +501,30 @@ describe('Ipex', () => { 'http://127.0.0.1:3901/identifiers/multisig/ipex/grant' ); - const [admit, asigs, aend] = await ipex.admit( - 'holder', - '', - grant.ked.d, - mockCredential.sad.a.dt - ); + const [admit, asigs, aend] = await ipex.admit({ + senderName: 'holder', + message: '', + grantSaid: grant.ked.d, + recipient: holder, + datetime: mockCredential.sad.a.dt, + }); assert.deepStrictEqual(admit.ked, { - v: 'KERI10JSON000120_', + v: 'KERI10JSON000178_', t: 'exn', - d: 'EHMPkdV7QJ3a4RoDg43ffa7ytO6VbvEE4WiIbfcYvZNe', + d: 'EJrfQsTZhkHC6vDEwkbWISpbBk9HFLO3NuI5uByYw8tH', i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', p: 'EPVuNFwXTG56BvNtGjeyxncY-MfZMXOAgEtsmIvktkdb', dt: '2023-08-23T15:16:07.553000+00:00', r: '/ipex/admit', - rp: '', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', q: {}, - a: { m: '', i: '' }, + a: { m: '', i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k' }, e: {}, }); assert.deepStrictEqual(asigs, [ - 'AADpPFED69bio-P5KtvUO46hkGzN-gGr2ob83jq_AGrmRcwUWIy71iClQ0YggT75T-ORwEIN4dIvIABv7z1r6UIH', + 'AAC4MTRQR-U8_3Hf53f2nJuh3n93lauXSHUkF1Yk2diTHwF-qkcBHn_jd-6pgRnRtBV2CInfwZyOsSL2CrRyuNEN', ]); await ipex.submitAdmit('multisig', admit, asigs, aend, [holder]); @@ -535,4 +536,372 @@ describe('Ipex', () => { assert.equal(aend, ''); }); + + it('IPEX - apply-admit flow initiated by disclosee', async () => { + await libsodium.ready; + const bran = '0123456789abcdefghijk'; + const client = new SignifyClient(url, bran, Tier.low, boot_url); + + await client.boot(); + await client.connect(); + + const ipex = client.ipex(); + + const holder = 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k'; + const [_, acdc] = Saider.saidify(mockCredential.sad); + + // Create iss + const vs = versify(Ident.KERI, undefined, Serials.JSON, 0); + const _iss = { + v: vs, + t: Ilks.iss, + d: '', + i: mockCredential.sad.d, + s: '0', + ri: mockCredential.sad.ri, + dt: mockCredential.sad.a.dt, + }; + + const [issSaider, iss] = Saider.saidify(_iss); + const iserder = new Serder(iss); + const anc = interact({ + pre: mockCredential.sad.i, + sn: 1, + data: [{}], + dig: mockCredential.sad.d, + version: undefined, + kind: undefined, + }); + + const [apply, applySigs, applyEnd] = await ipex.apply({ + senderName: 'multisig', + recipient: holder, + message: 'Applying', + schemaSaid: mockCredential.sad.s, + attributes: { LEI: mockCredential.sad.a.LEI }, + datetime: mockCredential.sad.a.dt, + }); + + assert.deepStrictEqual(apply.ked, { + v: 'KERI10JSON0001aa_', + t: 'exn', + d: 'ELjIE5cr_M2r7oUYw2pwcdNY_ZBuEgRlefaP0zSs_bXL', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: '', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/apply', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { + m: 'Applying', + i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + s: 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', + a: { LEI: '5493001KJTIIGC8Y1R17' }, + }, + e: {}, + }); + + assert.deepStrictEqual(applySigs, [ + 'AADJYSkOTxd8KfH4YUKWWjkNynAH4fm3wcKOPmepLiI_iuNPV9TL-sIRxLeCBG5rQmqXtnSP0Wi6jgI7sHC9PBgF', + ]); + + assert.equal(applyEnd, ''); + + await ipex.submitApply('multisig', apply, applySigs, [holder]); + let lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/apply' + ); + + const [offer, offerSigs, offerEnd] = await ipex.offer({ + senderName: 'multisig', + recipient: holder, + message: 'How about this', + acdc: new Serder(acdc), + datetime: mockCredential.sad.a.dt, + applySaid: apply.ked.d, + }); + + assert.deepStrictEqual(offer.ked, { + v: 'KERI10JSON000357_', + t: 'exn', + d: 'EBkyi_fhfnDWJXi4FW6t_o4F7Oep3PvSZ6E-qT716kfU', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'ELjIE5cr_M2r7oUYw2pwcdNY_ZBuEgRlefaP0zSs_bXL', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/offer', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { + m: 'How about this', + i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + }, + e: { + acdc: { + v: 'ACDC10JSON000197_', + d: 'EMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo', + i: 'EMQQpnSkgfUOgWdzQTWfrgiVHKIDAhvAZIPQ6z3EAfz1', + ri: 'EGK216v1yguLfex4YRFnG7k1sXRjh3OKY7QqzdKsx7df', + s: 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', + a: { + d: 'EK0GOjijKd8_RLYz9qDuuG29YbbXjU8yJuTQanf07b6P', + i: 'EKvn1M6shPLnXTb47bugVJblKMuWC0TcLIePP8p98Bby', + dt: '2023-08-23T15:16:07.553000+00:00', + LEI: '5493001KJTIIGC8Y1R17', + }, + }, + d: 'EK72JZyOyz81Jvt--iebptfhIWiw2ZdQg7ondKd-EyJF', + }, + }); + + assert.deepStrictEqual(offerSigs, [ + 'AADUeKpUxTKVS1DYRuHC3YDM8T4YMREnQLi00QiJH2Q_WjtMZTd7rBLH12xAJkt8h4KEOn4U_c-jpHdj9S9qKXsO', + ]); + assert.equal(offerEnd, ''); + + await ipex.submitOffer('multisig', offer, offerSigs, offerEnd, [ + holder, + ]); + lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/offer' + ); + + const [agree, agreeSigs, agreeEnd] = await ipex.agree({ + senderName: 'multisig', + recipient: holder, + message: 'OK!', + datetime: mockCredential.sad.a.dt, + offerSaid: offer.ked.d, + }); + + assert.deepStrictEqual(agree.ked, { + v: 'KERI10JSON00017b_', + t: 'exn', + d: 'EDLk56nlLrPHzhy3-5BHkhBNi-7tWUseWL_83I5QRmZ8', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'EBkyi_fhfnDWJXi4FW6t_o4F7Oep3PvSZ6E-qT716kfU', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/agree', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { + m: 'OK!', + i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + }, + e: {}, + }); + + assert.deepStrictEqual(agreeSigs, [ + 'AADgFlQVwRU7PF_gi4_o-wEgh3lZxzDtiwnIr9XFBrLOxhR6nBJNhrHZ_MkagCQcFHMpFkD9Vhxgq8HkV2gssPcO', + ]); + assert.equal(agreeEnd, ''); + + await ipex.submitAgree('multisig', agree, agreeSigs, [holder]); + lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/agree' + ); + + const [grant, gsigs, end] = await ipex.grant({ + senderName: 'multisig', + recipient: holder, + message: '', + acdc: new Serder(acdc), + iss: iserder, + anc, + datetime: mockCredential.sad.a.dt, + agreeSaid: agree.ked.d, + }); + + assert.deepStrictEqual(grant.ked, { + v: 'KERI10JSON000511_', + t: 'exn', + d: 'ENwwMpAuZ3NaZqqeydm3G18EDZFWuHzeJMfzfwNkb99N', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'EDLk56nlLrPHzhy3-5BHkhBNi-7tWUseWL_83I5QRmZ8', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/grant', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { m: '', i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k' }, + e: { + acdc: { + v: 'ACDC10JSON000197_', + d: 'EMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo', + i: 'EMQQpnSkgfUOgWdzQTWfrgiVHKIDAhvAZIPQ6z3EAfz1', + ri: 'EGK216v1yguLfex4YRFnG7k1sXRjh3OKY7QqzdKsx7df', + s: 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', + a: { + d: 'EK0GOjijKd8_RLYz9qDuuG29YbbXjU8yJuTQanf07b6P', + i: 'EKvn1M6shPLnXTb47bugVJblKMuWC0TcLIePP8p98Bby', + dt: '2023-08-23T15:16:07.553000+00:00', + LEI: '5493001KJTIIGC8Y1R17', + }, + }, + iss: { + v: 'KERI10JSON0000ed_', + t: 'iss', + d: 'ENf3IEYwYtFmlq5ZzoI-zFzeR7E3ZNRN2YH_0KAFbdJW', + i: 'EMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo', + s: '0', + ri: 'EGK216v1yguLfex4YRFnG7k1sXRjh3OKY7QqzdKsx7df', + dt: '2023-08-23T15:16:07.553000+00:00', + }, + anc: { + v: 'KERI10JSON0000cd_', + t: 'ixn', + d: 'ECVCyxNpB4PJkpLbWqI02WXs1wf7VUxPNY2W28SN2qqm', + i: 'EMQQpnSkgfUOgWdzQTWfrgiVHKIDAhvAZIPQ6z3EAfz1', + s: '1', + p: 'EMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo', + a: [{}], + }, + d: 'EGpSjqjavdzgjQiyt0AtrOutWfKrj5gR63lOUUq-1sL-', + }, + }); + + assert.deepStrictEqual(gsigs, [ + 'AAB61_g8jLGO1vx8Fadd6UrDItNACwFAiuAvWGrm_szxWWNZwT21V0N79Q7bRHNdVzZudgAKVUhNUHhnwrUW6jsK', + ]); + assert.equal( + end, + '-LAg4AACA-e-acdc-IABEMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo0AAAAAAAAAAAAAAAAAAAAAAAENf3IEYwYtFmlq5Zz' + + 'oI-zFzeR7E3ZNRN2YH_0KAFbdJW-LAW5AACAA-e-iss-VAS-GAB0AAAAAAAAAAAAAAAAAAAAAAAECVCyxNpB4PJkpLbWqI02WXs1wf7VU' + + 'xPNY2W28SN2qqm-LAa5AACAA-e-anc-AABAADMtDfNihvCSXJNp1VronVojcPGo--0YZ4Kh6CAnowRnn4Or4FgZQqaqCEv6XVS413qfZo' + + 'Vp8j2uxTTPkItO7ED' + ); + + const [ng, ngsigs, ngend] = await ipex.grant({ + senderName: 'multisig', + recipient: holder, + message: '', + acdc: new Serder(acdc), + acdcAttachment: d(serializeACDCAttachment(iserder)), + iss: iserder, + issAttachment: d(serializeIssExnAttachment(anc)), + anc, + ancAttachment: + '-AABAADMtDfNihvCSXJNp1VronVojcPGo--0YZ4Kh6CAnowRnn4Or4FgZQqaqCEv6XVS413qfZoVp8j2uxTTPkItO7ED', + datetime: mockCredential.sad.a.dt, + agreeSaid: agree.ked.d, + }); + + assert.deepStrictEqual(ng.ked, grant.ked); + assert.deepStrictEqual(ngsigs, gsigs); + assert.deepStrictEqual(ngend, ngend); + + await ipex.submitGrant('multisig', ng, ngsigs, ngend, [holder]); + lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/grant' + ); + + const [admit, asigs, aend] = await ipex.admit({ + senderName: 'holder', + message: '', + recipient: holder, + grantSaid: grant.ked.d, + datetime: mockCredential.sad.a.dt, + }); + + assert.deepStrictEqual(admit.ked, { + v: 'KERI10JSON000178_', + t: 'exn', + d: 'EPcEK9tPuLOHbLiPm_FETkIVLjHhwuUiZDRDKW6Hh0JF', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'ENwwMpAuZ3NaZqqeydm3G18EDZFWuHzeJMfzfwNkb99N', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/admit', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { m: '', i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k' }, + e: {}, + }); + + assert.deepStrictEqual(asigs, [ + 'AABqIUE6czxB5BotjxFUZT9Gu8tkFkAx7bOYQzWD422r-HS8z_6gaNuIlpnABHjxlX7PEXFDTj8WnoGVW197XlQP', + ]); + + await ipex.submitAdmit('multisig', admit, asigs, aend, [holder]); + lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/admit' + ); + + assert.equal(aend, ''); + }); + + it('IPEX - discloser can create an offer without apply', async () => { + await libsodium.ready; + const bran = '0123456789abcdefghijk'; + const client = new SignifyClient(url, bran, Tier.low, boot_url); + + await client.boot(); + await client.connect(); + + const ipex = client.ipex(); + + const holder = 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k'; + const [_, acdc] = Saider.saidify(mockCredential.sad); + + const [offer, offerSigs, offerEnd] = await ipex.offer({ + senderName: 'multisig', + recipient: holder, + message: 'Offering this', + acdc: new Serder(acdc), + datetime: mockCredential.sad.a.dt, + }); + + assert.deepStrictEqual(offer.ked, { + v: 'KERI10JSON00032a_', + t: 'exn', + d: 'EFmPdhVnJIrMZ0b6Nyk-4s2NP1InR3wgvBGcbxl2Cd8i', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: '', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/offer', + rp: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + q: {}, + a: { + m: 'Offering this', + i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + }, + e: { + acdc: { + v: 'ACDC10JSON000197_', + d: 'EMwcsEMUEruPXVwPCW7zmqmN8m0I3CihxolBm-RDrsJo', + i: 'EMQQpnSkgfUOgWdzQTWfrgiVHKIDAhvAZIPQ6z3EAfz1', + ri: 'EGK216v1yguLfex4YRFnG7k1sXRjh3OKY7QqzdKsx7df', + s: 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', + a: { + d: 'EK0GOjijKd8_RLYz9qDuuG29YbbXjU8yJuTQanf07b6P', + i: 'EKvn1M6shPLnXTb47bugVJblKMuWC0TcLIePP8p98Bby', + dt: '2023-08-23T15:16:07.553000+00:00', + LEI: '5493001KJTIIGC8Y1R17', + }, + }, + d: 'EK72JZyOyz81Jvt--iebptfhIWiw2ZdQg7ondKd-EyJF', + }, + }); + + assert.deepStrictEqual(offerSigs, [ + 'AACeQZ8RAcD2qFbkGXiUAQRJpZL4qanNH50a0LnkrflOC9JB2UJo3vvy3buiOSLoo0z9uMNhqa79ToXwVCAxg9MK', + ]); + assert.equal(offerEnd, ''); + + await ipex.submitOffer('multisig', offer, offerSigs, offerEnd, [ + holder, + ]); + let lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; + assert.equal( + lastCall[0], + 'http://127.0.0.1:3901/identifiers/multisig/ipex/offer' + ); + }); });