diff --git a/examples/integration-scripts/multisig-holder.test.ts b/examples/integration-scripts/multisig-holder.test.ts index 8d1939af..8d729407 100644 --- a/examples/integration-scripts/multisig-holder.test.ts +++ b/examples/integration-scripts/multisig-holder.test.ts @@ -460,23 +460,23 @@ async function createRegistry( async function issueCredential( client: SignifyClient, name: string, - data: CredentialData + acdc: CredentialData ) { - const result = await client.credentials().issue(name, data); + const result = await client.credentials().issue(name, { acdc }); await waitOperation(client, result.op); const creds = await client.credentials().list(); assert.equal(creds.length, 1); - assert.equal(creds[0].sad.s, data.s); + assert.equal(creds[0].sad.s, acdc.s); assert.equal(creds[0].status.s, '0'); const dt = createTimestamp(); - if (data.a.i) { + if (acdc.a.i) { const [grant, gsigs, end] = await client.ipex().grant({ senderName: name, - recipient: data.a.i, + recipient: acdc.a.i, datetime: dt, acdc: result.acdc, anc: result.anc, @@ -485,7 +485,7 @@ async function issueCredential( let op = await client .ipex() - .submitGrant(name, grant, gsigs, end, [data.a.i]); + .submitGrant(name, grant, gsigs, end, [acdc.a.i]); op = await waitOperation(client, op); } diff --git a/examples/integration-scripts/multisig.test.ts b/examples/integration-scripts/multisig.test.ts index 723ac04b..5ff10ff9 100644 --- a/examples/integration-scripts/multisig.test.ts +++ b/examples/integration-scripts/multisig.test.ts @@ -9,9 +9,7 @@ import { assertOperations, getOrCreateClient, getOrCreateIdentifier, - markNotification, waitAndMarkNotification, - waitForNotifications, waitOperation, warnNotifications, } from './utils/test-util'; @@ -882,12 +880,14 @@ test('multisig', async function run() { const TIME = new Date().toISOString().replace('Z', '000+00:00'); const credRes = await client1.credentials().issue('multisig', { - ri: regk, - s: SCHEMA_SAID, - a: { - i: holder, - dt: TIME, - ...vcdata, + acdc: { + ri: regk, + s: SCHEMA_SAID, + a: { + i: holder, + dt: TIME, + ...vcdata, + }, }, }); op1 = credRes.op; @@ -906,7 +906,7 @@ test('multisig', async function run() { exn = res[0].exn; const credentialSaid = exn.e.acdc.d; - const credRes2 = await client2.credentials().issue('multisig', exn.e.acdc); + const credRes2 = await client2.credentials().issue('multisig', exn.e); op2 = credRes2.op; await multisigIssue(client2, 'member2', 'multisig', credRes2); @@ -920,7 +920,7 @@ test('multisig', async function run() { res = await client3.groups().getRequest(msgSaid); exn = res[0].exn; - const credRes3 = await client3.credentials().issue('multisig', exn.e.acdc); + const credRes3 = await client3.credentials().issue('multisig', exn.e); op3 = credRes3.op; await multisigIssue(client3, 'member3', 'multisig', credRes3); diff --git a/examples/integration-scripts/utils/multisig-utils.ts b/examples/integration-scripts/utils/multisig-utils.ts index df6fbd59..e1bf5fc7 100644 --- a/examples/integration-scripts/utils/multisig-utils.ts +++ b/examples/integration-scripts/utils/multisig-utils.ts @@ -413,7 +413,7 @@ export async function issueCredentialMultisig( const credResult = await client .credentials() - .issue(multisigAIDName, kargsIss); + .issue(multisigAIDName, { acdc: kargsIss }); const op = credResult.op; const multisigAID = await client.identifiers().get(multisigAIDName); diff --git a/examples/integration-scripts/utils/test-util.ts b/examples/integration-scripts/utils/test-util.ts index f22b1950..3c17d7e1 100644 --- a/examples/integration-scripts/utils/test-util.ts +++ b/examples/integration-scripts/utils/test-util.ts @@ -318,16 +318,18 @@ export async function getOrIssueCredential( } const issResult = await issuerClient.credentials().issue(issuerAid.name, { - ri: issuerRegistry.regk, - s: schema, - u: privacy ? new Salter({}).qb64 : undefined, - a: { - i: recipientAid.prefix, + acdc: { + ri: issuerRegistry.regk, + s: schema, u: privacy ? new Salter({}).qb64 : undefined, - ...credData, + a: { + i: recipientAid.prefix, + u: privacy ? new Salter({}).qb64 : undefined, + ...credData, + }, + r: rules, + e: source, }, - r: rules, - e: source, }); await waitOperation(issuerClient, issResult.op); diff --git a/src/keri/app/credentialing.ts b/src/keri/app/credentialing.ts index a13ddba3..adf07cac 100644 --- a/src/keri/app/credentialing.ts +++ b/src/keri/app/credentialing.ts @@ -21,6 +21,7 @@ import { } from '../core/utils'; import { Operation } from './coring'; import { HabState } from '../core/state'; +import { CesrNumber } from '../core/number'; /** Types of credentials */ export class CredentialTypes { @@ -85,6 +86,29 @@ export interface CredentialData { r?: { [key: string]: unknown }; } +export interface IssueCredentialArgs { + /** + * The credential data. + */ + acdc: CredentialData; + + /** + * The issuance event to be anchored to the credential registry. If not provided, the issuance event will be derived + * from the credential data. + * + * If a credential is created as part of a multisig exchanged, the anchoring event can be found in the exchange message. + */ + iss?: Record; + + /** + * The anchoring event for the credential issuance. If not provided, the anchor event will be calculated from + * from the credential data and the issuance event. + * + * If a credential is created as part of a multisig exchanged, the anchoring event can be found in the exchange message. + */ + anc?: Record; +} + export interface IssueCredentialResult { acdc: Serder; anc: Serder; @@ -290,7 +314,7 @@ export class Credentials { */ async issue( name: string, - args: CredentialData + args: IssueCredentialArgs ): Promise { const hab = await this.client.identifiers().get(name); const estOnly = hab.state.c !== undefined && hab.state.c.includes('EO'); @@ -306,55 +330,65 @@ export class Credentials { const [, subject] = Saider.saidify({ d: '', - ...args.a, - dt: args.a.dt ?? new Date().toISOString().replace('Z', '000+00:00'), + ...args.acdc.a, + dt: + args.acdc.a.dt ?? + new Date().toISOString().replace('Z', '000+00:00'), }); - const [, acdc] = Saider.saidify({ - v: versify(Ident.ACDC, undefined, Serials.JSON, 0), - d: '', - u: args.u, - i: args.i ?? hab.prefix, - ri: args.ri, - s: args.s, - a: subject, - e: args.e, - r: args.r, - }); + const acdc = new Serder( + Saider.saidify({ + v: versify(Ident.ACDC, undefined, Serials.JSON, 0), + d: '', + u: args.acdc.u, + i: args.acdc.i ?? hab.prefix, + ri: args.acdc.ri, + s: args.acdc.s, + a: subject, + e: args.acdc.e, + r: args.acdc.r, + })[1] + ); - const [, iss] = Saider.saidify({ - v: versify(Ident.KERI, undefined, Serials.JSON, 0), - t: Ilks.iss, - d: '', - i: acdc.d, - s: '0', - ri: args.ri, - dt: subject.dt, - }); + const iss = new Serder( + args.iss ?? + Saider.saidify({ + v: versify(Ident.KERI, undefined, Serials.JSON, 0), + t: Ilks.iss, + d: '', + i: acdc.ked.d, + s: '0', + ri: args.acdc.ri, + dt: subject.dt, + })[1] + ); - const sn = parseInt(hab.state.s, 16); - const anc = interact({ - pre: hab.prefix, - sn: sn + 1, - data: [ - { - i: iss.i, - s: iss.s, - d: iss.d, - }, - ], - dig: hab.state.d, - version: undefined, - kind: undefined, - }); + const sn = new CesrNumber({}, parseInt(hab.state.s, 16) + 1); + const anc = new Serder( + args.anc ?? + Saider.saidify({ + v: versify(Ident.KERI, undefined, Serials.JSON, 0), + t: Ilks.ixn, + d: '', + i: hab.prefix, + s: sn.numh, + p: hab.state.d, + a: [ + { + i: iss.ked.i, + s: iss.ked.s, + d: iss.ked.d, + }, + ], + })[1] + ); const sigs = await keeper.sign(b(anc.raw)); - const path = `/identifiers/${hab.name}/credentials`; const method = 'POST'; const body = { - acdc: acdc, - iss: iss, + acdc: acdc.ked, + iss: iss.ked, ixn: anc.ked, sigs, [keeper.algo]: keeper.params(), @@ -368,8 +402,8 @@ export class Credentials { const op = await res.json(); return { - acdc: new Serder(acdc), - iss: new Serder(iss), + acdc, + iss, anc, op, }; diff --git a/test/app/credentialing.test.ts b/test/app/credentialing.test.ts index 99f36dcc..367221bd 100644 --- a/test/app/credentialing.test.ts +++ b/test/app/credentialing.test.ts @@ -263,9 +263,11 @@ describe('Credentialing', () => { const schema = 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao'; const isuee = 'EG2XjQN-3jPN5rcR4spLjaJyM4zA6Lgg-Hd5vSMymu5p'; await credentials.issue('aid1', { - ri: registry, - s: schema, - a: { i: isuee, LEI: '1234' }, + acdc: { + ri: registry, + s: schema, + a: { i: isuee, LEI: '1234' }, + }, }); lastCall = fetchMock.mock.calls[fetchMock.mock.calls.length - 1]!; lastBody = JSON.parse(lastCall[1]!.body!.toString());