@bitshard.io/bitshard-sdk 0.0.1

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 (117) hide show
  1. package/dist/BitShardSDK.d.ts +156 -0
  2. package/dist/BitShardSDK.d.ts.map +1 -0
  3. package/dist/BitShardSDK.js +350 -0
  4. package/dist/BitShardSDK.js.map +1 -0
  5. package/dist/chains/bitcoin/BitcoinChain.d.ts +6 -0
  6. package/dist/chains/bitcoin/BitcoinChain.d.ts.map +1 -0
  7. package/dist/chains/bitcoin/BitcoinChain.js +10 -0
  8. package/dist/chains/bitcoin/BitcoinChain.js.map +1 -0
  9. package/dist/chains/config.d.ts +5 -0
  10. package/dist/chains/config.d.ts.map +1 -0
  11. package/dist/chains/config.js +7 -0
  12. package/dist/chains/config.js.map +1 -0
  13. package/dist/chains/evm/EVMChain.d.ts +6 -0
  14. package/dist/chains/evm/EVMChain.d.ts.map +1 -0
  15. package/dist/chains/evm/EVMChain.js +10 -0
  16. package/dist/chains/evm/EVMChain.js.map +1 -0
  17. package/dist/core/DKLSParty.d.ts +132 -0
  18. package/dist/core/DKLSParty.d.ts.map +1 -0
  19. package/dist/core/DKLSParty.js +267 -0
  20. package/dist/core/DKLSParty.js.map +1 -0
  21. package/dist/core/DKLSService.d.ts +83 -0
  22. package/dist/core/DKLSService.d.ts.map +1 -0
  23. package/dist/core/DKLSService.js +325 -0
  24. package/dist/core/DKLSService.js.map +1 -0
  25. package/dist/core/ThresholdConfig.d.ts +76 -0
  26. package/dist/core/ThresholdConfig.d.ts.map +1 -0
  27. package/dist/core/ThresholdConfig.js +127 -0
  28. package/dist/core/ThresholdConfig.js.map +1 -0
  29. package/dist/core/types.d.ts +238 -0
  30. package/dist/core/types.d.ts.map +1 -0
  31. package/dist/core/types.js +3 -0
  32. package/dist/core/types.js.map +1 -0
  33. package/dist/crypto/addresses.d.ts +82 -0
  34. package/dist/crypto/addresses.d.ts.map +1 -0
  35. package/dist/crypto/addresses.js +242 -0
  36. package/dist/crypto/addresses.js.map +1 -0
  37. package/dist/crypto/elliptic.d.ts +19 -0
  38. package/dist/crypto/elliptic.d.ts.map +1 -0
  39. package/dist/crypto/elliptic.js +114 -0
  40. package/dist/crypto/elliptic.js.map +1 -0
  41. package/dist/crypto/encoding.d.ts +111 -0
  42. package/dist/crypto/encoding.d.ts.map +1 -0
  43. package/dist/crypto/encoding.js +224 -0
  44. package/dist/crypto/encoding.js.map +1 -0
  45. package/dist/index.d.ts +23 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +58 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/protocols/keygen.d.ts +6 -0
  50. package/dist/protocols/keygen.d.ts.map +1 -0
  51. package/dist/protocols/keygen.js +12 -0
  52. package/dist/protocols/keygen.js.map +1 -0
  53. package/dist/protocols/presignature.d.ts +68 -0
  54. package/dist/protocols/presignature.d.ts.map +1 -0
  55. package/dist/protocols/presignature.js +147 -0
  56. package/dist/protocols/presignature.js.map +1 -0
  57. package/dist/protocols/refresh.d.ts +5 -0
  58. package/dist/protocols/refresh.d.ts.map +1 -0
  59. package/dist/protocols/refresh.js +10 -0
  60. package/dist/protocols/refresh.js.map +1 -0
  61. package/dist/protocols/signing.d.ts +7 -0
  62. package/dist/protocols/signing.d.ts.map +1 -0
  63. package/dist/protocols/signing.js +12 -0
  64. package/dist/protocols/signing.js.map +1 -0
  65. package/dist/rpc/RPCProvider.d.ts +6 -0
  66. package/dist/rpc/RPCProvider.d.ts.map +1 -0
  67. package/dist/rpc/RPCProvider.js +6 -0
  68. package/dist/rpc/RPCProvider.js.map +1 -0
  69. package/dist/rpc/methods.d.ts +5 -0
  70. package/dist/rpc/methods.d.ts.map +1 -0
  71. package/dist/rpc/methods.js +10 -0
  72. package/dist/rpc/methods.js.map +1 -0
  73. package/dist/websocket/coordinator.d.ts +6 -0
  74. package/dist/websocket/coordinator.d.ts.map +1 -0
  75. package/dist/websocket/coordinator.js +10 -0
  76. package/dist/websocket/coordinator.js.map +1 -0
  77. package/dist/websocket/messages.d.ts +9 -0
  78. package/dist/websocket/messages.d.ts.map +1 -0
  79. package/dist/websocket/messages.js +7 -0
  80. package/dist/websocket/messages.js.map +1 -0
  81. package/dist/websocket/session.d.ts +5 -0
  82. package/dist/websocket/session.d.ts.map +1 -0
  83. package/dist/websocket/session.js +7 -0
  84. package/dist/websocket/session.js.map +1 -0
  85. package/dist/wire/format.d.ts +8 -0
  86. package/dist/wire/format.d.ts.map +1 -0
  87. package/dist/wire/format.js +11 -0
  88. package/dist/wire/format.js.map +1 -0
  89. package/dist/wire/validation.d.ts +6 -0
  90. package/dist/wire/validation.d.ts.map +1 -0
  91. package/dist/wire/validation.js +13 -0
  92. package/dist/wire/validation.js.map +1 -0
  93. package/package.json +67 -0
  94. package/src/BitShardSDK.ts +428 -0
  95. package/src/chains/bitcoin/BitcoinChain.ts +7 -0
  96. package/src/chains/config.ts +7 -0
  97. package/src/chains/evm/EVMChain.ts +7 -0
  98. package/src/core/DKLSParty.ts +317 -0
  99. package/src/core/DKLSService.ts +426 -0
  100. package/src/core/ThresholdConfig.ts +159 -0
  101. package/src/core/types.ts +253 -0
  102. package/src/crypto/addresses.ts +282 -0
  103. package/src/crypto/elliptic.ts +133 -0
  104. package/src/crypto/encoding.ts +227 -0
  105. package/src/index.ts +40 -0
  106. package/src/protocols/keygen.ts +8 -0
  107. package/src/protocols/presignature.ts +196 -0
  108. package/src/protocols/refresh.ts +7 -0
  109. package/src/protocols/signing.ts +9 -0
  110. package/src/rpc/RPCProvider.ts +7 -0
  111. package/src/rpc/methods.ts +7 -0
  112. package/src/websocket/coordinator.ts +7 -0
  113. package/src/websocket/messages.ts +11 -0
  114. package/src/websocket/session.ts +7 -0
  115. package/src/wire/format.ts +10 -0
  116. package/src/wire/validation.ts +14 -0
  117. package/test-sdk.js +234 -0
@@ -0,0 +1,426 @@
1
+ import crypto from 'crypto';
2
+ import {
3
+ KeygenSession,
4
+ SignSession,
5
+ Keyshare,
6
+ Message
7
+ } from '@silencelaboratories/dkls-wasm-ll-node';
8
+ import type {
9
+ DKGResult,
10
+ FlexibleDKGResult,
11
+ SignatureResult,
12
+ MPCSession,
13
+ PartyKeyshare,
14
+ BlockchainAddresses
15
+ } from './types';
16
+ import { deriveAddressesFromBytes } from '../crypto/addresses';
17
+
18
+ /**
19
+ * Core DKLS protocol service for distributed key generation and threshold signatures
20
+ */
21
+ export class DKLSService {
22
+ private sessions: Map<string, MPCSession> = new Map();
23
+
24
+ /**
25
+ * Wire format for DKLS Message over WS
26
+ */
27
+ static toWireMessage(msg: Message): { from_id: number; to_id?: number; payload: string } {
28
+ return {
29
+ from_id: msg.from_id,
30
+ to_id: msg.to_id,
31
+ payload: Buffer.from(msg.payload).toString('base64')
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Reconstruct DKLS Message from wire payload
37
+ */
38
+ static fromWireMessage(w: { from_id: number; to_id?: number; payload: string }): Message {
39
+ const bytes = new Uint8Array(Buffer.from(w.payload, 'base64'));
40
+ // Note: Message constructor requires payload, from, and optional to
41
+ return new Message(bytes, w.from_id, w.to_id);
42
+ }
43
+
44
+ /**
45
+ * Generate DKG using DKLS protocol with flexible threshold
46
+ * @param totalParties Total number of parties (n)
47
+ * @param threshold Threshold required for signing (t)
48
+ * @param partyIds Optional array of party IDs (defaults to 0..n-1)
49
+ */
50
+ async generateDKG(
51
+ totalParties: number = 3,
52
+ threshold: number = 2,
53
+ partyIds?: number[]
54
+ ): Promise<FlexibleDKGResult> {
55
+ try {
56
+ console.log(`🔐 Starting DKLS DKG for ${threshold}-of-${totalParties} threshold scheme...`);
57
+
58
+ // Validate parameters
59
+ if (threshold > totalParties) {
60
+ throw new Error('Threshold cannot be greater than total parties');
61
+ }
62
+ if (threshold < 2) {
63
+ throw new Error('Threshold must be at least 2');
64
+ }
65
+
66
+ // Use provided party IDs or default to 0..n-1
67
+ const ids = partyIds ?? Array.from({ length: totalParties }, (_, i) => i);
68
+ if (ids.length !== totalParties) {
69
+ throw new Error('Party IDs array length must match totalParties');
70
+ }
71
+
72
+ // Create KeygenSession for each party
73
+ const parties: KeygenSession[] = [];
74
+ for (let i = 0; i < totalParties; i++) {
75
+ parties.push(new KeygenSession(totalParties, threshold, ids[i]!));
76
+ }
77
+
78
+ // Execute DKG protocol
79
+ const keyshares = this.executeDKG(parties);
80
+
81
+ // Get public key from first keyshare
82
+ const publicKeyBytes = keyshares[0]!.publicKey;
83
+ const publicKeyHex = Buffer.from(publicKeyBytes).toString('hex');
84
+
85
+ // Generate addresses for different blockchains
86
+ const addresses = this.deriveAllAddresses(publicKeyBytes);
87
+
88
+ // Create party keyshares with commitments
89
+ const partyKeyshares: PartyKeyshare[] = keyshares.map((ks, idx) => {
90
+ const shareBytes = ks.toBytes();
91
+ const shareData = Buffer.from(shareBytes).toString('base64');
92
+ const commitment = crypto.createHash('sha256')
93
+ .update(shareData)
94
+ .digest('hex');
95
+
96
+ return {
97
+ partyId: ids[idx]!,
98
+ share: shareData,
99
+ commitment
100
+ };
101
+ });
102
+
103
+ // Generate DKG commitment
104
+ const dkgCommitment = crypto.createHash('sha256')
105
+ .update(publicKeyHex)
106
+ .digest('hex');
107
+
108
+ console.log(`✅ DKLS DKG completed successfully`);
109
+ console.log(`📍 Ethereum address: ${addresses.ethereum}`);
110
+ console.log(`📍 Bitcoin address: ${addresses.bitcoin}`);
111
+
112
+ return {
113
+ totalParties,
114
+ threshold,
115
+ publicKey: '0x' + publicKeyHex,
116
+ dkgCommitment,
117
+ keyshares: partyKeyshares,
118
+ addresses
119
+ };
120
+ } catch (error) {
121
+ console.error('❌ DKLS DKG generation error:', error);
122
+ throw new Error(`Failed to generate DKG: ${(error as Error).message}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Legacy method for backward compatibility - generates standard 2-of-3 setup
128
+ */
129
+ async generateDKGCommitments(): Promise<DKGResult> {
130
+ const result = await this.generateDKG(3, 2);
131
+
132
+ // Map to legacy format
133
+ return {
134
+ dkgCommitment: result.dkgCommitment,
135
+ serverShareCommitment: result.keyshares[0]!.commitment,
136
+ masterPublicKey: result.publicKey,
137
+ serverShare: result.keyshares[0]!.share,
138
+ backupShare: result.keyshares[1]!.share,
139
+ mobileShare: result.keyshares[2]!.share,
140
+ publicKey: result.publicKey,
141
+ ethAddress: result.addresses.ethereum,
142
+ btcAddress: result.addresses.bitcoin,
143
+ cosmosAddress: result.addresses.cosmos,
144
+ bnbAddress: result.addresses.bnb,
145
+ polygonAddress: result.addresses.polygon,
146
+ avaxAddress: result.addresses.avalanche,
147
+ arbAddress: result.addresses.arbitrum
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Execute the DKG protocol rounds
153
+ */
154
+ protected executeDKG(parties: KeygenSession[]): Keyshare[] {
155
+ // Round 1: Create first messages and capture each session's self id
156
+ const msg1: Message[] = parties.map(p => p.createFirstMessage());
157
+ const selfIds: number[] = msg1.map(m => m.from_id);
158
+
159
+ // Broadcast round 1 messages (exclude own by selfId)
160
+ const msg2: Message[] = parties.flatMap((p, idx) =>
161
+ p.handleMessages(this.filterMessages(msg1, selfIds[idx]!))
162
+ );
163
+
164
+ // Calculate chain code commitments and index them by actual party id
165
+ const commitmentsRaw = parties.map(p => p.calculateChainCodeCommitment());
166
+ const commitments: any[] = new Array(parties.length);
167
+ selfIds.forEach((id, idx) => {
168
+ commitments[id] = commitmentsRaw[idx];
169
+ });
170
+
171
+ // Handle P2P messages (round 2) using selfId mapping
172
+ const msg3: Message[] = parties.flatMap((p, idx) =>
173
+ p.handleMessages(this.selectMessages(msg2, selfIds[idx]!))
174
+ );
175
+
176
+ // Handle P2P messages with commitments (round 3)
177
+ const msg4: Message[] = parties.flatMap((p, idx) =>
178
+ p.handleMessages(this.selectMessages(msg3, selfIds[idx]!), commitments)
179
+ );
180
+
181
+ // Handle final broadcast messages
182
+ parties.forEach((p, idx) =>
183
+ p.handleMessages(this.filterMessages(msg4, selfIds[idx]!))
184
+ );
185
+
186
+ // Extract keyshares
187
+ return parties.map(p => p.keyshare());
188
+ }
189
+
190
+ /**
191
+ * Sign a message using threshold signatures
192
+ * @param messageHash The message hash to sign
193
+ * @param keyshares Array of keyshares (at least threshold number required)
194
+ * @param threshold Threshold value (must match keyshare generation)
195
+ * @param publicKey Optional public key for v calculation
196
+ */
197
+ async signMessage(
198
+ messageHash: Uint8Array,
199
+ keyshares: Keyshare[],
200
+ threshold: number,
201
+ publicKey?: string
202
+ ): Promise<SignatureResult> {
203
+ try {
204
+ console.log(`🖊️ Starting DKLS signature generation (${threshold}-of-${keyshares.length})...`);
205
+
206
+ if (keyshares.length < threshold) {
207
+ throw new Error(`Insufficient keyshares: need ${threshold}, got ${keyshares.length}`);
208
+ }
209
+
210
+ // Clone keyshares since SignSession consumes them
211
+ const signingShares = keyshares.slice(0, threshold).map(ks => {
212
+ const bytes = ks.toBytes();
213
+ return Keyshare.fromBytes(bytes);
214
+ });
215
+
216
+ // Create sign sessions
217
+ // NOTE: DKLS library currently only supports "m" path
218
+ const parties: SignSession[] = signingShares.map(ks =>
219
+ new SignSession(ks, "m")
220
+ );
221
+
222
+ // Round 1: Create first messages and capture self ids
223
+ const msg1: Message[] = parties.map(p => p.createFirstMessage());
224
+ const selfIds: number[] = msg1.map(m => m.from_id);
225
+
226
+ // Broadcast the first message to all parties
227
+ const msg2: Message[] = parties.flatMap((p, idx) =>
228
+ p.handleMessages(this.filterMessages(msg1, selfIds[idx]!))
229
+ );
230
+
231
+ // Handle P2P messages (round 2)
232
+ const msg3: Message[] = parties.flatMap((p, idx) =>
233
+ p.handleMessages(this.selectMessages(msg2, selfIds[idx]!))
234
+ );
235
+
236
+ // Complete pre-signature (round 3)
237
+ parties.forEach((p, idx) =>
238
+ p.handleMessages(this.selectMessages(msg3, selfIds[idx]!))
239
+ );
240
+
241
+ // WARNING: PRE-SIGNATURES MUST NOT BE REUSED
242
+ // Generate final signature with message hash
243
+ const msg4: Message[] = parties.map(p => p.lastMessage(messageHash));
244
+
245
+ // Combine to produce signature
246
+ const signatures = parties.map((p, idx) =>
247
+ p.combine(this.filterMessages(msg4, selfIds[idx]!))
248
+ );
249
+
250
+ // All parties should produce the same signature
251
+ const signatureComponents = signatures[0]!;
252
+ const [r_bytes, s_bytes] = signatureComponents;
253
+
254
+ const r = Buffer.from(r_bytes).toString('hex');
255
+ const s = Buffer.from(s_bytes).toString('hex');
256
+ const signatureHex = r + s;
257
+
258
+ // Calculate v value for Ethereum signature recovery
259
+ let v = 27; // Default value
260
+
261
+ if (publicKey) {
262
+ // Try to recover with both v values and see which matches our public key
263
+ const ethers = require('ethers');
264
+ const { deriveEthereumAddress } = require('../crypto/addresses');
265
+
266
+ const expectedAddress = deriveEthereumAddress(publicKey);
267
+ const msgHashHex = '0x' + Buffer.from(messageHash).toString('hex');
268
+
269
+ for (const testV of [27, 28]) {
270
+ try {
271
+ const sig = { r: '0x' + r, s: '0x' + s, v: testV };
272
+ const recoveredAddress = ethers.utils.recoverAddress(msgHashHex, sig);
273
+
274
+ if (recoveredAddress.toLowerCase() === expectedAddress.toLowerCase()) {
275
+ v = testV;
276
+ break;
277
+ }
278
+ } catch (e) {
279
+ // Try next v value
280
+ }
281
+ }
282
+ }
283
+
284
+ console.log('✅ DKLS signature generated successfully');
285
+
286
+ return {
287
+ signature: '0x' + signatureHex,
288
+ r: '0x' + r,
289
+ s: '0x' + s,
290
+ v
291
+ };
292
+ } catch (error) {
293
+ console.error('❌ DKLS signing error:', error);
294
+ throw new Error(`Failed to sign message: ${(error as Error).message}`);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Legacy signing method for backward compatibility
300
+ */
301
+ async signMessageLegacy(
302
+ messageHash: Uint8Array,
303
+ serverShareData: string,
304
+ backupShareData: string,
305
+ _chain: 'ethereum' | 'bitcoin' | 'solana' = 'ethereum'
306
+ ): Promise<SignatureResult> {
307
+ // Deserialize keyshares
308
+ const serverKeyshare = Keyshare.fromBytes(
309
+ new Uint8Array(Buffer.from(serverShareData, 'base64'))
310
+ );
311
+ const backupKeyshare = Keyshare.fromBytes(
312
+ new Uint8Array(Buffer.from(backupShareData, 'base64'))
313
+ );
314
+
315
+ return this.signMessage(messageHash, [serverKeyshare, backupKeyshare], 2);
316
+ }
317
+
318
+ /**
319
+ * Refresh shares (key rotation) while preserving the public key
320
+ */
321
+ async refreshShares(existingShares: Keyshare[]): Promise<{
322
+ newShares: Keyshare[];
323
+ publicKey: string;
324
+ }> {
325
+ try {
326
+ console.log('🔄 Starting DKLS key rotation...');
327
+ console.log(` ${existingShares.length} parties participating in rotation`);
328
+
329
+ // Create rotation sessions
330
+ const rotationParties: KeygenSession[] = existingShares.map(share =>
331
+ KeygenSession.initKeyRotation(share)
332
+ );
333
+
334
+ console.log('Executing key generation protocol for rotation...');
335
+
336
+ // Execute DKG protocol for rotation
337
+ const newKeyshares = this.executeDKG(rotationParties);
338
+
339
+ // Verify all parties got keyshares
340
+ if (!newKeyshares.every(ks => ks !== null)) {
341
+ throw new Error('Key rotation failed: Not all parties generated new shares');
342
+ }
343
+
344
+ // Get public key (should be same as before)
345
+ const publicKeyHex = Buffer.from(newKeyshares[0]!.publicKey).toString('hex');
346
+ const originalPubKey = Buffer.from(existingShares[0]!.publicKey).toString('hex');
347
+
348
+ console.log('Original public key:', originalPubKey.substring(0, 20) + '...');
349
+ console.log('Rotated public key:', publicKeyHex.substring(0, 20) + '...');
350
+
351
+ // Finalize rotation
352
+ console.log('Finalizing key rotation...');
353
+ for (let i = 0; i < newKeyshares.length; i++) {
354
+ newKeyshares[i]!.finishKeyRotation(existingShares[i]!);
355
+ console.log(` Party ${i} rotation finalized`);
356
+ }
357
+
358
+ console.log('✅ DKLS key rotation completed successfully');
359
+
360
+ if (publicKeyHex === originalPubKey) {
361
+ console.log(' ✅ Public key preserved: addresses remain unchanged');
362
+ } else {
363
+ console.log(' ⚠️ Public key changed (unexpected for rotation)');
364
+ }
365
+
366
+ return {
367
+ newShares: newKeyshares,
368
+ publicKey: '0x' + publicKeyHex
369
+ };
370
+ } catch (error) {
371
+ console.error('❌ DKLS key rotation error:', error);
372
+ throw new Error(`Failed to refresh shares: ${(error as Error).message}`);
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Filter messages for broadcast (exclude own messages)
378
+ */
379
+ protected filterMessages(msgs: Message[], party: number): Message[] {
380
+ return msgs.filter((m) => m.from_id !== party).map(m => m.clone());
381
+ }
382
+
383
+ /**
384
+ * Select P2P messages for a specific party
385
+ */
386
+ protected selectMessages(msgs: Message[], party: number): Message[] {
387
+ return msgs.filter((m) => m.to_id === party).map(m => m.clone());
388
+ }
389
+
390
+ /**
391
+ * Derive all blockchain addresses from public key
392
+ */
393
+ protected deriveAllAddresses(publicKey: Uint8Array): BlockchainAddresses {
394
+ return deriveAddressesFromBytes(publicKey);
395
+ }
396
+
397
+ /**
398
+ * Get active session by ID
399
+ */
400
+ getSession(sessionId: string): MPCSession | undefined {
401
+ return this.sessions.get(sessionId);
402
+ }
403
+
404
+ /**
405
+ * Create a new MPC session
406
+ */
407
+ createSession(session: Omit<MPCSession, 'createdAt' | 'updatedAt'>): MPCSession {
408
+ const fullSession: MPCSession = {
409
+ ...session,
410
+ createdAt: new Date(),
411
+ updatedAt: new Date()
412
+ };
413
+ this.sessions.set(session.id, fullSession);
414
+ return fullSession;
415
+ }
416
+
417
+ /**
418
+ * Update session status
419
+ */
420
+ updateSession(sessionId: string, updates: Partial<MPCSession>): void {
421
+ const session = this.sessions.get(sessionId);
422
+ if (session) {
423
+ Object.assign(session, updates, { updatedAt: new Date() });
424
+ }
425
+ }
426
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Configuration for threshold signature schemes
3
+ */
4
+ export class ThresholdConfig {
5
+ /**
6
+ * Total number of parties
7
+ */
8
+ public readonly totalParties: number;
9
+
10
+ /**
11
+ * Threshold required for signing (t-of-n)
12
+ */
13
+ public readonly threshold: number;
14
+
15
+ /**
16
+ * Party identifiers (optional, defaults to 0..n-1)
17
+ */
18
+ public readonly partyIds: number[];
19
+
20
+ /**
21
+ * Create a new threshold configuration
22
+ * @param totalParties Total number of parties (n)
23
+ * @param threshold Threshold required for signing (t)
24
+ * @param partyIds Optional array of party IDs
25
+ */
26
+ constructor(totalParties: number, threshold: number, partyIds?: number[]) {
27
+ // Validate parameters
28
+ if (totalParties < 2) {
29
+ throw new Error('Total parties must be at least 2');
30
+ }
31
+
32
+ if (threshold < 2) {
33
+ throw new Error('Threshold must be at least 2');
34
+ }
35
+
36
+ if (threshold > totalParties) {
37
+ throw new Error('Threshold cannot be greater than total parties');
38
+ }
39
+
40
+ this.totalParties = totalParties;
41
+ this.threshold = threshold;
42
+
43
+ // Use provided party IDs or default to sequential
44
+ if (partyIds) {
45
+ if (partyIds.length !== totalParties) {
46
+ throw new Error('Party IDs array length must match totalParties');
47
+ }
48
+ // Check for duplicates
49
+ const uniqueIds = new Set(partyIds);
50
+ if (uniqueIds.size !== partyIds.length) {
51
+ throw new Error('Party IDs must be unique');
52
+ }
53
+ this.partyIds = [...partyIds];
54
+ } else {
55
+ this.partyIds = Array.from({ length: totalParties }, (_, i) => i);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Create a standard 2-of-3 configuration
61
+ */
62
+ static createStandard(): ThresholdConfig {
63
+ return new ThresholdConfig(3, 2);
64
+ }
65
+
66
+ /**
67
+ * Create a 3-of-4 configuration with additional signer
68
+ */
69
+ static createEnhanced(): ThresholdConfig {
70
+ return new ThresholdConfig(4, 3);
71
+ }
72
+
73
+ /**
74
+ * Create an enterprise configuration
75
+ * @param totalParties Total number of parties (5-7 recommended)
76
+ * @param threshold Threshold (3-4 recommended)
77
+ */
78
+ static createEnterprise(totalParties: number = 5, threshold: number = 3): ThresholdConfig {
79
+ if (totalParties < 5 || totalParties > 20) {
80
+ throw new Error('Enterprise configuration should have 5-20 parties');
81
+ }
82
+ return new ThresholdConfig(totalParties, threshold);
83
+ }
84
+
85
+ /**
86
+ * Check if a set of party IDs can sign
87
+ * @param signingParties Array of party IDs that want to sign
88
+ * @returns True if the parties can meet the threshold
89
+ */
90
+ canSign(signingParties: number[]): boolean {
91
+ // Check if all signing parties are valid
92
+ const validParties = signingParties.filter(id => this.partyIds.includes(id));
93
+
94
+ // Check if we have enough valid parties
95
+ return validParties.length >= this.threshold;
96
+ }
97
+
98
+ /**
99
+ * Get the minimum number of additional parties needed to sign
100
+ * @param currentParties Array of party IDs currently available
101
+ * @returns Number of additional parties needed, or 0 if threshold is met
102
+ */
103
+ additionalPartiesNeeded(currentParties: number[]): number {
104
+ const validParties = currentParties.filter(id => this.partyIds.includes(id));
105
+ const needed = this.threshold - validParties.length;
106
+ return Math.max(0, needed);
107
+ }
108
+
109
+ /**
110
+ * Get a subset of parties for signing
111
+ * @param availableParties Array of available party IDs
112
+ * @returns Array of party IDs to use for signing (threshold size)
113
+ */
114
+ selectSigningParties(availableParties: number[]): number[] {
115
+ const validParties = availableParties.filter(id => this.partyIds.includes(id));
116
+
117
+ if (validParties.length < this.threshold) {
118
+ throw new Error(
119
+ `Insufficient parties: need ${this.threshold}, have ${validParties.length}`
120
+ );
121
+ }
122
+
123
+ // Return first threshold number of valid parties
124
+ return validParties.slice(0, this.threshold);
125
+ }
126
+
127
+ /**
128
+ * Validate party ID
129
+ * @param partyId Party ID to validate
130
+ * @returns True if party ID is valid
131
+ */
132
+ isValidParty(partyId: number): boolean {
133
+ return this.partyIds.includes(partyId);
134
+ }
135
+
136
+ /**
137
+ * Get configuration summary
138
+ */
139
+ toString(): string {
140
+ return `${this.threshold}-of-${this.totalParties} threshold scheme`;
141
+ }
142
+
143
+ /**
144
+ * Get detailed configuration info
145
+ */
146
+ toJSON(): {
147
+ totalParties: number;
148
+ threshold: number;
149
+ partyIds: number[];
150
+ scheme: string;
151
+ } {
152
+ return {
153
+ totalParties: this.totalParties,
154
+ threshold: this.threshold,
155
+ partyIds: this.partyIds,
156
+ scheme: this.toString()
157
+ };
158
+ }
159
+ }