Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: a way to prevent duplicated anchor events for multisig issuance #286

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions examples/integration-scripts/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,13 @@
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,
},
},
});

Expand Down Expand Up @@ -293,7 +295,7 @@
});

await step('verifier IPEX apply', async () => {
const [apply, sigs, _] = await verifierClient.ipex().apply({

Check warning on line 298 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and macOS-latest

'_' is assigned a value but never used

Check warning on line 298 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

'_' is assigned a value but never used

Check warning on line 298 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and ubuntu-latest

'_' is assigned a value but never used

Check warning on line 298 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and ubuntu-latest

'_' is assigned a value but never used
senderName: verifierAid.name,
schemaSaid: QVI_SCHEMA_SAID,
attributes: { LEI: '5493001KJTIIGC8Y1R17' },
Expand All @@ -319,7 +321,7 @@
const apply = await holderClient.exchanges().get(holderApplyNote.a.d);
applySaid = apply.exn.d;

let filter: { [x: string]: any } = { '-s': apply.exn.a.s };

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and macOS-latest

'filter' is never reassigned. Use 'const' instead

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and macOS-latest

Unexpected any. Specify a different type

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

'filter' is never reassigned. Use 'const' instead

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

Unexpected any. Specify a different type

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and ubuntu-latest

'filter' is never reassigned. Use 'const' instead

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and ubuntu-latest

Unexpected any. Specify a different type

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and ubuntu-latest

'filter' is never reassigned. Use 'const' instead

Check warning on line 324 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and ubuntu-latest

Unexpected any. Specify a different type
for (const key in apply.exn.a.a) {
filter[`-a-${key}`] = apply.exn.a.a[key];
}
Expand Down Expand Up @@ -364,7 +366,7 @@

await markAndRemoveNotification(verifierClient, verifierOfferNote);

const [agree, sigs, _] = await verifierClient.ipex().agree({

Check warning on line 369 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and macOS-latest

'_' is assigned a value but never used

Check warning on line 369 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and macOS-latest

'_' is assigned a value but never used

Check warning on line 369 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 18 and ubuntu-latest

'_' is assigned a value but never used

Check warning on line 369 in examples/integration-scripts/credentials.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test on Node 20 and ubuntu-latest

'_' is assigned a value but never used
senderName: verifierAid.name,
recipient: holderAid.prefix,
offerSaid: offerSaid,
Expand Down Expand Up @@ -492,28 +494,30 @@
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);
Expand Down
12 changes: 6 additions & 6 deletions examples/integration-scripts/multisig-holder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}

Expand Down
20 changes: 10 additions & 10 deletions examples/integration-scripts/multisig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import {
assertOperations,
getOrCreateClient,
getOrCreateIdentifier,
markNotification,
waitAndMarkNotification,
waitForNotifications,
waitOperation,
warnNotifications,
} from './utils/test-util';
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion examples/integration-scripts/utils/multisig-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 10 additions & 8 deletions examples/integration-scripts/utils/test-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
120 changes: 77 additions & 43 deletions src/keri/app/credentialing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<string, unknown>;

/**
* 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<string, unknown>;
}

export interface IssueCredentialResult {
acdc: Serder;
anc: Serder;
Expand Down Expand Up @@ -290,7 +314,7 @@ export class Credentials {
*/
async issue(
name: string,
args: CredentialData
args: IssueCredentialArgs
): Promise<IssueCredentialResult> {
const hab = await this.client.identifiers().get(name);
const estOnly = hab.state.c !== undefined && hab.state.c.includes('EO');
Expand All @@ -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({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the reason I inlined this is because I think it makes it much more obvious what we are actually posting down to KERIA. I do not think the indirection of calling interact provides any readability improvements. But I am OK with either. Just provide your feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good with me

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(),
Expand All @@ -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,
};
Expand Down
8 changes: 5 additions & 3 deletions test/app/credentialing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading