keri 0.0.8-dev.bfac33d → 0.0.8-dev.e44ed64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -8
  2. package/dist/controller/controller.d.ts +109 -0
  3. package/dist/controller/controller.js +545 -0
  4. package/dist/controller/controller.js.map +1 -0
  5. package/dist/controller/encrypt.d.ts +45 -0
  6. package/dist/controller/encrypt.js +120 -0
  7. package/dist/controller/encrypt.js.map +1 -0
  8. package/dist/core/credential-event.d.ts +58 -0
  9. package/dist/core/credential-event.js +28 -0
  10. package/dist/core/credential-event.js.map +1 -0
  11. package/dist/core/credential.d.ts +79 -0
  12. package/dist/core/credential.js +25 -0
  13. package/dist/core/credential.js.map +1 -0
  14. package/dist/core/digest.d.ts +1 -0
  15. package/dist/core/digest.js +7 -0
  16. package/dist/core/digest.js.map +1 -0
  17. package/dist/core/endpoint-discovery.d.ts +20 -0
  18. package/dist/core/endpoint-discovery.js +60 -0
  19. package/dist/core/endpoint-discovery.js.map +1 -0
  20. package/dist/core/events.d.ts +12 -0
  21. package/dist/core/events.js +25 -0
  22. package/dist/core/events.js.map +1 -0
  23. package/dist/core/kawa.d.ts +17 -0
  24. package/dist/core/kawa.js +79 -0
  25. package/dist/core/kawa.js.map +1 -0
  26. package/dist/core/key-event-log.d.ts +13 -0
  27. package/dist/core/key-event-log.js +154 -0
  28. package/dist/core/key-event-log.js.map +1 -0
  29. package/dist/core/key-event.d.ts +96 -0
  30. package/dist/core/key-event.js +89 -0
  31. package/dist/core/key-event.js.map +1 -0
  32. package/dist/core/keys.d.ts +9 -0
  33. package/dist/core/keys.js +17 -0
  34. package/dist/core/keys.js.map +1 -0
  35. package/dist/core/mailbox-client.d.ts +17 -0
  36. package/dist/core/mailbox-client.js +57 -0
  37. package/dist/core/mailbox-client.js.map +1 -0
  38. package/dist/core/main.d.ts +47 -0
  39. package/dist/core/main.js +44 -0
  40. package/dist/core/main.js.map +1 -0
  41. package/dist/core/receipt-event.d.ts +15 -0
  42. package/dist/core/receipt-event.js +13 -0
  43. package/dist/core/receipt-event.js.map +1 -0
  44. package/dist/core/registry-event.d.ts +28 -0
  45. package/dist/core/registry-event.js +18 -0
  46. package/dist/core/registry-event.js.map +1 -0
  47. package/dist/core/routed-event.d.ts +73 -0
  48. package/dist/core/routed-event.js +53 -0
  49. package/dist/core/routed-event.js.map +1 -0
  50. package/dist/core/said.d.ts +4 -0
  51. package/dist/core/said.js +26 -0
  52. package/dist/core/said.js.map +1 -0
  53. package/dist/core/sign.d.ts +5 -0
  54. package/dist/core/sign.js +10 -0
  55. package/dist/core/sign.js.map +1 -0
  56. package/dist/core/threshold.d.ts +6 -0
  57. package/dist/core/threshold.js +58 -0
  58. package/dist/core/threshold.js.map +1 -0
  59. package/dist/core/verify.d.ts +14 -0
  60. package/dist/core/verify.js +43 -0
  61. package/dist/core/verify.js.map +1 -0
  62. package/dist/main.d.ts +2 -7
  63. package/dist/main.js +2 -7
  64. package/dist/main.js.map +1 -1
  65. package/dist/storage/sqlite/node-sqlite.d.ts +12 -0
  66. package/dist/storage/sqlite/node-sqlite.js +25 -0
  67. package/dist/storage/sqlite/node-sqlite.js.map +1 -0
  68. package/dist/storage/sqlite/schema.d.ts +2 -0
  69. package/dist/storage/sqlite/schema.js +49 -0
  70. package/dist/storage/sqlite/schema.js.map +1 -0
  71. package/dist/storage/sqlite/sqlite-database.d.ts +13 -0
  72. package/dist/storage/sqlite/sqlite-database.js +2 -0
  73. package/dist/storage/sqlite/sqlite-database.js.map +1 -0
  74. package/dist/storage/sqlite/storage-sqlite.d.ts +26 -0
  75. package/dist/storage/sqlite/storage-sqlite.js +213 -0
  76. package/dist/storage/sqlite/storage-sqlite.js.map +1 -0
  77. package/package.json +10 -11
  78. package/dist/cli/main.d.ts +0 -2
  79. package/dist/cli/main.js +0 -182
  80. package/dist/cli/main.js.map +0 -1
  81. package/dist/client.d.ts +0 -17
  82. package/dist/client.js +0 -53
  83. package/dist/client.js.map +0 -1
  84. package/dist/controller.d.ts +0 -63
  85. package/dist/controller.js +0 -451
  86. package/dist/controller.js.map +0 -1
  87. package/dist/db/storage-sqlite.d.ts +0 -12
  88. package/dist/db/storage-sqlite.js +0 -53
  89. package/dist/db/storage-sqlite.js.map +0 -1
  90. package/dist/db/storage.d.ts +0 -18
  91. package/dist/db/storage.js +0 -29
  92. package/dist/db/storage.js.map +0 -1
  93. package/dist/events/event-store.d.ts +0 -116
  94. package/dist/events/event-store.js +0 -254
  95. package/dist/events/event-store.js.map +0 -1
  96. package/dist/events/events.d.ts +0 -248
  97. package/dist/events/events.js +0 -186
  98. package/dist/events/events.js.map +0 -1
  99. package/dist/keystore/encrypt.d.ts +0 -10
  100. package/dist/keystore/encrypt.js +0 -39
  101. package/dist/keystore/encrypt.js.map +0 -1
  102. package/dist/keystore/key-manager.d.ts +0 -27
  103. package/dist/keystore/key-manager.js +0 -76
  104. package/dist/keystore/key-manager.js.map +0 -1
package/README.md CHANGED
@@ -1,10 +1,3 @@
1
1
  # KERI for JS
2
2
 
3
- Playground KERI library for javascript. See test_scripts for KERIpy compatibility tests. The library can create events, messages and credentials that can be verified by KERIpy. Currently, it does no verification at all.
4
-
5
- ## Usage command line
6
-
7
- ```bash
8
- npm install -g keri
9
- keri incept --passcode secret
10
- ```
3
+ Playground KERI library for javascript. See test_interop for KERIpy compatibility tests. The library can create events, messages and credentials that can be verified by KERIpy. Currently, it does minimal verification.
@@ -0,0 +1,109 @@
1
+ import { type Encrypter } from "./encrypt.ts";
2
+ import { KeyEventLog, Message, type CredentialBody, type Endpoint, type InceptEvent, type InteractEvent, type RotateEvent, type KeyEvent, type RegistryInceptEvent, type IssueEvent, type ReplyEvent, type RevokeEvent, type KeyState } from "../core/main.ts";
3
+ export interface ControllerStorage {
4
+ saveMessage(message: Message): void;
5
+ saveKey(publicKey: string, digest: string, encryptedPrivKey: string): void;
6
+ getKey(publicKey: string): string;
7
+ getPublicKeyByDigest(digest: string): string;
8
+ getReplies(filter?: {
9
+ route?: string;
10
+ eid?: string;
11
+ cid?: string;
12
+ }): Generator<Message<ReplyEvent>>;
13
+ getKeyEvents(prefix: string): Generator<KeyEvent>;
14
+ getCredentialEvents(id: string): Generator<Message<IssueEvent | RevokeEvent>>;
15
+ getRegistry(id: string): Message<RegistryInceptEvent> | null;
16
+ getRegistriesByOwner(owner: string): Generator<Message<RegistryInceptEvent>>;
17
+ getCredential(id: string): CredentialBody | null;
18
+ getCredentialsByRegistry(registryId: string): CredentialBody[];
19
+ getMailboxOffset(prefix: string, topic: string): number;
20
+ saveMailboxOffset(prefix: string, topic: string, offset: number): void;
21
+ }
22
+ export interface ForwardArgs {
23
+ sender: string;
24
+ recipient: string;
25
+ topic: string;
26
+ message: Message;
27
+ timestamp?: string;
28
+ }
29
+ export interface ControllerDeps {
30
+ storage: ControllerStorage;
31
+ encrypter?: Encrypter;
32
+ passphrase?: string;
33
+ }
34
+ export interface ReplyArgs {
35
+ id: string;
36
+ route: string;
37
+ record: Record<string, unknown>;
38
+ }
39
+ export interface InceptArgs {
40
+ wits?: string[];
41
+ toad?: number;
42
+ }
43
+ export interface InceptResult {
44
+ id: string;
45
+ event: InceptEvent;
46
+ }
47
+ export interface AnchorResult {
48
+ id: string;
49
+ event: InteractEvent;
50
+ }
51
+ export interface RotateResult {
52
+ id: string;
53
+ event: RotateEvent;
54
+ }
55
+ export interface RotateArgs {
56
+ data?: Record<string, unknown>;
57
+ }
58
+ export interface AnchorArgs {
59
+ data?: Record<string, unknown>;
60
+ }
61
+ export interface CreateCredentialArgs {
62
+ registryId: string;
63
+ schemaId: string;
64
+ holder: string;
65
+ salt?: string;
66
+ timestamp?: Date;
67
+ data?: Record<string, unknown>;
68
+ rules?: Record<string, unknown>;
69
+ edges?: Record<string, unknown>;
70
+ }
71
+ export interface IpexGrantArgs {
72
+ credential: CredentialBody;
73
+ recipient?: string;
74
+ timestamp?: string;
75
+ }
76
+ export declare class Controller {
77
+ #private;
78
+ constructor(deps: ControllerDeps);
79
+ private generateKey;
80
+ private signWithKey;
81
+ introduce(oobi: string): Promise<KeyState>;
82
+ loadEventLog(id: string): Promise<KeyEventLog>;
83
+ resolveEndpoint(aid: string, role?: string): Endpoint;
84
+ sign(raw: Uint8Array, keys: string[]): Promise<string[]>;
85
+ incept(args?: InceptArgs): Promise<InceptResult>;
86
+ processMessage(message: Message): Promise<void>;
87
+ commit(log: KeyEventLog, event: KeyEvent): Promise<void>;
88
+ anchor(id: string, anchor: AnchorArgs): Promise<AnchorResult>;
89
+ rotate(id: string, args: RotateArgs): Promise<RotateResult>;
90
+ /**
91
+ * Creates and stores a signed reply message and submits to all witnesses.
92
+ */
93
+ reply(args: ReplyArgs): Promise<void>;
94
+ forward(args: ForwardArgs): Promise<void>;
95
+ createRegistry(owner: string): Promise<RegistryInceptEvent>;
96
+ listRegistries(owner: string): Promise<RegistryInceptEvent[]>;
97
+ createCredential(args: CreateCredentialArgs): Promise<CredentialBody>;
98
+ getCredential(id: string): Promise<CredentialBody | null>;
99
+ listCredentials(registryId: string): Promise<CredentialBody[]>;
100
+ issueCredential(credential: CredentialBody): Promise<void>;
101
+ private getIssueEvent;
102
+ private getAnchorFromSeal;
103
+ private buildCredentialMessage;
104
+ sendCredentialArtifacts(credential: CredentialBody, recipient: string): Promise<void>;
105
+ grant(args: IpexGrantArgs): Promise<void>;
106
+ query(id: string, topic: string): Promise<Message[]>;
107
+ receiveGrants(holderId: string): Promise<CredentialBody[]>;
108
+ export(id: string): Promise<Message[]>;
109
+ }
@@ -0,0 +1,545 @@
1
+ import { PassphraseEncrypter } from "./encrypt.js";
2
+ import { keri, sign, KeyEventLog, Message, Attachments, MailboxClient, submitToWitnesses, resolveEndRole, resolveLocation, } from "../core/main.js";
3
+ import { cesr, Matter, parse } from "cesr";
4
+ import { decodeBase64Url, encodeBase64Url } from "cesr/__unstable__";
5
+ export class Controller {
6
+ #storage;
7
+ #encrypter;
8
+ constructor(deps) {
9
+ this.#storage = deps.storage;
10
+ this.#encrypter = deps.encrypter ?? new PassphraseEncrypter(deps.passphrase ?? "default-passphrase");
11
+ }
12
+ async generateKey() {
13
+ const key = keri.utils.generateKeyPair();
14
+ if (!key.privateKey || !key.publicKey || !key.publicKeyDigest) {
15
+ throw new Error("Failed to generate key pair");
16
+ }
17
+ const encrypted = await this.#encrypter.encrypt(key.privateKey);
18
+ this.#storage.saveKey(key.publicKey, key.publicKeyDigest, encodeBase64Url(encrypted));
19
+ return key.publicKey;
20
+ }
21
+ async signWithKey(publicKey, raw) {
22
+ const encoded = this.#storage.getKey(publicKey);
23
+ const encrypted = decodeBase64Url(encoded);
24
+ const privateKey = await this.#encrypter.decrypt(encrypted);
25
+ return sign(raw, { key: privateKey });
26
+ }
27
+ async introduce(oobi) {
28
+ const response = await fetch(oobi);
29
+ if (!response.ok) {
30
+ throw new Error(`Failed to fetch oobi: ${response.status} ${response.statusText}`);
31
+ }
32
+ if (!response.body) {
33
+ throw new Error(`No body in response`);
34
+ }
35
+ let log = KeyEventLog.empty();
36
+ for await (const message of parse(response.body)) {
37
+ switch (message.body.t) {
38
+ case "dip":
39
+ case "icp":
40
+ case "rot":
41
+ case "ixn": {
42
+ log = log.append(message);
43
+ await this.processMessage(message);
44
+ break;
45
+ }
46
+ case "rpy": {
47
+ await this.processMessage(new Message(message.body));
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ return log.state;
53
+ }
54
+ async loadEventLog(id) {
55
+ const log = KeyEventLog.from(this.#storage.getKeyEvents(id));
56
+ if (log.events.length === 0) {
57
+ throw new Error(`State for id ${id} not found`);
58
+ }
59
+ return log;
60
+ }
61
+ resolveEndpoint(aid, role = "controller") {
62
+ const endRole = resolveEndRole(this.#storage.getReplies({ cid: aid, route: "/end/role/add" }), aid, role);
63
+ if (!endRole) {
64
+ throw new Error(`Could not find end role '${role}' for aid '${aid}'`);
65
+ }
66
+ const location = resolveLocation(this.#storage.getReplies({ eid: endRole.eid, route: "/loc/scheme" }), endRole.eid);
67
+ if (!location) {
68
+ throw new Error(`No valid location found for aid ${aid}`);
69
+ }
70
+ return {
71
+ aid,
72
+ url: location.url,
73
+ scheme: location.scheme,
74
+ role: endRole.role,
75
+ };
76
+ }
77
+ async sign(raw, keys) {
78
+ return Promise.all(keys.map(async (key, idx) => {
79
+ const sig = await this.signWithKey(key, raw);
80
+ return cesr.index(Matter.parse(sig), idx).text();
81
+ }));
82
+ }
83
+ async incept(args = {}) {
84
+ const publicKey = await this.generateKey();
85
+ const nextPublicKey = await this.generateKey();
86
+ const nextPublicKeyDigest = keri.utils.digest(nextPublicKey);
87
+ const event = keri.incept({
88
+ signingKeys: [publicKey],
89
+ nextKeys: [nextPublicKeyDigest],
90
+ wits: args.wits ?? [],
91
+ toad: args.toad,
92
+ });
93
+ await this.commit(KeyEventLog.empty(), event);
94
+ return {
95
+ id: event.body.i,
96
+ event: event.body,
97
+ };
98
+ }
99
+ async processMessage(message) {
100
+ if (message.version.protocol === "ACDC") {
101
+ // TODO: verify ACDC credential SAID and anchors in TEL or KEL
102
+ this.#storage.saveMessage(message);
103
+ return;
104
+ }
105
+ switch (message.body.t) {
106
+ case "icp":
107
+ case "rot":
108
+ case "ixn": {
109
+ const body = message.body;
110
+ const log = KeyEventLog.from(this.#storage.getKeyEvents(body.i));
111
+ // TODO: Detect duplicituous key events
112
+ if (!log.events.find((event) => event.body.d === message.body.d)) {
113
+ log.append(message); // throws if verification fails
114
+ this.#storage.saveMessage(message);
115
+ }
116
+ break;
117
+ }
118
+ case "vcp":
119
+ case "iss":
120
+ case "rev":
121
+ // TODO: verify is anchored to a valid ixn in the issuer's KEL
122
+ this.#storage.saveMessage(message);
123
+ break;
124
+ case "rpy":
125
+ // TODO: Verify that is signed by the controller
126
+ this.#storage.saveMessage(message);
127
+ break;
128
+ default:
129
+ // TODO: Handle other message types
130
+ // this.#storage.saveMessage(message);
131
+ break;
132
+ }
133
+ }
134
+ async commit(log, event) {
135
+ const signingKeys = event.body.t === "icp" ? event.body.k : log.state.signingKeys;
136
+ const backers = event.body.t === "icp" ? (event.body.b ?? []) : (log.state.backers ?? []);
137
+ const sigs = await this.sign(event.raw, signingKeys);
138
+ event.attachments.ControllerIdxSigs.push(...sigs);
139
+ const endpoints = await Promise.all(backers.map((wit) => this.resolveEndpoint(wit)));
140
+ const wigs = await submitToWitnesses(event, endpoints);
141
+ event.attachments.WitnessIdxSigs.push(...wigs);
142
+ await this.processMessage(event);
143
+ }
144
+ async anchor(id, anchor) {
145
+ const log = await this.loadEventLog(id);
146
+ const event = keri.interact(log.state, { data: anchor.data });
147
+ await this.commit(log, event);
148
+ return {
149
+ id: event.body.i,
150
+ event: event.body,
151
+ };
152
+ }
153
+ async rotate(id, args) {
154
+ const log = await this.loadEventLog(id);
155
+ const state = log.state;
156
+ const publicKeys = await Promise.all(state.nextKeyDigests.map((digest) => this.#storage.getPublicKeyByDigest(digest)));
157
+ const nextPublicKey = await this.generateKey();
158
+ const nextPublicKeyDigest = keri.utils.digest(nextPublicKey);
159
+ const event = keri.rotate(state, {
160
+ signingKeys: publicKeys,
161
+ nextKeyDigests: [nextPublicKeyDigest],
162
+ data: args.data,
163
+ });
164
+ await this.commit(log, event);
165
+ return {
166
+ id: event.body.i,
167
+ event: event.body,
168
+ };
169
+ }
170
+ /**
171
+ * Creates and stores a signed reply message and submits to all witnesses.
172
+ */
173
+ async reply(args) {
174
+ const log = await this.loadEventLog(args.id);
175
+ const state = log.state;
176
+ const rpy = keri.reply({
177
+ r: args.route,
178
+ a: args.record,
179
+ });
180
+ const sigs = await this.sign(rpy.raw, state.signingKeys);
181
+ rpy.attachments.TransIdxSigGroups.push({
182
+ snu: state.lastEstablishment.s,
183
+ digest: state.lastEstablishment.d,
184
+ prefix: state.identifier,
185
+ ControllerIdxSigs: sigs,
186
+ });
187
+ await this.processMessage(rpy);
188
+ for (const wit of state.backers) {
189
+ const endpoint = this.resolveEndpoint(wit, "controller");
190
+ const client = new MailboxClient({
191
+ id: wit,
192
+ url: endpoint.url,
193
+ });
194
+ await client.sendMessage(rpy);
195
+ }
196
+ }
197
+ async forward(args) {
198
+ const endpoint = this.resolveEndpoint(args.recipient, "mailbox");
199
+ const client = new MailboxClient({
200
+ id: endpoint.aid,
201
+ url: endpoint.url,
202
+ });
203
+ const log = await this.loadEventLog(args.sender);
204
+ const state = log.state;
205
+ const hasAttachments = args.message.attachments.frames().length > 1;
206
+ if (!hasAttachments) {
207
+ args.message.attachments.TransIdxSigGroups.push({
208
+ snu: state.lastEstablishment.s,
209
+ digest: state.lastEstablishment.d,
210
+ prefix: args.sender,
211
+ ControllerIdxSigs: await this.sign(args.message.raw, state.signingKeys),
212
+ });
213
+ }
214
+ const fwd = keri.exchange({
215
+ sender: args.sender,
216
+ route: "/fwd",
217
+ timestamp: args.timestamp,
218
+ // rp: args.recipient,
219
+ query: { pre: args.recipient, topic: args.topic },
220
+ anchor: {},
221
+ embeds: {
222
+ evt: args.message,
223
+ },
224
+ });
225
+ const fwdsigs = await this.sign(fwd.raw, state.signingKeys);
226
+ fwd.attachments = {
227
+ TransIdxSigGroups: [
228
+ {
229
+ prefix: args.sender,
230
+ ControllerIdxSigs: fwdsigs,
231
+ snu: state.lastEstablishment.s,
232
+ digest: state.lastEstablishment.d,
233
+ },
234
+ ],
235
+ PathedMaterialCouples: fwd.attachments.PathedMaterialCouples.map((couple) => ({
236
+ ...couple,
237
+ grouped: false,
238
+ })),
239
+ };
240
+ await client.sendMessage(fwd);
241
+ }
242
+ async createRegistry(owner) {
243
+ const log = await this.loadEventLog(owner);
244
+ const vcp = keri.registry({
245
+ ii: owner,
246
+ });
247
+ const anchor = await this.anchor(owner, {
248
+ data: {
249
+ d: vcp.body.d,
250
+ s: vcp.body.s,
251
+ i: vcp.body.i,
252
+ },
253
+ });
254
+ const seal = { digest: anchor.event.d, snu: anchor.event.s };
255
+ vcp.attachments.SealSourceCouples.push(seal);
256
+ const state = log.state;
257
+ for (const wit of state.backers) {
258
+ const endpoint = this.resolveEndpoint(wit, "controller");
259
+ const client = new MailboxClient({
260
+ id: wit,
261
+ url: endpoint.url,
262
+ });
263
+ await client.sendMessage(vcp);
264
+ }
265
+ await this.processMessage(vcp);
266
+ return vcp.body;
267
+ }
268
+ async listRegistries(owner) {
269
+ return Array.from(this.#storage.getRegistriesByOwner(owner)).map((message) => message.body);
270
+ }
271
+ async createCredential(args) {
272
+ const registry = this.#storage.getRegistry(args.registryId);
273
+ if (!registry) {
274
+ throw new Error(`Registry ${args.registryId} not found`);
275
+ }
276
+ const log = await this.loadEventLog(registry.body.ii);
277
+ const state = log.state;
278
+ const credential = keri.credential({
279
+ i: state.identifier,
280
+ ri: registry.body.i,
281
+ s: args.schemaId,
282
+ u: args.salt,
283
+ a: {
284
+ i: args.holder,
285
+ dt: keri.utils.formatDate(args.timestamp ?? new Date()),
286
+ ...args.data,
287
+ },
288
+ r: args.rules,
289
+ e: args.edges,
290
+ });
291
+ await this.processMessage(credential);
292
+ return credential.body;
293
+ }
294
+ async getCredential(id) {
295
+ return this.#storage.getCredential(id);
296
+ }
297
+ async listCredentials(registryId) {
298
+ return this.#storage.getCredentialsByRegistry(registryId);
299
+ }
300
+ async issueCredential(credential) {
301
+ const log = await this.loadEventLog(credential.i);
302
+ const iss = keri.issue({
303
+ i: credential.d,
304
+ ri: credential.ri,
305
+ dt: credential.a.dt,
306
+ });
307
+ const anchor = await this.anchor(credential.i, {
308
+ data: {
309
+ d: iss.body.d,
310
+ s: iss.body.s,
311
+ i: iss.body.i,
312
+ },
313
+ });
314
+ const seal = { digest: anchor.event.d, snu: anchor.event.s };
315
+ iss.attachments.SealSourceCouples.push(seal);
316
+ const state = log.state;
317
+ for (const wit of state.backers) {
318
+ const endpoint = this.resolveEndpoint(wit, "controller");
319
+ const client = new MailboxClient({
320
+ id: wit,
321
+ url: endpoint.url,
322
+ });
323
+ await client.sendMessage(iss);
324
+ }
325
+ await this.processMessage(iss);
326
+ }
327
+ getIssueEvent(credentialSaid) {
328
+ const [iss] = [...this.#storage.getCredentialEvents(credentialSaid)];
329
+ if (!iss) {
330
+ throw new Error(`No issuance found for said ${credentialSaid}`);
331
+ }
332
+ return iss;
333
+ }
334
+ getAnchorFromSeal(aid, digest) {
335
+ const log = KeyEventLog.from(this.#storage.getKeyEvents(aid));
336
+ const anchor = log.events.find((message) => message.body.d === digest);
337
+ if (!anchor) {
338
+ throw new Error(`No anchor found for digest ${digest}`);
339
+ }
340
+ return anchor;
341
+ }
342
+ buildCredentialMessage(credential) {
343
+ const [iss] = [...this.#storage.getCredentialEvents(credential.d)];
344
+ if (!iss) {
345
+ return null;
346
+ }
347
+ return new Message(credential, {
348
+ SealSourceTriples: [
349
+ {
350
+ prefix: iss.body.i,
351
+ snu: iss.body.s,
352
+ digest: iss.body.d,
353
+ },
354
+ ],
355
+ });
356
+ }
357
+ async sendCredentialArtifacts(credential, recipient) {
358
+ const log = await this.loadEventLog(credential.i);
359
+ const state = log.state;
360
+ if (credential.e) {
361
+ for (const [, edge] of Object.entries(credential.e)) {
362
+ if (typeof edge === "object" && edge !== null && "n" in edge && typeof edge.n === "string") {
363
+ const source = this.#storage.getCredential(edge.n);
364
+ if (!source) {
365
+ throw new Error(`No source found for edge ${edge.n}`);
366
+ }
367
+ await this.sendCredentialArtifacts(source, recipient);
368
+ const sourceMessage = this.buildCredentialMessage(source);
369
+ if (sourceMessage) {
370
+ await this.forward({
371
+ message: sourceMessage,
372
+ recipient: recipient,
373
+ sender: source.i,
374
+ topic: "credential",
375
+ });
376
+ }
377
+ }
378
+ }
379
+ }
380
+ const endpoint = this.resolveEndpoint(recipient, "mailbox");
381
+ const mailbox = new MailboxClient({
382
+ id: endpoint.aid,
383
+ url: endpoint.url,
384
+ });
385
+ for (const event of log.events) {
386
+ await mailbox.sendMessage(event);
387
+ }
388
+ for (const event of log.events) {
389
+ await this.forward({
390
+ message: event,
391
+ recipient,
392
+ sender: state.identifier,
393
+ topic: "credential",
394
+ });
395
+ }
396
+ const registryMessage = this.#storage.getRegistry(credential.ri);
397
+ if (!registryMessage) {
398
+ throw new Error(`Registry with id ${credential.ri} not found`);
399
+ }
400
+ if (!registryMessage.attachments.SealSourceCouples.length &&
401
+ !registryMessage.attachments.SealSourceTriples.length) {
402
+ throw new Error("No seal found for registry");
403
+ }
404
+ await this.forward({
405
+ message: registryMessage,
406
+ recipient,
407
+ sender: state.identifier,
408
+ topic: "credential",
409
+ });
410
+ for (const message of this.#storage.getCredentialEvents(credential.d)) {
411
+ if (!message.attachments.SealSourceCouples.length && !message.attachments.SealSourceTriples.length) {
412
+ throw new Error("No seal found for issuance");
413
+ }
414
+ await this.forward({
415
+ message,
416
+ recipient,
417
+ sender: state.identifier,
418
+ topic: "credential",
419
+ });
420
+ }
421
+ }
422
+ async grant(args) {
423
+ const log = await this.loadEventLog(args.credential.i);
424
+ const state = log.state;
425
+ const registry = this.#storage.getRegistry(args.credential.ri);
426
+ if (!registry) {
427
+ throw new Error(`Registry not found for said ${args.credential.ri}`);
428
+ }
429
+ const issuee = args.credential.a.i;
430
+ const recipient = args.recipient || (typeof issuee === "string" && issuee ? issuee : undefined);
431
+ if (!recipient) {
432
+ throw new Error("No recipient specified and the credential has no issuee");
433
+ }
434
+ const iss = this.getIssueEvent(args.credential.d);
435
+ const anchorSeal = iss.attachments.SealSourceCouples[0] || iss.attachments.SealSourceTriples[0];
436
+ if (!anchorSeal) {
437
+ throw new Error(`No seal found for issuance ${iss.body.d}`);
438
+ }
439
+ const anchor = this.getAnchorFromSeal(args.credential.i, anchorSeal.digest);
440
+ const grant = keri.exchange({
441
+ sender: state.identifier,
442
+ route: "/ipex/grant",
443
+ timestamp: args.timestamp,
444
+ query: {},
445
+ anchor: {
446
+ m: "",
447
+ i: recipient,
448
+ },
449
+ embeds: {
450
+ acdc: new Message(args.credential, {
451
+ SealSourceTriples: [
452
+ {
453
+ prefix: iss.body.i,
454
+ snu: iss.body.s,
455
+ digest: iss.body.d,
456
+ },
457
+ ],
458
+ }),
459
+ iss: new Message(iss.body, {
460
+ SealSourceCouples: [
461
+ {
462
+ digest: anchor.body.d,
463
+ snu: anchor.body.s,
464
+ },
465
+ ],
466
+ }),
467
+ anc: new Message(anchor.body, {
468
+ ControllerIdxSigs: anchor.attachments.ControllerIdxSigs,
469
+ WitnessIdxSigs: anchor.attachments.WitnessIdxSigs,
470
+ }),
471
+ },
472
+ });
473
+ const grantsigs = await this.sign(grant.raw, state.signingKeys);
474
+ grant.attachments.TransIdxSigGroups.push({
475
+ snu: state.lastEstablishment.s,
476
+ digest: state.lastEstablishment.d,
477
+ prefix: state.identifier,
478
+ ControllerIdxSigs: grantsigs,
479
+ });
480
+ await this.forward({
481
+ message: grant,
482
+ recipient,
483
+ sender: state.identifier,
484
+ topic: "credential",
485
+ timestamp: args.timestamp,
486
+ });
487
+ }
488
+ async query(id, topic) {
489
+ const log = await this.loadEventLog(id);
490
+ const state = log.state;
491
+ const endpoint = this.resolveEndpoint(id, "mailbox");
492
+ const client = new MailboxClient({ id: endpoint.aid, url: endpoint.url });
493
+ const offset = this.#storage.getMailboxOffset(id, topic);
494
+ const queryMessage = keri.query({
495
+ r: "mbx",
496
+ q: {
497
+ src: endpoint.aid,
498
+ i: id,
499
+ topics: { [`/${topic}`]: offset },
500
+ },
501
+ });
502
+ queryMessage.attachments = {
503
+ TransLastIdxSigGroups: [
504
+ {
505
+ prefix: id,
506
+ ControllerIdxSigs: await this.sign(queryMessage.raw, state.signingKeys),
507
+ },
508
+ ],
509
+ };
510
+ const result = await client.sendMessage(queryMessage, AbortSignal.timeout(10000));
511
+ for (const incoming of result) {
512
+ this.#storage.saveMessage(incoming);
513
+ }
514
+ this.#storage.saveMailboxOffset(id, topic, offset + result.length);
515
+ return result;
516
+ }
517
+ async receiveGrants(holderId) {
518
+ const messages = await this.query(holderId, "credential");
519
+ const credentials = [];
520
+ for (const message of messages) {
521
+ const body = message.body;
522
+ if (body.t !== "exn" || body.r !== "/ipex/grant") {
523
+ continue;
524
+ }
525
+ const acdcBody = body.e?.acdc;
526
+ const issBody = body.e?.iss;
527
+ if (!acdcBody || !issBody) {
528
+ throw new Error("Invalid grant message: missing acdc or iss embed");
529
+ }
530
+ const acdcCouple = message.attachments.PathedMaterialCouples.find((c) => c.path === "-e-acdc");
531
+ const issCouple = message.attachments.PathedMaterialCouples.find((c) => c.path === "-e-iss");
532
+ this.#storage.saveMessage(new Message(acdcBody, acdcCouple?.attachments ?? new Attachments()));
533
+ if (issBody) {
534
+ this.#storage.saveMessage(new Message(issBody, issCouple?.attachments ?? new Attachments()));
535
+ }
536
+ credentials.push(acdcBody);
537
+ }
538
+ return credentials;
539
+ }
540
+ async export(id) {
541
+ const log = await this.loadEventLog(id);
542
+ return log.events;
543
+ }
544
+ }
545
+ //# sourceMappingURL=controller.js.map