diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index abdfb49d..a26cc96d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest'] - keria-version: ['latest'] + keria-version: ['0.2.0-dev1'] node-version: ['20'] env: KERIA_IMAGE_TAG: ${{ matrix.keria-version }} diff --git a/docker-compose.yaml b/docker-compose.yaml index 1fb5e6ab..6beb2588 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -26,7 +26,7 @@ services: - 7723:7723 keria: - image: ${KERIA_IMAGE:-weboftrust/keria}:${KERIA_IMAGE_TAG:-latest} + image: ${KERIA_IMAGE:-weboftrust/keria}:${KERIA_IMAGE_TAG:-0.2.0-dev1} environment: - KERI_AGENT_CORS=1 - KERI_URL=http://keria:3902 diff --git a/examples/integration-scripts/credentials.test.ts b/examples/integration-scripts/credentials.test.ts index 0bdbacfe..cc43093c 100644 --- a/examples/integration-scripts/credentials.test.ts +++ b/examples/integration-scripts/credentials.test.ts @@ -51,6 +51,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); @@ -296,7 +300,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, + schema: 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), + apply: 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, + offer: 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); @@ -310,6 +414,7 @@ test('single signature credentials', async () => { acdcAttachment: holderCredential.atc, ancAttachment: holderCredential.ancatc, issAttachment: holderCredential.issAtc, + agree: agreeSaid, datetime: createTimestamp(), }); @@ -328,6 +433,10 @@ test('single signature credentials', async () => { ); const verifierGrantNote = verifierNotifications[0]; + assert(verifierGrantNote.a.d); + + const grant = await holderClient.exchanges().get(verifierGrantNote.a.d); + expect(grant.exn.p).toBe(agreeSaid); const [admit3, sigs3, aend3] = await verifierClient .ipex() @@ -361,6 +470,7 @@ test('single signature credentials', async () => { holderClient, '/exn/ipex/admit' ); + await markAndRemoveNotification(holderClient, holderNotifications[0]); }); diff --git a/examples/integration-scripts/salty.test.ts b/examples/integration-scripts/salty.test.ts index 95b88d19..39be3d73 100644 --- a/examples/integration-scripts/salty.test.ts +++ b/examples/integration-scripts/salty.test.ts @@ -156,13 +156,13 @@ test('salty', async () => { const events = client1.keyEvents(); const log = await events.get(aid['prefix']); assert.equal(log.length, 3); - let serder = new signify.Serder(log[0]); + let serder = new signify.Serder(log[0].ked); assert.equal(serder.pre, icp.pre); assert.equal(serder.ked['d'], icp.ked['d']); - serder = new signify.Serder(log[1]); + serder = new signify.Serder(log[1].ked); assert.equal(serder.pre, rot.pre); assert.equal(serder.ked['d'], rot.ked['d']); - serder = new signify.Serder(log[2]); + serder = new signify.Serder(log[2].ked); assert.equal(serder.pre, ixn.pre); assert.equal(serder.ked['d'], ixn.ked['d']); diff --git a/examples/integration-scripts/test-setup-single-client.test.ts b/examples/integration-scripts/test-setup-single-client.test.ts index cf2f941b..bed582a0 100644 --- a/examples/integration-scripts/test-setup-single-client.test.ts +++ b/examples/integration-scripts/test-setup-single-client.test.ts @@ -19,9 +19,6 @@ afterAll(async () => { describe('test-setup-single-client', () => { test('step1', async () => { - expect(client.agent?.pre).toEqual( - 'EC60ue9GOpQGrLBlS9T0dO6JkBTbv3V05Y4O730QBBoc' - ); expect(client.controller?.pre).toEqual( 'EB3UGWwIMq7ppzcQ697ImQIuXlBG5jzh-baSx-YG3-tY' ); @@ -33,7 +30,7 @@ describe('test-setup-single-client', () => { switch (env.preset) { case 'local': expect(name1_oobi).toEqual( - `http://127.0.0.1:3902/oobi/${name1_id}/agent/EC60ue9GOpQGrLBlS9T0dO6JkBTbv3V05Y4O730QBBoc` + `http://127.0.0.1:3902/oobi/${name1_id}/agent/${client.agent?.pre}` ); expect(oobi.oobis[0]).toEqual( `http://127.0.0.1:5642/oobi/${name1_id}/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha` @@ -47,7 +44,7 @@ describe('test-setup-single-client', () => { break; case 'docker': expect(name1_oobi).toEqual( - `http://keria:3902/oobi/${name1_id}/agent/EC60ue9GOpQGrLBlS9T0dO6JkBTbv3V05Y4O730QBBoc` + `http://keria:3902/oobi/${name1_id}/agent/${client.agent?.pre}` ); expect(oobi.oobis[0]).toEqual( `http://witness-demo:5642/oobi/${name1_id}/witness/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha` diff --git a/src/keri/app/aiding.ts b/src/keri/app/aiding.ts index 011848fa..02a1b1ac 100644 --- a/src/keri/app/aiding.ts +++ b/src/keri/app/aiding.ts @@ -227,14 +227,8 @@ export class Identifier { icp: serder.ked, sigs: sigs, proxy: proxy, - smids: - states != undefined - ? states.map((state) => state.i) - : undefined, - rmids: - rstates != undefined - ? rstates.map((state) => state.i) - : undefined, + smids: states, + rmids: rstates, }; jsondata[algo] = keeper.params(); @@ -278,8 +272,8 @@ export class Identifier { jsondata[keeper.algo] = keeper.params(); const res = await this.client.fetch( - '/identifiers/' + name + '?type=ixn', - 'PUT', + '/identifiers/' + name + '/events', + 'POST', jsondata ); return new EventResult(serder, sigs, res); @@ -376,8 +370,8 @@ export class Identifier { jsondata[keeper.algo] = keeper.params(); const res = await this.client.fetch( - '/identifiers/' + name, - 'PUT', + '/identifiers/' + name + '/events', + 'POST', jsondata ); return new EventResult(serder, sigs, res); diff --git a/src/keri/app/credentialing.ts b/src/keri/app/credentialing.ts index f6a4e4f3..54b4b78e 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 + */ + schema: 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 + */ + apply?: 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 + */ + offer: string; + datetime?: string; +} + export interface IpexGrantArgs { /** * Alias for the IPEX sender AID @@ -735,6 +814,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.schema, + a: args.attributes ?? {}, + i: args.recipient, + }; + + return this.client + .exchanges() + .createExchangeMessage( + hab, + '/ipex/apply', + data, + {}, + undefined, + 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] }, + undefined, + args.datetime, + args.apply + ); + } + + 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, + {}, + undefined, + args.datetime, + args.offer + ); + } + + 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 */ diff --git a/test/app/aiding.test.ts b/test/app/aiding.test.ts index 0872fc6c..9a579bd0 100644 --- a/test/app/aiding.test.ts +++ b/test/app/aiding.test.ts @@ -169,8 +169,8 @@ describe('Aiding', () => { await client.identifiers().rotate('aid1'); const lastCall = client.getLastMockRequest(); - assert.equal(lastCall.path, '/identifiers/aid1'); - assert.equal(lastCall.method, 'PUT'); + assert.equal(lastCall.path, '/identifiers/aid1/events'); + assert.equal(lastCall.method, 'POST'); assert.deepEqual(lastCall.body.rot, { v: 'KERI10JSON000160_', t: 'rot', @@ -215,8 +215,8 @@ describe('Aiding', () => { await client.identifiers().rotate('aid1'); const lastCall = client.getLastMockRequest(); - assert.equal(lastCall.path, '/identifiers/aid1'); - assert.equal(lastCall.method, 'PUT'); + assert.equal(lastCall.path, '/identifiers/aid1/events'); + assert.equal(lastCall.method, 'POST'); expect(lastCall.body.rot).toMatchObject({ v: 'KERI10JSON000160_', t: 'rot', @@ -241,8 +241,8 @@ describe('Aiding', () => { const lastCall = client.getLastMockRequest(); - expect(lastCall.path).toEqual('/identifiers/aid1?type=ixn'); - expect(lastCall.method).toEqual('PUT'); + expect(lastCall.path).toEqual('/identifiers/aid1/events'); + expect(lastCall.method).toEqual('POST'); expect(lastCall.body.ixn).toMatchObject({ v: 'KERI10JSON000138_', t: 'ixn', @@ -283,8 +283,8 @@ describe('Aiding', () => { const lastCall = client.getLastMockRequest(); - expect(lastCall.path).toEqual('/identifiers/aid1?type=ixn'); - expect(lastCall.method).toEqual('PUT'); + expect(lastCall.path).toEqual('/identifiers/aid1/events'); + expect(lastCall.method).toEqual('POST'); expect(lastCall.body.ixn).toMatchObject({ s: 'b', a: data, diff --git a/test/app/credentialing.test.ts b/test/app/credentialing.test.ts index dfefeaca..99cf05e1 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); @@ -533,4 +533,362 @@ 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', + schema: mockCredential.sad.s, + attributes: { LEI: mockCredential.sad.a.LEI }, + datetime: mockCredential.sad.a.dt, + }); + + assert.deepStrictEqual(apply.ked, { + v: 'KERI10JSON000176_', + t: 'exn', + d: 'EBbgtaWX8DYihokxDbq0k0RFjaQM7VgH3_88y3grPNsh', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: '', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/apply', + q: {}, + a: { + m: 'Applying', + i: 'ELjSFdrTdCebJlmvbFNX9-TLhR2PO0_60al1kQp5_e6k', + s: 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', + a: { LEI: '5493001KJTIIGC8Y1R17' }, + }, + e: {}, + }); + + assert.deepStrictEqual(applySigs, [ + 'AAADMrTKGghic8ye_BSoo1I3G5oit5GXvo3RXXTerZQCYtA4tYbji0bURgQII_ACT8louPDUgpWPQ_WscIa4v64I', + ]); + + 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, + apply: apply.ked.d, + }); + + assert.deepStrictEqual(offer.ked, { + v: 'KERI10JSON0002f0_', + t: 'exn', + d: 'EIOCPiCWlfxq4XnqZDaLjx4IOT4AIYqut3dKKIw5xnIa', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'EBbgtaWX8DYihokxDbq0k0RFjaQM7VgH3_88y3grPNsh', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/offer', + q: {}, + a: { + m: 'How about this', + }, + 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, [ + 'AADWWHG9LHz5sLeE98HnsbYWyVl7zO4z4FOyWVyjOyWT5-VTa1ZTYRddScEmyOcG-E9XK0XSWe-cM5L3GJqpn5cN', + ]); + 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, + offer: offer.ked.d, + }); + + assert.deepStrictEqual(agree.ked, { + v: 'KERI10JSON000114_', + t: 'exn', + d: 'ECVqeKGCclO-u0DEbJQoRwepE9RKGZRo_zArRgGpdZbI', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'EIOCPiCWlfxq4XnqZDaLjx4IOT4AIYqut3dKKIw5xnIa', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/agree', + q: {}, + a: { + m: 'OK!', + }, + e: {}, + }); + + assert.deepStrictEqual(agreeSigs, [ + 'AADPaleEvvzOop-8iBDh3LWcjVzl0QzLL6UWYd0xTO02hWEIGVYGalKk1DYJFzwarMf6bAmDA1Betp_dyRNmT38N', + ]); + 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, + agree: agree.ked.d, + }); + + assert.deepStrictEqual(grant.ked, { + v: 'KERI10JSON0004dd_', + t: 'exn', + d: 'EDQ6lmaSIJxFPv7McRa69ljVtSrcY3cS8YYJgpn8DdhX', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'ECVqeKGCclO-u0DEbJQoRwepE9RKGZRo_zArRgGpdZbI', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/grant', + 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, [ + 'AAC1EXfIqOP25RQYZocaXyTDDMykxNxV5a926DC74jhEqDkWseHCWaW5BZb-vNMfcR2n0zdaptQ-QbWxKKf3QqsB', + ]); + 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, + agree: 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( + 'holder', + '', + grant.ked.d, + mockCredential.sad.a.dt + ); + + assert.deepStrictEqual(admit.ked, { + v: 'KERI10JSON000111_', + t: 'exn', + d: 'ELMGqQV8nYJnrgjbJAKW3wmuhqzq5WL9gSvSP_d8wlYH', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: 'EDQ6lmaSIJxFPv7McRa69ljVtSrcY3cS8YYJgpn8DdhX', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/admit', + q: {}, + a: { m: '' }, + e: {}, + }); + + assert.deepStrictEqual(asigs, [ + 'AADLGgf7zJ_Cxn86LOw9UWj_2YEXzRObJMssrdhH772ZOZU2D5PBDwV4-4-DLH24foLuT_dQmkwj70WlOWgL284F', + ]); + + 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: 'KERI10JSON0002c3_', + t: 'exn', + d: 'EPRsOfUsy_Wlrv49K1IvoaW2KYa701dRAqFsjP11HfnH', + i: 'ELUvZ8aJEHAQE-0nsevyYTP98rBbGJUrTj5an-pCmwrK', + p: '', + dt: '2023-08-23T15:16:07.553000+00:00', + r: '/ipex/offer', + q: {}, + a: { + m: 'Offering this', + }, + 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, [ + 'AAAnd2tFMyrr0O91ilWCr6ae1UCK69k5_B4L6rrLTEzYF-Nw7rTVeveRaR-i4VL3An3yOsLaAupD0uHyyXkQWmIN', + ]); + 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' + ); + }); });