@bcts/frost-hubert 1.0.0-alpha.22 → 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.
- package/dist/bin/frost.cjs +347 -75
- package/dist/bin/frost.cjs.map +1 -1
- package/dist/bin/frost.mjs +347 -75
- package/dist/bin/frost.mjs.map +1 -1
- package/dist/busy-DkM2jAIZ.mjs +27 -0
- package/dist/busy-DkM2jAIZ.mjs.map +1 -0
- package/dist/busy-EZU7EKr6.cjs +38 -0
- package/dist/busy-EZU7EKr6.cjs.map +1 -0
- package/dist/{chunk-uaV2rQ02.cjs → chunk-CZWwpsFl.cjs} +22 -32
- package/dist/{chunk-ClPoSABd.mjs → chunk-CjcI7cDX.mjs} +6 -12
- package/dist/cmd/index.cjs +46 -43
- package/dist/cmd/index.d.cts +2 -4
- package/dist/cmd/index.d.mts +2 -4
- package/dist/cmd/index.mjs +7 -6
- package/dist/cmd-Bw9_i2_f.cjs +130 -0
- package/dist/cmd-Bw9_i2_f.cjs.map +1 -0
- package/dist/cmd-CS1uJtuD.mjs +113 -0
- package/dist/cmd-CS1uJtuD.mjs.map +1 -0
- package/dist/common-CvH6dFvQ.mjs +282 -0
- package/dist/common-CvH6dFvQ.mjs.map +1 -0
- package/dist/common-DUWvtc08.mjs +96 -0
- package/dist/common-DUWvtc08.mjs.map +1 -0
- package/dist/common-lKP5EzHy.cjs +372 -0
- package/dist/common-lKP5EzHy.cjs.map +1 -0
- package/dist/common-lThIvJmZ.cjs +114 -0
- package/dist/common-lThIvJmZ.cjs.map +1 -0
- package/dist/dkg/index.cjs +245 -7
- package/dist/dkg/index.cjs.map +1 -0
- package/dist/dkg/index.d.cts +2 -2
- package/dist/dkg/index.d.mts +2 -2
- package/dist/dkg/index.mjs +238 -2
- package/dist/dkg/index.mjs.map +1 -0
- package/dist/finalize-BRgJK-Xv.cjs +402 -0
- package/dist/finalize-BRgJK-Xv.cjs.map +1 -0
- package/dist/finalize-BfLgzn8f.cjs +303 -0
- package/dist/finalize-BfLgzn8f.cjs.map +1 -0
- package/dist/finalize-CNTDj6aS.mjs +389 -0
- package/dist/finalize-CNTDj6aS.mjs.map +1 -0
- package/dist/finalize-EC3ikHQq.mjs +252 -0
- package/dist/finalize-EC3ikHQq.mjs.map +1 -0
- package/dist/finalize-IA01t_Qq.mjs +290 -0
- package/dist/finalize-IA01t_Qq.mjs.map +1 -0
- package/dist/finalize-UPyI1yb1.cjs +265 -0
- package/dist/finalize-UPyI1yb1.cjs.map +1 -0
- package/dist/frost/index.cjs +8 -9
- package/dist/frost/index.cjs.map +1 -1
- package/dist/frost/index.mjs +2 -3
- package/dist/frost/index.mjs.map +1 -1
- package/dist/{group-invite-Dz1Jmiky.d.cts → index-B3c-80VS.d.cts} +25 -2
- package/dist/index-B3c-80VS.d.cts.map +1 -0
- package/dist/{index-CcvTi5EA.d.cts → index-BgbSGpxn.d.mts} +102 -80
- package/dist/index-BgbSGpxn.d.mts.map +1 -0
- package/dist/{registry-impl-CE76sTXQ.d.cts → index-C8QeHNwa.d.cts} +46 -2
- package/dist/index-C8QeHNwa.d.cts.map +1 -0
- package/dist/{group-invite-Wk9CIbHL.d.mts → index-D3QTWkEm.d.mts} +25 -2
- package/dist/index-D3QTWkEm.d.mts.map +1 -0
- package/dist/{registry-impl-BETn_lEO.d.mts → index-DVbWyOs7.d.mts} +46 -2
- package/dist/index-DVbWyOs7.d.mts.map +1 -0
- package/dist/{index-DNCPeLNM.d.mts → index-F1iNEAJR.d.cts} +102 -80
- package/dist/index-F1iNEAJR.d.cts.map +1 -0
- package/dist/index.cjs +72 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +4 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11 -10
- package/dist/index.mjs.map +1 -1
- package/dist/invite-5277FQVT.cjs +274 -0
- package/dist/invite-5277FQVT.cjs.map +1 -0
- package/dist/invite-DUTcfTgX.cjs +109 -0
- package/dist/invite-DUTcfTgX.cjs.map +1 -0
- package/dist/invite-IU4n0dq2.mjs +96 -0
- package/dist/invite-IU4n0dq2.mjs.map +1 -0
- package/dist/invite-RU-OXTNS.mjs +219 -0
- package/dist/invite-RU-OXTNS.mjs.map +1 -0
- package/dist/parallel-D1R6ZGlY.cjs +318 -0
- package/dist/parallel-D1R6ZGlY.cjs.map +1 -0
- package/dist/parallel-D6zc6VW4.mjs +235 -0
- package/dist/parallel-D6zc6VW4.mjs.map +1 -0
- package/dist/proposed-participant-Dm1Eq6mX.cjs +141 -0
- package/dist/proposed-participant-Dm1Eq6mX.cjs.map +1 -0
- package/dist/proposed-participant-cWM7iUrO.mjs +129 -0
- package/dist/proposed-participant-cWM7iUrO.mjs.map +1 -0
- package/dist/receive-CAI-x4II.cjs +213 -0
- package/dist/receive-CAI-x4II.cjs.map +1 -0
- package/dist/receive-D2Nn68L7.mjs +188 -0
- package/dist/receive-D2Nn68L7.mjs.map +1 -0
- package/dist/receive-DA_KQEgk.mjs +177 -0
- package/dist/receive-DA_KQEgk.mjs.map +1 -0
- package/dist/receive-kZMsXhbK.cjs +190 -0
- package/dist/receive-kZMsXhbK.cjs.map +1 -0
- package/dist/registry/index.cjs +881 -13
- package/dist/registry/index.cjs.map +1 -0
- package/dist/registry/index.d.cts +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +867 -2
- package/dist/registry/index.mjs.map +1 -0
- package/dist/{registry-FMU-ec5K.cjs → registry-9puTaRrD.cjs} +28 -31
- package/dist/registry-9puTaRrD.cjs.map +1 -0
- package/dist/{registry-BDnNV1Rk.mjs → registry-BpCwtrRt.mjs} +7 -10
- package/dist/{registry-BDnNV1Rk.mjs.map → registry-BpCwtrRt.mjs.map} +1 -1
- package/dist/round1-4Hyx8w0x.cjs +422 -0
- package/dist/round1-4Hyx8w0x.cjs.map +1 -0
- package/dist/round1-7v9LlE11.mjs +373 -0
- package/dist/round1-7v9LlE11.mjs.map +1 -0
- package/dist/round1-BHBjru1m.cjs +465 -0
- package/dist/round1-BHBjru1m.cjs.map +1 -0
- package/dist/round1-CMLKN2RR.mjs +195 -0
- package/dist/round1-CMLKN2RR.mjs.map +1 -0
- package/dist/round1-CWSXZx5R.cjs +208 -0
- package/dist/round1-CWSXZx5R.cjs.map +1 -0
- package/dist/round1-CcQCGlIT.mjs +208 -0
- package/dist/round1-CcQCGlIT.mjs.map +1 -0
- package/dist/round1-Cgm7j1kI.mjs +452 -0
- package/dist/round1-Cgm7j1kI.mjs.map +1 -0
- package/dist/round1-DQ0fnc1H.cjs +221 -0
- package/dist/round1-DQ0fnc1H.cjs.map +1 -0
- package/dist/round2-BWz9SQIi.cjs +305 -0
- package/dist/round2-BWz9SQIi.cjs.map +1 -0
- package/dist/round2-BkNRCXgS.mjs +292 -0
- package/dist/round2-BkNRCXgS.mjs.map +1 -0
- package/dist/round2-Bl2uK93U.mjs +450 -0
- package/dist/round2-Bl2uK93U.mjs.map +1 -0
- package/dist/round2-CdUT-AhH.cjs +499 -0
- package/dist/round2-CdUT-AhH.cjs.map +1 -0
- package/dist/round2-DOA3rnV-.mjs +280 -0
- package/dist/round2-DOA3rnV-.mjs.map +1 -0
- package/dist/round2-Dg24w-TU.mjs +397 -0
- package/dist/round2-Dg24w-TU.mjs.map +1 -0
- package/dist/round2-LylCa84n.cjs +293 -0
- package/dist/round2-LylCa84n.cjs.map +1 -0
- package/dist/round2-o2Q-GMbX.cjs +410 -0
- package/dist/round2-o2Q-GMbX.cjs.map +1 -0
- package/dist/storage-B-Gu68-O.cjs +79 -0
- package/dist/storage-B-Gu68-O.cjs.map +1 -0
- package/dist/storage-Bkkliz0K.mjs +74 -0
- package/dist/storage-Bkkliz0K.mjs.map +1 -0
- package/package.json +17 -17
- package/src/bin/frost.ts +849 -128
- package/src/cmd/common.ts +19 -1
- package/src/cmd/dkg/common.ts +97 -10
- package/src/cmd/dkg/coordinator/invite.ts +5 -2
- package/src/cmd/dkg/participant/finalize.ts +52 -18
- package/src/cmd/dkg/participant/round1.ts +39 -38
- package/src/cmd/dkg/participant/round2.ts +60 -26
- package/src/cmd/sign/coordinator/round2.ts +5 -1
- package/src/cmd/sign/participant/finalize.ts +6 -2
- package/src/cmd/sign/participant/receive.ts +5 -2
- package/src/dkg/group-invite.ts +12 -2
- package/src/dkg/proposed-participant.ts +33 -5
- package/src/frost/index.ts +1 -1
- package/src/registry/owner-record.ts +13 -2
- package/src/registry/participant-record.ts +36 -4
- package/src/registry/registry-impl.ts +74 -18
- package/dist/group-invite-CrbOabFL.cjs +0 -368
- package/dist/group-invite-CrbOabFL.cjs.map +0 -1
- package/dist/group-invite-Dz1Jmiky.d.cts.map +0 -1
- package/dist/group-invite-RPElq-fm.mjs +0 -338
- package/dist/group-invite-RPElq-fm.mjs.map +0 -1
- package/dist/group-invite-Wk9CIbHL.d.mts.map +0 -1
- package/dist/index-CcvTi5EA.d.cts.map +0 -1
- package/dist/index-DNCPeLNM.d.mts.map +0 -1
- package/dist/registry-FMU-ec5K.cjs.map +0 -1
- package/dist/registry-impl-BETn_lEO.d.mts.map +0 -1
- package/dist/registry-impl-C7w4awTv.cjs +0 -865
- package/dist/registry-impl-C7w4awTv.cjs.map +0 -1
- package/dist/registry-impl-CE76sTXQ.d.cts.map +0 -1
- package/dist/registry-impl-eYXVSPwM.mjs +0 -797
- package/dist/registry-impl-eYXVSPwM.mjs.map +0 -1
- package/dist/sign-2bOp18Fs.cjs +0 -4875
- package/dist/sign-2bOp18Fs.cjs.map +0 -1
- package/dist/sign-D8C3HJ4B.mjs +0 -4736
- package/dist/sign-D8C3HJ4B.mjs.map +0 -1
package/src/dkg/group-invite.ts
CHANGED
|
@@ -355,7 +355,14 @@ export class DkgInvitation {
|
|
|
355
355
|
recipientPrivateKeys,
|
|
356
356
|
);
|
|
357
357
|
|
|
358
|
-
|
|
358
|
+
// Rust `group_invite.rs:275-279` uses `!=` on `XID`, which is the
|
|
359
|
+
// derived `PartialEq` (byte-wise compare on the 32-byte ARID). TS
|
|
360
|
+
// `!==` is reference identity — two distinct XID instances with
|
|
361
|
+
// the same bytes would fail. Compare via hex equality instead.
|
|
362
|
+
if (
|
|
363
|
+
expectedSender !== undefined &&
|
|
364
|
+
!sealedRequest.sender().xid().equals(expectedSender.xid())
|
|
365
|
+
) {
|
|
359
366
|
throw new Error("Invite sender does not match expected sender");
|
|
360
367
|
}
|
|
361
368
|
|
|
@@ -391,7 +398,10 @@ export class DkgInvitation {
|
|
|
391
398
|
XIDVerifySignature.Inception,
|
|
392
399
|
);
|
|
393
400
|
|
|
394
|
-
|
|
401
|
+
// Rust `group_invite.rs` compares via `XID::PartialEq` (byte-wise).
|
|
402
|
+
// TS reference identity would never match for two distinct
|
|
403
|
+
// `XID` instances representing the same identity.
|
|
404
|
+
if (!xidDocument.xid().equals(recipientXid)) {
|
|
395
405
|
continue;
|
|
396
406
|
}
|
|
397
407
|
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { type ARID, type XID } from "@bcts/components";
|
|
14
|
-
import { type Cbor } from "@bcts/dcbor";
|
|
15
14
|
import { Envelope } from "@bcts/envelope";
|
|
16
15
|
import { UR } from "@bcts/uniform-resources";
|
|
17
16
|
import { XIDDocument, XIDVerifySignature } from "@bcts/xid";
|
|
@@ -96,12 +95,41 @@ export class DkgProposedParticipant {
|
|
|
96
95
|
|
|
97
96
|
/**
|
|
98
97
|
* Compare participants by XID for sorting.
|
|
98
|
+
*
|
|
99
|
+
* Mirrors Rust `PartialOrd::partial_cmp` which uses
|
|
100
|
+
* `self.xid().cmp(&other.xid())` — i.e. the underlying 32-byte XID
|
|
101
|
+
* data is compared lexicographically (`Vec<u8>` ordering). The
|
|
102
|
+
* earlier port used `xid.toString().localeCompare(...)`, which (a)
|
|
103
|
+
* compares the UR-encoded base32-ish string, not the bytes, and (b)
|
|
104
|
+
* is locale-aware. Sorting on UR strings differs from the byte
|
|
105
|
+
* order whenever the underlying bytes contain values ≥ 0x80, so
|
|
106
|
+
* Rust and TS would assign different FROST identifiers to the same
|
|
107
|
+
* participant set — producing different secret shares.
|
|
99
108
|
*/
|
|
100
109
|
compareTo(other: DkgProposedParticipant): number {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
return compareXidBytes(this.xid().toData(), other.xid().toData());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Lexicographic byte compare matching Rust's `Vec<u8>::cmp` /
|
|
116
|
+
* `XID::cmp`. Exported so the cmd-tree call sites (round1 / finalize)
|
|
117
|
+
* can use the same comparator when they sort deduplicated XID lists.
|
|
118
|
+
*
|
|
119
|
+
* `XID` is exactly 32 bytes so this only ever compares two equal-length
|
|
120
|
+
* inputs; the length-tiebreak branch mirrors the generic `Ord` impl on
|
|
121
|
+
* `Vec<u8>` and is included for correctness if ever applied to other
|
|
122
|
+
* byte sequences.
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
126
|
+
export function compareXidBytes(a: Uint8Array, b: Uint8Array): number {
|
|
127
|
+
const minLen = Math.min(a.length, b.length);
|
|
128
|
+
for (let i = 0; i < minLen; i++) {
|
|
129
|
+
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
104
130
|
}
|
|
131
|
+
if (a.length !== b.length) return a.length < b.length ? -1 : 1;
|
|
132
|
+
return 0;
|
|
105
133
|
}
|
|
106
134
|
|
|
107
135
|
/**
|
|
@@ -121,7 +149,7 @@ function parseXidEnvelope(input: string): [Envelope, XIDDocument] {
|
|
|
121
149
|
throw new Error(`Expected a ur:xid document, found ur:${urType}`);
|
|
122
150
|
}
|
|
123
151
|
|
|
124
|
-
const envelopeCbor = ur.cbor()
|
|
152
|
+
const envelopeCbor = ur.cbor();
|
|
125
153
|
// Try tagged CBOR first, then untagged
|
|
126
154
|
let envelope: Envelope;
|
|
127
155
|
try {
|
package/src/frost/index.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type DkgRound2SecretPackage = keys.dkg.round2.SecretPackage;
|
|
|
61
61
|
*/
|
|
62
62
|
export class SecureRng implements RandomSource {
|
|
63
63
|
fill(array: Uint8Array): void {
|
|
64
|
-
globalThis.crypto.getRandomValues(array);
|
|
64
|
+
globalThis.crypto.getRandomValues(array as Uint8Array<ArrayBuffer>);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { type XID } from "@bcts/components";
|
|
14
|
-
import { type Cbor } from "@bcts/dcbor";
|
|
15
14
|
import { Envelope } from "@bcts/envelope";
|
|
16
15
|
import { UR } from "@bcts/uniform-resources";
|
|
17
16
|
import { XIDDocument, XIDVerifySignature } from "@bcts/xid";
|
|
@@ -102,8 +101,20 @@ export class OwnerRecord {
|
|
|
102
101
|
|
|
103
102
|
/**
|
|
104
103
|
* Deserialize from JSON object.
|
|
104
|
+
*
|
|
105
|
+
* Mirrors Rust's `#[serde(deny_unknown_fields)]` derive on
|
|
106
|
+
* `OwnerRecord` (`owner_record.rs:13-17`) — any field not in
|
|
107
|
+
* `{xid_document, pet_name}` causes Rust's `serde_json::from_str`
|
|
108
|
+
* to error with `unknown field`, and we mirror that here so a
|
|
109
|
+
* registry file produced by a future Rust version with extra
|
|
110
|
+
* fields is rejected explicitly rather than silently lossy.
|
|
105
111
|
*/
|
|
106
112
|
static fromJSON(json: Record<string, unknown>): OwnerRecord {
|
|
113
|
+
for (const key of Object.keys(json)) {
|
|
114
|
+
if (key !== "xid_document" && key !== "pet_name") {
|
|
115
|
+
throw new Error(`unknown field \`${key}\`, expected \`xid_document\` or \`pet_name\``);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
107
118
|
const xidDocumentUr = json["xid_document"] as string;
|
|
108
119
|
const petName = json["pet_name"] as string | undefined;
|
|
109
120
|
return OwnerRecord.fromSignedXidUr(xidDocumentUr, petName);
|
|
@@ -123,7 +134,7 @@ function parseRelaxedXidDocument(xidDocumentUr: string): [string, XIDDocument] {
|
|
|
123
134
|
throw new Error(`Expected a ur:xid document, found ur:${ur.urTypeStr()}`);
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
const envelopeCbor = ur.cbor()
|
|
137
|
+
const envelopeCbor = ur.cbor();
|
|
127
138
|
let envelope: Envelope;
|
|
128
139
|
try {
|
|
129
140
|
envelope = Envelope.fromTaggedCbor(envelopeCbor);
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { type PublicKeys, type XID } from "@bcts/components";
|
|
14
|
-
import { type Cbor } from "@bcts/dcbor";
|
|
15
14
|
import { Envelope } from "@bcts/envelope";
|
|
16
15
|
import { UR } from "@bcts/uniform-resources";
|
|
17
16
|
import { XIDDocument, XIDVerifySignature } from "@bcts/xid";
|
|
@@ -142,8 +141,21 @@ export class ParticipantRecord {
|
|
|
142
141
|
|
|
143
142
|
/**
|
|
144
143
|
* Deserialize from JSON object.
|
|
144
|
+
*
|
|
145
|
+
* Mirrors Rust's `#[serde(deny_unknown_fields)]` derive on
|
|
146
|
+
* `ParticipantRecord` (`participant_record.rs:12-17`) — Rust's
|
|
147
|
+
* `serde_json::from_str` errors with `unknown field` for any
|
|
148
|
+
* field outside `{xid_document, pet_name}`. We mirror that
|
|
149
|
+
* behaviour so a registry file produced by a future Rust
|
|
150
|
+
* version with extra fields is rejected explicitly rather than
|
|
151
|
+
* silently lossy.
|
|
145
152
|
*/
|
|
146
153
|
static fromJSON(json: Record<string, unknown>): ParticipantRecord {
|
|
154
|
+
for (const key of Object.keys(json)) {
|
|
155
|
+
if (key !== "xid_document" && key !== "pet_name") {
|
|
156
|
+
throw new Error(`unknown field \`${key}\`, expected \`xid_document\` or \`pet_name\``);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
147
159
|
const xidDocumentUr = json["xid_document"] as string;
|
|
148
160
|
const petName = json["pet_name"] as string | undefined;
|
|
149
161
|
return ParticipantRecord.recreateFromSerialized(xidDocumentUr, petName);
|
|
@@ -163,15 +175,35 @@ function parseSignedXidDocument(xidDocumentUr: string): [string, XIDDocument] {
|
|
|
163
175
|
throw new Error(`Expected a ur:xid document, found ur:${ur.urTypeStr()}`);
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
const envelopeCbor = ur.cbor()
|
|
178
|
+
const envelopeCbor = ur.cbor();
|
|
167
179
|
let envelope: Envelope;
|
|
168
180
|
try {
|
|
169
181
|
envelope = Envelope.fromTaggedCbor(envelopeCbor);
|
|
170
182
|
} catch {
|
|
171
|
-
|
|
183
|
+
try {
|
|
184
|
+
envelope = Envelope.fromUntaggedCbor(envelopeCbor);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Unable to decode XID document envelope: ${(e as Error).message ?? String(e)}`,
|
|
188
|
+
{
|
|
189
|
+
cause: e,
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
}
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
|
|
195
|
+
// Mirror Rust `participant_record.rs:198-203`'s `.context(...)` wrap:
|
|
196
|
+
// any failure from `XIDDocument::from_envelope(..., XIDVerifySignature::Inception)`
|
|
197
|
+
// is surfaced as "XID document must be signed by its inception key: <cause>".
|
|
198
|
+
let document: XIDDocument;
|
|
199
|
+
try {
|
|
200
|
+
document = XIDDocument.fromEnvelope(envelope, undefined, XIDVerifySignature.Inception);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`XID document must be signed by its inception key: ${(e as Error).message ?? String(e)}`,
|
|
204
|
+
{ cause: e },
|
|
205
|
+
);
|
|
206
|
+
}
|
|
175
207
|
|
|
176
208
|
return [sanitized, document];
|
|
177
209
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
|
|
16
|
-
import { type ARID, type XID } from "@bcts/components";
|
|
16
|
+
import { type ARID, type PublicKeys, type XID } from "@bcts/components";
|
|
17
17
|
|
|
18
18
|
import { GroupRecord } from "./group-record.js";
|
|
19
19
|
import { OwnerRecord } from "./owner-record.js";
|
|
@@ -140,20 +140,57 @@ export class Registry {
|
|
|
140
140
|
/**
|
|
141
141
|
* Add a participant.
|
|
142
142
|
*
|
|
143
|
-
*
|
|
143
|
+
* Mirrors Rust `Registry::add_participant`
|
|
144
|
+
* (`registry_impl.rs:83-124`):
|
|
145
|
+
*
|
|
146
|
+
* 1. If `record.pet_name()` is already used by some other XID,
|
|
147
|
+
* bail with `"Pet name '{name}' already used by another
|
|
148
|
+
* participant"`.
|
|
149
|
+
* 2. If `record.pet_name()` matches an existing record under the
|
|
150
|
+
* *same* XID and the public keys also match, return
|
|
151
|
+
* `AlreadyPresent`.
|
|
152
|
+
* 3. If the pet name matches the same XID but public keys don't,
|
|
153
|
+
* bail with `"Participant already exists with a different pet
|
|
154
|
+
* name"`.
|
|
155
|
+
* 4. Otherwise look up by XID. If present and public-keys + pet-name
|
|
156
|
+
* both match, return `AlreadyPresent`; if XID is present but
|
|
157
|
+
* anything differs, bail. If XID is new, insert and return
|
|
158
|
+
* `Inserted`.
|
|
159
|
+
*
|
|
160
|
+
* The earlier port short-circuited on `participants.has(xidUr)` and
|
|
161
|
+
* always returned `AlreadyPresent` — silently allowing re-adds with
|
|
162
|
+
* a different pet name or different public keys, which Rust
|
|
163
|
+
* correctly forbids.
|
|
144
164
|
*/
|
|
145
165
|
addParticipant(xid: XID, record: ParticipantRecord): AddOutcome {
|
|
146
166
|
const xidUr = xid.urString();
|
|
167
|
+
const petName = record.petName();
|
|
147
168
|
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
|
|
169
|
+
// Steps 1–3: pet-name conflict resolution.
|
|
170
|
+
if (petName !== undefined) {
|
|
171
|
+
for (const [existingXidUr, existingRecord] of this._participants) {
|
|
172
|
+
if (existingRecord.petName() === petName) {
|
|
173
|
+
if (existingXidUr !== xidUr) {
|
|
174
|
+
throw new Error(`Pet name '${petName}' already used by another participant`);
|
|
175
|
+
}
|
|
176
|
+
if (publicKeysEqual(existingRecord.publicKeys(), record.publicKeys())) {
|
|
177
|
+
return AddOutcome.AlreadyPresent;
|
|
178
|
+
}
|
|
179
|
+
throw new Error("Participant already exists with a different pet name");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
151
182
|
}
|
|
152
183
|
|
|
153
|
-
//
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
|
|
184
|
+
// Step 4: XID lookup.
|
|
185
|
+
const existing = this._participants.get(xidUr);
|
|
186
|
+
if (existing !== undefined) {
|
|
187
|
+
if (
|
|
188
|
+
publicKeysEqual(existing.publicKeys(), record.publicKeys()) &&
|
|
189
|
+
existing.petName() === record.petName()
|
|
190
|
+
) {
|
|
191
|
+
return AddOutcome.AlreadyPresent;
|
|
192
|
+
}
|
|
193
|
+
throw new Error("Participant already exists with a different pet name");
|
|
157
194
|
}
|
|
158
195
|
|
|
159
196
|
this._participants.set(xidUr, record);
|
|
@@ -287,26 +324,34 @@ export class Registry {
|
|
|
287
324
|
|
|
288
325
|
/**
|
|
289
326
|
* Serialize to JSON object.
|
|
327
|
+
*
|
|
328
|
+
* Mirrors Rust `Registry`'s field declaration order
|
|
329
|
+
* (`registry_impl.rs:8-14` — `owner, participants, groups`). JSON
|
|
330
|
+
* object member order is not semantically significant, but
|
|
331
|
+
* `serde_json::to_string_pretty` emits keys in declaration order,
|
|
332
|
+
* so for byte-equal `registry.json` (used by the integration tests
|
|
333
|
+
* as a string assertion) the TS port must match Rust's order.
|
|
334
|
+
* Empty `owner` is omitted via `Option::None` skip in Rust; we
|
|
335
|
+
* reproduce that by only setting the key when the owner exists.
|
|
290
336
|
*/
|
|
291
337
|
toJSON(): Record<string, unknown> {
|
|
338
|
+
const obj: Record<string, unknown> = {};
|
|
339
|
+
|
|
340
|
+
if (this._owner !== undefined) {
|
|
341
|
+
obj["owner"] = this._owner.toJSON();
|
|
342
|
+
}
|
|
343
|
+
|
|
292
344
|
const participants: Record<string, unknown> = {};
|
|
293
345
|
for (const [xidUr, record] of this._participants) {
|
|
294
346
|
participants[xidUr] = record.toJSON();
|
|
295
347
|
}
|
|
348
|
+
obj["participants"] = participants;
|
|
296
349
|
|
|
297
350
|
const groups: Record<string, unknown> = {};
|
|
298
351
|
for (const [aridHex, record] of this._groups) {
|
|
299
352
|
groups[aridHex] = record.toJSON();
|
|
300
353
|
}
|
|
301
|
-
|
|
302
|
-
const obj: Record<string, unknown> = {
|
|
303
|
-
groups,
|
|
304
|
-
participants,
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
if (this._owner !== undefined) {
|
|
308
|
-
obj["owner"] = this._owner.toJSON();
|
|
309
|
-
}
|
|
354
|
+
obj["groups"] = groups;
|
|
310
355
|
|
|
311
356
|
return obj;
|
|
312
357
|
}
|
|
@@ -366,3 +411,14 @@ export function resolveRegistryPath(registryArg: string | undefined, cwd: string
|
|
|
366
411
|
// Otherwise, treat as relative path
|
|
367
412
|
return path.resolve(cwd, registryArg);
|
|
368
413
|
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Structural equality on `PublicKeys`, mirroring Rust's
|
|
417
|
+
* `PartialEq::eq` derive (per-component comparison of the signing
|
|
418
|
+
* and encapsulation public keys).
|
|
419
|
+
*
|
|
420
|
+
* @internal
|
|
421
|
+
*/
|
|
422
|
+
function publicKeysEqual(a: PublicKeys, b: PublicKeys): boolean {
|
|
423
|
+
return a.equals(b);
|
|
424
|
+
}
|