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

feat: single-sig IPEX apply, offer, agree #234

Merged
112 changes: 111 additions & 1 deletion examples/integration-scripts/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
let verifierAid: Aid;
let legalEntityAid: Aid;

let applySaid: string;
let offerSaid: string;
let agreeSaid: string;

beforeAll(async () => {
[issuerClient, holderClient, verifierClient, legalEntityClient] =
await getOrCreateClients(4);
Expand Down Expand Up @@ -125,7 +129,7 @@
.rename(issuerAid.name, registryName, updatedRegistryName);

registries = await issuerClient.registries().list(issuerAid.name);
let updateRegistry: { name: string; regk: string } = registries[0];

Check warning on line 132 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

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

Check warning on line 132 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

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

Check warning on line 132 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

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

Check warning on line 132 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

'updateRegistry' is never reassigned. Use 'const' instead
assert.equal(registries.length, 1);
assert.equal(updateRegistry.name, updatedRegistryName);

Expand Down Expand Up @@ -243,7 +247,7 @@
datetime: dt,
});

let op = await issuerClient

Check warning on line 250 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

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

Check warning on line 250 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

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

Check warning on line 250 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

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

Check warning on line 250 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

'op' is never reassigned. Use 'const' instead
.ipex()
.submitGrant(issuerAid.name, grant, gsigs, gend, [
holderAid.prefix,
Expand All @@ -266,7 +270,7 @@
grantNotification.a.d!,
createTimestamp()
);
let op = await holderClient

Check warning on line 273 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

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

Check warning on line 273 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

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

Check warning on line 273 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

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

Check warning on line 273 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

'op' is never reassigned. Use 'const' instead
.ipex()
.submitAdmit(holderAid.name, admit, sigs, aend, [issuerAid.prefix]);
await waitOperation(holderClient, op);
Expand Down Expand Up @@ -296,7 +300,107 @@
assert(holderCredential.atc !== undefined);
});

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

Check warning on line 304 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 304 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 304 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 304 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,
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 };

Check warning on line 330 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 330 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 330 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 330 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 330 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 330 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 330 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 330 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];
}

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({

Check warning on line 375 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 375 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 375 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 375 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,
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);
Expand All @@ -310,10 +414,11 @@
acdcAttachment: holderCredential.atc,
ancAttachment: holderCredential.ancatc,
issAttachment: holderCredential.issAtc,
agree: agreeSaid,
datetime: createTimestamp(),
});

let op = await holderClient

Check warning on line 421 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

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

Check warning on line 421 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

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

Check warning on line 421 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

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

Check warning on line 421 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

'op' is never reassigned. Use 'const' instead
.ipex()
.submitGrant(holderAid.name, grant2, gsigs2, gend2, [
verifierAid.prefix,
Expand All @@ -328,6 +433,10 @@
);

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()
Expand Down Expand Up @@ -361,6 +470,7 @@
holderClient,
'/exn/ipex/admit'
);

await markAndRemoveNotification(holderClient, holderNotifications[0]);
});

Expand Down
212 changes: 212 additions & 0 deletions src/keri/app/credentialing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
lenkan marked this conversation as resolved.
Show resolved Hide resolved

/**
* Optional attributes for selective disclosure
*/
attributes?: Record<string, unknown>;
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
Expand Down Expand Up @@ -731,6 +810,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,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure on when i should be part of the attributes here. It is for grant and not for apply. From what I remember when checking out keripy, it seems to auto add i if an exn message is created there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@iFergal Do you have a link to the lines of code in keripy where this is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

@lenkan lenkan Apr 5, 2024

Choose a reason for hiding this comment

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

I had a look here and it is being done in here as well:

if (recipient !== undefined) {
attrs['i'] = recipient;
}
const a = {
...attrs,
...payload,
};
const _ked = {
v: vs,
t: ilk,
d: '',
i: sender,
p: p,
dt: dt,
r: route,
q: q,
a: a,
e: e,
};

I think this is more explicit, we can amend the interface for the payload parameter in createExchangeMessage to be something like

interface ExchangeMessageData {
   [key: string]: unknown,
   /**
     * The recipient of the message
     */
   i: string
}

that way there is only one way to specify the recipient and we can remove the recipient parameter. This would be more in line with the changes I proposed in #222.

I don't mean to make that change here, but it could be done as a separate PR to tidy up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I see, though that line of code is never being hit for IPEX since recipient is always passed as undefined to createExchangeMessage. The only other place that createExchangeMessage is called is from the send method of exchanges, which also doesn't pass the recipient.

Maybe @pfeairheller can shed more light on when it should be passed as an attribute too (at least for IPEX).

But agree on the interface tidy up, makes sense to me.

Copy link
Contributor Author

@iFergal iFergal May 9, 2024

Choose a reason for hiding this comment

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

@lenkan We discussed this in the dev call today, and I also can see you have #252 open that also does this. I've pushed the change for the CICD and compose file only to see if it will pass now. Not sure if additionally all of the other changes you have in draft are required too or not with this new version.

cc @rodolfomiranda

Edit - OK, looks like the tests are indeed broken for the new version

Copy link
Collaborator

Choose a reason for hiding this comment

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

@iFergal The changes in aiding.ts is needed for the upgrade. The other changes were only to highlight what is broken.

Copy link
Contributor Author

@iFergal iFergal May 13, 2024

Choose a reason for hiding this comment

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

@lenkan Do you think that PR will merge this week or shall I cherry pick changes to aiding.ts across?

edit - hitting this now, will try to dig into why when I've more time. (on first call to await grantMultisig)

HTTP POST /identifiers/GEDA/ipex/grant - 400 Bad Request - {"title": "400 Bad Request", "description": "attempt to send to unknown AID=EJSc85y7RLnwYDU1BGuVKAY8GKde6zvbEFOgkP-aOEBR"}

Copy link
Collaborator

@lenkan lenkan May 13, 2024

Choose a reason for hiding this comment

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

I believe that is the exact same error I reproduced in #252. I created the issue in keria that needs resolution WebOfTrust/keria#230. I have not yet debugged keria for that issue.

It would be good to see if it can be reproduced in plain keripy first I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some other adjustments in my latest push - smids/rmids only changed on group creation, not rotation.

There were also some changes in keripy on not using hard coded salts so our tests can't assume the agent AID anymore.

Once WebOfTrust/keria#243 merges, the only failing test is the one you described for vlei issuance. Haven't got a chance to support on that yet.

};

return this.client
.exchanges()
.createExchangeMessage(
hab,
'/ipex/apply',
data,
{},
undefined,
args.datetime,
undefined
);
}

async submitApply(
name: string,
exn: Serder,
sigs: string[],
recp: string[]
): Promise<any> {
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<any> {
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<any> {
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
*/
Expand Down
Loading
Loading