diff --git a/examples/integration-scripts/credentials.test.ts b/examples/integration-scripts/credentials.test.ts index 220fa276..1babbc26 100644 --- a/examples/integration-scripts/credentials.test.ts +++ b/examples/integration-scripts/credentials.test.ts @@ -482,7 +482,7 @@ test('single signature credentials', async () => { .credentials() .revoke(issuerAid.name, qviCredentialId); - await waitOperation(issuerClient, revokeOperation); + await waitOperation(issuerClient, revokeOperation.op); const issuerCredential = await issuerClient .credentials() .get(qviCredentialId); diff --git a/examples/integration-scripts/multisig.test.ts b/examples/integration-scripts/multisig.test.ts index 7eb75625..ae03baaf 100644 --- a/examples/integration-scripts/multisig.test.ts +++ b/examples/integration-scripts/multisig.test.ts @@ -904,6 +904,8 @@ test('multisig', async function run() { res = await client2.groups().getRequest(msgSaid); exn = res[0].exn; + const credentialSaid = exn.e.acdc.d; + const credRes2 = await client2.credentials().issue({ issuerName: 'multisig', registryId: regk2, @@ -1115,7 +1117,75 @@ test('multisig', async function run() { await assertOperations(client1, client2, client3, client4); await warnNotifications(client1, client2, client3, client4); -}, 360000); + + console.log('Revoking credential...'); + const REVTIME = new Date().toISOString().replace('Z', '000+00:00'); + const revokeRes = await client1 + .credentials() + .revoke('multisig', credentialSaid, REVTIME); + op1 = revokeRes.op; + + await multisigRevoke( + client1, + 'member1', + 'multisig', + revokeRes.rev, + revokeRes.anc + ); + + console.log( + 'Member1 initiated credential revocation, waiting for others to join...' + ); + + // Member2 check for notifications and join the credential create event + msgSaid = await waitAndMarkNotification(client2, '/multisig/rev'); + console.log( + 'Member2 received exchange message to join the credential revocation event' + ); + res = await client2.groups().getRequest(msgSaid); + + const revokeRes2 = await client2 + .credentials() + .revoke('multisig', credentialSaid, REVTIME); + + op2 = revokeRes2.op; + await multisigRevoke( + client2, + 'member2', + 'multisig', + revokeRes2.rev, + revokeRes2.anc + ); + console.log('Member2 joins credential revoke event, waiting for others...'); + + // Member3 check for notifications and join the create registry event + msgSaid = await waitAndMarkNotification(client3, '/multisig/rev'); + console.log( + 'Member3 received exchange message to join the credential revocation event' + ); + res = await client3.groups().getRequest(msgSaid); + + const revokeRes3 = await client3 + .credentials() + .revoke('multisig', credentialSaid, REVTIME); + + op3 = revokeRes3.op; + + await multisigRevoke( + client3, + 'member3', + 'multisig', + revokeRes3.rev, + revokeRes3.anc + ); + console.log('Member3 joins credential revoke event, waiting for others...'); + + // Check completion + op1 = await waitOperation(client1, op1); + op2 = await waitOperation(client2, op2); + op3 = await waitOperation(client3, op3); + console.log('Multisig credential revocation completed!'); +}, 400000); async function waitAndMarkNotification(client: SignifyClient, route: string) { const notes = await waitForNotifications(client, route); @@ -1175,3 +1245,42 @@ async function multisigIssue( recipients ); } + +async function multisigRevoke( + client: SignifyClient, + memberName: string, + groupName: string, + rev: Serder, + anc: Serder +) { + const leaderHab = await client.identifiers().get(memberName); + const groupHab = await client.identifiers().get(groupName); + const members = await client.identifiers().members(groupName); + + const keeper = client.manager!.get(groupHab); + const sigs = await keeper.sign(signify.b(anc.raw)); + const sigers = sigs.map((sig: string) => new signify.Siger({ qb64: sig })); + const ims = signify.d(signify.messagize(anc, sigers)); + const atc = ims.substring(anc.size); + + const embeds = { + iss: [rev, ''], + anc: [anc, atc], + }; + + const recipients = members.signing + .map((m: { aid: string }) => m.aid) + .filter((aid: string) => aid !== leaderHab.prefix); + + await client + .exchanges() + .send( + memberName, + 'multisig', + leaderHab, + '/multisig/rev', + { gid: groupHab.prefix }, + embeds, + recipients + ); +} diff --git a/package.json b/package.json index f43dea07..b8e95a00 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.2.0", + "version": "0.2.1", "license": "Apache-2.0", "exports": { ".": { diff --git a/src/keri/app/credentialing.ts b/src/keri/app/credentialing.ts index 058ccdd4..ebe3dfc5 100644 --- a/src/keri/app/credentialing.ts +++ b/src/keri/app/credentialing.ts @@ -90,6 +90,12 @@ export interface IssueCredentialResult { op: Operation; } +export interface RevokeCredentialResult { + anc: Serder; + rev: Serder; + op: Operation; +} + export interface IpexGrantArgs { /** * Alias for the IPEX sender AID @@ -272,14 +278,20 @@ export class Credentials { * @async * @param {string} name Name or alias of the identifier * @param {string} said SAID of the credential + * @param {string} datetime date time of revocation * @returns {Promise} A promise to the long-running operation */ - async revoke(name: string, said: string): Promise { + async revoke( + name: string, + said: string, + datetime?: string + ): Promise { const hab = await this.client.identifiers().get(name); const pre: string = hab.prefix; const vs = versify(Ident.KERI, undefined, Serials.JSON, 0); - const dt = new Date().toISOString().replace('Z', '000+00:00'); + const dt = + datetime ?? new Date().toISOString().replace('Z', '000+00:00'); const cred = await this.get(said); @@ -318,6 +330,9 @@ export class Credentials { d: rev.d, }, ]; + + const keeper = this.client!.manager!.get(hab); + if (estOnly) { // TODO implement rotation event throw new Error('Establishment only not implemented'); @@ -330,7 +345,6 @@ export class Credentials { version: undefined, kind: undefined, }); - const keeper = this.client!.manager!.get(hab); sigs = await keeper.sign(b(serder.raw)); ixn = serder.ked; } @@ -339,6 +353,7 @@ export class Credentials { rev: rev, ixn: ixn, sigs: sigs, + [keeper.algo]: keeper.params(), }; const path = `/identifiers/${name}/credentials/${said}`; @@ -347,7 +362,13 @@ export class Credentials { Accept: 'application/json+cesr', }); const res = await this.client.fetch(path, method, body, headers); - return await res.json(); + const op = await res.json(); + + return { + rev: new Serder(rev), + anc: new Serder(ixn), + op, + }; } /**