@bcts/frost-hubert 1.0.0-alpha.17

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 (114) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +35 -0
  3. package/dist/bin/frost.cjs +109 -0
  4. package/dist/bin/frost.cjs.map +1 -0
  5. package/dist/bin/frost.d.cts +1 -0
  6. package/dist/bin/frost.d.mts +1 -0
  7. package/dist/bin/frost.mjs +109 -0
  8. package/dist/bin/frost.mjs.map +1 -0
  9. package/dist/chunk-CQwRTUmo.cjs +53 -0
  10. package/dist/chunk-D3JzZLW2.mjs +21 -0
  11. package/dist/cmd/index.cjs +45 -0
  12. package/dist/cmd/index.d.cts +4 -0
  13. package/dist/cmd/index.d.mts +4 -0
  14. package/dist/cmd/index.mjs +7 -0
  15. package/dist/cmd-C8pmNd28.mjs +4664 -0
  16. package/dist/cmd-C8pmNd28.mjs.map +1 -0
  17. package/dist/cmd-CxUgryx_.cjs +4803 -0
  18. package/dist/cmd-CxUgryx_.cjs.map +1 -0
  19. package/dist/dkg/index.cjs +7 -0
  20. package/dist/dkg/index.d.cts +2 -0
  21. package/dist/dkg/index.d.mts +2 -0
  22. package/dist/dkg/index.mjs +3 -0
  23. package/dist/dkg-D4RcblWl.cjs +364 -0
  24. package/dist/dkg-D4RcblWl.cjs.map +1 -0
  25. package/dist/dkg-DqGrAV81.mjs +334 -0
  26. package/dist/dkg-DqGrAV81.mjs.map +1 -0
  27. package/dist/frost/index.cjs +37 -0
  28. package/dist/frost/index.d.cts +207 -0
  29. package/dist/frost/index.d.cts.map +1 -0
  30. package/dist/frost/index.d.mts +207 -0
  31. package/dist/frost/index.d.mts.map +1 -0
  32. package/dist/frost/index.mjs +3 -0
  33. package/dist/frost-CMH1K0Cw.cjs +511 -0
  34. package/dist/frost-CMH1K0Cw.cjs.map +1 -0
  35. package/dist/frost-Csp0IOrd.mjs +326 -0
  36. package/dist/frost-Csp0IOrd.mjs.map +1 -0
  37. package/dist/index-BGVoWW5P.d.cts +172 -0
  38. package/dist/index-BGVoWW5P.d.cts.map +1 -0
  39. package/dist/index-BJeUYrdE.d.mts +396 -0
  40. package/dist/index-BJeUYrdE.d.mts.map +1 -0
  41. package/dist/index-ByMDUYKw.d.mts +1098 -0
  42. package/dist/index-ByMDUYKw.d.mts.map +1 -0
  43. package/dist/index-DejLkr_F.d.mts +172 -0
  44. package/dist/index-DejLkr_F.d.mts.map +1 -0
  45. package/dist/index-Dib1OE-e.d.cts +1098 -0
  46. package/dist/index-Dib1OE-e.d.cts.map +1 -0
  47. package/dist/index-DnvBKgec.d.cts +396 -0
  48. package/dist/index-DnvBKgec.d.cts.map +1 -0
  49. package/dist/index.cjs +85 -0
  50. package/dist/index.cjs.map +1 -0
  51. package/dist/index.d.cts +15 -0
  52. package/dist/index.d.cts.map +1 -0
  53. package/dist/index.d.mts +15 -0
  54. package/dist/index.d.mts.map +1 -0
  55. package/dist/index.mjs +24 -0
  56. package/dist/index.mjs.map +1 -0
  57. package/dist/registry/index.cjs +13 -0
  58. package/dist/registry/index.d.cts +2 -0
  59. package/dist/registry/index.d.mts +2 -0
  60. package/dist/registry/index.mjs +3 -0
  61. package/dist/registry-CBjRRqNv.mjs +144 -0
  62. package/dist/registry-CBjRRqNv.mjs.map +1 -0
  63. package/dist/registry-CWp2amuo.mjs +789 -0
  64. package/dist/registry-CWp2amuo.mjs.map +1 -0
  65. package/dist/registry-D5yh293y.cjs +857 -0
  66. package/dist/registry-D5yh293y.cjs.map +1 -0
  67. package/dist/registry-DNUNW6SH.cjs +163 -0
  68. package/dist/registry-DNUNW6SH.cjs.map +1 -0
  69. package/package.json +119 -0
  70. package/src/bin/frost.ts +218 -0
  71. package/src/cmd/busy.ts +64 -0
  72. package/src/cmd/check.ts +20 -0
  73. package/src/cmd/common.ts +40 -0
  74. package/src/cmd/dkg/common.ts +275 -0
  75. package/src/cmd/dkg/coordinator/finalize.ts +592 -0
  76. package/src/cmd/dkg/coordinator/index.ts +12 -0
  77. package/src/cmd/dkg/coordinator/invite.ts +217 -0
  78. package/src/cmd/dkg/coordinator/round1.ts +889 -0
  79. package/src/cmd/dkg/coordinator/round2.ts +959 -0
  80. package/src/cmd/dkg/index.ts +11 -0
  81. package/src/cmd/dkg/participant/finalize.ts +575 -0
  82. package/src/cmd/dkg/participant/index.ts +12 -0
  83. package/src/cmd/dkg/participant/receive.ts +348 -0
  84. package/src/cmd/dkg/participant/round1.ts +464 -0
  85. package/src/cmd/dkg/participant/round2.ts +627 -0
  86. package/src/cmd/index.ts +18 -0
  87. package/src/cmd/parallel.ts +334 -0
  88. package/src/cmd/registry/index.ts +88 -0
  89. package/src/cmd/registry/owner/index.ts +9 -0
  90. package/src/cmd/registry/owner/set.ts +70 -0
  91. package/src/cmd/registry/participant/add.ts +70 -0
  92. package/src/cmd/registry/participant/index.ts +9 -0
  93. package/src/cmd/sign/common.ts +108 -0
  94. package/src/cmd/sign/coordinator/index.ts +11 -0
  95. package/src/cmd/sign/coordinator/invite.ts +431 -0
  96. package/src/cmd/sign/coordinator/round1.ts +751 -0
  97. package/src/cmd/sign/coordinator/round2.ts +836 -0
  98. package/src/cmd/sign/index.ts +11 -0
  99. package/src/cmd/sign/participant/finalize.ts +823 -0
  100. package/src/cmd/sign/participant/index.ts +12 -0
  101. package/src/cmd/sign/participant/receive.ts +378 -0
  102. package/src/cmd/sign/participant/round1.ts +479 -0
  103. package/src/cmd/sign/participant/round2.ts +748 -0
  104. package/src/cmd/storage.ts +116 -0
  105. package/src/dkg/group-invite.ts +414 -0
  106. package/src/dkg/index.ts +10 -0
  107. package/src/dkg/proposed-participant.ts +132 -0
  108. package/src/frost/index.ts +456 -0
  109. package/src/index.ts +45 -0
  110. package/src/registry/group-record.ts +392 -0
  111. package/src/registry/index.ts +12 -0
  112. package/src/registry/owner-record.ts +146 -0
  113. package/src/registry/participant-record.ts +186 -0
  114. package/src/registry/registry-impl.ts +364 -0
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Sign coordinator invite command.
3
+ *
4
+ * Port of cmd/sign/coordinator/invite.rs from frost-hubert-rust.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
10
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
11
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
12
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
13
+
14
+ import * as fs from "node:fs";
15
+ import * as path from "node:path";
16
+
17
+ import { ARID } from "@bcts/components";
18
+ import { CborDate } from "@bcts/dcbor";
19
+ import { Envelope } from "@bcts/envelope";
20
+ import { SealedRequest } from "@bcts/gstp";
21
+ import { type XIDDocument } from "@bcts/xid";
22
+
23
+ import {
24
+ Registry,
25
+ resolveRegistryPath,
26
+ type GroupRecord,
27
+ type GroupParticipant,
28
+ type OwnerRecord,
29
+ } from "../../../registry/index.js";
30
+ import { putWithIndicator } from "../../busy.js";
31
+ import { type StorageClient } from "../../storage.js";
32
+ import { parseAridUr } from "../../dkg/common.js";
33
+ import { signingStateDir } from "../common.js";
34
+
35
+ // -----------------------------------------------------------------------------
36
+ // Session ARID management
37
+ // -----------------------------------------------------------------------------
38
+
39
+ /**
40
+ * Session ARIDs for tracking the signing session.
41
+ *
42
+ * Port of `struct SessionArids` from cmd/sign/coordinator/invite.rs lines 151-156.
43
+ */
44
+ export interface SessionArids {
45
+ sessionId: ARID;
46
+ startArid: ARID;
47
+ commitArids: Map<string, ARID>; // Map<XID.urString(), ARID>
48
+ shareArids: Map<string, ARID>; // Map<XID.urString(), ARID>
49
+ }
50
+
51
+ /**
52
+ * Create new session ARIDs for all participants.
53
+ *
54
+ * Port of `SessionArids::new()` from cmd/sign/coordinator/invite.rs lines 158-173.
55
+ */
56
+ export function createSessionArids(participants: GroupParticipant[]): SessionArids {
57
+ const commitArids = new Map<string, ARID>();
58
+ const shareArids = new Map<string, ARID>();
59
+
60
+ for (const participant of participants) {
61
+ const xidKey = participant.xid().urString();
62
+ commitArids.set(xidKey, ARID.new());
63
+ shareArids.set(xidKey, ARID.new());
64
+ }
65
+
66
+ return {
67
+ sessionId: ARID.new(),
68
+ startArid: ARID.new(),
69
+ commitArids,
70
+ shareArids,
71
+ };
72
+ }
73
+
74
+ // -----------------------------------------------------------------------------
75
+ // Validation
76
+ // -----------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Validate that the owner is the coordinator of the group.
80
+ *
81
+ * Port of `validate_coordinator()` from cmd/sign/coordinator/invite.rs lines 179-192.
82
+ */
83
+ export function validateCoordinator(groupRecord: GroupRecord, owner: OwnerRecord): void {
84
+ if (groupRecord.coordinator().xid().urString() !== owner.xid().urString()) {
85
+ throw new Error(
86
+ `Only the coordinator can start signing. Coordinator: ${groupRecord.coordinator().xid().urString()}, Owner: ${owner.xid().urString()}`,
87
+ );
88
+ }
89
+ }
90
+
91
+ // -----------------------------------------------------------------------------
92
+ // Participant document gathering
93
+ // -----------------------------------------------------------------------------
94
+
95
+ /**
96
+ * Gather XIDDocuments for all participants from the registry.
97
+ *
98
+ * Port of `gather_recipient_documents()` from cmd/sign/coordinator/invite.rs lines 198-222.
99
+ */
100
+ export function gatherRecipientDocuments(
101
+ participants: GroupParticipant[],
102
+ owner: OwnerRecord,
103
+ registry: Registry,
104
+ ): XIDDocument[] {
105
+ const recipientDocs: XIDDocument[] = [];
106
+
107
+ for (const participant of participants) {
108
+ const xid = participant.xid();
109
+ if (xid.urString() === owner.xid().urString()) {
110
+ recipientDocs.push(owner.xidDocument());
111
+ } else {
112
+ const record = registry.participant(xid);
113
+ if (record === undefined) {
114
+ throw new Error(`Participant ${xid.urString()} not found in registry`);
115
+ }
116
+ recipientDocs.push(record.xidDocument());
117
+ }
118
+ }
119
+
120
+ return recipientDocs;
121
+ }
122
+
123
+ // -----------------------------------------------------------------------------
124
+ // Request building
125
+ // -----------------------------------------------------------------------------
126
+
127
+ /**
128
+ * Context for building the sign invite request.
129
+ *
130
+ * Port of `struct SignInviteContext` from cmd/sign/coordinator/invite.rs lines 228-237.
131
+ */
132
+ export interface SignInviteContext {
133
+ arids: SessionArids;
134
+ groupId: ARID;
135
+ targetEnvelope: Envelope;
136
+ groupRecord: GroupRecord;
137
+ owner: OwnerRecord;
138
+ registry: Registry;
139
+ participants: GroupParticipant[];
140
+ validUntil: Date;
141
+ }
142
+
143
+ /**
144
+ * Build the sign invite request.
145
+ *
146
+ * Port of `build_sign_invite_request()` from cmd/sign/coordinator/invite.rs lines 239-284.
147
+ */
148
+ export function buildSignInviteRequest(ctx: SignInviteContext): SealedRequest {
149
+ let request = SealedRequest.new("signInvite", ctx.arids.sessionId, ctx.owner.xidDocument())
150
+ .withParameter("group", ctx.groupId)
151
+ .withParameter("session", ctx.arids.sessionId)
152
+ .withParameter("target", ctx.targetEnvelope)
153
+ .withParameter("minSigners", ctx.groupRecord.minSigners())
154
+ .withDate(new Date())
155
+ .withParameter("validUntil", CborDate.fromDatetime(ctx.validUntil));
156
+
157
+ for (const participant of ctx.participants) {
158
+ const xid = participant.xid();
159
+ const xidKey = xid.urString();
160
+
161
+ // Get participant document
162
+ let participantDoc: XIDDocument;
163
+ if (xidKey === ctx.owner.xid().urString()) {
164
+ participantDoc = ctx.owner.xidDocument();
165
+ } else {
166
+ const record = ctx.registry.participant(xid);
167
+ if (record === undefined) {
168
+ throw new Error("Participant not found in registry");
169
+ }
170
+ participantDoc = record.xidDocument();
171
+ }
172
+
173
+ // Get encryption key
174
+ const encryptionKey = participantDoc.encryptionKey();
175
+ if (encryptionKey === undefined) {
176
+ throw new Error("Participant XID document has no encryption key");
177
+ }
178
+
179
+ // Get commit ARID for this participant
180
+ const responseArid = ctx.arids.commitArids.get(xidKey);
181
+ if (responseArid === undefined) {
182
+ throw new Error("commit ARID not found for participant");
183
+ }
184
+
185
+ // Encrypt response ARID to participant
186
+ // @ts-expect-error TS2339 - API mismatch: toEnvelope/encryptToRecipient methods
187
+ const encryptedResponseArid = responseArid.toEnvelope().encryptToRecipient(encryptionKey);
188
+
189
+ // Build participant entry envelope
190
+ const participantEntry = Envelope.new(xid).addAssertion("response_arid", encryptedResponseArid);
191
+
192
+ request = request.withParameter("participant", participantEntry);
193
+ }
194
+
195
+ return request;
196
+ }
197
+
198
+ // -----------------------------------------------------------------------------
199
+ // State persistence
200
+ // -----------------------------------------------------------------------------
201
+
202
+ /**
203
+ * Build the session state JSON for persistence.
204
+ *
205
+ * Port of `build_session_state_json()` from cmd/sign/coordinator/invite.rs lines 290-346.
206
+ */
207
+ export function buildSessionStateJson(
208
+ arids: SessionArids,
209
+ groupId: ARID,
210
+ groupRecord: GroupRecord,
211
+ participants: GroupParticipant[],
212
+ targetEnvelope: Envelope,
213
+ ): Record<string, unknown> {
214
+ const participantsMap: Record<string, unknown> = {};
215
+
216
+ for (const participant of participants) {
217
+ const xid = participant.xid();
218
+ const xidKey = xid.urString();
219
+
220
+ const commitArid = arids.commitArids.get(xidKey);
221
+ const shareArid = arids.shareArids.get(xidKey);
222
+
223
+ if (commitArid === undefined || shareArid === undefined) {
224
+ throw new Error("ARID not found for participant");
225
+ }
226
+
227
+ participantsMap[xidKey] = {
228
+ commit_arid: commitArid.urString(),
229
+ share_arid: shareArid.urString(),
230
+ };
231
+ }
232
+
233
+ return {
234
+ session_id: arids.sessionId.urString(),
235
+ start_arid: arids.startArid.urString(),
236
+ group: groupId.urString(),
237
+ min_signers: groupRecord.minSigners(),
238
+ participants: participantsMap,
239
+ target: targetEnvelope.urString(),
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Persist the session state to disk.
245
+ *
246
+ * Port of `persist_session_state()` from cmd/sign/coordinator/invite.rs lines 348-356.
247
+ */
248
+ export function persistSessionState(signingDir: string, stateJson: Record<string, unknown>): void {
249
+ fs.mkdirSync(signingDir, { recursive: true });
250
+ const startStatePath = path.join(signingDir, "start.json");
251
+ fs.writeFileSync(startStatePath, JSON.stringify(stateJson, null, 2));
252
+ }
253
+
254
+ // -----------------------------------------------------------------------------
255
+ // File loading
256
+ // -----------------------------------------------------------------------------
257
+
258
+ /**
259
+ * Load an envelope from a file path.
260
+ *
261
+ * Port of `load_envelope_from_path()` from cmd/sign/coordinator/invite.rs lines 385-392.
262
+ */
263
+ export function loadEnvelopeFromPath(filePath: string): Envelope {
264
+ if (!fs.existsSync(filePath)) {
265
+ throw new Error(`Failed to read target envelope from ${filePath}`);
266
+ }
267
+
268
+ const data = fs.readFileSync(filePath, "utf-8");
269
+ const trimmed = data.trim();
270
+
271
+ try {
272
+ return Envelope.fromURString(trimmed);
273
+ } catch (e) {
274
+ throw new Error(`Failed to load target envelope from ${filePath}: ${String(e)}`);
275
+ }
276
+ }
277
+
278
+ // -----------------------------------------------------------------------------
279
+ // Options and Result types
280
+ // -----------------------------------------------------------------------------
281
+
282
+ /**
283
+ * Options for the sign invite command.
284
+ */
285
+ export interface SignInviteOptions {
286
+ registryPath?: string;
287
+ groupId: string;
288
+ targetFile: string;
289
+ validDays?: number;
290
+ verbose?: boolean;
291
+ preview?: boolean;
292
+ }
293
+
294
+ /**
295
+ * Result of the sign invite command.
296
+ */
297
+ export interface SignInviteResult {
298
+ sessionId: string;
299
+ startArid: string;
300
+ }
301
+
302
+ // -----------------------------------------------------------------------------
303
+ // Main invite function
304
+ // -----------------------------------------------------------------------------
305
+
306
+ /**
307
+ * Execute the sign coordinator invite command.
308
+ *
309
+ * Invites participants to sign a target envelope.
310
+ *
311
+ * Port of `CommandArgs::exec()` from cmd/sign/coordinator/invite.rs lines 44-144.
312
+ */
313
+ export async function invite(
314
+ client: StorageClient | undefined,
315
+ options: SignInviteOptions,
316
+ cwd: string,
317
+ ): Promise<SignInviteResult> {
318
+ // Validate preview mode
319
+ if (client !== undefined && options.preview === true) {
320
+ throw new Error("--preview cannot be used with Hubert storage options");
321
+ }
322
+
323
+ const registryPath = resolveRegistryPath(options.registryPath, cwd);
324
+ const registry = Registry.load(registryPath);
325
+
326
+ const owner = registry.owner();
327
+ if (owner === undefined) {
328
+ throw new Error("Registry owner is required");
329
+ }
330
+
331
+ const groupId = parseAridUr(options.groupId);
332
+ const groupRecord = registry.group(groupId);
333
+
334
+ if (groupRecord === undefined) {
335
+ throw new Error(`Group ${options.groupId} not found in registry`);
336
+ }
337
+
338
+ // Validate sender is coordinator
339
+ validateCoordinator(groupRecord, owner);
340
+
341
+ // Load target envelope
342
+ const targetPath = path.resolve(cwd, options.targetFile);
343
+ const targetEnvelope = loadEnvelopeFromPath(targetPath);
344
+
345
+ // Get participants
346
+ const participants = groupRecord.participants();
347
+
348
+ // Gather recipient documents
349
+ const recipientDocs = gatherRecipientDocuments(participants, owner, registry);
350
+
351
+ // Get signer keys
352
+ const signerKeys = owner.xidDocument().inceptionPrivateKeys();
353
+ if (signerKeys === undefined) {
354
+ throw new Error("Coordinator XID document has no signing keys");
355
+ }
356
+
357
+ // Generate session ARIDs
358
+ const sessionArids = createSessionArids(participants);
359
+
360
+ // Calculate valid until date (default 1 hour)
361
+ const validDays = options.validDays ?? 1 / 24; // 1 hour default
362
+ const validUntil = new Date(Date.now() + validDays * 24 * 60 * 60 * 1000);
363
+
364
+ // Build request context
365
+ const ctx: SignInviteContext = {
366
+ arids: sessionArids,
367
+ groupId,
368
+ targetEnvelope,
369
+ groupRecord,
370
+ owner,
371
+ registry,
372
+ participants,
373
+ validUntil,
374
+ };
375
+
376
+ // Build request
377
+ const request = buildSignInviteRequest(ctx);
378
+
379
+ // Build state for persistence
380
+ const stateJson = buildSessionStateJson(
381
+ sessionArids,
382
+ groupId,
383
+ groupRecord,
384
+ participants,
385
+ targetEnvelope,
386
+ );
387
+
388
+ // Build envelope for recipients
389
+ const recipientRefs = recipientDocs;
390
+ const sealedEnvelope = request.toEnvelopeForRecipients(validUntil, signerKeys, recipientRefs);
391
+
392
+ // Handle preview mode
393
+ if (options.preview === true) {
394
+ const unsealed = request.toEnvelope(undefined, signerKeys, undefined);
395
+ console.log(unsealed.urString());
396
+ return {
397
+ sessionId: sessionArids.sessionId.urString(),
398
+ startArid: sessionArids.startArid.urString(),
399
+ };
400
+ }
401
+
402
+ // Persist state
403
+ const signingDir = signingStateDir(registryPath, groupId.hex(), sessionArids.sessionId.hex());
404
+ persistSessionState(signingDir, stateJson);
405
+
406
+ // Post to Hubert storage
407
+ if (client === undefined) {
408
+ throw new Error("Hubert storage is required for sign start");
409
+ }
410
+
411
+ await putWithIndicator(
412
+ client,
413
+ sessionArids.startArid,
414
+ sealedEnvelope,
415
+ "Signing invite",
416
+ options.verbose ?? false,
417
+ );
418
+
419
+ if (options.verbose === true) {
420
+ console.log(`Session ID: ${sessionArids.sessionId.urString()}`);
421
+ console.log(`Start ARID: ${sessionArids.startArid.urString()}`);
422
+ }
423
+
424
+ // Output the start ARID
425
+ console.log(sessionArids.startArid.urString());
426
+
427
+ return {
428
+ sessionId: sessionArids.sessionId.urString(),
429
+ startArid: sessionArids.startArid.urString(),
430
+ };
431
+ }