From f855b409433c2ffb39b8ce9583ba1454af4369ba Mon Sep 17 00:00:00 2001 From: lenkan Date: Wed, 16 Oct 2024 12:36:09 +0200 Subject: [PATCH 1/2] fix: provide a way to pass the anchor and issuance events when creating credential --- .../multisig-holder.test.ts | 12 +- examples/integration-scripts/multisig.test.ts | 20 +-- .../utils/multisig-utils.ts | 2 +- .../integration-scripts/utils/test-util.ts | 18 +-- src/keri/app/credentialing.ts | 120 +++++++++++------- test/app/credentialing.test.ts | 8 +- 6 files changed, 109 insertions(+), 71 deletions(-) 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 39d535af..f6c0bb22 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 4156dcb6..2061657c 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()); From d2278e5775275afbdc0eddccc01db518b91d14e5 Mon Sep 17 00:00:00 2001 From: lenkan Date: Wed, 16 Oct 2024 13:43:42 +0200 Subject: [PATCH 2/2] fix test --- .../integration-scripts/credentials.test.ts | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/examples/integration-scripts/credentials.test.ts b/examples/integration-scripts/credentials.test.ts index 3ae1f6d4..13d51c53 100644 --- a/examples/integration-scripts/credentials.test.ts +++ b/examples/integration-scripts/credentials.test.ts @@ -154,11 +154,13 @@ test('single signature credentials', async () => { const issResult = await issuerClient .credentials() .issue(issuerAid.name, { - ri: registry.regk, - s: QVI_SCHEMA_SAID, - a: { - i: holderAid.prefix, - ...vcdata, + acdc: { + ri: registry.regk, + s: QVI_SCHEMA_SAID, + a: { + i: holderAid.prefix, + ...vcdata, + }, }, }); @@ -492,28 +494,30 @@ test('single signature credentials', async () => { const result = await holderClient .credentials() .issue(holderAid.name, { - a: { - i: legalEntityAid.prefix, - LEI: '5493001KJTIIGC8Y1R17', - }, - ri: holderRegistry.regk, - s: LE_SCHEMA_SAID, - r: Saider.saidify({ - d: '', - usageDisclaimer: { - l: 'Usage of a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, does not assert that the Legal Entity is trustworthy, honest, reputable in its business dealings, safe to do business with, or compliant with any laws or that an implied or expressly intended purpose will be fulfilled.', + acdc: { + a: { + i: legalEntityAid.prefix, + LEI: '5493001KJTIIGC8Y1R17', }, - issuanceDisclaimer: { - l: 'All information in a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, is accurate as of the date the validation process was complete. The vLEI Credential has been issued to the legal entity or person named in the vLEI Credential as the subject; and the qualified vLEI Issuer exercised reasonable care to perform the validation process set forth in the vLEI Ecosystem Governance Framework.', - }, - })[1], - e: Saider.saidify({ - d: '', - qvi: { - n: qviCredential.sad.d, - s: qviCredential.sad.s, - }, - })[1], + ri: holderRegistry.regk, + s: LE_SCHEMA_SAID, + r: Saider.saidify({ + d: '', + usageDisclaimer: { + l: 'Usage of a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, does not assert that the Legal Entity is trustworthy, honest, reputable in its business dealings, safe to do business with, or compliant with any laws or that an implied or expressly intended purpose will be fulfilled.', + }, + issuanceDisclaimer: { + l: 'All information in a valid, unexpired, and non-revoked vLEI Credential, as defined in the associated Ecosystem Governance Framework, is accurate as of the date the validation process was complete. The vLEI Credential has been issued to the legal entity or person named in the vLEI Credential as the subject; and the qualified vLEI Issuer exercised reasonable care to perform the validation process set forth in the vLEI Ecosystem Governance Framework.', + }, + })[1], + e: Saider.saidify({ + d: '', + qvi: { + n: qviCredential.sad.d, + s: qviCredential.sad.s, + }, + })[1], + }, }); await waitOperation(holderClient, result.op);