@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.
- package/dist/BitShardSDK.d.ts +156 -0
- package/dist/BitShardSDK.d.ts.map +1 -0
- package/dist/BitShardSDK.js +350 -0
- package/dist/BitShardSDK.js.map +1 -0
- package/dist/chains/bitcoin/BitcoinChain.d.ts +6 -0
- package/dist/chains/bitcoin/BitcoinChain.d.ts.map +1 -0
- package/dist/chains/bitcoin/BitcoinChain.js +10 -0
- package/dist/chains/bitcoin/BitcoinChain.js.map +1 -0
- package/dist/chains/config.d.ts +5 -0
- package/dist/chains/config.d.ts.map +1 -0
- package/dist/chains/config.js +7 -0
- package/dist/chains/config.js.map +1 -0
- package/dist/chains/evm/EVMChain.d.ts +6 -0
- package/dist/chains/evm/EVMChain.d.ts.map +1 -0
- package/dist/chains/evm/EVMChain.js +10 -0
- package/dist/chains/evm/EVMChain.js.map +1 -0
- package/dist/core/DKLSParty.d.ts +132 -0
- package/dist/core/DKLSParty.d.ts.map +1 -0
- package/dist/core/DKLSParty.js +267 -0
- package/dist/core/DKLSParty.js.map +1 -0
- package/dist/core/DKLSService.d.ts +83 -0
- package/dist/core/DKLSService.d.ts.map +1 -0
- package/dist/core/DKLSService.js +325 -0
- package/dist/core/DKLSService.js.map +1 -0
- package/dist/core/ThresholdConfig.d.ts +76 -0
- package/dist/core/ThresholdConfig.d.ts.map +1 -0
- package/dist/core/ThresholdConfig.js +127 -0
- package/dist/core/ThresholdConfig.js.map +1 -0
- package/dist/core/types.d.ts +238 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/crypto/addresses.d.ts +82 -0
- package/dist/crypto/addresses.d.ts.map +1 -0
- package/dist/crypto/addresses.js +242 -0
- package/dist/crypto/addresses.js.map +1 -0
- package/dist/crypto/elliptic.d.ts +19 -0
- package/dist/crypto/elliptic.d.ts.map +1 -0
- package/dist/crypto/elliptic.js +114 -0
- package/dist/crypto/elliptic.js.map +1 -0
- package/dist/crypto/encoding.d.ts +111 -0
- package/dist/crypto/encoding.d.ts.map +1 -0
- package/dist/crypto/encoding.js +224 -0
- package/dist/crypto/encoding.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/protocols/keygen.d.ts +6 -0
- package/dist/protocols/keygen.d.ts.map +1 -0
- package/dist/protocols/keygen.js +12 -0
- package/dist/protocols/keygen.js.map +1 -0
- package/dist/protocols/presignature.d.ts +68 -0
- package/dist/protocols/presignature.d.ts.map +1 -0
- package/dist/protocols/presignature.js +147 -0
- package/dist/protocols/presignature.js.map +1 -0
- package/dist/protocols/refresh.d.ts +5 -0
- package/dist/protocols/refresh.d.ts.map +1 -0
- package/dist/protocols/refresh.js +10 -0
- package/dist/protocols/refresh.js.map +1 -0
- package/dist/protocols/signing.d.ts +7 -0
- package/dist/protocols/signing.d.ts.map +1 -0
- package/dist/protocols/signing.js +12 -0
- package/dist/protocols/signing.js.map +1 -0
- package/dist/rpc/RPCProvider.d.ts +6 -0
- package/dist/rpc/RPCProvider.d.ts.map +1 -0
- package/dist/rpc/RPCProvider.js +6 -0
- package/dist/rpc/RPCProvider.js.map +1 -0
- package/dist/rpc/methods.d.ts +5 -0
- package/dist/rpc/methods.d.ts.map +1 -0
- package/dist/rpc/methods.js +10 -0
- package/dist/rpc/methods.js.map +1 -0
- package/dist/websocket/coordinator.d.ts +6 -0
- package/dist/websocket/coordinator.d.ts.map +1 -0
- package/dist/websocket/coordinator.js +10 -0
- package/dist/websocket/coordinator.js.map +1 -0
- package/dist/websocket/messages.d.ts +9 -0
- package/dist/websocket/messages.d.ts.map +1 -0
- package/dist/websocket/messages.js +7 -0
- package/dist/websocket/messages.js.map +1 -0
- package/dist/websocket/session.d.ts +5 -0
- package/dist/websocket/session.d.ts.map +1 -0
- package/dist/websocket/session.js +7 -0
- package/dist/websocket/session.js.map +1 -0
- package/dist/wire/format.d.ts +8 -0
- package/dist/wire/format.d.ts.map +1 -0
- package/dist/wire/format.js +11 -0
- package/dist/wire/format.js.map +1 -0
- package/dist/wire/validation.d.ts +6 -0
- package/dist/wire/validation.d.ts.map +1 -0
- package/dist/wire/validation.js +13 -0
- package/dist/wire/validation.js.map +1 -0
- package/package.json +67 -0
- package/src/BitShardSDK.ts +428 -0
- package/src/chains/bitcoin/BitcoinChain.ts +7 -0
- package/src/chains/config.ts +7 -0
- package/src/chains/evm/EVMChain.ts +7 -0
- package/src/core/DKLSParty.ts +317 -0
- package/src/core/DKLSService.ts +426 -0
- package/src/core/ThresholdConfig.ts +159 -0
- package/src/core/types.ts +253 -0
- package/src/crypto/addresses.ts +282 -0
- package/src/crypto/elliptic.ts +133 -0
- package/src/crypto/encoding.ts +227 -0
- package/src/index.ts +40 -0
- package/src/protocols/keygen.ts +8 -0
- package/src/protocols/presignature.ts +196 -0
- package/src/protocols/refresh.ts +7 -0
- package/src/protocols/signing.ts +9 -0
- package/src/rpc/RPCProvider.ts +7 -0
- package/src/rpc/methods.ts +7 -0
- package/src/websocket/coordinator.ts +7 -0
- package/src/websocket/messages.ts +11 -0
- package/src/websocket/session.ts +7 -0
- package/src/wire/format.ts +10 -0
- package/src/wire/validation.ts +14 -0
- 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
|
+
}
|