@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.
- package/LICENSE +48 -0
- package/README.md +35 -0
- package/dist/bin/frost.cjs +109 -0
- package/dist/bin/frost.cjs.map +1 -0
- package/dist/bin/frost.d.cts +1 -0
- package/dist/bin/frost.d.mts +1 -0
- package/dist/bin/frost.mjs +109 -0
- package/dist/bin/frost.mjs.map +1 -0
- package/dist/chunk-CQwRTUmo.cjs +53 -0
- package/dist/chunk-D3JzZLW2.mjs +21 -0
- package/dist/cmd/index.cjs +45 -0
- package/dist/cmd/index.d.cts +4 -0
- package/dist/cmd/index.d.mts +4 -0
- package/dist/cmd/index.mjs +7 -0
- package/dist/cmd-C8pmNd28.mjs +4664 -0
- package/dist/cmd-C8pmNd28.mjs.map +1 -0
- package/dist/cmd-CxUgryx_.cjs +4803 -0
- package/dist/cmd-CxUgryx_.cjs.map +1 -0
- package/dist/dkg/index.cjs +7 -0
- package/dist/dkg/index.d.cts +2 -0
- package/dist/dkg/index.d.mts +2 -0
- package/dist/dkg/index.mjs +3 -0
- package/dist/dkg-D4RcblWl.cjs +364 -0
- package/dist/dkg-D4RcblWl.cjs.map +1 -0
- package/dist/dkg-DqGrAV81.mjs +334 -0
- package/dist/dkg-DqGrAV81.mjs.map +1 -0
- package/dist/frost/index.cjs +37 -0
- package/dist/frost/index.d.cts +207 -0
- package/dist/frost/index.d.cts.map +1 -0
- package/dist/frost/index.d.mts +207 -0
- package/dist/frost/index.d.mts.map +1 -0
- package/dist/frost/index.mjs +3 -0
- package/dist/frost-CMH1K0Cw.cjs +511 -0
- package/dist/frost-CMH1K0Cw.cjs.map +1 -0
- package/dist/frost-Csp0IOrd.mjs +326 -0
- package/dist/frost-Csp0IOrd.mjs.map +1 -0
- package/dist/index-BGVoWW5P.d.cts +172 -0
- package/dist/index-BGVoWW5P.d.cts.map +1 -0
- package/dist/index-BJeUYrdE.d.mts +396 -0
- package/dist/index-BJeUYrdE.d.mts.map +1 -0
- package/dist/index-ByMDUYKw.d.mts +1098 -0
- package/dist/index-ByMDUYKw.d.mts.map +1 -0
- package/dist/index-DejLkr_F.d.mts +172 -0
- package/dist/index-DejLkr_F.d.mts.map +1 -0
- package/dist/index-Dib1OE-e.d.cts +1098 -0
- package/dist/index-Dib1OE-e.d.cts.map +1 -0
- package/dist/index-DnvBKgec.d.cts +396 -0
- package/dist/index-DnvBKgec.d.cts.map +1 -0
- package/dist/index.cjs +85 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +24 -0
- package/dist/index.mjs.map +1 -0
- package/dist/registry/index.cjs +13 -0
- package/dist/registry/index.d.cts +2 -0
- package/dist/registry/index.d.mts +2 -0
- package/dist/registry/index.mjs +3 -0
- package/dist/registry-CBjRRqNv.mjs +144 -0
- package/dist/registry-CBjRRqNv.mjs.map +1 -0
- package/dist/registry-CWp2amuo.mjs +789 -0
- package/dist/registry-CWp2amuo.mjs.map +1 -0
- package/dist/registry-D5yh293y.cjs +857 -0
- package/dist/registry-D5yh293y.cjs.map +1 -0
- package/dist/registry-DNUNW6SH.cjs +163 -0
- package/dist/registry-DNUNW6SH.cjs.map +1 -0
- package/package.json +119 -0
- package/src/bin/frost.ts +218 -0
- package/src/cmd/busy.ts +64 -0
- package/src/cmd/check.ts +20 -0
- package/src/cmd/common.ts +40 -0
- package/src/cmd/dkg/common.ts +275 -0
- package/src/cmd/dkg/coordinator/finalize.ts +592 -0
- package/src/cmd/dkg/coordinator/index.ts +12 -0
- package/src/cmd/dkg/coordinator/invite.ts +217 -0
- package/src/cmd/dkg/coordinator/round1.ts +889 -0
- package/src/cmd/dkg/coordinator/round2.ts +959 -0
- package/src/cmd/dkg/index.ts +11 -0
- package/src/cmd/dkg/participant/finalize.ts +575 -0
- package/src/cmd/dkg/participant/index.ts +12 -0
- package/src/cmd/dkg/participant/receive.ts +348 -0
- package/src/cmd/dkg/participant/round1.ts +464 -0
- package/src/cmd/dkg/participant/round2.ts +627 -0
- package/src/cmd/index.ts +18 -0
- package/src/cmd/parallel.ts +334 -0
- package/src/cmd/registry/index.ts +88 -0
- package/src/cmd/registry/owner/index.ts +9 -0
- package/src/cmd/registry/owner/set.ts +70 -0
- package/src/cmd/registry/participant/add.ts +70 -0
- package/src/cmd/registry/participant/index.ts +9 -0
- package/src/cmd/sign/common.ts +108 -0
- package/src/cmd/sign/coordinator/index.ts +11 -0
- package/src/cmd/sign/coordinator/invite.ts +431 -0
- package/src/cmd/sign/coordinator/round1.ts +751 -0
- package/src/cmd/sign/coordinator/round2.ts +836 -0
- package/src/cmd/sign/index.ts +11 -0
- package/src/cmd/sign/participant/finalize.ts +823 -0
- package/src/cmd/sign/participant/index.ts +12 -0
- package/src/cmd/sign/participant/receive.ts +378 -0
- package/src/cmd/sign/participant/round1.ts +479 -0
- package/src/cmd/sign/participant/round2.ts +748 -0
- package/src/cmd/storage.ts +116 -0
- package/src/dkg/group-invite.ts +414 -0
- package/src/dkg/index.ts +10 -0
- package/src/dkg/proposed-participant.ts +132 -0
- package/src/frost/index.ts +456 -0
- package/src/index.ts +45 -0
- package/src/registry/group-record.ts +392 -0
- package/src/registry/index.ts +12 -0
- package/src/registry/owner-record.ts +146 -0
- package/src/registry/participant-record.ts +186 -0
- package/src/registry/registry-impl.ts +364 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DKG participant round 1 command.
|
|
3
|
+
*
|
|
4
|
+
* Port of cmd/dkg/participant/round1.rs from frost-hubert-rust.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
|
|
12
|
+
import { ARID, JSON as JSONWrapper, XID } from "@bcts/components";
|
|
13
|
+
import { Envelope } from "@bcts/envelope";
|
|
14
|
+
import { SealedResponse } from "@bcts/gstp";
|
|
15
|
+
import type { XIDDocument } from "@bcts/xid";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
ContributionPaths,
|
|
19
|
+
GroupRecord,
|
|
20
|
+
Registry,
|
|
21
|
+
resolveRegistryPath,
|
|
22
|
+
} from "../../../registry/index.js";
|
|
23
|
+
import { getWithIndicator, putWithIndicator } from "../../busy.js";
|
|
24
|
+
import { createStorageClient, type StorageClient, type StorageSelection } from "../../storage.js";
|
|
25
|
+
import { groupStateDir } from "../../common.js";
|
|
26
|
+
import {
|
|
27
|
+
buildGroupParticipants,
|
|
28
|
+
groupParticipantFromRegistry,
|
|
29
|
+
parseAridUr,
|
|
30
|
+
parseEnvelopeUr,
|
|
31
|
+
} from "../common.js";
|
|
32
|
+
import {
|
|
33
|
+
dkgPart1,
|
|
34
|
+
identifierFromU16,
|
|
35
|
+
createRng,
|
|
36
|
+
bytesToHex,
|
|
37
|
+
type DkgRound1Package,
|
|
38
|
+
type DkgRound1SecretPackage,
|
|
39
|
+
} from "../../../frost/index.js";
|
|
40
|
+
import { Ed25519Sha512, serde } from "@frosts/ed25519";
|
|
41
|
+
import { decodeInviteDetails } from "./receive.js";
|
|
42
|
+
import { CborDate } from "@bcts/dcbor";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Options for the DKG round1 command.
|
|
46
|
+
*/
|
|
47
|
+
export interface DkgRound1Options {
|
|
48
|
+
registryPath?: string;
|
|
49
|
+
timeoutSeconds?: number;
|
|
50
|
+
responseArid?: string;
|
|
51
|
+
preview?: boolean;
|
|
52
|
+
rejectReason?: string;
|
|
53
|
+
sender?: string;
|
|
54
|
+
invite: string;
|
|
55
|
+
storageSelection?: StorageSelection;
|
|
56
|
+
verbose?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Result of the DKG round1 command.
|
|
61
|
+
*/
|
|
62
|
+
export interface DkgRound1Result {
|
|
63
|
+
accepted: boolean;
|
|
64
|
+
listeningArid?: string;
|
|
65
|
+
envelopeUr?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve an invite envelope from either storage (ARID) or direct UR.
|
|
70
|
+
*
|
|
71
|
+
* Port of `resolve_invite_envelope()` from cmd/dkg/participant/round1.rs lines 256-288.
|
|
72
|
+
*/
|
|
73
|
+
async function resolveInviteEnvelope(
|
|
74
|
+
selection: StorageSelection | undefined,
|
|
75
|
+
invite: string,
|
|
76
|
+
timeout?: number,
|
|
77
|
+
): Promise<Envelope> {
|
|
78
|
+
if (selection !== undefined) {
|
|
79
|
+
// Try to parse as ARID
|
|
80
|
+
try {
|
|
81
|
+
const arid = parseAridUr(invite);
|
|
82
|
+
const client = await createStorageClient(selection);
|
|
83
|
+
const envelope = await getWithIndicator(client, arid, "Invite", timeout, false);
|
|
84
|
+
if (envelope === null || envelope === undefined) {
|
|
85
|
+
throw new Error("Invite not found in Hubert storage");
|
|
86
|
+
}
|
|
87
|
+
return envelope;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Not an ARID, fall through to envelope parsing
|
|
90
|
+
if (e instanceof Error && e.message.includes("Invite not found in Hubert storage")) {
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (timeout !== undefined) {
|
|
96
|
+
throw new Error("--timeout is only valid when retrieving invites from Hubert");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return parseEnvelopeUr(invite);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// No storage selection
|
|
103
|
+
try {
|
|
104
|
+
parseAridUr(invite);
|
|
105
|
+
throw new Error("Hubert storage parameters are required to retrieve invites by ARID");
|
|
106
|
+
} catch (e) {
|
|
107
|
+
// Not an ARID, parse as envelope
|
|
108
|
+
if (e instanceof Error && e.message.includes("Hubert storage parameters are required")) {
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return parseEnvelopeUr(invite);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build the response body envelope.
|
|
118
|
+
*
|
|
119
|
+
* Port of `build_response_body()` from cmd/dkg/participant/round1.rs lines 290-308.
|
|
120
|
+
*/
|
|
121
|
+
function buildResponseBody(
|
|
122
|
+
groupId: ARID,
|
|
123
|
+
participant: XID,
|
|
124
|
+
responseArid: ARID,
|
|
125
|
+
round1Package: DkgRound1Package | undefined,
|
|
126
|
+
): Envelope {
|
|
127
|
+
let envelope = Envelope.unit()
|
|
128
|
+
.addType("dkgRound1Response")
|
|
129
|
+
.addAssertion("group", groupId)
|
|
130
|
+
.addAssertion("participant", participant)
|
|
131
|
+
.addAssertion("response_arid", responseArid);
|
|
132
|
+
|
|
133
|
+
if (round1Package !== undefined) {
|
|
134
|
+
// Serialize the package to JSON and wrap as CBOR JSON
|
|
135
|
+
const packageJson = serde.round1PackageToJson(round1Package);
|
|
136
|
+
const jsonStr = globalThis.JSON.stringify(packageJson);
|
|
137
|
+
const jsonBytes = new TextEncoder().encode(jsonStr);
|
|
138
|
+
const jsonWrapper = JSONWrapper.fromData(jsonBytes);
|
|
139
|
+
// Pass the JSONWrapper directly - it implements CborTaggedEncodable
|
|
140
|
+
envelope = envelope.addAssertion("round1_package", jsonWrapper);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return envelope;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Serialize round 1 secret package to JSON-compatible format.
|
|
148
|
+
*
|
|
149
|
+
* The @frosts/ed25519 serde module doesn't provide a serializer for SecretPackage,
|
|
150
|
+
* so we manually serialize it here.
|
|
151
|
+
*/
|
|
152
|
+
function serializeRound1SecretPackage(secret: DkgRound1SecretPackage): Record<string, unknown> {
|
|
153
|
+
// Access the coefficients and serialize them
|
|
154
|
+
const coefficients = secret.coefficients();
|
|
155
|
+
const serializedCoefficients = coefficients.map((c: unknown) =>
|
|
156
|
+
bytesToHex(
|
|
157
|
+
Ed25519Sha512.serializeScalar(c as Parameters<typeof Ed25519Sha512.serializeScalar>[0]),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Get the commitment coefficients
|
|
162
|
+
const commitment = secret.commitment;
|
|
163
|
+
const commitmentCoefficients = commitment.serialize().map((c: Uint8Array) => bytesToHex(c));
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
header: serde.DEFAULT_HEADER,
|
|
167
|
+
identifier: bytesToHex(secret.identifier.serialize()),
|
|
168
|
+
coefficients: serializedCoefficients,
|
|
169
|
+
commitment: commitmentCoefficients,
|
|
170
|
+
min_signers: secret.minSigners,
|
|
171
|
+
max_signers: secret.maxSigners,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Persist round 1 state to disk.
|
|
177
|
+
*
|
|
178
|
+
* Port of `persist_round1_state()` from cmd/dkg/participant/round1.rs lines 310-337.
|
|
179
|
+
*/
|
|
180
|
+
function persistRound1State(
|
|
181
|
+
registryPath: string,
|
|
182
|
+
groupId: ARID,
|
|
183
|
+
round1Secret: DkgRound1SecretPackage,
|
|
184
|
+
round1Package: DkgRound1Package,
|
|
185
|
+
): ContributionPaths {
|
|
186
|
+
const dir = groupStateDir(registryPath, groupId.hex());
|
|
187
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
188
|
+
|
|
189
|
+
const secretPath = path.join(dir, "round1_secret.json");
|
|
190
|
+
const packagePath = path.join(dir, "round1_package.json");
|
|
191
|
+
|
|
192
|
+
// Serialize the secret package manually since serde doesn't provide it
|
|
193
|
+
const secretJson = serializeRound1SecretPackage(round1Secret);
|
|
194
|
+
// Serialize the public package using the standard serde function
|
|
195
|
+
const packageJson = serde.round1PackageToJson(round1Package);
|
|
196
|
+
|
|
197
|
+
fs.writeFileSync(secretPath, globalThis.JSON.stringify(secretJson, null, 2));
|
|
198
|
+
fs.writeFileSync(packagePath, globalThis.JSON.stringify(packageJson, null, 2));
|
|
199
|
+
|
|
200
|
+
return new ContributionPaths({
|
|
201
|
+
round1Secret: secretPath,
|
|
202
|
+
round1Package: packagePath,
|
|
203
|
+
round2Secret: undefined,
|
|
204
|
+
keyPackage: undefined,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Execute the DKG participant round 1 command.
|
|
210
|
+
*
|
|
211
|
+
* Responds to the DKG invite with commitment packages.
|
|
212
|
+
*
|
|
213
|
+
* Port of `CommandArgs::exec()` from cmd/dkg/participant/round1.rs lines 66-254.
|
|
214
|
+
*/
|
|
215
|
+
export async function round1(
|
|
216
|
+
_client: StorageClient | undefined,
|
|
217
|
+
options: DkgRound1Options,
|
|
218
|
+
cwd: string,
|
|
219
|
+
): Promise<DkgRound1Result> {
|
|
220
|
+
// Validate options
|
|
221
|
+
if (options.storageSelection === undefined && options.timeoutSeconds !== undefined) {
|
|
222
|
+
throw new Error("--timeout requires Hubert storage parameters");
|
|
223
|
+
}
|
|
224
|
+
if (options.storageSelection !== undefined && options.preview === true) {
|
|
225
|
+
throw new Error("--preview cannot be used with Hubert storage options");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const registryPath = resolveRegistryPath(options.registryPath, cwd);
|
|
229
|
+
const registry = Registry.load(registryPath);
|
|
230
|
+
|
|
231
|
+
const owner = registry.owner();
|
|
232
|
+
if (!owner) {
|
|
233
|
+
throw new Error("Registry owner with private keys is required");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Resolve expected sender if provided
|
|
237
|
+
let expectedSender: XIDDocument | undefined;
|
|
238
|
+
if (options.sender !== undefined) {
|
|
239
|
+
expectedSender = resolveSenderXidDocument(registry, options.sender);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const nextResponseArid =
|
|
243
|
+
options.responseArid !== undefined ? parseAridUr(options.responseArid) : ARID.new();
|
|
244
|
+
|
|
245
|
+
// Resolve the invite envelope
|
|
246
|
+
const inviteEnvelope = await resolveInviteEnvelope(
|
|
247
|
+
options.storageSelection,
|
|
248
|
+
options.invite,
|
|
249
|
+
options.timeoutSeconds,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// Decode the invite details
|
|
253
|
+
const now = CborDate.now().datetime();
|
|
254
|
+
const details = decodeInviteDetails(
|
|
255
|
+
inviteEnvelope,
|
|
256
|
+
now,
|
|
257
|
+
registry,
|
|
258
|
+
owner.xidDocument(),
|
|
259
|
+
expectedSender,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Sort participants by XID and find our position
|
|
263
|
+
const sortedParticipants = [...details.participants].sort((a, b) =>
|
|
264
|
+
a.xid().urString().localeCompare(b.xid().urString()),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const ownerIndex = sortedParticipants.findIndex(
|
|
268
|
+
(doc) => doc.xid().urString() === owner.xid().urString(),
|
|
269
|
+
);
|
|
270
|
+
if (ownerIndex === -1) {
|
|
271
|
+
throw new Error("Invite does not include the registry owner");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const identifierIndex = ownerIndex + 1; // FROST uses 1-indexed identifiers
|
|
275
|
+
if (identifierIndex > 65535) {
|
|
276
|
+
throw new Error("Too many participants for identifiers");
|
|
277
|
+
}
|
|
278
|
+
const identifier = identifierFromU16(identifierIndex);
|
|
279
|
+
|
|
280
|
+
const total = sortedParticipants.length;
|
|
281
|
+
if (total > 65535) {
|
|
282
|
+
throw new Error("Too many participants for FROST identifiers");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const minSigners = details.invitation.minSigners();
|
|
286
|
+
if (minSigners > 65535) {
|
|
287
|
+
throw new Error("min_signers does not fit into identifier space");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Build group participants for the registry
|
|
291
|
+
const groupParticipants = buildGroupParticipants(registry, owner, sortedParticipants);
|
|
292
|
+
const coordinator = groupParticipantFromRegistry(registry, owner, details.invitation.sender());
|
|
293
|
+
|
|
294
|
+
// Check if we're posting to storage
|
|
295
|
+
const isPosting = options.storageSelection !== undefined;
|
|
296
|
+
|
|
297
|
+
// Build the response body
|
|
298
|
+
let responseBody: Envelope;
|
|
299
|
+
let contributions: ContributionPaths | undefined;
|
|
300
|
+
|
|
301
|
+
if (options.rejectReason === undefined && isPosting) {
|
|
302
|
+
// Actually posting - generate and persist round1 state
|
|
303
|
+
const [round1Secret, round1Package] = dkgPart1(identifier, total, minSigners, createRng());
|
|
304
|
+
|
|
305
|
+
contributions = persistRound1State(
|
|
306
|
+
registryPath,
|
|
307
|
+
details.invitation.groupId(),
|
|
308
|
+
round1Secret,
|
|
309
|
+
round1Package,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
responseBody = buildResponseBody(
|
|
313
|
+
details.invitation.groupId(),
|
|
314
|
+
owner.xid(),
|
|
315
|
+
nextResponseArid,
|
|
316
|
+
round1Package,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Create and save group record
|
|
320
|
+
const groupRecord = new GroupRecord(
|
|
321
|
+
details.invitation.charter(),
|
|
322
|
+
details.invitation.minSigners(),
|
|
323
|
+
coordinator,
|
|
324
|
+
groupParticipants,
|
|
325
|
+
);
|
|
326
|
+
groupRecord.setContributions(contributions);
|
|
327
|
+
groupRecord.setListeningAtArid(nextResponseArid);
|
|
328
|
+
|
|
329
|
+
registry.recordGroup(details.invitation.groupId(), groupRecord);
|
|
330
|
+
registry.save(registryPath);
|
|
331
|
+
} else if (options.rejectReason === undefined) {
|
|
332
|
+
// Preview mode - generate dummy round1 for envelope structure only
|
|
333
|
+
const [, round1Package] = dkgPart1(identifier, total, minSigners, createRng());
|
|
334
|
+
|
|
335
|
+
responseBody = buildResponseBody(
|
|
336
|
+
details.invitation.groupId(),
|
|
337
|
+
owner.xid(),
|
|
338
|
+
nextResponseArid,
|
|
339
|
+
round1Package,
|
|
340
|
+
);
|
|
341
|
+
} else {
|
|
342
|
+
// Rejecting - no round1 needed
|
|
343
|
+
responseBody = buildResponseBody(
|
|
344
|
+
details.invitation.groupId(),
|
|
345
|
+
owner.xid(),
|
|
346
|
+
nextResponseArid,
|
|
347
|
+
undefined,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Build the sealed response
|
|
352
|
+
const signerPrivateKeys = owner.xidDocument().inceptionPrivateKeys();
|
|
353
|
+
if (signerPrivateKeys === undefined) {
|
|
354
|
+
throw new Error("Owner XID document has no signing keys");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let sealed: SealedResponse;
|
|
358
|
+
if (options.rejectReason !== undefined) {
|
|
359
|
+
// Build rejection error body
|
|
360
|
+
const errorBody = Envelope.new("dkgInviteReject")
|
|
361
|
+
.addAssertion("group", details.invitation.groupId())
|
|
362
|
+
.addAssertion("response_arid", nextResponseArid)
|
|
363
|
+
.addAssertion("reason", options.rejectReason);
|
|
364
|
+
|
|
365
|
+
sealed = SealedResponse.newFailure(details.invitation.requestId(), owner.xidDocument())
|
|
366
|
+
.withError(errorBody)
|
|
367
|
+
.withState(nextResponseArid);
|
|
368
|
+
} else {
|
|
369
|
+
sealed = SealedResponse.newSuccess(details.invitation.requestId(), owner.xidDocument())
|
|
370
|
+
.withResult(responseBody)
|
|
371
|
+
.withState(nextResponseArid);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Add peer continuation if present
|
|
375
|
+
const peerContinuation = details.invitation.peerContinuation();
|
|
376
|
+
if (peerContinuation !== undefined) {
|
|
377
|
+
sealed = sealed.withPeerContinuation(peerContinuation);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Handle output based on storage selection
|
|
381
|
+
if (options.storageSelection !== undefined) {
|
|
382
|
+
const responseEnvelope = sealed.toEnvelope(
|
|
383
|
+
details.invitation.validUntil(),
|
|
384
|
+
signerPrivateKeys,
|
|
385
|
+
details.invitation.sender(),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const responseTarget = details.invitation.responseArid();
|
|
389
|
+
const client = await createStorageClient(options.storageSelection);
|
|
390
|
+
|
|
391
|
+
await putWithIndicator(
|
|
392
|
+
client,
|
|
393
|
+
responseTarget,
|
|
394
|
+
responseEnvelope,
|
|
395
|
+
"Round 1 Response",
|
|
396
|
+
options.verbose ?? false,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
if (options.verbose === true) {
|
|
400
|
+
console.log(`Sent round 1 response`);
|
|
401
|
+
console.log(`Listening at: ${nextResponseArid.urString()}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
accepted: options.rejectReason === undefined,
|
|
406
|
+
listeningArid: nextResponseArid.urString(),
|
|
407
|
+
};
|
|
408
|
+
} else if (options.preview === true) {
|
|
409
|
+
// Show the GSTP response structure without encryption
|
|
410
|
+
const unsealedEnvelope = sealed.toEnvelope(undefined, signerPrivateKeys, undefined);
|
|
411
|
+
const envelopeUr = unsealedEnvelope.urString();
|
|
412
|
+
console.log(envelopeUr);
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
accepted: options.rejectReason === undefined,
|
|
416
|
+
envelopeUr,
|
|
417
|
+
};
|
|
418
|
+
} else {
|
|
419
|
+
// Print the sealed envelope
|
|
420
|
+
const responseEnvelope = sealed.toEnvelope(
|
|
421
|
+
details.invitation.validUntil(),
|
|
422
|
+
signerPrivateKeys,
|
|
423
|
+
details.invitation.sender(),
|
|
424
|
+
);
|
|
425
|
+
const envelopeUr = responseEnvelope.urString();
|
|
426
|
+
console.log(envelopeUr);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
accepted: options.rejectReason === undefined,
|
|
430
|
+
envelopeUr,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Resolve a sender XID document from the registry by UR or pet name.
|
|
437
|
+
*/
|
|
438
|
+
function resolveSenderXidDocument(registry: Registry, raw: string): XIDDocument {
|
|
439
|
+
// Try parsing as XID UR first
|
|
440
|
+
try {
|
|
441
|
+
const xid = XID.fromURString(raw.trim());
|
|
442
|
+
const record = registry.participant(xid);
|
|
443
|
+
if (record) {
|
|
444
|
+
return record.xidDocument();
|
|
445
|
+
}
|
|
446
|
+
const owner = registry.owner();
|
|
447
|
+
if (owner?.xid().urString() === xid.urString()) {
|
|
448
|
+
return owner.xidDocument();
|
|
449
|
+
}
|
|
450
|
+
throw new Error(`Sender with XID ${xid.urString()} not found in registry`);
|
|
451
|
+
} catch {
|
|
452
|
+
// Try looking up by pet name
|
|
453
|
+
const result = registry.participantByPetName(raw.trim());
|
|
454
|
+
if (result) {
|
|
455
|
+
const [, record] = result;
|
|
456
|
+
return record.xidDocument();
|
|
457
|
+
}
|
|
458
|
+
const owner = registry.owner();
|
|
459
|
+
if (owner?.petName() === raw.trim()) {
|
|
460
|
+
return owner.xidDocument();
|
|
461
|
+
}
|
|
462
|
+
throw new Error(`Sender '${raw}' not found in registry`);
|
|
463
|
+
}
|
|
464
|
+
}
|