@bsv/sdk 2.0.11 → 2.0.13

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 (106) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +827 -0
  3. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
  4. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +654 -0
  5. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
  6. package/dist/cjs/src/overlay-tools/HostReputationTracker.js +21 -13
  7. package/dist/cjs/src/overlay-tools/HostReputationTracker.js.map +1 -1
  8. package/dist/cjs/src/primitives/PrivateKey.js +3 -3
  9. package/dist/cjs/src/primitives/PrivateKey.js.map +1 -1
  10. package/dist/cjs/src/script/Spend.js +17 -9
  11. package/dist/cjs/src/script/Spend.js.map +1 -1
  12. package/dist/cjs/src/storage/StorageDownloader.js +6 -6
  13. package/dist/cjs/src/storage/StorageDownloader.js.map +1 -1
  14. package/dist/cjs/src/storage/StorageUtils.js +1 -1
  15. package/dist/cjs/src/storage/StorageUtils.js.map +1 -1
  16. package/dist/cjs/src/transaction/MerklePath.js +168 -27
  17. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  18. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  19. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +825 -0
  20. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
  21. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +619 -0
  22. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
  23. package/dist/esm/src/overlay-tools/HostReputationTracker.js +21 -13
  24. package/dist/esm/src/overlay-tools/HostReputationTracker.js.map +1 -1
  25. package/dist/esm/src/primitives/PrivateKey.js +3 -3
  26. package/dist/esm/src/primitives/PrivateKey.js.map +1 -1
  27. package/dist/esm/src/script/Spend.js +17 -9
  28. package/dist/esm/src/script/Spend.js.map +1 -1
  29. package/dist/esm/src/storage/StorageDownloader.js +6 -6
  30. package/dist/esm/src/storage/StorageDownloader.js.map +1 -1
  31. package/dist/esm/src/storage/StorageUtils.js +1 -1
  32. package/dist/esm/src/storage/StorageUtils.js.map +1 -1
  33. package/dist/esm/src/transaction/MerklePath.js +168 -27
  34. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  35. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  36. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +21 -0
  37. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +1 -0
  38. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +2 -0
  39. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +1 -0
  40. package/dist/types/src/overlay-tools/HostReputationTracker.d.ts.map +1 -1
  41. package/dist/types/src/script/Spend.d.ts.map +1 -1
  42. package/dist/types/src/transaction/MerklePath.d.ts +27 -0
  43. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  44. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  45. package/dist/umd/bundle.js +3 -3
  46. package/dist/umd/bundle.js.map +1 -1
  47. package/docs/reference/storage.md +1 -1
  48. package/docs/reference/transaction.md +40 -0
  49. package/package.json +1 -1
  50. package/src/auth/clients/__tests__/AuthFetch.additional.test.ts +1131 -0
  51. package/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.ts +770 -0
  52. package/src/auth/utils/__tests/validateCertificates.test.ts +12 -9
  53. package/src/compat/__tests/Mnemonic.additional.test.ts +64 -0
  54. package/src/identity/__tests/IdentityClient.additional.test.ts +767 -0
  55. package/src/kvstore/__tests/LocalKVStore.additional.test.ts +611 -0
  56. package/src/kvstore/__tests/LocalKVStore.test.ts +4 -6
  57. package/src/kvstore/__tests/kvStoreInterpreter.test.ts +327 -0
  58. package/src/overlay-tools/HostReputationTracker.ts +17 -14
  59. package/src/overlay-tools/__tests/HostReputationTracker.additional.test.ts +561 -0
  60. package/src/overlay-tools/__tests/LookupResolver.additional.test.ts +612 -0
  61. package/src/overlay-tools/__tests/withDoubleSpendRetry.test.ts +278 -0
  62. package/src/primitives/PrivateKey.ts +3 -3
  63. package/src/primitives/__tests/BigNumber.additional.test.ts +79 -0
  64. package/src/primitives/__tests/Curve.additional.test.ts +208 -0
  65. package/src/primitives/__tests/ECDSA.additional.test.ts +122 -0
  66. package/src/primitives/__tests/Hash.additional.test.ts +59 -0
  67. package/src/primitives/__tests/JacobianPoint.test.ts +308 -0
  68. package/src/primitives/__tests/Point.additional.test.ts +503 -0
  69. package/src/primitives/__tests/PublicKey.additional.test.ts +383 -0
  70. package/src/primitives/__tests/Random.additional.test.ts +262 -0
  71. package/src/primitives/__tests/Signature.test.ts +333 -0
  72. package/src/primitives/__tests/TransactionSignature.additional.test.ts +241 -0
  73. package/src/registry/__tests/RegistryClient.additional.test.ts +750 -0
  74. package/src/remittance/__tests/BasicBRC29.additional.test.ts +657 -0
  75. package/src/remittance/__tests/RemittanceManager.additional.test.ts +1272 -0
  76. package/src/script/Spend.ts +19 -11
  77. package/src/script/__tests/LockingUnlockingScript.test.ts +79 -0
  78. package/src/script/__tests/Script.additional.test.ts +100 -0
  79. package/src/script/__tests/ScriptEvaluationError.test.ts +98 -0
  80. package/src/script/__tests/Spend.additional.test.ts +837 -0
  81. package/src/script/templates/__tests/RPuzzle.test.ts +134 -0
  82. package/src/storage/StorageDownloader.ts +6 -6
  83. package/src/storage/StorageUtils.ts +1 -1
  84. package/src/transaction/MerklePath.ts +196 -36
  85. package/src/transaction/__tests/BeefParty.additional.test.ts +22 -0
  86. package/src/transaction/__tests/Broadcaster.test.ts +159 -0
  87. package/src/transaction/__tests/MerklePath.bench.test.ts +105 -0
  88. package/src/transaction/__tests/MerklePath.test.ts +232 -21
  89. package/src/transaction/__tests/Transaction.additional.test.ts +225 -0
  90. package/src/transaction/broadcasters/__tests/ARC.additional.test.ts +585 -0
  91. package/src/transaction/broadcasters/__tests/Teranode.test.ts +349 -0
  92. package/src/transaction/chaintrackers/__tests/BlockHeadersService.test.ts +253 -0
  93. package/src/transaction/chaintrackers/__tests/DefaultChainTracker.test.ts +44 -0
  94. package/src/transaction/chaintrackers/__tests/WhatsOnChain.additional.test.ts +193 -0
  95. package/src/transaction/fee-models/__tests/SatoshisPerKilobyte.test.ts +262 -0
  96. package/src/transaction/http/__tests/BinaryFetchClient.test.ts +212 -0
  97. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +192 -0
  98. package/src/transaction/http/__tests/DefaultHttpClient.test.ts +71 -0
  99. package/src/wallet/__tests/ProtoWallet.additional.test.ts +134 -0
  100. package/src/wallet/__tests/WERR.test.ts +212 -0
  101. package/src/wallet/__tests/WalletClient.additional.test.ts +699 -0
  102. package/src/wallet/__tests/WalletClient.substrate.test.ts +759 -0
  103. package/src/wallet/__tests/WalletError.test.ts +290 -0
  104. package/src/wallet/__tests/validationHelpers.test.ts +1218 -0
  105. package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +496 -0
  106. package/src/wallet/substrates/__tests/HTTPWalletWire.test.ts +273 -0
@@ -0,0 +1,278 @@
1
+ /** eslint-env jest */
2
+ import { withDoubleSpendRetry } from '../withDoubleSpendRetry'
3
+ import { WERR_REVIEW_ACTIONS } from '../../wallet/WERR_REVIEW_ACTIONS'
4
+ import Transaction from '../../transaction/Transaction'
5
+ import { ReviewActionResult } from '../../wallet/Wallet.interfaces'
6
+ import TopicBroadcaster from '../SHIPBroadcaster'
7
+
8
+ // --- Module mocks -----------------------------------------------------------
9
+
10
+ jest.mock('../../transaction/Transaction.js', () => ({
11
+ fromBEEF: jest.fn()
12
+ }))
13
+
14
+ jest.mock('../SHIPBroadcaster.js', () => {
15
+ return jest.fn().mockImplementation(() => ({
16
+ broadcast: jest.fn()
17
+ }))
18
+ })
19
+
20
+ // --- Typed mock refs --------------------------------------------------------
21
+
22
+ const MockedTransaction = Transaction as jest.Mocked<typeof Transaction>
23
+
24
+ // --- Helpers ----------------------------------------------------------------
25
+
26
+ const MAX_DOUBLE_SPEND_RETRIES = 5
27
+
28
+ function makeMockBroadcaster (): jest.Mocked<TopicBroadcaster> {
29
+ return {
30
+ broadcast: jest.fn()
31
+ } as unknown as jest.Mocked<TopicBroadcaster>
32
+ }
33
+
34
+ function makeDoubleSpendError (
35
+ competingBeef: number[] | null = [0x01, 0x02],
36
+ competingTxs: string[] | null = ['competingtxid111111111111111111111111111111111111111111111111111111']
37
+ ): WERR_REVIEW_ACTIONS {
38
+ const result: ReviewActionResult = {
39
+ txid: 'originaltxid1111111111111111111111111111111111111111111111111111111',
40
+ status: 'doubleSpend',
41
+ ...(competingBeef != null && { competingBeef }),
42
+ ...(competingTxs != null && { competingTxs })
43
+ }
44
+ return new WERR_REVIEW_ACTIONS([result], [])
45
+ }
46
+
47
+ function makeNonDoubleSpendError (name: string = 'WERR_REVIEW_ACTIONS'): WERR_REVIEW_ACTIONS {
48
+ const result: ReviewActionResult = {
49
+ txid: 'originaltxid1111111111111111111111111111111111111111111111111111111',
50
+ status: 'serviceError'
51
+ }
52
+ const err = new WERR_REVIEW_ACTIONS([result], [])
53
+ err.name = name
54
+ return err
55
+ }
56
+
57
+ // --- Tests ------------------------------------------------------------------
58
+
59
+ describe('withDoubleSpendRetry', () => {
60
+ let broadcaster: jest.Mocked<TopicBroadcaster>
61
+ let mockCompetingTx: Partial<Transaction>
62
+
63
+ beforeEach(() => {
64
+ jest.clearAllMocks()
65
+ broadcaster = makeMockBroadcaster()
66
+ mockCompetingTx = {}
67
+ ;(MockedTransaction.fromBEEF as jest.Mock).mockReturnValue(mockCompetingTx as Transaction)
68
+ })
69
+
70
+ // --- Happy path -----------------------------------------------------------
71
+
72
+ describe('succeeds without retry', () => {
73
+ it('returns operation result immediately on first successful attempt', async () => {
74
+ const expectedResult = { success: true }
75
+ const operation = jest.fn().mockResolvedValue(expectedResult)
76
+
77
+ const result = await withDoubleSpendRetry(operation, broadcaster)
78
+
79
+ expect(result).toBe(expectedResult)
80
+ expect(operation).toHaveBeenCalledTimes(1)
81
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
82
+ })
83
+
84
+ it('returns operation result for non-object results (string)', async () => {
85
+ const operation = jest.fn().mockResolvedValue('done')
86
+
87
+ const result = await withDoubleSpendRetry(operation, broadcaster)
88
+
89
+ expect(result).toBe('done')
90
+ expect(operation).toHaveBeenCalledTimes(1)
91
+ })
92
+
93
+ it('returns operation result for undefined', async () => {
94
+ const operation = jest.fn().mockResolvedValue(undefined)
95
+
96
+ const result = await withDoubleSpendRetry(operation, broadcaster)
97
+
98
+ expect(result).toBeUndefined()
99
+ expect(operation).toHaveBeenCalledTimes(1)
100
+ })
101
+ })
102
+
103
+ // --- Non-double-spend errors rethrown immediately -------------------------
104
+
105
+ describe('rethrows non-WERR_REVIEW_ACTIONS errors immediately', () => {
106
+ it('rethrows a plain Error without retrying', async () => {
107
+ const plainError = new Error('Network error')
108
+ const operation = jest.fn().mockRejectedValue(plainError)
109
+
110
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow('Network error')
111
+ expect(operation).toHaveBeenCalledTimes(1)
112
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
113
+ })
114
+
115
+ it('rethrows errors with other error names without retrying', async () => {
116
+ const otherError = new Error('other error')
117
+ otherError.name = 'SOME_OTHER_ERROR'
118
+ const operation = jest.fn().mockRejectedValue(otherError)
119
+
120
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow('other error')
121
+ expect(operation).toHaveBeenCalledTimes(1)
122
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
123
+ })
124
+ })
125
+
126
+ // --- WERR_REVIEW_ACTIONS without doubleSpend rethrown immediately ---------
127
+
128
+ describe('rethrows WERR_REVIEW_ACTIONS that do not represent a valid doubleSpend', () => {
129
+ it('rethrows WERR_REVIEW_ACTIONS with no doubleSpend result in reviewActionResults', async () => {
130
+ const error = makeNonDoubleSpendError()
131
+ const operation = jest.fn().mockRejectedValue(error)
132
+
133
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow(error)
134
+ expect(operation).toHaveBeenCalledTimes(1)
135
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
136
+ })
137
+
138
+ it('rethrows WERR_REVIEW_ACTIONS where doubleSpend result has no competingBeef', async () => {
139
+ const error = makeDoubleSpendError(null, ['competingtxid'])
140
+ const operation = jest.fn().mockRejectedValue(error)
141
+
142
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow(error)
143
+ expect(operation).toHaveBeenCalledTimes(1)
144
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
145
+ })
146
+
147
+ it('rethrows WERR_REVIEW_ACTIONS where doubleSpend result has no competingTxs', async () => {
148
+ const error = makeDoubleSpendError([0x01, 0x02], null)
149
+ const operation = jest.fn().mockRejectedValue(error)
150
+
151
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow(error)
152
+ expect(operation).toHaveBeenCalledTimes(1)
153
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
154
+ })
155
+
156
+ it('rethrows WERR_REVIEW_ACTIONS where competingTxs is an empty array', async () => {
157
+ const error = makeDoubleSpendError([0x01, 0x02], [])
158
+ const operation = jest.fn().mockRejectedValue(error)
159
+
160
+ await expect(withDoubleSpendRetry(operation, broadcaster)).rejects.toThrow(error)
161
+ expect(operation).toHaveBeenCalledTimes(1)
162
+ expect(broadcaster.broadcast).not.toHaveBeenCalled()
163
+ })
164
+ })
165
+
166
+ // --- Retry on doubleSpend -------------------------------------------------
167
+
168
+ describe('retries after broadcasting the competing transaction', () => {
169
+ it('broadcasts the competing tx and retries the operation when doubleSpend is detected', async () => {
170
+ const competingBeef = [0xbe, 0xef]
171
+ const competingTxId = 'competingtxid111111111111111111111111111111111111111111111111111111'
172
+ const doubleSpendError = makeDoubleSpendError(competingBeef, [competingTxId])
173
+
174
+ broadcaster.broadcast.mockResolvedValue({ status: 'success', txid: competingTxId } as any)
175
+
176
+ const expectedResult = { done: true }
177
+ const operation = jest.fn()
178
+ .mockRejectedValueOnce(doubleSpendError) // first attempt: double-spend
179
+ .mockResolvedValueOnce(expectedResult) // second attempt: success
180
+
181
+ const result = await withDoubleSpendRetry(operation, broadcaster)
182
+
183
+ expect(result).toBe(expectedResult)
184
+ expect(operation).toHaveBeenCalledTimes(2)
185
+ expect(MockedTransaction.fromBEEF).toHaveBeenCalledWith(competingBeef, competingTxId)
186
+ expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
187
+ expect(broadcaster.broadcast).toHaveBeenCalledWith(mockCompetingTx)
188
+ })
189
+
190
+ it('calls Transaction.fromBEEF with competingBeef and the first competingTx', async () => {
191
+ const competingBeef = [0x01, 0x02, 0x03]
192
+ const firstTxId = 'firstcompetingtxid1111111111111111111111111111111111111111111111111'
193
+ const secondTxId = 'secondcompetingtxid111111111111111111111111111111111111111111111111'
194
+ const doubleSpendError = makeDoubleSpendError(competingBeef, [firstTxId, secondTxId])
195
+
196
+ broadcaster.broadcast.mockResolvedValue({ status: 'success', txid: firstTxId } as any)
197
+ const operation = jest.fn()
198
+ .mockRejectedValueOnce(doubleSpendError)
199
+ .mockResolvedValueOnce('ok')
200
+
201
+ await withDoubleSpendRetry(operation, broadcaster)
202
+
203
+ // Only the first competingTx should be used
204
+ expect(MockedTransaction.fromBEEF).toHaveBeenCalledWith(competingBeef, firstTxId)
205
+ })
206
+
207
+ it('retries multiple times until success', async () => {
208
+ const doubleSpendError = makeDoubleSpendError()
209
+ broadcaster.broadcast.mockResolvedValue({ status: 'success' } as any)
210
+
211
+ const operation = jest.fn()
212
+ .mockRejectedValueOnce(doubleSpendError) // attempt 1
213
+ .mockRejectedValueOnce(doubleSpendError) // attempt 2
214
+ .mockRejectedValueOnce(doubleSpendError) // attempt 3
215
+ .mockResolvedValueOnce('finally succeeded') // attempt 4
216
+
217
+ const result = await withDoubleSpendRetry(operation, broadcaster)
218
+
219
+ expect(result).toBe('finally succeeded')
220
+ expect(operation).toHaveBeenCalledTimes(4)
221
+ expect(broadcaster.broadcast).toHaveBeenCalledTimes(3)
222
+ })
223
+ })
224
+
225
+ // --- MAX_DOUBLE_SPEND_RETRIES enforcement ----------------------------------
226
+
227
+ describe('throws after MAX_DOUBLE_SPEND_RETRIES is exceeded', () => {
228
+ it('throws the error after MAX_DOUBLE_SPEND_RETRIES (5) failed attempts', async () => {
229
+ const doubleSpendError = makeDoubleSpendError()
230
+ broadcaster.broadcast.mockResolvedValue({ status: 'success' } as any)
231
+
232
+ // Operation always double-spends — should fail after maxRetries
233
+ const operation = jest.fn().mockRejectedValue(doubleSpendError)
234
+
235
+ await expect(
236
+ withDoubleSpendRetry(operation, broadcaster, MAX_DOUBLE_SPEND_RETRIES)
237
+ ).rejects.toThrow(doubleSpendError)
238
+
239
+ // Called maxRetries times; the last attempt's error is rethrown without broadcasting
240
+ expect(operation).toHaveBeenCalledTimes(MAX_DOUBLE_SPEND_RETRIES)
241
+ // Broadcast is called for all but the final attempt (last error is rethrown directly)
242
+ expect(broadcaster.broadcast).toHaveBeenCalledTimes(MAX_DOUBLE_SPEND_RETRIES - 1)
243
+ })
244
+
245
+ it('throws after custom maxRetries value is exceeded', async () => {
246
+ const doubleSpendError = makeDoubleSpendError()
247
+ broadcaster.broadcast.mockResolvedValue({ status: 'success' } as any)
248
+ const operation = jest.fn().mockRejectedValue(doubleSpendError)
249
+
250
+ await expect(
251
+ withDoubleSpendRetry(operation, broadcaster, 2)
252
+ ).rejects.toThrow(doubleSpendError)
253
+
254
+ expect(operation).toHaveBeenCalledTimes(2)
255
+ expect(broadcaster.broadcast).toHaveBeenCalledTimes(1)
256
+ })
257
+ })
258
+
259
+ // --- Broadcaster interaction -----------------------------------------------
260
+
261
+ describe('broadcaster.broadcast is called with the correct transaction', () => {
262
+ it('passes the Transaction.fromBEEF result to broadcaster.broadcast', async () => {
263
+ const competingTxMock = { id: jest.fn().mockReturnValue('abc') }
264
+ ;(MockedTransaction.fromBEEF as jest.Mock).mockReturnValue(competingTxMock)
265
+
266
+ const doubleSpendError = makeDoubleSpendError([0xaa, 0xbb], ['txid'])
267
+ broadcaster.broadcast.mockResolvedValue({ status: 'success' } as any)
268
+
269
+ const operation = jest.fn()
270
+ .mockRejectedValueOnce(doubleSpendError)
271
+ .mockResolvedValueOnce('done')
272
+
273
+ await withDoubleSpendRetry(operation, broadcaster)
274
+
275
+ expect(broadcaster.broadcast).toHaveBeenCalledWith(competingTxMock)
276
+ })
277
+ })
278
+ })
@@ -55,7 +55,7 @@ export class KeyShares {
55
55
  const [x, y, t, i] = shareParts
56
56
  if (t === undefined) throw new Error('Threshold not found in share ' + idx.toString())
57
57
  if (i === undefined) throw new Error('Integrity not found in share ' + idx.toString())
58
- const tInt = parseInt(t)
58
+ const tInt = Number.parseInt(t, 10)
59
59
  if (idx !== 0 && threshold !== tInt) { throw new Error('Threshold mismatch in share ' + idx.toString()) }
60
60
  if (idx !== 0 && integrity !== i) { throw new Error('Integrity mismatch in share ' + idx.toString()) }
61
61
  threshold = tInt
@@ -399,7 +399,7 @@ export default class PrivateKey extends BigNumber {
399
399
  let sharedSecret: Point
400
400
  if (typeof retrieveCachedSharedSecret === 'function') {
401
401
  const retrieved = retrieveCachedSharedSecret(this, publicKey)
402
- if (typeof retrieved !== 'undefined') {
402
+ if (retrieved !== undefined) {
403
403
  sharedSecret = retrieved
404
404
  } else {
405
405
  sharedSecret = this.deriveSharedSecret(publicKey)
@@ -429,7 +429,7 @@ export default class PrivateKey extends BigNumber {
429
429
  * const shares = key.toKeyShares(2, 5)
430
430
  */
431
431
  toKeyShares (threshold: number, totalShares: number): KeyShares {
432
- if (typeof threshold !== 'number' || typeof totalShares !== 'number') { throw new Error('threshold and totalShares must be numbers') }
432
+ if (typeof threshold !== 'number' || typeof totalShares !== 'number') { throw new TypeError('threshold and totalShares must be numbers') }
433
433
  if (threshold < 2) throw new Error('threshold must be at least 2')
434
434
  if (totalShares < 2) throw new Error('totalShares must be at least 2')
435
435
  if (threshold > totalShares) { throw new Error('threshold should be less than or equal to totalShares') }
@@ -0,0 +1,79 @@
1
+ import BigNumber from '../BigNumber'
2
+
3
+ describe('BigNumber – additional coverage', () => {
4
+ describe('negative setter', () => {
5
+ it('sets sign to 0 when magnitude is zero (setting val=1 on zero BN)', () => {
6
+ const bn = new BigNumber(0)
7
+ bn.negative = 1
8
+ expect(bn.negative).toBe(0) // magnitude is 0 so sign stays 0
9
+ })
10
+
11
+ it('sets sign to 1 on a non-zero BigNumber', () => {
12
+ const bn = new BigNumber(5)
13
+ bn.negative = 1
14
+ expect(bn.negative).toBe(1)
15
+ })
16
+
17
+ it('sets sign to 0 on a non-zero BigNumber', () => {
18
+ const bn = new BigNumber(5)
19
+ bn.negative = 1
20
+ bn.negative = 0
21
+ expect(bn.negative).toBe(0)
22
+ })
23
+ })
24
+
25
+ describe('inspect', () => {
26
+ it('returns inspection string for a positive BigNumber', () => {
27
+ const bn = new BigNumber(255)
28
+ const s = bn.inspect()
29
+ expect(s).toContain('ff')
30
+ expect(s).toContain('BN')
31
+ })
32
+ })
33
+
34
+ describe('toBitArray', () => {
35
+ it('returns empty array for zero (static)', () => {
36
+ expect(BigNumber.toBitArray(new BigNumber(0))).toEqual([])
37
+ })
38
+
39
+ it('instance method returns same as static', () => {
40
+ const bn = new BigNumber(5) // binary: 101
41
+ expect(bn.toBitArray()).toEqual([1, 0, 1])
42
+ })
43
+ })
44
+
45
+ describe('toString with non-standard base', () => {
46
+ it('converts to base-3 string', () => {
47
+ const bn = new BigNumber(9)
48
+ expect(bn.toString(3)).toBe('100') // 9 in base 3 = 100
49
+ })
50
+ })
51
+
52
+ describe('fromBits / toBits edge cases', () => {
53
+ it('fromBits(0) returns zero BigNumber', () => {
54
+ const bn = BigNumber.fromBits(0)
55
+ expect(bn.toNumber()).toBe(0)
56
+ })
57
+
58
+ it('toBits for zero returns 0', () => {
59
+ expect(new BigNumber(0).toBits()).toBe(0)
60
+ })
61
+
62
+ it('toBits for a 3-byte number with MSB set in mantissa (triggers shift)', () => {
63
+ // mB[0] >= 0x80 → (nWordNum & 0x00800000) !== 0 → shift branch
64
+ const bn = BigNumber.fromHex('800001')
65
+ const bits = bn.toBits()
66
+ expect(bits).toBeGreaterThan(0)
67
+ })
68
+ })
69
+
70
+ describe('toSm', () => {
71
+ it('returns [0x80] for negative zero (magnitude 0, sign 1)', () => {
72
+ const bn = new BigNumber(0)
73
+ bn.negative = 1
74
+ const result = bn.toSm()
75
+ // magnitude is 0 so sign gets normalized to 0; returns []
76
+ expect(Array.isArray(result)).toBe(true)
77
+ })
78
+ })
79
+ })
@@ -0,0 +1,208 @@
1
+ import Curve from '../../primitives/Curve'
2
+ import Point from '../../primitives/Point'
3
+ import BigNumber from '../../primitives/BigNumber'
4
+
5
+ describe('Curve – additional coverage', () => {
6
+ const curve = new Curve()
7
+ const G = curve.g as Point
8
+
9
+ // --------------------------------------------------------------------------
10
+ // assert
11
+ // --------------------------------------------------------------------------
12
+ describe('Curve.assert', () => {
13
+ it('does not throw when expression is truthy', () => {
14
+ expect(() => Curve.assert(true)).not.toThrow()
15
+ expect(() => Curve.assert(1)).not.toThrow()
16
+ expect(() => Curve.assert('hello')).not.toThrow()
17
+ })
18
+
19
+ it('throws default message when expression is falsy', () => {
20
+ expect(() => Curve.assert(false)).toThrow('Elliptic curve assertion failed')
21
+ })
22
+
23
+ it('throws custom message', () => {
24
+ expect(() => Curve.assert(false, 'custom error')).toThrow('custom error')
25
+ })
26
+ })
27
+
28
+ // --------------------------------------------------------------------------
29
+ // getNAF
30
+ // --------------------------------------------------------------------------
31
+ describe('getNAF', () => {
32
+ it('returns non-empty array for a positive number', () => {
33
+ const naf = curve.getNAF(new BigNumber(7), 2, 256)
34
+ expect(Array.isArray(naf)).toBe(true)
35
+ expect(naf.length).toBeGreaterThan(0)
36
+ })
37
+
38
+ it('returns all zeros for BigNumber(0)', () => {
39
+ const naf = curve.getNAF(new BigNumber(0), 2, 256)
40
+ // For zero, all entries should be 0
41
+ expect(naf.every(x => x === 0)).toBe(true)
42
+ })
43
+
44
+ it('handles odd number', () => {
45
+ const naf = curve.getNAF(new BigNumber(15), 2, 256)
46
+ expect(Array.isArray(naf)).toBe(true)
47
+ })
48
+ })
49
+
50
+ // --------------------------------------------------------------------------
51
+ // getJSF
52
+ // --------------------------------------------------------------------------
53
+ describe('getJSF', () => {
54
+ it('returns two arrays (JSF of k1 and k2)', () => {
55
+ const k1 = new BigNumber(7)
56
+ const k2 = new BigNumber(11)
57
+ const jsf = curve.getJSF(k1, k2)
58
+ expect(Array.isArray(jsf)).toBe(true)
59
+ expect(jsf.length).toBe(2)
60
+ expect(Array.isArray(jsf[0])).toBe(true)
61
+ expect(Array.isArray(jsf[1])).toBe(true)
62
+ })
63
+
64
+ it('handles large numbers', () => {
65
+ const k1 = new BigNumber('deadbeef', 16)
66
+ const k2 = new BigNumber('cafebabe', 16)
67
+ const jsf = curve.getJSF(k1, k2)
68
+ expect(jsf[0].length).toBeGreaterThan(0)
69
+ })
70
+ })
71
+
72
+ // --------------------------------------------------------------------------
73
+ // parseBytes
74
+ // --------------------------------------------------------------------------
75
+ describe('Curve.parseBytes', () => {
76
+ it('converts hex string to byte array', () => {
77
+ const bytes = Curve.parseBytes('deadbeef')
78
+ expect(bytes).toEqual([0xde, 0xad, 0xbe, 0xef])
79
+ })
80
+
81
+ it('passes byte array through unchanged', () => {
82
+ const arr = [0x01, 0x02, 0x03]
83
+ const result = Curve.parseBytes(arr)
84
+ expect(result).toEqual(arr)
85
+ })
86
+ })
87
+
88
+ // --------------------------------------------------------------------------
89
+ // intFromLE
90
+ // --------------------------------------------------------------------------
91
+ describe('Curve.intFromLE', () => {
92
+ it('converts little-endian bytes to BigNumber', () => {
93
+ const bn = Curve.intFromLE([0x01, 0x00])
94
+ // 0x01 in LE means 0x0001 = 1
95
+ expect(bn.toNumber()).toBe(1)
96
+ })
97
+
98
+ it('converts multi-byte LE number', () => {
99
+ const bn = Curve.intFromLE([0x02, 0x01])
100
+ // 0x0102 = 258
101
+ expect(bn.toNumber()).toBe(0x0102)
102
+ })
103
+ })
104
+
105
+ // --------------------------------------------------------------------------
106
+ // cachedProperty
107
+ // --------------------------------------------------------------------------
108
+ describe('Curve.cachedProperty', () => {
109
+ it('caches the result after first call', () => {
110
+ let computeCount = 0
111
+ // Create a class to attach the property to
112
+ class TestClass {
113
+ _myProp: any = undefined
114
+ }
115
+ Curve.cachedProperty(TestClass, 'myProp', function () {
116
+ computeCount++
117
+ return 42
118
+ })
119
+ const instance = new TestClass()
120
+ // Calling twice — should compute once
121
+ const val1 = (instance as any).myProp()
122
+ const val2 = (instance as any).myProp()
123
+ expect(val1).toBe(42)
124
+ expect(val2).toBe(42)
125
+ expect(computeCount).toBe(1)
126
+ })
127
+ })
128
+
129
+ // --------------------------------------------------------------------------
130
+ // validate
131
+ // --------------------------------------------------------------------------
132
+ describe('validate', () => {
133
+ it('returns true for point at infinity', () => {
134
+ const inf = new Point(null, null)
135
+ expect(curve.validate(inf)).toBe(true)
136
+ })
137
+
138
+ it('returns true for a valid curve point', () => {
139
+ const p = Point.fromString('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
140
+ expect(curve.validate(p)).toBe(true)
141
+ })
142
+
143
+ it('returns false for an off-curve point', () => {
144
+ // Create point with modified y to be off-curve
145
+ const p = Point.fromString('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
146
+ const yModified = (p.y as BigNumber).clone().redIAdd(curve.one)
147
+ // Access internal: just test via validate
148
+ // Use a different approach to create an off-curve point
149
+ const offCurve = new Point(p.x, yModified, false)
150
+ // We need to bypass the isRed check since the modified point isn't on the curve
151
+ // Just call curve.validate
152
+ expect(curve.validate(offCurve)).toBe(false)
153
+ })
154
+ })
155
+
156
+ // --------------------------------------------------------------------------
157
+ // _endoSplit
158
+ // --------------------------------------------------------------------------
159
+ describe('_endoSplit', () => {
160
+ it('splits a scalar into balanced k1 and k2', () => {
161
+ const k = new BigNumber('deadbeefcafe', 16)
162
+ const split = curve._endoSplit(k)
163
+ expect(split).toHaveProperty('k1')
164
+ expect(split).toHaveProperty('k2')
165
+ // k1 + lambda * k2 ≡ k (mod n)
166
+ // This is an endomorphism property; just verify the result is plausible
167
+ expect(BigNumber.isBN(split.k1)).toBe(true)
168
+ expect(BigNumber.isBN(split.k2)).toBe(true)
169
+ })
170
+ })
171
+
172
+ // --------------------------------------------------------------------------
173
+ // _getEndoRoots
174
+ // --------------------------------------------------------------------------
175
+ describe('_getEndoRoots', () => {
176
+ it('computes two roots for the curve order n', () => {
177
+ const roots = curve._getEndoRoots(curve.n)
178
+ expect(Array.isArray(roots)).toBe(true)
179
+ expect(roots.length).toBe(2)
180
+ expect(BigNumber.isBN(roots[0])).toBe(true)
181
+ expect(BigNumber.isBN(roots[1])).toBe(true)
182
+ })
183
+
184
+ it('computes two roots for the curve field prime p', () => {
185
+ const roots = curve._getEndoRoots(curve.p)
186
+ expect(Array.isArray(roots)).toBe(true)
187
+ expect(roots.length).toBe(2)
188
+ })
189
+ })
190
+
191
+ // --------------------------------------------------------------------------
192
+ // _getEndoBasis
193
+ // --------------------------------------------------------------------------
194
+ describe('_getEndoBasis', () => {
195
+ it('returns a basis of two vectors', () => {
196
+ // Use the curve's lambda if available
197
+ if (curve.endo != null) {
198
+ const basis = curve._getEndoBasis(curve.endo.lambda)
199
+ expect(Array.isArray(basis)).toBe(true)
200
+ expect(basis.length).toBe(2)
201
+ expect(basis[0]).toHaveProperty('a')
202
+ expect(basis[0]).toHaveProperty('b')
203
+ expect(basis[1]).toHaveProperty('a')
204
+ expect(basis[1]).toHaveProperty('b')
205
+ }
206
+ })
207
+ })
208
+ })