Skip to content

Commit

Permalink
add reproduction script for issuing chained credential
Browse files Browse the repository at this point in the history
  • Loading branch information
lenkan committed Nov 22, 2023
1 parent bcd83fc commit 2b7383d
Showing 1 changed file with 368 additions and 0 deletions.
368 changes: 368 additions & 0 deletions examples/integration-scripts/single-issuer-chained-credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
import assert from 'node:assert';
import signify, {
SignifyClient,
CredentialResult,
messagize,
d,
Siger,
Saider,
} from 'signify-ts';

const URL = 'http://127.0.0.1:3901';
const BOOT_URL = 'http://127.0.0.1:3903';
const VLEI_SERVER_HOST = 'localhost';
const QVI_SCHEMA_SAID = 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao';
const LE_SCHEMA_SAID = 'ENPXp1vQzRF6JwIuS-mp2U8Uf1MoADoP_GqQ62VsDZWY';
const WITNESS_AIDS: string[] = ['BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha'];

const QVI_SCHEMA_OOBI = `http://${VLEI_SERVER_HOST}:7723/oobi/${QVI_SCHEMA_SAID}`;
const LE_SCHEMA_OOBI = `http://${VLEI_SERVER_HOST}:7723/oobi/${LE_SCHEMA_SAID}`;

function createTimestamp() {
const dt = new Date().toISOString().replace('Z', '000+00:00');
return dt;
}

async function connect(url: string, bootUrl: string) {
const client = new signify.SignifyClient(
url,
signify.randomPasscode(),
signify.Tier.low,
bootUrl
);

await client.boot();
await client.connect();

return client;
}

async function createIdentifier(
client: signify.SignifyClient,
name: string,
witnesses: string[]
) {
const icpResult1 = await client.identifiers().create(name, {
toad: witnesses.length,
wits: witnesses,
});
const op = await icpResult1.op();
await waitOperation(client, op, 15000);
const aid = await client.identifiers().get(name);

if (!client.agent) {
throw new Error('No agent on client');
}

await client.identifiers().addEndRole(name, 'agent', client.agent.pre);

return aid.prefix;
}

async function getAgentOobi(
client: signify.SignifyClient,
name: string
): Promise<string> {
const result = await client.oobis().get(name, 'agent');
return result.oobis[0];
}

async function resolveOobi(
client: SignifyClient,
oobi: string,
alias: string
): Promise<void> {
console.log(`Resolve ${alias} -> ${oobi}`);
const op = await client.oobis().resolve(oobi, alias);
await waitOperation(client, op, 5000);
}

async function createRegistry(
client: SignifyClient,
name: string,
registryName: string
) {
const result = await client.registries().create({ name, registryName });
const op = await result.op();
await waitOperation(client, op, 5000);

const registries = await client.registries().list(name);
assert.equal(registries.length, 1);
assert.equal(registries[0].name, registryName);

return registries[0];
}

async function issueCredential(
client: SignifyClient,
name: string,
args: {
registry: string;
schema: string;
recipient: string;
data: unknown;
source?: unknown;
rules?: unknown;
}
) {
const result: CredentialResult = await client
.credentials()
.issue(
name,
args.registry,
args.schema,
args.recipient,
args.data,
args.rules && Saider.saidify({ d: '', ...args.rules })[1],
args.source && Saider.saidify({ d: '', ...args.source })[1]
);

const op = await result.op();
await waitOperation(client, op, 10000);

const creds = await client.credentials().list();

const acdc = new signify.Serder(result.acdc);
const ianc = result.anc;

const sigers = result.sigs.map((sig: string) => new Siger({ qb64: sig }));
const ims = d(messagize(ianc, sigers));

const atc = ims.substring(result.anc.size);
const dt = createTimestamp();

const [grant, gsigs, end] = await client
.ipex()
.grant(
name,
args.recipient,
'',
acdc,
result.acdcSaider,
result.iserder,
result.issExnSaider,
result.anc,
atc,
undefined,
dt
);
await client
.exchanges()
.sendFromEvents(name, 'credential', grant, gsigs, end, [
args.recipient,
]);

console.log('Grant message sent');

return creds[0];
}

interface Notification {
i: string;
dt: string;
r: boolean;
a: { r: string; d?: string; m?: string };
}

async function waitForNotification(
client: SignifyClient,
route: string
): Promise<Notification> {
// eslint-disable-next-line no-constant-condition
while (true) {
const notifications = await client.notifications().list();
for (const notif of notifications.notes) {
if (notif.a.r == route) {
return notif;
}
}

await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

async function admitCredential(
client: SignifyClient,
name: string,
said: string,
recipient: string
) {
const dt = createTimestamp();

const [admit, sigs, end] = await client.ipex().admit(name, '', said, dt);

await client.ipex().submitAdmit(name, admit, sigs, end, [recipient]);
}

async function wait<T>(fn: () => Promise<T>, timeout: number = 10000) {
const start = Date.now();
const errors: Error[] = [];
while (Date.now() - start < timeout) {
try {
const result = await fn();
return result;
} catch (error) {
errors.push(error as Error);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

throw new RetryError(`Retry failed after ${Date.now() - start} ms`, errors);
}

async function waitOperation<T extends { done: boolean; name: string }>(
client: SignifyClient,
op: T,
timeout?: number
): Promise<T> {
const now = Date.now();
op = await client.operations().get(op.name);

while (!op['done']) {
op = await client.operations().get(op.name);

const elapsed = Date.now() - now;
if (timeout !== undefined && elapsed > timeout) {
throw new Error(
`Operation '${op.name}' time out after ${elapsed} ms`
);
}

await new Promise((resolve) => setTimeout(resolve, 1000));
}

return op;
}

class RetryError extends Error {
constructor(
message: string,
public errors: Error[]
) {
super(message);
}
}

await signify.ready();
const issuerClient = await connect(URL, BOOT_URL);
const holderClient = await connect(URL, BOOT_URL);

await issuerClient.state();
await holderClient.state();

// await resolveOobi(
// issuerClient,
// `http://${WITNESS_HOST}:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha`,
// 'wan'
// );
// await resolveOobi(
// issuerClient,
// `http://${WITNESS_HOST}:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM`,
// 'wes'
// );
// await resolveOobi(
// issuerClient,
// `http://${WITNESS_HOST}:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX`,
// 'wil'
// );
// await resolveOobi(
// holderClient,
// `http://${WITNESS_HOST}:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha`,
// 'wan'
// );
// await resolveOobi(
// holderClient,
// `http://${WITNESS_HOST}:5643/oobi/BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM`,
// 'wes'
// );
// await resolveOobi(
// holderClient,
// `http://${WITNESS_HOST}:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX`,
// 'wil'
// );

// Create two identifiers, one for each client
const issuerPrefix = await createIdentifier(
issuerClient,
'issuer',
WITNESS_AIDS
);
console.log('Creates issuer');
const holderPrefix = await createIdentifier(
holderClient,
'holder',
WITNESS_AIDS
);
console.log('Creates holder');

// Exchange OOBIs
const issuerOobi = await getAgentOobi(issuerClient, 'issuer');
const holderOobi = await getAgentOobi(holderClient, 'holder');
await resolveOobi(issuerClient, holderOobi, 'holder');
await resolveOobi(issuerClient, QVI_SCHEMA_OOBI, 'qvi-schema');
await resolveOobi(issuerClient, LE_SCHEMA_OOBI, 'le-schema');
await resolveOobi(holderClient, issuerOobi, 'issuer');
await resolveOobi(holderClient, QVI_SCHEMA_OOBI, 'qvi-schema');
await resolveOobi(holderClient, LE_SCHEMA_OOBI, 'le-schema');

await createRegistry(issuerClient, 'issuer', 'vLEI');

const registires = await issuerClient.registries().list('issuer');
const result: CredentialResult = await issuerClient
.credentials()
.issue('issuer', registires[0].regk, QVI_SCHEMA_SAID, issuerPrefix, {
LEI: '5493001KJTIIGC8Y1R17',
});

const op = await result.op();
await waitOperation(issuerClient, op, 5000);
const sourceCredential = await wait(async () => {
const result = await issuerClient.credentials().list();
assert(result.length >= 1);
return result[0];
});

await issueCredential(issuerClient, 'issuer', {
registry: registires[0].regk,
schema: LE_SCHEMA_SAID,
// schema: QVI_SCHEMA_SAID,
recipient: holderPrefix,
data: {
LEI: '5493001KJTIIGC8Y1R17',
},
source: {
qvi: {
n: sourceCredential.sad.d,
s: sourceCredential.sad.s,
},
},
rules: {
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.',
},
},
});

const grantNotification = await waitForNotification(
holderClient,
'/exn/ipex/grant'
);
console.dir(grantNotification, { depth: 100 });

await admitCredential(
holderClient,
'holder',
grantNotification.a.d!,
issuerPrefix
);

await holderClient.notifications().mark(grantNotification.i);

await wait(async () => {
const creds = await holderClient.credentials().list();
console.log(creds);
assert(creds.length >= 1);
}, 30000);

0 comments on commit 2b7383d

Please sign in to comment.