@bcts/frost-hubert 1.0.0-alpha.23 → 1.0.0-beta.0

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 (154) hide show
  1. package/dist/bin/frost.cjs +345 -71
  2. package/dist/bin/frost.cjs.map +1 -1
  3. package/dist/bin/frost.mjs +345 -71
  4. package/dist/bin/frost.mjs.map +1 -1
  5. package/dist/busy-DkM2jAIZ.mjs +27 -0
  6. package/dist/busy-DkM2jAIZ.mjs.map +1 -0
  7. package/dist/busy-EZU7EKr6.cjs +38 -0
  8. package/dist/busy-EZU7EKr6.cjs.map +1 -0
  9. package/dist/cmd/index.cjs +28 -22
  10. package/dist/cmd/index.d.cts +2 -2
  11. package/dist/cmd/index.d.mts +2 -2
  12. package/dist/cmd/index.mjs +7 -3
  13. package/dist/cmd-Bw9_i2_f.cjs +130 -0
  14. package/dist/cmd-Bw9_i2_f.cjs.map +1 -0
  15. package/dist/cmd-CS1uJtuD.mjs +113 -0
  16. package/dist/cmd-CS1uJtuD.mjs.map +1 -0
  17. package/dist/common-CvH6dFvQ.mjs +282 -0
  18. package/dist/common-CvH6dFvQ.mjs.map +1 -0
  19. package/dist/common-DUWvtc08.mjs +96 -0
  20. package/dist/common-DUWvtc08.mjs.map +1 -0
  21. package/dist/common-lKP5EzHy.cjs +372 -0
  22. package/dist/common-lKP5EzHy.cjs.map +1 -0
  23. package/dist/common-lThIvJmZ.cjs +114 -0
  24. package/dist/common-lThIvJmZ.cjs.map +1 -0
  25. package/dist/dkg/index.cjs +6 -102
  26. package/dist/dkg/index.cjs.map +1 -1
  27. package/dist/dkg/index.d.cts +2 -2
  28. package/dist/dkg/index.d.mts +2 -2
  29. package/dist/dkg/index.mjs +4 -101
  30. package/dist/dkg/index.mjs.map +1 -1
  31. package/dist/finalize-BRgJK-Xv.cjs +402 -0
  32. package/dist/finalize-BRgJK-Xv.cjs.map +1 -0
  33. package/dist/finalize-BfLgzn8f.cjs +303 -0
  34. package/dist/finalize-BfLgzn8f.cjs.map +1 -0
  35. package/dist/finalize-CNTDj6aS.mjs +389 -0
  36. package/dist/finalize-CNTDj6aS.mjs.map +1 -0
  37. package/dist/finalize-EC3ikHQq.mjs +252 -0
  38. package/dist/finalize-EC3ikHQq.mjs.map +1 -0
  39. package/dist/finalize-IA01t_Qq.mjs +290 -0
  40. package/dist/finalize-IA01t_Qq.mjs.map +1 -0
  41. package/dist/finalize-UPyI1yb1.cjs +265 -0
  42. package/dist/finalize-UPyI1yb1.cjs.map +1 -0
  43. package/dist/{index-BkqLimZT.d.mts → index-B3c-80VS.d.cts} +26 -3
  44. package/dist/index-B3c-80VS.d.cts.map +1 -0
  45. package/dist/{index-BJlwbPYu.d.cts → index-BgbSGpxn.d.mts} +102 -80
  46. package/dist/index-BgbSGpxn.d.mts.map +1 -0
  47. package/dist/{index-BMbPgH0W.d.cts → index-C8QeHNwa.d.cts} +46 -2
  48. package/dist/{index-BMbPgH0W.d.cts.map → index-C8QeHNwa.d.cts.map} +1 -1
  49. package/dist/{index-DmxfT59Y.d.cts → index-D3QTWkEm.d.mts} +26 -3
  50. package/dist/index-D3QTWkEm.d.mts.map +1 -0
  51. package/dist/{index-DoV5HFvV.d.mts → index-DVbWyOs7.d.mts} +46 -2
  52. package/dist/{index-DoV5HFvV.d.mts.map → index-DVbWyOs7.d.mts.map} +1 -1
  53. package/dist/{index-Dzm1v4_4.d.mts → index-F1iNEAJR.d.cts} +102 -80
  54. package/dist/index-F1iNEAJR.d.cts.map +1 -0
  55. package/dist/index.cjs +31 -23
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +4 -4
  58. package/dist/index.d.mts +4 -4
  59. package/dist/index.mjs +9 -4
  60. package/dist/index.mjs.map +1 -1
  61. package/dist/invite-5277FQVT.cjs +274 -0
  62. package/dist/invite-5277FQVT.cjs.map +1 -0
  63. package/dist/invite-DUTcfTgX.cjs +109 -0
  64. package/dist/invite-DUTcfTgX.cjs.map +1 -0
  65. package/dist/invite-IU4n0dq2.mjs +96 -0
  66. package/dist/invite-IU4n0dq2.mjs.map +1 -0
  67. package/dist/invite-RU-OXTNS.mjs +219 -0
  68. package/dist/invite-RU-OXTNS.mjs.map +1 -0
  69. package/dist/parallel-D1R6ZGlY.cjs +318 -0
  70. package/dist/parallel-D1R6ZGlY.cjs.map +1 -0
  71. package/dist/parallel-D6zc6VW4.mjs +235 -0
  72. package/dist/parallel-D6zc6VW4.mjs.map +1 -0
  73. package/dist/proposed-participant-Dm1Eq6mX.cjs +141 -0
  74. package/dist/proposed-participant-Dm1Eq6mX.cjs.map +1 -0
  75. package/dist/proposed-participant-cWM7iUrO.mjs +129 -0
  76. package/dist/proposed-participant-cWM7iUrO.mjs.map +1 -0
  77. package/dist/receive-CAI-x4II.cjs +213 -0
  78. package/dist/receive-CAI-x4II.cjs.map +1 -0
  79. package/dist/receive-D2Nn68L7.mjs +188 -0
  80. package/dist/receive-D2Nn68L7.mjs.map +1 -0
  81. package/dist/receive-DA_KQEgk.mjs +177 -0
  82. package/dist/receive-DA_KQEgk.mjs.map +1 -0
  83. package/dist/receive-kZMsXhbK.cjs +190 -0
  84. package/dist/receive-kZMsXhbK.cjs.map +1 -0
  85. package/dist/registry/index.cjs +85 -10
  86. package/dist/registry/index.cjs.map +1 -1
  87. package/dist/registry/index.d.cts +1 -1
  88. package/dist/registry/index.d.mts +1 -1
  89. package/dist/registry/index.mjs +85 -10
  90. package/dist/registry/index.mjs.map +1 -1
  91. package/dist/{registry-loI1_Mh1.cjs → registry-9puTaRrD.cjs} +1 -1
  92. package/dist/{registry-loI1_Mh1.cjs.map → registry-9puTaRrD.cjs.map} +1 -1
  93. package/dist/{registry-CgrCZ4En.mjs → registry-BpCwtrRt.mjs} +1 -1
  94. package/dist/{registry-CgrCZ4En.mjs.map → registry-BpCwtrRt.mjs.map} +1 -1
  95. package/dist/round1-4Hyx8w0x.cjs +422 -0
  96. package/dist/round1-4Hyx8w0x.cjs.map +1 -0
  97. package/dist/round1-7v9LlE11.mjs +373 -0
  98. package/dist/round1-7v9LlE11.mjs.map +1 -0
  99. package/dist/round1-BHBjru1m.cjs +465 -0
  100. package/dist/round1-BHBjru1m.cjs.map +1 -0
  101. package/dist/round1-CMLKN2RR.mjs +195 -0
  102. package/dist/round1-CMLKN2RR.mjs.map +1 -0
  103. package/dist/round1-CWSXZx5R.cjs +208 -0
  104. package/dist/round1-CWSXZx5R.cjs.map +1 -0
  105. package/dist/round1-CcQCGlIT.mjs +208 -0
  106. package/dist/round1-CcQCGlIT.mjs.map +1 -0
  107. package/dist/round1-Cgm7j1kI.mjs +452 -0
  108. package/dist/round1-Cgm7j1kI.mjs.map +1 -0
  109. package/dist/round1-DQ0fnc1H.cjs +221 -0
  110. package/dist/round1-DQ0fnc1H.cjs.map +1 -0
  111. package/dist/round2-BWz9SQIi.cjs +305 -0
  112. package/dist/round2-BWz9SQIi.cjs.map +1 -0
  113. package/dist/round2-BkNRCXgS.mjs +292 -0
  114. package/dist/round2-BkNRCXgS.mjs.map +1 -0
  115. package/dist/round2-Bl2uK93U.mjs +450 -0
  116. package/dist/round2-Bl2uK93U.mjs.map +1 -0
  117. package/dist/round2-CdUT-AhH.cjs +499 -0
  118. package/dist/round2-CdUT-AhH.cjs.map +1 -0
  119. package/dist/round2-DOA3rnV-.mjs +280 -0
  120. package/dist/round2-DOA3rnV-.mjs.map +1 -0
  121. package/dist/round2-Dg24w-TU.mjs +397 -0
  122. package/dist/round2-Dg24w-TU.mjs.map +1 -0
  123. package/dist/round2-LylCa84n.cjs +293 -0
  124. package/dist/round2-LylCa84n.cjs.map +1 -0
  125. package/dist/round2-o2Q-GMbX.cjs +410 -0
  126. package/dist/round2-o2Q-GMbX.cjs.map +1 -0
  127. package/dist/storage-B-Gu68-O.cjs +79 -0
  128. package/dist/storage-B-Gu68-O.cjs.map +1 -0
  129. package/dist/storage-Bkkliz0K.mjs +74 -0
  130. package/dist/storage-Bkkliz0K.mjs.map +1 -0
  131. package/package.json +10 -10
  132. package/src/bin/frost.ts +849 -128
  133. package/src/cmd/common.ts +19 -1
  134. package/src/cmd/dkg/common.ts +97 -10
  135. package/src/cmd/dkg/coordinator/invite.ts +5 -2
  136. package/src/cmd/dkg/participant/finalize.ts +51 -17
  137. package/src/cmd/dkg/participant/round1.ts +39 -38
  138. package/src/cmd/dkg/participant/round2.ts +60 -26
  139. package/src/cmd/sign/coordinator/round2.ts +5 -1
  140. package/src/cmd/sign/participant/finalize.ts +6 -2
  141. package/src/cmd/sign/participant/receive.ts +5 -2
  142. package/src/dkg/group-invite.ts +12 -2
  143. package/src/dkg/proposed-participant.ts +32 -3
  144. package/src/registry/owner-record.ts +12 -0
  145. package/src/registry/participant-record.ts +35 -2
  146. package/src/registry/registry-impl.ts +74 -18
  147. package/dist/cmd-5yLeC_QL.mjs +0 -4708
  148. package/dist/cmd-5yLeC_QL.mjs.map +0 -1
  149. package/dist/cmd-BfZjC3Uh.cjs +0 -4847
  150. package/dist/cmd-BfZjC3Uh.cjs.map +0 -1
  151. package/dist/index-BJlwbPYu.d.cts.map +0 -1
  152. package/dist/index-BkqLimZT.d.mts.map +0 -1
  153. package/dist/index-DmxfT59Y.d.cts.map +0 -1
  154. package/dist/index-Dzm1v4_4.d.mts.map +0 -1
package/src/bin/frost.ts CHANGED
@@ -3,12 +3,18 @@
3
3
  * Copyright © 2023-2026 Blockchain Commons, LLC
4
4
  * Copyright © 2025-2026 Parity Technologies
5
5
  *
6
- */
7
-
8
- /**
6
+ *
9
7
  * FROST CLI binary entry point.
10
8
  *
11
- * Port of main.rs and lib.rs CLI from frost-hubert-rust.
9
+ * Port of `frost-hubert-rust/src/main.rs` and the per-subcommand
10
+ * `clap::Parser` definitions in
11
+ * `frost-hubert-rust/src/cmd/{registry,dkg,sign}/...`.
12
+ *
13
+ * Each subcommand is a thin wrapper that:
14
+ * 1. parses CLI args into the matching `*Options` interface,
15
+ * 2. constructs a `StorageClient` if a backend is requested,
16
+ * 3. delegates to the library function in `src/cmd/...`,
17
+ * 4. prints the same single-line output Rust prints.
12
18
  *
13
19
  * @module
14
20
  */
@@ -17,13 +23,48 @@ import { program } from "commander";
17
23
 
18
24
  import { ownerSet } from "../cmd/registry/owner/set.js";
19
25
  import { participantAdd } from "../cmd/registry/participant/add.js";
26
+ import { type StorageClient, type StorageSelection, createStorageClient } from "../cmd/storage.js";
27
+
28
+ // DKG / sign command modules transitively load `@frosts/core`, whose
29
+ // published dist auto-imports `vitest` as a side-effect (a known
30
+ // upstream packaging bug). Importing them at module-load time would
31
+ // crash the registry-only CLI, so we lazy-import them inside the
32
+ // command handlers below. Once `@frosts/core` ships a clean dist this
33
+ // can be flipped back to static imports.
34
+ const lazyDkgCoord = {
35
+ invite: () => import("../cmd/dkg/coordinator/invite.js").then((m) => m.invite),
36
+ round1: () => import("../cmd/dkg/coordinator/round1.js").then((m) => m.round1),
37
+ round2: () => import("../cmd/dkg/coordinator/round2.js").then((m) => m.round2),
38
+ finalize: () => import("../cmd/dkg/coordinator/finalize.js").then((m) => m.finalize),
39
+ };
40
+ const lazyDkgPart = {
41
+ receive: () => import("../cmd/dkg/participant/receive.js").then((m) => m.receive),
42
+ round1: () => import("../cmd/dkg/participant/round1.js").then((m) => m.round1),
43
+ round2: () => import("../cmd/dkg/participant/round2.js").then((m) => m.round2),
44
+ finalize: () => import("../cmd/dkg/participant/finalize.js").then((m) => m.finalize),
45
+ };
46
+ const lazySignCoord = {
47
+ invite: () => import("../cmd/sign/coordinator/invite.js").then((m) => m.invite),
48
+ round1: () => import("../cmd/sign/coordinator/round1.js").then((m) => m.round1),
49
+ round2: () => import("../cmd/sign/coordinator/round2.js").then((m) => m.round2),
50
+ };
51
+ const lazySignPart = {
52
+ receive: () => import("../cmd/sign/participant/receive.js").then((m) => m.receive),
53
+ round1: () => import("../cmd/sign/participant/round1.js").then((m) => m.round1),
54
+ round2: () => import("../cmd/sign/participant/round2.js").then((m) => m.round2),
55
+ finalize: () => import("../cmd/sign/participant/finalize.js").then((m) => m.finalize),
56
+ };
20
57
 
21
58
  // Type for modules that may have registerTags
22
59
  interface TagModule {
23
60
  registerTags?: () => void;
24
61
  }
25
62
 
26
- // Register CBOR tags using dynamic import
63
+ /**
64
+ * Initialize the global CBOR tag registry. Mirrors the
65
+ * Rust `lib.rs::run` initialization step (`bc_components::register_tags();`
66
+ * `bc_envelope::register_tags();` `provenance_mark::register_tags();`).
67
+ */
27
68
  async function registerTags(): Promise<void> {
28
69
  try {
29
70
  const components = (await import("@bcts/components")) as TagModule;
@@ -44,40 +85,145 @@ async function registerTags(): Promise<void> {
44
85
  }
45
86
  }
46
87
 
47
- // Main CLI function
88
+ /**
89
+ * Storage-related CLI options shared across most subcommands.
90
+ *
91
+ * Mirrors Rust `OptionalStorageSelector` (clap-flatten) — the user can
92
+ * pass exactly one of `--storage server|mainline|ipfs|hybrid` to enable
93
+ * a Hubert backend. When omitted, the subcommand runs in local-only
94
+ * preview/state mode.
95
+ */
96
+ interface StorageOpts {
97
+ storage?: StorageSelection;
98
+ host?: string;
99
+ port?: string;
100
+ }
101
+
102
+ function isStorageSelection(value: string): value is StorageSelection {
103
+ return value === "server" || value === "mainline" || value === "ipfs" || value === "hybrid";
104
+ }
105
+
106
+ function parseStorageSelection(opts: StorageOpts): StorageSelection | undefined {
107
+ const value = opts.storage;
108
+ if (value === undefined) return undefined;
109
+ if (!isStorageSelection(value)) {
110
+ throw new Error(`Unknown --storage value: ${String(value)}`);
111
+ }
112
+ return value;
113
+ }
114
+
115
+ async function buildClient(opts: StorageOpts): Promise<StorageClient | undefined> {
116
+ const selection = parseStorageSelection(opts);
117
+ if (selection === undefined) return undefined;
118
+ const serverUrl =
119
+ opts.host !== undefined && opts.port !== undefined
120
+ ? `http://${opts.host}:${opts.port}`
121
+ : opts.host;
122
+ return createStorageClient(selection, serverUrl);
123
+ }
124
+
125
+ /** Convert a comma-separated, repeated-flag, or positional list. */
126
+ function asStringArray(value: unknown): string[] {
127
+ if (Array.isArray(value)) return value as string[];
128
+ if (typeof value === "string") return [value];
129
+ return [];
130
+ }
131
+
132
+ function asNumber(value: unknown): number | undefined {
133
+ if (value === undefined || value === null) return undefined;
134
+ if (typeof value !== "string" && typeof value !== "number") {
135
+ throw new Error(`Expected a number, got: ${typeof value}`);
136
+ }
137
+ const n = Number(value);
138
+ if (Number.isNaN(n)) throw new Error(`Expected a number, got: ${value as string}`);
139
+ return n;
140
+ }
141
+
142
+ /**
143
+ * Build an object containing only the keys whose values are defined.
144
+ *
145
+ * The frost-hubert library types use `exactOptionalPropertyTypes: true`,
146
+ * which means `{ foo?: string }` rejects `{ foo: undefined }` — only
147
+ * `{ foo: "x" }` or `{}` is allowed. Spreading this helper's return
148
+ * value into an options object preserves that contract: at runtime we
149
+ * filter out the `undefined` keys, and we strip `undefined` from the
150
+ * value type so the spread satisfies `exactOptionalPropertyTypes`.
151
+ */
152
+ type DefinedValues<T> = { [K in keyof T]?: Exclude<T[K], undefined> };
153
+ function defined<T extends Record<string, unknown>>(obj: T): DefinedValues<T> {
154
+ const out: Record<string, unknown> = {};
155
+ for (const [key, value] of Object.entries(obj)) {
156
+ if (value !== undefined) out[key] = value;
157
+ }
158
+ return out as DefinedValues<T>;
159
+ }
160
+
161
+ /**
162
+ * Wrap a CLI action handler so any thrown error prints to stderr and
163
+ * exits with code 1. Mirrors Rust's `anyhow::Result<()>` propagation.
164
+ */
165
+ function runAsync<TArgs extends unknown[]>(
166
+ handler: (...args: TArgs) => Promise<void>,
167
+ ): (...args: TArgs) => void {
168
+ return (...args: TArgs): void => {
169
+ handler(...args).catch((err: unknown) => {
170
+ console.error(err instanceof Error ? err.message : String(err));
171
+ process.exit(1);
172
+ });
173
+ };
174
+ }
175
+
176
+ function runSync<TArgs extends unknown[]>(
177
+ handler: (...args: TArgs) => void,
178
+ ): (...args: TArgs) => void {
179
+ return (...args: TArgs): void => {
180
+ try {
181
+ handler(...args);
182
+ } catch (err) {
183
+ console.error(err instanceof Error ? err.message : String(err));
184
+ process.exit(1);
185
+ }
186
+ };
187
+ }
188
+
189
+ // Common option attachers — keep the per-subcommand definitions short.
190
+ const addStorageOpts = (cmd: ReturnType<typeof program.command>): typeof cmd =>
191
+ cmd
192
+ .option("--storage <selection>", "Storage backend (server|mainline|ipfs|hybrid)")
193
+ .option("--host <host>", "Server host (server backend only)")
194
+ .option("--port <port>", "Server port (server backend only)");
195
+
196
+ const addRegistryOpt = (cmd: ReturnType<typeof program.command>): typeof cmd =>
197
+ cmd.option("-r, --registry <path>", "Registry path or filename override");
198
+
199
+ const addTimeoutOpt = (cmd: ReturnType<typeof program.command>): typeof cmd =>
200
+ cmd.option("--timeout <seconds>", "Wait up to this many seconds");
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // CLI definition
204
+ // ---------------------------------------------------------------------------
205
+
48
206
  async function main(): Promise<void> {
49
- // Initialize tags before CLI runs
50
207
  await registerTags();
51
208
 
52
209
  program.name("frost").description("FROST threshold signing CLI").version("1.0.0");
53
210
 
54
- // Registry commands
211
+ // ─── Registry ──────────────────────────────────────────────────────────
55
212
  const registryCmd = program.command("registry").description("Manage the registry");
56
-
57
- // Registry owner commands
58
213
  const ownerCmd = registryCmd.command("owner").description("Manage the registry owner");
59
214
 
60
215
  ownerCmd
61
216
  .command("set <xid-document> [pet-name]")
62
217
  .description("Set the registry owner using an ur:xid document that includes private keys")
63
218
  .option("-r, --registry <path>", "Registry path or filename override")
64
- .action((xidDocument: string, petName: string | undefined, options: { registry?: string }) => {
65
- try {
66
- ownerSet(
67
- {
68
- xidDocument,
69
- petName,
70
- registryPath: options.registry,
71
- },
72
- process.cwd(),
73
- );
74
- } catch (error) {
75
- console.error((error as Error).message);
76
- process.exit(1);
77
- }
78
- });
219
+ .action(
220
+ runSync(
221
+ (xidDocument: string, petName: string | undefined, options: { registry?: string }) => {
222
+ ownerSet({ xidDocument, petName, registryPath: options.registry }, process.cwd());
223
+ },
224
+ ),
225
+ );
79
226
 
80
- // Registry participant commands
81
227
  const participantCmd = registryCmd
82
228
  .command("participant")
83
229
  .description("Manage registry participants");
@@ -86,138 +232,713 @@ async function main(): Promise<void> {
86
232
  .command("add <xid-document> [pet-name]")
87
233
  .description("Add a participant using an ur:xid document")
88
234
  .option("-r, --registry <path>", "Registry path or filename override")
89
- .action((xidDocument: string, petName: string | undefined, options: { registry?: string }) => {
90
- try {
91
- participantAdd(
235
+ .action(
236
+ runSync(
237
+ (xidDocument: string, petName: string | undefined, options: { registry?: string }) => {
238
+ participantAdd({ xidDocument, petName, registryPath: options.registry }, process.cwd());
239
+ },
240
+ ),
241
+ );
242
+
243
+ // ─── DKG ──────────────────────────────────────────────────────────────
244
+ const dkgCmd = program.command("dkg").description("Distributed Key Generation commands");
245
+ const dkgCoordinatorCmd = dkgCmd.command("coordinator").description("DKG coordinator commands");
246
+
247
+ // dkg coordinator invite
248
+ addStorageOpts(
249
+ addRegistryOpt(
250
+ dkgCoordinatorCmd
251
+ .command("invite <participants...>")
252
+ .description("Compose or send a DKG invite")
253
+ .option("--min-signers <n>", "Minimum signers required; defaults to participant count")
254
+ .option("--charter <string>", "Charter statement for the DKG group", "")
255
+ .option("--valid-days <days>", "Days the invite is valid for", "30")
256
+ .option("--preview", "Print the preview invite envelope UR instead of the sealed envelope"),
257
+ ),
258
+ ).action(
259
+ runAsync(
260
+ async (
261
+ participants: string[],
262
+ opts: StorageOpts & {
263
+ registry?: string;
264
+ minSigners?: string;
265
+ charter?: string;
266
+ validDays?: string;
267
+ preview?: boolean;
268
+ },
269
+ ) => {
270
+ const selection = parseStorageSelection(opts);
271
+ if (selection !== undefined && opts.preview === true) {
272
+ throw new Error("--preview cannot be used with Hubert storage options");
273
+ }
274
+ const client = await buildClient(opts);
275
+ const fn = await lazyDkgCoord.invite();
276
+ const result = await fn(
277
+ // dkgCoordInvite requires a defined client; for preview the
278
+ // invite is built but not posted, so we pass a no-op client.
279
+ client ?? noOpClient(),
92
280
  {
93
- xidDocument,
94
- petName,
95
- registryPath: options.registry,
281
+ charter: opts.charter ?? "",
282
+ validDays: asNumber(opts.validDays) ?? 30,
283
+ participantNames: asStringArray(participants),
284
+ ...defined({
285
+ registryPath: opts.registry,
286
+ minSigners: asNumber(opts.minSigners),
287
+ }),
96
288
  },
97
289
  process.cwd(),
98
290
  );
99
- } catch (error) {
100
- console.error((error as Error).message);
101
- process.exit(1);
102
- }
103
- });
104
-
105
- // DKG commands (placeholder)
106
- const dkgCmd = program.command("dkg").description("Distributed Key Generation commands");
291
+ console.log(result.envelopeUr);
292
+ },
293
+ ),
294
+ );
107
295
 
108
- const dkgCoordinatorCmd = dkgCmd.command("coordinator").description("DKG coordinator commands");
109
- dkgCoordinatorCmd
110
- .command("invite")
111
- .description("Send DKG invite to participants")
112
- .action(() => {
113
- console.log("DKG coordinator invite command not yet implemented");
114
- });
296
+ // dkg coordinator round1
297
+ addStorageOpts(
298
+ addTimeoutOpt(
299
+ addRegistryOpt(
300
+ dkgCoordinatorCmd
301
+ .command("round1 <group-id>")
302
+ .description("Collect round 1 responses")
303
+ .option("--preview", "Preview one of the round 2 requests while sending")
304
+ .option("--parallel", "Use parallel fetch/send"),
305
+ ),
306
+ ),
307
+ ).action(
308
+ runAsync(
309
+ async (
310
+ groupId: string,
311
+ opts: StorageOpts & {
312
+ registry?: string;
313
+ timeout?: string;
314
+ preview?: boolean;
315
+ parallel?: boolean;
316
+ },
317
+ ) => {
318
+ const client = await buildClient(opts);
319
+ if (client === undefined) {
320
+ throw new Error("dkg coordinator round1 requires --storage");
321
+ }
322
+ const fn = await lazyDkgCoord.round1();
323
+ const result = await fn(
324
+ client,
325
+ {
326
+ groupId,
327
+ ...defined({
328
+ registryPath: opts.registry,
329
+ timeoutSeconds: asNumber(opts.timeout),
330
+ preview: opts.preview,
331
+ parallel: opts.parallel,
332
+ }),
333
+ },
334
+ process.cwd(),
335
+ );
336
+ console.log(JSON.stringify(result));
337
+ },
338
+ ),
339
+ );
115
340
 
116
- dkgCoordinatorCmd
117
- .command("round1")
118
- .description("Collect round 1 responses")
119
- .action(() => {
120
- console.log("DKG coordinator round1 command not yet implemented");
121
- });
341
+ // dkg coordinator round2
342
+ addStorageOpts(
343
+ addTimeoutOpt(
344
+ addRegistryOpt(
345
+ dkgCoordinatorCmd
346
+ .command("round2 <group-id>")
347
+ .description("Collect round 2 responses")
348
+ .option("--preview", "Preview one of the finalize requests while sending")
349
+ .option("--parallel", "Use parallel fetch/send"),
350
+ ),
351
+ ),
352
+ ).action(
353
+ runAsync(
354
+ async (
355
+ groupId: string,
356
+ opts: StorageOpts & {
357
+ registry?: string;
358
+ timeout?: string;
359
+ preview?: boolean;
360
+ parallel?: boolean;
361
+ },
362
+ ) => {
363
+ const client = await buildClient(opts);
364
+ if (client === undefined) {
365
+ throw new Error("dkg coordinator round2 requires --storage");
366
+ }
367
+ const fn = await lazyDkgCoord.round2();
368
+ const result = await fn(
369
+ client,
370
+ {
371
+ groupId,
372
+ ...defined({
373
+ registryPath: opts.registry,
374
+ timeoutSeconds: asNumber(opts.timeout),
375
+ preview: opts.preview,
376
+ parallel: opts.parallel,
377
+ }),
378
+ },
379
+ process.cwd(),
380
+ );
381
+ console.log(JSON.stringify(result));
382
+ },
383
+ ),
384
+ );
122
385
 
123
- dkgCoordinatorCmd
124
- .command("round2")
125
- .description("Collect round 2 responses")
126
- .action(() => {
127
- console.log("DKG coordinator round2 command not yet implemented");
128
- });
386
+ // dkg coordinator finalize
387
+ addStorageOpts(
388
+ addTimeoutOpt(
389
+ addRegistryOpt(
390
+ dkgCoordinatorCmd
391
+ .command("finalize <group-id>")
392
+ .description("Collect finalize responses")
393
+ .option("--parallel", "Use parallel fetch"),
394
+ ),
395
+ ),
396
+ ).action(
397
+ runAsync(
398
+ async (
399
+ groupId: string,
400
+ opts: StorageOpts & {
401
+ registry?: string;
402
+ timeout?: string;
403
+ parallel?: boolean;
404
+ },
405
+ ) => {
406
+ const client = await buildClient(opts);
407
+ if (client === undefined) {
408
+ throw new Error("dkg coordinator finalize requires --storage");
409
+ }
410
+ const fn = await lazyDkgCoord.finalize();
411
+ const result = await fn(
412
+ client,
413
+ {
414
+ groupId,
415
+ ...defined({
416
+ registryPath: opts.registry,
417
+ timeoutSeconds: asNumber(opts.timeout),
418
+ parallel: opts.parallel,
419
+ }),
420
+ },
421
+ process.cwd(),
422
+ );
423
+ console.log(result.verifyingKey);
424
+ },
425
+ ),
426
+ );
129
427
 
428
+ // ─── DKG participant ───────────────────────────────────────────────────
130
429
  const dkgParticipantCmd = dkgCmd.command("participant").description("DKG participant commands");
131
- dkgParticipantCmd
132
- .command("receive")
133
- .description("Receive DKG invite")
134
- .action(() => {
135
- console.log("DKG participant receive command not yet implemented");
136
- });
137
430
 
138
- dkgParticipantCmd
139
- .command("round1")
140
- .description("Send round 1 response")
141
- .action(() => {
142
- console.log("DKG participant round1 command not yet implemented");
143
- });
431
+ // dkg participant receive
432
+ addStorageOpts(
433
+ addTimeoutOpt(
434
+ addRegistryOpt(
435
+ dkgParticipantCmd
436
+ .command("receive <invite>")
437
+ .description("Receive a DKG invite (ur:arid or ur:envelope)")
438
+ .option("--no-envelope", "Suppress printing the invite envelope UR")
439
+ .option("--info", "Show invite details")
440
+ .option("--sender <sender>", "Require the invite to come from this sender"),
441
+ ),
442
+ ),
443
+ ).action(
444
+ runAsync(
445
+ async (
446
+ invite: string,
447
+ opts: StorageOpts & {
448
+ registry?: string;
449
+ timeout?: string;
450
+ envelope?: boolean;
451
+ info?: boolean;
452
+ sender?: string;
453
+ },
454
+ ) => {
455
+ const selection = parseStorageSelection(opts);
456
+ const client = await buildClient(opts);
457
+ const fn = await lazyDkgPart.receive();
458
+ const result = await fn(
459
+ client,
460
+ {
461
+ invite,
462
+ // commander auto-negates `--no-envelope` into `envelope: false`.
463
+ noEnvelope: opts.envelope === false,
464
+ ...defined({
465
+ registryPath: opts.registry,
466
+ timeoutSeconds: asNumber(opts.timeout),
467
+ info: opts.info,
468
+ sender: opts.sender,
469
+ storageSelection: selection,
470
+ }),
471
+ },
472
+ process.cwd(),
473
+ );
474
+ if (result.envelopeUr !== undefined) console.log(result.envelopeUr);
475
+ },
476
+ ),
477
+ );
144
478
 
145
- dkgParticipantCmd
146
- .command("round2")
147
- .description("Send round 2 response")
148
- .action(() => {
149
- console.log("DKG participant round2 command not yet implemented");
150
- });
479
+ // dkg participant round1
480
+ addStorageOpts(
481
+ addTimeoutOpt(
482
+ addRegistryOpt(
483
+ dkgParticipantCmd
484
+ .command("round1 <invite>")
485
+ .description("Send round 1 response")
486
+ .option("--response-arid <ur>", "Optional ARID for the next response")
487
+ .option("--preview", "Print the preview response envelope UR instead")
488
+ .option("--reject <reason>", "Reject the invite with the provided reason")
489
+ .option("--sender <sender>", "Require the invite to come from this sender"),
490
+ ),
491
+ ),
492
+ ).action(
493
+ runAsync(
494
+ async (
495
+ invite: string,
496
+ opts: StorageOpts & {
497
+ registry?: string;
498
+ timeout?: string;
499
+ responseArid?: string;
500
+ preview?: boolean;
501
+ reject?: string;
502
+ sender?: string;
503
+ },
504
+ ) => {
505
+ const selection = parseStorageSelection(opts);
506
+ const client = await buildClient(opts);
507
+ const fn = await lazyDkgPart.round1();
508
+ const result = await fn(
509
+ client,
510
+ {
511
+ invite,
512
+ ...defined({
513
+ registryPath: opts.registry,
514
+ timeoutSeconds: asNumber(opts.timeout),
515
+ responseArid: opts.responseArid,
516
+ preview: opts.preview,
517
+ rejectReason: opts.reject,
518
+ sender: opts.sender,
519
+ storageSelection: selection,
520
+ }),
521
+ },
522
+ process.cwd(),
523
+ );
524
+ if (result.envelopeUr !== undefined) console.log(result.envelopeUr);
525
+ else if (result.listeningArid !== undefined) console.log(result.listeningArid);
526
+ },
527
+ ),
528
+ );
151
529
 
152
- dkgParticipantCmd
153
- .command("finalize")
154
- .description("Receive finalize package")
155
- .action(() => {
156
- console.log("DKG participant finalize command not yet implemented");
157
- });
530
+ // dkg participant round2
531
+ addStorageOpts(
532
+ addTimeoutOpt(
533
+ addRegistryOpt(
534
+ dkgParticipantCmd
535
+ .command("round2 <group-id>")
536
+ .description("Send round 2 response")
537
+ .option("--preview", "Also print the preview response envelope"),
538
+ ),
539
+ ),
540
+ ).action(
541
+ runAsync(
542
+ async (
543
+ groupId: string,
544
+ opts: StorageOpts & { registry?: string; timeout?: string; preview?: boolean },
545
+ ) => {
546
+ const selection = parseStorageSelection(opts);
547
+ const client = await buildClient(opts);
548
+ const fn = await lazyDkgPart.round2();
549
+ const result = await fn(
550
+ client,
551
+ {
552
+ groupId,
553
+ ...defined({
554
+ registryPath: opts.registry,
555
+ timeoutSeconds: asNumber(opts.timeout),
556
+ preview: opts.preview,
557
+ storageSelection: selection,
558
+ }),
559
+ },
560
+ process.cwd(),
561
+ );
562
+ if (result.envelopeUr !== undefined) console.log(result.envelopeUr);
563
+ else console.log(result.listeningArid);
564
+ },
565
+ ),
566
+ );
158
567
 
159
- // Sign commands (placeholder)
160
- const signCmd = program.command("sign").description("Threshold signing commands");
568
+ // dkg participant finalize
569
+ addStorageOpts(
570
+ addTimeoutOpt(
571
+ addRegistryOpt(
572
+ dkgParticipantCmd
573
+ .command("finalize <group-id>")
574
+ .description("Receive finalize package")
575
+ .option("--preview", "Also print the preview response envelope"),
576
+ ),
577
+ ),
578
+ ).action(
579
+ runAsync(
580
+ async (
581
+ groupId: string,
582
+ opts: StorageOpts & { registry?: string; timeout?: string; preview?: boolean },
583
+ ) => {
584
+ const selection = parseStorageSelection(opts);
585
+ const client = await buildClient(opts);
586
+ const fn = await lazyDkgPart.finalize();
587
+ const result = await fn(
588
+ client,
589
+ {
590
+ groupId,
591
+ ...defined({
592
+ registryPath: opts.registry,
593
+ timeoutSeconds: asNumber(opts.timeout),
594
+ preview: opts.preview,
595
+ storageSelection: selection,
596
+ }),
597
+ },
598
+ process.cwd(),
599
+ );
600
+ console.log(result.verifyingKey);
601
+ },
602
+ ),
603
+ );
161
604
 
605
+ // ─── Sign ──────────────────────────────────────────────────────────────
606
+ const signCmd = program.command("sign").description("Threshold signing commands");
162
607
  const signCoordinatorCmd = signCmd
163
608
  .command("coordinator")
164
609
  .description("Sign coordinator commands");
165
- signCoordinatorCmd
166
- .command("invite")
167
- .description("Send sign invite to participants")
168
- .action(() => {
169
- console.log("Sign coordinator invite command not yet implemented");
170
- });
171
610
 
172
- signCoordinatorCmd
173
- .command("round1")
174
- .description("Collect round 1 responses")
175
- .action(() => {
176
- console.log("Sign coordinator round1 command not yet implemented");
177
- });
611
+ // sign coordinator invite
612
+ addStorageOpts(
613
+ addRegistryOpt(
614
+ signCoordinatorCmd
615
+ .command("invite <group-id>")
616
+ .description("Send sign invite to participants")
617
+ .requiredOption("--target <path>", "Path to a file containing the target envelope UR")
618
+ .option("--preview", "Print the preview request envelope UR instead of sending"),
619
+ ),
620
+ ).action(
621
+ runAsync(
622
+ async (
623
+ groupId: string,
624
+ opts: StorageOpts & { registry?: string; target: string; preview?: boolean },
625
+ ) => {
626
+ const client = await buildClient(opts);
627
+ const fn = await lazySignCoord.invite();
628
+ const result = await fn(
629
+ client,
630
+ {
631
+ groupId,
632
+ targetFile: opts.target,
633
+ ...defined({
634
+ registryPath: opts.registry,
635
+ preview: opts.preview,
636
+ }),
637
+ },
638
+ process.cwd(),
639
+ );
640
+ console.log(result.startArid);
641
+ },
642
+ ),
643
+ );
178
644
 
179
- signCoordinatorCmd
180
- .command("round2")
181
- .description("Collect round 2 responses and finalize signature")
182
- .action(() => {
183
- console.log("Sign coordinator round2 command not yet implemented");
184
- });
645
+ // sign coordinator round1
646
+ addStorageOpts(
647
+ addTimeoutOpt(
648
+ addRegistryOpt(
649
+ signCoordinatorCmd
650
+ .command("round1 <session-id>")
651
+ .description("Collect round 1 (commitment) responses")
652
+ .option("--group <ur:arid>", "Optional group ID hint")
653
+ .option("--preview-share", "Print a sample unsealed signRound2 request")
654
+ .option("--parallel", "Use parallel fetch/send"),
655
+ ),
656
+ ),
657
+ ).action(
658
+ runAsync(
659
+ async (
660
+ sessionId: string,
661
+ opts: StorageOpts & {
662
+ registry?: string;
663
+ timeout?: string;
664
+ group?: string;
665
+ previewShare?: boolean;
666
+ parallel?: boolean;
667
+ },
668
+ ) => {
669
+ const client = await buildClient(opts);
670
+ if (client === undefined) {
671
+ throw new Error("sign coordinator round1 requires --storage");
672
+ }
673
+ const fn = await lazySignCoord.round1();
674
+ const result = await fn(
675
+ client,
676
+ {
677
+ sessionId,
678
+ ...defined({
679
+ registryPath: opts.registry,
680
+ groupId: opts.group,
681
+ timeoutSeconds: asNumber(opts.timeout),
682
+ previewShare: opts.previewShare,
683
+ parallel: opts.parallel,
684
+ }),
685
+ },
686
+ process.cwd(),
687
+ );
688
+ console.log(JSON.stringify(result));
689
+ },
690
+ ),
691
+ );
185
692
 
693
+ // sign coordinator round2
694
+ addStorageOpts(
695
+ addTimeoutOpt(
696
+ addRegistryOpt(
697
+ signCoordinatorCmd
698
+ .command("round2 <session-id>")
699
+ .description("Collect round 2 responses and finalize signature")
700
+ .option("--group <ur:arid>", "Optional group ID hint")
701
+ .option("--preview-finalize", "Print a sample unsealed finalize package")
702
+ .option("--parallel", "Use parallel fetch/send"),
703
+ ),
704
+ ),
705
+ ).action(
706
+ runAsync(
707
+ async (
708
+ sessionId: string,
709
+ opts: StorageOpts & {
710
+ registry?: string;
711
+ timeout?: string;
712
+ group?: string;
713
+ previewFinalize?: boolean;
714
+ parallel?: boolean;
715
+ },
716
+ ) => {
717
+ const client = await buildClient(opts);
718
+ if (client === undefined) {
719
+ throw new Error("sign coordinator round2 requires --storage");
720
+ }
721
+ const fn = await lazySignCoord.round2();
722
+ const result = await fn(
723
+ client,
724
+ {
725
+ sessionId,
726
+ ...defined({
727
+ registryPath: opts.registry,
728
+ groupId: opts.group,
729
+ timeoutSeconds: asNumber(opts.timeout),
730
+ previewFinalize: opts.previewFinalize,
731
+ parallel: opts.parallel,
732
+ }),
733
+ },
734
+ process.cwd(),
735
+ );
736
+ console.log(result.signedEnvelope);
737
+ },
738
+ ),
739
+ );
740
+
741
+ // ─── Sign participant ──────────────────────────────────────────────────
186
742
  const signParticipantCmd = signCmd
187
743
  .command("participant")
188
744
  .description("Sign participant commands");
189
- signParticipantCmd
190
- .command("receive")
191
- .description("Receive sign invite")
192
- .action(() => {
193
- console.log("Sign participant receive command not yet implemented");
194
- });
195
745
 
196
- signParticipantCmd
197
- .command("round1")
198
- .description("Send commitment")
199
- .action(() => {
200
- console.log("Sign participant round1 command not yet implemented");
201
- });
746
+ // sign participant receive
747
+ addStorageOpts(
748
+ addTimeoutOpt(
749
+ addRegistryOpt(
750
+ signParticipantCmd
751
+ .command("receive <request>")
752
+ .description("Receive a signInvite request (ur:arid or ur:envelope)")
753
+ .option("--info", "Show request details")
754
+ .option("--sender <sender>", "Require the request to come from this sender"),
755
+ ),
756
+ ),
757
+ ).action(
758
+ runAsync(
759
+ async (
760
+ request: string,
761
+ opts: StorageOpts & {
762
+ registry?: string;
763
+ timeout?: string;
764
+ info?: boolean;
765
+ sender?: string;
766
+ },
767
+ ) => {
768
+ const selection = parseStorageSelection(opts);
769
+ const client = await buildClient(opts);
770
+ const fn = await lazySignPart.receive();
771
+ const result = await fn(
772
+ client,
773
+ selection,
774
+ {
775
+ request,
776
+ ...defined({
777
+ registryPath: opts.registry,
778
+ timeoutSeconds: asNumber(opts.timeout),
779
+ info: opts.info,
780
+ sender: opts.sender,
781
+ }),
782
+ },
783
+ process.cwd(),
784
+ );
785
+ console.log(JSON.stringify(result));
786
+ },
787
+ ),
788
+ );
202
789
 
203
- signParticipantCmd
204
- .command("round2")
205
- .description("Send signature share")
206
- .action(() => {
207
- console.log("Sign participant round2 command not yet implemented");
208
- });
790
+ // sign participant round1
791
+ addStorageOpts(
792
+ addRegistryOpt(
793
+ signParticipantCmd
794
+ .command("round1 <session-id>")
795
+ .description("Send commitment")
796
+ .option("--preview", "Print the preview response envelope UR instead of sending")
797
+ .option("--reject <reason>", "Reject the signInvite request with the provided reason")
798
+ .option("--group <ur:arid>", "Optional group ID hint"),
799
+ ),
800
+ ).action(
801
+ runAsync(
802
+ async (
803
+ sessionId: string,
804
+ opts: StorageOpts & {
805
+ registry?: string;
806
+ preview?: boolean;
807
+ reject?: string;
808
+ group?: string;
809
+ },
810
+ ) => {
811
+ const selection = parseStorageSelection(opts);
812
+ const client = await buildClient(opts);
813
+ const fn = await lazySignPart.round1();
814
+ const result = await fn(
815
+ client,
816
+ {
817
+ sessionId,
818
+ ...defined({
819
+ registryPath: opts.registry,
820
+ groupId: opts.group,
821
+ preview: opts.preview,
822
+ rejectReason: opts.reject,
823
+ storageSelection: selection,
824
+ }),
825
+ },
826
+ process.cwd(),
827
+ );
828
+ if (result.envelopeUr !== undefined) console.log(result.envelopeUr);
829
+ else if (result.listeningArid !== undefined) console.log(result.listeningArid);
830
+ },
831
+ ),
832
+ );
209
833
 
210
- signParticipantCmd
211
- .command("finalize")
212
- .description("Receive finalize event")
213
- .action(() => {
214
- console.log("Sign participant finalize command not yet implemented");
215
- });
834
+ // sign participant round2
835
+ addStorageOpts(
836
+ addTimeoutOpt(
837
+ addRegistryOpt(
838
+ signParticipantCmd
839
+ .command("round2 <session-id>")
840
+ .description("Send signature share")
841
+ .option("--preview", "Print the unsealed response envelope UR instead of sending")
842
+ .option("--group <ur:arid>", "Optional group ID hint"),
843
+ ),
844
+ ),
845
+ ).action(
846
+ runAsync(
847
+ async (
848
+ sessionId: string,
849
+ opts: StorageOpts & {
850
+ registry?: string;
851
+ timeout?: string;
852
+ preview?: boolean;
853
+ group?: string;
854
+ },
855
+ ) => {
856
+ const client = await buildClient(opts);
857
+ if (client === undefined) {
858
+ throw new Error("sign participant round2 requires --storage");
859
+ }
860
+ const fn = await lazySignPart.round2();
861
+ const result = await fn(
862
+ client,
863
+ {
864
+ sessionId,
865
+ ...defined({
866
+ registryPath: opts.registry,
867
+ groupId: opts.group,
868
+ timeoutSeconds: asNumber(opts.timeout),
869
+ preview: opts.preview,
870
+ }),
871
+ },
872
+ process.cwd(),
873
+ );
874
+ console.log(result.listeningArid);
875
+ },
876
+ ),
877
+ );
878
+
879
+ // sign participant finalize
880
+ addStorageOpts(
881
+ addTimeoutOpt(
882
+ addRegistryOpt(
883
+ signParticipantCmd
884
+ .command("finalize <session-id>")
885
+ .description("Receive finalize event")
886
+ .option("--group <ur:arid>", "Optional group ID hint"),
887
+ ),
888
+ ),
889
+ ).action(
890
+ runAsync(
891
+ async (
892
+ sessionId: string,
893
+ opts: StorageOpts & {
894
+ registry?: string;
895
+ timeout?: string;
896
+ group?: string;
897
+ },
898
+ ) => {
899
+ const client = await buildClient(opts);
900
+ if (client === undefined) {
901
+ throw new Error("sign participant finalize requires --storage");
902
+ }
903
+ const fn = await lazySignPart.finalize();
904
+ const result = await fn(
905
+ client,
906
+ {
907
+ sessionId,
908
+ ...defined({
909
+ registryPath: opts.registry,
910
+ groupId: opts.group,
911
+ timeoutSeconds: asNumber(opts.timeout),
912
+ }),
913
+ },
914
+ process.cwd(),
915
+ );
916
+ console.log(result.signature);
917
+ },
918
+ ),
919
+ );
216
920
 
217
921
  program.parse();
218
922
  }
219
923
 
924
+ /**
925
+ * Build a no-op StorageClient for preview-only paths (the
926
+ * coordinator-invite library expects a defined client even when no
927
+ * envelope is sent, because it threads through some helpers).
928
+ *
929
+ * Mirrors Rust's behaviour: `--preview` skips the
930
+ * `StorageClient::from_selection` call entirely, but the underlying
931
+ * library still walks through the same code path with a stub.
932
+ */
933
+ function noOpClient(): StorageClient {
934
+ return {
935
+ put: () => Promise.reject(new Error("no-op storage client used in preview mode")),
936
+ get: () => Promise.reject(new Error("no-op storage client used in preview mode")),
937
+ exists: () => Promise.resolve(false),
938
+ };
939
+ }
940
+
220
941
  main().catch((error: unknown) => {
221
- console.error((error as Error).message);
942
+ console.error(error instanceof Error ? error.message : String(error));
222
943
  process.exit(1);
223
944
  });