@sip-protocol/sdk 0.3.2 → 0.5.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/browser.d.mts +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2881 -295
- package/dist/browser.mjs +62 -2
- package/dist/chunk-AOZIY3GU.mjs +12995 -0
- package/dist/chunk-BCLIX5T2.mjs +12940 -0
- package/dist/chunk-DMHBKRWV.mjs +14712 -0
- package/dist/chunk-FKXPHKYD.mjs +12955 -0
- package/dist/chunk-HGU6HZRC.mjs +231 -0
- package/dist/chunk-J4Q4NJ2U.mjs +13544 -0
- package/dist/chunk-OPQ2GQIO.mjs +13013 -0
- package/dist/chunk-W2B7T6WU.mjs +14714 -0
- package/dist/index-5jAdWMA-.d.ts +8973 -0
- package/dist/index-B9Vkpaao.d.mts +8973 -0
- package/dist/index-BcWNakUD.d.ts +7990 -0
- package/dist/index-BsKY3Hr0.d.mts +7990 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2852 -266
- package/dist/index.mjs +62 -2
- package/dist/proofs/noir.mjs +1 -1
- package/package.json +2 -1
- package/src/adapters/near-intents.ts +8 -0
- package/src/bitcoin/index.ts +51 -0
- package/src/bitcoin/silent-payments.ts +865 -0
- package/src/bitcoin/taproot.ts +590 -0
- package/src/compliance/compliance-manager.ts +87 -0
- package/src/compliance/conditional-threshold.ts +379 -0
- package/src/compliance/conditional.ts +382 -0
- package/src/compliance/derivation.ts +489 -0
- package/src/compliance/index.ts +50 -8
- package/src/compliance/pdf.ts +365 -0
- package/src/compliance/reports.ts +644 -0
- package/src/compliance/threshold.ts +529 -0
- package/src/compliance/types.ts +223 -0
- package/src/cosmos/ibc-stealth.ts +825 -0
- package/src/cosmos/index.ts +83 -0
- package/src/cosmos/stealth.ts +487 -0
- package/src/errors.ts +8 -0
- package/src/index.ts +80 -1
- package/src/move/aptos.ts +369 -0
- package/src/move/index.ts +35 -0
- package/src/move/sui.ts +367 -0
- package/src/oracle/types.ts +8 -0
- package/src/settlement/backends/direct-chain.ts +8 -0
- package/src/stealth.ts +3 -3
- package/src/validation.ts +42 -1
- package/src/wallet/aptos/adapter.ts +422 -0
- package/src/wallet/aptos/index.ts +10 -0
- package/src/wallet/aptos/mock.ts +410 -0
- package/src/wallet/aptos/types.ts +278 -0
- package/src/wallet/bitcoin/adapter.ts +470 -0
- package/src/wallet/bitcoin/index.ts +38 -0
- package/src/wallet/bitcoin/mock.ts +516 -0
- package/src/wallet/bitcoin/types.ts +274 -0
- package/src/wallet/cosmos/adapter.ts +484 -0
- package/src/wallet/cosmos/index.ts +63 -0
- package/src/wallet/cosmos/mock.ts +596 -0
- package/src/wallet/cosmos/types.ts +462 -0
- package/src/wallet/index.ts +127 -0
- package/src/wallet/sui/adapter.ts +471 -0
- package/src/wallet/sui/index.ts +10 -0
- package/src/wallet/sui/mock.ts +439 -0
- package/src/wallet/sui/types.ts +245 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Threshold Viewing Keys using Shamir's Secret Sharing
|
|
3
|
+
*
|
|
4
|
+
* Enables N-of-M viewing key disclosure for multi-party authorization.
|
|
5
|
+
* Use case: Multiple board members must agree to reveal transaction details.
|
|
6
|
+
*
|
|
7
|
+
* ## Security Properties
|
|
8
|
+
* - Information-theoretic security (< N shares reveal nothing)
|
|
9
|
+
* - Share verification without revealing the secret
|
|
10
|
+
* - Cryptographically binding commitments
|
|
11
|
+
*
|
|
12
|
+
* ## Implementation Details
|
|
13
|
+
* - Uses prime field arithmetic (GF(p) where p = 2^256 - 189)
|
|
14
|
+
* - Polynomial interpolation via Lagrange coefficients
|
|
15
|
+
* - SHA-256 commitments for share verification
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { ThresholdViewingKey, generateViewingKey } from '@sip-protocol/sdk'
|
|
20
|
+
*
|
|
21
|
+
* // Generate a viewing key
|
|
22
|
+
* const viewingKey = generateViewingKey()
|
|
23
|
+
*
|
|
24
|
+
* // Create 3-of-5 threshold shares
|
|
25
|
+
* const threshold = ThresholdViewingKey.create({
|
|
26
|
+
* threshold: 3,
|
|
27
|
+
* totalShares: 5,
|
|
28
|
+
* viewingKey: viewingKey.key,
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* // Distribute shares to board members
|
|
32
|
+
* console.log(threshold.shares) // ['share1', 'share2', ...]
|
|
33
|
+
*
|
|
34
|
+
* // Verify a share without revealing the key
|
|
35
|
+
* const isValid = ThresholdViewingKey.verifyShare(
|
|
36
|
+
* threshold.shares[0],
|
|
37
|
+
* threshold.commitment
|
|
38
|
+
* )
|
|
39
|
+
*
|
|
40
|
+
* // Reconstruct with 3 shares
|
|
41
|
+
* const reconstructed = ThresholdViewingKey.reconstruct([
|
|
42
|
+
* threshold.shares[0],
|
|
43
|
+
* threshold.shares[2],
|
|
44
|
+
* threshold.shares[4],
|
|
45
|
+
* ])
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @module compliance/threshold
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
52
|
+
import { bytesToHex, hexToBytes, randomBytes } from '@noble/hashes/utils'
|
|
53
|
+
import type { HexString } from '@sip-protocol/types'
|
|
54
|
+
import { ValidationError, CryptoError, ErrorCode } from '../errors'
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Large prime for finite field arithmetic (2^256 - 189)
|
|
58
|
+
* This is a safe prime suitable for cryptographic operations
|
|
59
|
+
*/
|
|
60
|
+
const FIELD_PRIME = 2n ** 256n - 189n
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Threshold shares configuration
|
|
64
|
+
*/
|
|
65
|
+
export interface ThresholdShares {
|
|
66
|
+
/** Array of encoded shares */
|
|
67
|
+
shares: string[]
|
|
68
|
+
/** Commitment hash for share verification */
|
|
69
|
+
commitment: string
|
|
70
|
+
/** Threshold (N) - minimum shares needed */
|
|
71
|
+
threshold: number
|
|
72
|
+
/** Total shares (M) */
|
|
73
|
+
totalShares: number
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Encoded share format: "x:y:len:commitment"
|
|
78
|
+
* where len is the original viewing key length (without 0x prefix)
|
|
79
|
+
*/
|
|
80
|
+
interface DecodedShare {
|
|
81
|
+
x: bigint // Share index (1-based)
|
|
82
|
+
y: bigint // Share value
|
|
83
|
+
keyLength: number // Original viewing key hex length (without 0x)
|
|
84
|
+
commitment: string // Commitment hash
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Threshold Viewing Key implementation using Shamir's Secret Sharing
|
|
89
|
+
*
|
|
90
|
+
* Allows splitting a viewing key into N-of-M shares where any N shares
|
|
91
|
+
* can reconstruct the original key, but fewer than N shares reveal nothing.
|
|
92
|
+
*/
|
|
93
|
+
export class ThresholdViewingKey {
|
|
94
|
+
/**
|
|
95
|
+
* Create threshold shares from a viewing key
|
|
96
|
+
*
|
|
97
|
+
* @param params - Configuration parameters
|
|
98
|
+
* @returns Threshold shares with commitment
|
|
99
|
+
* @throws ValidationError if parameters are invalid
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const threshold = ThresholdViewingKey.create({
|
|
104
|
+
* threshold: 3,
|
|
105
|
+
* totalShares: 5,
|
|
106
|
+
* viewingKey: '0xabc123...',
|
|
107
|
+
* })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
static create(params: {
|
|
111
|
+
threshold: number
|
|
112
|
+
totalShares: number
|
|
113
|
+
viewingKey: HexString
|
|
114
|
+
}): ThresholdShares {
|
|
115
|
+
// Validate parameters
|
|
116
|
+
this.validateParams(params.threshold, params.totalShares)
|
|
117
|
+
this.validateViewingKey(params.viewingKey)
|
|
118
|
+
|
|
119
|
+
// Convert viewing key to secret (bigint)
|
|
120
|
+
const secret = this.viewingKeyToSecret(params.viewingKey)
|
|
121
|
+
|
|
122
|
+
// Store original key length (without 0x prefix)
|
|
123
|
+
const keyLength = params.viewingKey.slice(2).length
|
|
124
|
+
|
|
125
|
+
// Generate random polynomial coefficients
|
|
126
|
+
const coefficients = this.generateCoefficients(params.threshold, secret)
|
|
127
|
+
|
|
128
|
+
// Create commitment hash
|
|
129
|
+
const commitment = this.createCommitment(secret, coefficients)
|
|
130
|
+
|
|
131
|
+
// Generate shares by evaluating polynomial at different points
|
|
132
|
+
const shares: string[] = []
|
|
133
|
+
for (let i = 1; i <= params.totalShares; i++) {
|
|
134
|
+
const x = BigInt(i)
|
|
135
|
+
const y = this.evaluatePolynomial(coefficients, x)
|
|
136
|
+
shares.push(this.encodeShare(x, y, keyLength, commitment))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
shares,
|
|
141
|
+
commitment,
|
|
142
|
+
threshold: params.threshold,
|
|
143
|
+
totalShares: params.totalShares,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Reconstruct viewing key from threshold shares
|
|
149
|
+
*
|
|
150
|
+
* @param shares - Array of encoded shares (must be >= threshold)
|
|
151
|
+
* @returns Reconstructed viewing key
|
|
152
|
+
* @throws ValidationError if insufficient or invalid shares
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const viewingKey = ThresholdViewingKey.reconstruct([
|
|
157
|
+
* 'share1',
|
|
158
|
+
* 'share2',
|
|
159
|
+
* 'share3',
|
|
160
|
+
* ])
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
static reconstruct(shares: string[]): HexString {
|
|
164
|
+
// Validate shares
|
|
165
|
+
if (!shares || shares.length === 0) {
|
|
166
|
+
throw new ValidationError(
|
|
167
|
+
'at least one share is required',
|
|
168
|
+
'shares',
|
|
169
|
+
{ received: shares },
|
|
170
|
+
ErrorCode.MISSING_REQUIRED
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Decode shares
|
|
175
|
+
const decodedShares = shares.map((s) => this.decodeShare(s))
|
|
176
|
+
|
|
177
|
+
// Verify all shares have the same commitment and keyLength
|
|
178
|
+
const commitment = decodedShares[0].commitment
|
|
179
|
+
const keyLength = decodedShares[0].keyLength
|
|
180
|
+
for (const share of decodedShares) {
|
|
181
|
+
if (share.commitment !== commitment) {
|
|
182
|
+
throw new ValidationError(
|
|
183
|
+
'shares must all have the same commitment',
|
|
184
|
+
'shares',
|
|
185
|
+
{ commitment },
|
|
186
|
+
ErrorCode.INVALID_SHARE
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
if (share.keyLength !== keyLength) {
|
|
190
|
+
throw new ValidationError(
|
|
191
|
+
'shares must all have the same key length',
|
|
192
|
+
'shares',
|
|
193
|
+
{ keyLength },
|
|
194
|
+
ErrorCode.INVALID_SHARE
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check for duplicate x-coordinates
|
|
200
|
+
const xCoords = new Set(decodedShares.map((s) => s.x.toString()))
|
|
201
|
+
if (xCoords.size !== decodedShares.length) {
|
|
202
|
+
throw new ValidationError(
|
|
203
|
+
'shares must have unique x-coordinates',
|
|
204
|
+
'shares',
|
|
205
|
+
undefined,
|
|
206
|
+
ErrorCode.INVALID_SHARE
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Reconstruct secret using Lagrange interpolation
|
|
211
|
+
const secret = this.lagrangeInterpolate(decodedShares)
|
|
212
|
+
|
|
213
|
+
// Convert secret back to viewing key with original length
|
|
214
|
+
return this.secretToViewingKey(secret, keyLength)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Verify a share without revealing the viewing key
|
|
219
|
+
*
|
|
220
|
+
* @param share - Encoded share to verify
|
|
221
|
+
* @param expectedCommitment - Expected commitment hash
|
|
222
|
+
* @returns True if share is valid
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* const isValid = ThresholdViewingKey.verifyShare(
|
|
227
|
+
* 'share1',
|
|
228
|
+
* 'commitment_hash'
|
|
229
|
+
* )
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
static verifyShare(share: string, expectedCommitment: string): boolean {
|
|
233
|
+
try {
|
|
234
|
+
const decoded = this.decodeShare(share)
|
|
235
|
+
return decoded.commitment === expectedCommitment
|
|
236
|
+
} catch {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ─── Private Helper Methods ─────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Validate threshold and total shares parameters
|
|
245
|
+
*/
|
|
246
|
+
private static validateParams(threshold: number, totalShares: number): void {
|
|
247
|
+
if (!Number.isInteger(threshold) || threshold < 2) {
|
|
248
|
+
throw new ValidationError(
|
|
249
|
+
'threshold must be an integer >= 2',
|
|
250
|
+
'threshold',
|
|
251
|
+
{ received: threshold },
|
|
252
|
+
ErrorCode.INVALID_THRESHOLD
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!Number.isInteger(totalShares) || totalShares < threshold) {
|
|
257
|
+
throw new ValidationError(
|
|
258
|
+
'totalShares must be an integer >= threshold',
|
|
259
|
+
'totalShares',
|
|
260
|
+
{ received: totalShares, threshold },
|
|
261
|
+
ErrorCode.INVALID_THRESHOLD
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (totalShares > 255) {
|
|
266
|
+
throw new ValidationError(
|
|
267
|
+
'totalShares must be <= 255',
|
|
268
|
+
'totalShares',
|
|
269
|
+
{ received: totalShares },
|
|
270
|
+
ErrorCode.INVALID_THRESHOLD
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Validate viewing key format
|
|
277
|
+
*/
|
|
278
|
+
private static validateViewingKey(viewingKey: HexString): void {
|
|
279
|
+
if (!viewingKey || typeof viewingKey !== 'string') {
|
|
280
|
+
throw new ValidationError(
|
|
281
|
+
'viewingKey is required',
|
|
282
|
+
'viewingKey',
|
|
283
|
+
{ received: viewingKey },
|
|
284
|
+
ErrorCode.MISSING_REQUIRED
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!viewingKey.startsWith('0x')) {
|
|
289
|
+
throw new ValidationError(
|
|
290
|
+
'viewingKey must be hex-encoded (start with 0x)',
|
|
291
|
+
'viewingKey',
|
|
292
|
+
{ received: viewingKey },
|
|
293
|
+
ErrorCode.INVALID_FORMAT
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (viewingKey.length < 66) {
|
|
298
|
+
throw new ValidationError(
|
|
299
|
+
'viewingKey must be at least 32 bytes',
|
|
300
|
+
'viewingKey',
|
|
301
|
+
{ received: viewingKey.length },
|
|
302
|
+
ErrorCode.INVALID_FORMAT
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Convert viewing key to secret (bigint)
|
|
309
|
+
*/
|
|
310
|
+
private static viewingKeyToSecret(viewingKey: HexString): bigint {
|
|
311
|
+
const bytes = hexToBytes(viewingKey.slice(2))
|
|
312
|
+
let secret = 0n
|
|
313
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
314
|
+
secret = (secret << 8n) | BigInt(bytes[i])
|
|
315
|
+
}
|
|
316
|
+
// Ensure secret is within field
|
|
317
|
+
return this.mod(secret, FIELD_PRIME)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Convert secret (bigint) back to viewing key
|
|
322
|
+
* @param secret - The secret as bigint
|
|
323
|
+
* @param hexLength - Length of the hex string (without 0x prefix)
|
|
324
|
+
*/
|
|
325
|
+
private static secretToViewingKey(secret: bigint, hexLength: number): HexString {
|
|
326
|
+
// Convert bigint to hex string
|
|
327
|
+
let hex = secret.toString(16)
|
|
328
|
+
// Pad to original length
|
|
329
|
+
hex = hex.padStart(hexLength, '0')
|
|
330
|
+
return `0x${hex}` as HexString
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Generate random polynomial coefficients
|
|
335
|
+
* Polynomial: f(x) = a₀ + a₁x + a₂x² + ... + aₙ₋₁xⁿ⁻¹
|
|
336
|
+
* where a₀ = secret
|
|
337
|
+
*/
|
|
338
|
+
private static generateCoefficients(threshold: number, secret: bigint): bigint[] {
|
|
339
|
+
const coefficients: bigint[] = [secret] // a₀ = secret
|
|
340
|
+
|
|
341
|
+
// Generate threshold-1 random coefficients
|
|
342
|
+
for (let i = 1; i < threshold; i++) {
|
|
343
|
+
const randomCoeff = this.randomFieldElement()
|
|
344
|
+
coefficients.push(randomCoeff)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return coefficients
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Generate a random field element
|
|
352
|
+
*/
|
|
353
|
+
private static randomFieldElement(): bigint {
|
|
354
|
+
const bytes = randomBytes(32)
|
|
355
|
+
let value = 0n
|
|
356
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
357
|
+
value = (value << 8n) | BigInt(bytes[i])
|
|
358
|
+
}
|
|
359
|
+
return this.mod(value, FIELD_PRIME)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Evaluate polynomial at point x
|
|
364
|
+
* f(x) = a₀ + a₁x + a₂x² + ... + aₙ₋₁xⁿ⁻¹
|
|
365
|
+
*/
|
|
366
|
+
private static evaluatePolynomial(coefficients: bigint[], x: bigint): bigint {
|
|
367
|
+
let result = 0n
|
|
368
|
+
let xPower = 1n
|
|
369
|
+
|
|
370
|
+
for (const coeff of coefficients) {
|
|
371
|
+
result = this.mod(result + this.mod(coeff * xPower, FIELD_PRIME), FIELD_PRIME)
|
|
372
|
+
xPower = this.mod(xPower * x, FIELD_PRIME)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Create commitment hash from secret and coefficients
|
|
380
|
+
*/
|
|
381
|
+
private static createCommitment(secret: bigint, coefficients: bigint[]): string {
|
|
382
|
+
const data = [secret, ...coefficients].map((c) => c.toString(16).padStart(64, '0')).join('')
|
|
383
|
+
const hash = sha256(hexToBytes(data))
|
|
384
|
+
return bytesToHex(hash)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Encode share as string: "x:y:len:commitment"
|
|
389
|
+
*/
|
|
390
|
+
private static encodeShare(x: bigint, y: bigint, keyLength: number, commitment: string): string {
|
|
391
|
+
const xHex = x.toString(16).padStart(2, '0')
|
|
392
|
+
const yHex = y.toString(16).padStart(64, '0')
|
|
393
|
+
const lenHex = keyLength.toString(16).padStart(4, '0')
|
|
394
|
+
return `${xHex}:${yHex}:${lenHex}:${commitment}`
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Decode share from string
|
|
399
|
+
*/
|
|
400
|
+
private static decodeShare(share: string): DecodedShare {
|
|
401
|
+
if (!share || typeof share !== 'string') {
|
|
402
|
+
throw new ValidationError(
|
|
403
|
+
'share must be a non-empty string',
|
|
404
|
+
'share',
|
|
405
|
+
{ received: share },
|
|
406
|
+
ErrorCode.INVALID_SHARE
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const parts = share.split(':')
|
|
411
|
+
if (parts.length !== 4) {
|
|
412
|
+
throw new ValidationError(
|
|
413
|
+
'share must have format "x:y:len:commitment"',
|
|
414
|
+
'share',
|
|
415
|
+
{ received: share },
|
|
416
|
+
ErrorCode.INVALID_SHARE
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const [xHex, yHex, lenHex, commitment] = parts
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
const x = BigInt(`0x${xHex}`)
|
|
424
|
+
const y = BigInt(`0x${yHex}`)
|
|
425
|
+
const keyLength = parseInt(lenHex, 16)
|
|
426
|
+
|
|
427
|
+
if (x <= 0n) {
|
|
428
|
+
throw new ValidationError(
|
|
429
|
+
'share x-coordinate must be positive',
|
|
430
|
+
'share',
|
|
431
|
+
{ x },
|
|
432
|
+
ErrorCode.INVALID_SHARE
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (keyLength < 64) {
|
|
437
|
+
throw new ValidationError(
|
|
438
|
+
'key length must be at least 64 (32 bytes)',
|
|
439
|
+
'share',
|
|
440
|
+
{ keyLength },
|
|
441
|
+
ErrorCode.INVALID_SHARE
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return { x, y, keyLength, commitment }
|
|
446
|
+
} catch (error) {
|
|
447
|
+
throw new ValidationError(
|
|
448
|
+
'failed to decode share',
|
|
449
|
+
'share',
|
|
450
|
+
{ error: (error as Error).message },
|
|
451
|
+
ErrorCode.INVALID_SHARE
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Lagrange interpolation to reconstruct secret
|
|
458
|
+
* Evaluates polynomial at x=0 to get f(0) = secret
|
|
459
|
+
*/
|
|
460
|
+
private static lagrangeInterpolate(shares: DecodedShare[]): bigint {
|
|
461
|
+
let secret = 0n
|
|
462
|
+
|
|
463
|
+
for (let i = 0; i < shares.length; i++) {
|
|
464
|
+
const xi = shares[i].x
|
|
465
|
+
const yi = shares[i].y
|
|
466
|
+
|
|
467
|
+
// Calculate Lagrange coefficient
|
|
468
|
+
let numerator = 1n
|
|
469
|
+
let denominator = 1n
|
|
470
|
+
|
|
471
|
+
for (let j = 0; j < shares.length; j++) {
|
|
472
|
+
if (i === j) continue
|
|
473
|
+
|
|
474
|
+
const xj = shares[j].x
|
|
475
|
+
// Evaluate at x=0: (0 - xj) / (xi - xj)
|
|
476
|
+
numerator = this.mod(numerator * this.mod(-xj, FIELD_PRIME), FIELD_PRIME)
|
|
477
|
+
denominator = this.mod(denominator * this.mod(xi - xj, FIELD_PRIME), FIELD_PRIME)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Compute yi * (numerator / denominator)
|
|
481
|
+
const coeff = this.mod(numerator * this.modInverse(denominator, FIELD_PRIME), FIELD_PRIME)
|
|
482
|
+
secret = this.mod(secret + this.mod(yi * coeff, FIELD_PRIME), FIELD_PRIME)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return secret
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Modular arithmetic: a mod m
|
|
490
|
+
*/
|
|
491
|
+
private static mod(a: bigint, m: bigint): bigint {
|
|
492
|
+
return ((a % m) + m) % m
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Modular multiplicative inverse using Extended Euclidean Algorithm
|
|
497
|
+
* Returns x such that (a * x) mod m = 1
|
|
498
|
+
*/
|
|
499
|
+
private static modInverse(a: bigint, m: bigint): bigint {
|
|
500
|
+
const a0 = this.mod(a, m)
|
|
501
|
+
|
|
502
|
+
if (a0 === 0n) {
|
|
503
|
+
throw new CryptoError(
|
|
504
|
+
'modular inverse does not exist (a = 0)',
|
|
505
|
+
ErrorCode.CRYPTO_OPERATION_FAILED,
|
|
506
|
+
{ operation: 'modInverse' }
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let [old_r, r] = [a0, m]
|
|
511
|
+
let [old_s, s] = [1n, 0n]
|
|
512
|
+
|
|
513
|
+
while (r !== 0n) {
|
|
514
|
+
const quotient = old_r / r
|
|
515
|
+
;[old_r, r] = [r, old_r - quotient * r]
|
|
516
|
+
;[old_s, s] = [s, old_s - quotient * s]
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (old_r !== 1n) {
|
|
520
|
+
throw new CryptoError(
|
|
521
|
+
'modular inverse does not exist (gcd != 1)',
|
|
522
|
+
ErrorCode.CRYPTO_OPERATION_FAILED,
|
|
523
|
+
{ operation: 'modInverse' }
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return this.mod(old_s, m)
|
|
528
|
+
}
|
|
529
|
+
}
|