@bsv/sdk 2.0.12 → 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 (77) 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/transaction/MerklePath.js +132 -0
  7. package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
  8. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  9. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +825 -0
  10. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
  11. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +619 -0
  12. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
  13. package/dist/esm/src/transaction/MerklePath.js +132 -0
  14. package/dist/esm/src/transaction/MerklePath.js.map +1 -1
  15. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  16. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +21 -0
  17. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +1 -0
  18. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +2 -0
  19. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +1 -0
  20. package/dist/types/src/transaction/MerklePath.d.ts +27 -0
  21. package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
  22. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  23. package/dist/umd/bundle.js +1 -1
  24. package/dist/umd/bundle.js.map +1 -1
  25. package/docs/reference/storage.md +1 -1
  26. package/docs/reference/transaction.md +40 -0
  27. package/package.json +1 -1
  28. package/src/auth/clients/__tests__/AuthFetch.additional.test.ts +1131 -0
  29. package/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.ts +770 -0
  30. package/src/compat/__tests/Mnemonic.additional.test.ts +64 -0
  31. package/src/identity/__tests/IdentityClient.additional.test.ts +767 -0
  32. package/src/kvstore/__tests/LocalKVStore.additional.test.ts +611 -0
  33. package/src/kvstore/__tests/kvStoreInterpreter.test.ts +327 -0
  34. package/src/overlay-tools/__tests/HostReputationTracker.additional.test.ts +561 -0
  35. package/src/overlay-tools/__tests/LookupResolver.additional.test.ts +612 -0
  36. package/src/overlay-tools/__tests/withDoubleSpendRetry.test.ts +278 -0
  37. package/src/primitives/__tests/BigNumber.additional.test.ts +79 -0
  38. package/src/primitives/__tests/Curve.additional.test.ts +208 -0
  39. package/src/primitives/__tests/ECDSA.additional.test.ts +122 -0
  40. package/src/primitives/__tests/Hash.additional.test.ts +59 -0
  41. package/src/primitives/__tests/JacobianPoint.test.ts +308 -0
  42. package/src/primitives/__tests/Point.additional.test.ts +503 -0
  43. package/src/primitives/__tests/PublicKey.additional.test.ts +383 -0
  44. package/src/primitives/__tests/Random.additional.test.ts +262 -0
  45. package/src/primitives/__tests/Signature.test.ts +333 -0
  46. package/src/primitives/__tests/TransactionSignature.additional.test.ts +241 -0
  47. package/src/registry/__tests/RegistryClient.additional.test.ts +750 -0
  48. package/src/remittance/__tests/BasicBRC29.additional.test.ts +657 -0
  49. package/src/remittance/__tests/RemittanceManager.additional.test.ts +1272 -0
  50. package/src/script/__tests/LockingUnlockingScript.test.ts +79 -0
  51. package/src/script/__tests/Script.additional.test.ts +100 -0
  52. package/src/script/__tests/ScriptEvaluationError.test.ts +98 -0
  53. package/src/script/__tests/Spend.additional.test.ts +837 -0
  54. package/src/script/templates/__tests/RPuzzle.test.ts +134 -0
  55. package/src/transaction/MerklePath.ts +155 -0
  56. package/src/transaction/__tests/BeefParty.additional.test.ts +22 -0
  57. package/src/transaction/__tests/Broadcaster.test.ts +159 -0
  58. package/src/transaction/__tests/MerklePath.bench.test.ts +105 -0
  59. package/src/transaction/__tests/MerklePath.test.ts +80 -0
  60. package/src/transaction/__tests/Transaction.additional.test.ts +225 -0
  61. package/src/transaction/broadcasters/__tests/ARC.additional.test.ts +585 -0
  62. package/src/transaction/broadcasters/__tests/Teranode.test.ts +349 -0
  63. package/src/transaction/chaintrackers/__tests/BlockHeadersService.test.ts +253 -0
  64. package/src/transaction/chaintrackers/__tests/DefaultChainTracker.test.ts +44 -0
  65. package/src/transaction/chaintrackers/__tests/WhatsOnChain.additional.test.ts +193 -0
  66. package/src/transaction/fee-models/__tests/SatoshisPerKilobyte.test.ts +262 -0
  67. package/src/transaction/http/__tests/BinaryFetchClient.test.ts +212 -0
  68. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +192 -0
  69. package/src/transaction/http/__tests/DefaultHttpClient.test.ts +71 -0
  70. package/src/wallet/__tests/ProtoWallet.additional.test.ts +134 -0
  71. package/src/wallet/__tests/WERR.test.ts +212 -0
  72. package/src/wallet/__tests/WalletClient.additional.test.ts +699 -0
  73. package/src/wallet/__tests/WalletClient.substrate.test.ts +759 -0
  74. package/src/wallet/__tests/WalletError.test.ts +290 -0
  75. package/src/wallet/__tests/validationHelpers.test.ts +1218 -0
  76. package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +496 -0
  77. package/src/wallet/substrates/__tests/HTTPWalletWire.test.ts +273 -0
@@ -0,0 +1,333 @@
1
+ import Signature from '../../primitives/Signature'
2
+ import BigNumber from '../../primitives/BigNumber'
3
+ import PrivateKey from '../../primitives/PrivateKey'
4
+ import PublicKey from '../../primitives/PublicKey'
5
+ import * as ECDSA from '../../primitives/ECDSA'
6
+ import Curve from '../../primitives/Curve'
7
+
8
+ const key = new BigNumber(
9
+ '1e5edd45de6d22deebef4596b80444ffcc29143839c1dce18db470e25b4be7b5',
10
+ 16
11
+ )
12
+ const curve = new Curve()
13
+ const msg = new BigNumber('deadbeef', 16)
14
+
15
+ describe('Signature', () => {
16
+ // --------------------------------------------------------------------------
17
+ // fromDER – error paths
18
+ // --------------------------------------------------------------------------
19
+ describe('fromDER error paths', () => {
20
+ it('throws when DER does not start with 0x30', () => {
21
+ // Replace leading 0x30 with 0x31
22
+ const sig = ECDSA.sign(msg, key)
23
+ const der = sig.toDER() as number[]
24
+ der[0] = 0x31
25
+ expect(() => Signature.fromDER(der)).toThrow('Signature DER must start with 0x30')
26
+ })
27
+
28
+ it('throws when outer length byte has high bit set (multi-byte length)', () => {
29
+ // Set the length byte to 0x81 (high bit set) to trigger 'Invalid DER entity length'
30
+ const der = [0x30, 0x81, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01]
31
+ expect(() => Signature.fromDER(der)).toThrow('Invalid DER entity length')
32
+ })
33
+
34
+ it('throws when outer length does not match data length', () => {
35
+ const sig = ECDSA.sign(msg, key)
36
+ const der = sig.toDER() as number[]
37
+ // Corrupt the length byte to be too small
38
+ der[1] = 1
39
+ expect(() => Signature.fromDER(der)).toThrow('Signature DER invalid')
40
+ })
41
+
42
+ it('throws when second marker is not 0x02 (r marker)', () => {
43
+ const sig = ECDSA.sign(msg, key)
44
+ const der = sig.toDER() as number[]
45
+ // Byte at index 2 should be 0x02
46
+ der[2] = 0x03
47
+ expect(() => Signature.fromDER(der)).toThrow('Signature DER invalid')
48
+ })
49
+
50
+ it('throws when s marker is not 0x02', () => {
51
+ const sig = ECDSA.sign(msg, key)
52
+ const der = sig.toDER() as number[]
53
+ const rlen = der[3]
54
+ // s marker is at position 4 + rlen
55
+ der[4 + rlen] = 0x03
56
+ expect(() => Signature.fromDER(der)).toThrow('Signature DER invalid')
57
+ })
58
+
59
+ it('throws when r starts with 0x00 but next byte is not high-bit', () => {
60
+ // Construct a DER where r has leading 0x00 but r[1] high bit is 0 (invalid padding)
61
+ // r = [0x00, 0x01], s = [0x01]
62
+ const rBytes = [0x00, 0x01]
63
+ const sBytes = [0x01]
64
+ const der = [
65
+ 0x30,
66
+ 2 + rBytes.length + 2 + sBytes.length,
67
+ 0x02, rBytes.length, ...rBytes,
68
+ 0x02, sBytes.length, ...sBytes
69
+ ]
70
+ expect(() => Signature.fromDER(der)).toThrow('Invalid R-value in signature DER')
71
+ })
72
+
73
+ it('throws when s starts with 0x00 but next byte is not high-bit', () => {
74
+ // r = [0x01], s = [0x00, 0x01]
75
+ const rBytes = [0x01]
76
+ const sBytes = [0x00, 0x01]
77
+ const der = [
78
+ 0x30,
79
+ 2 + rBytes.length + 2 + sBytes.length,
80
+ 0x02, rBytes.length, ...rBytes,
81
+ 0x02, sBytes.length, ...sBytes
82
+ ]
83
+ expect(() => Signature.fromDER(der)).toThrow('Invalid S-value in signature DER')
84
+ })
85
+
86
+ it('throws on s-length mismatch', () => {
87
+ const sig = ECDSA.sign(msg, key)
88
+ const der = sig.toDER() as number[]
89
+ // Corrupt the s-length byte to be too large
90
+ const rlen = der[3]
91
+ der[4 + rlen + 1] = 0x7f
92
+ expect(() => Signature.fromDER(der)).toThrow()
93
+ })
94
+
95
+ it('parses from hex string', () => {
96
+ const sig = ECDSA.sign(msg, key)
97
+ const hexDER = sig.toDER('hex') as string
98
+ const recovered = Signature.fromDER(hexDER, 'hex')
99
+ expect(recovered.r.eq(sig.r)).toBe(true)
100
+ expect(recovered.s.eq(sig.s)).toBe(true)
101
+ })
102
+
103
+ it('parses from base64 string', () => {
104
+ const sig = ECDSA.sign(msg, key)
105
+ const b64DER = sig.toDER('base64') as string
106
+ const recovered = Signature.fromDER(b64DER, 'base64')
107
+ expect(recovered.r.eq(sig.r)).toBe(true)
108
+ expect(recovered.s.eq(sig.s)).toBe(true)
109
+ })
110
+ })
111
+
112
+ // --------------------------------------------------------------------------
113
+ // fromCompact
114
+ // --------------------------------------------------------------------------
115
+ describe('fromCompact', () => {
116
+ it('throws when data is not 65 bytes', () => {
117
+ expect(() => Signature.fromCompact(new Array(64).fill(0))).toThrow('Invalid Compact Signature')
118
+ expect(() => Signature.fromCompact(new Array(66).fill(0))).toThrow('Invalid Compact Signature')
119
+ })
120
+
121
+ it('throws when compact byte < 27', () => {
122
+ const data = new Array(65).fill(0)
123
+ data[0] = 26
124
+ expect(() => Signature.fromCompact(data)).toThrow('Invalid Compact Byte')
125
+ })
126
+
127
+ it('throws when compact byte >= 35', () => {
128
+ const data = new Array(65).fill(0)
129
+ data[0] = 35
130
+ expect(() => Signature.fromCompact(data)).toThrow('Invalid Compact Byte')
131
+ })
132
+
133
+ it('parses a valid compact signature', () => {
134
+ const sig = ECDSA.sign(msg, key)
135
+ const compact = sig.toCompact(0, true) as number[]
136
+ const recovered = Signature.fromCompact(compact)
137
+ expect(recovered.r.eq(sig.r)).toBe(true)
138
+ expect(recovered.s.eq(sig.s)).toBe(true)
139
+ })
140
+
141
+ it('parses from hex compact string', () => {
142
+ const sig = ECDSA.sign(msg, key)
143
+ const hexCompact = sig.toCompact(0, true, 'hex') as string
144
+ const recovered = Signature.fromCompact(hexCompact, 'hex')
145
+ expect(recovered.r.eq(sig.r)).toBe(true)
146
+ })
147
+
148
+ it('parses from base64 compact string', () => {
149
+ const sig = ECDSA.sign(msg, key)
150
+ const b64Compact = sig.toCompact(0, true, 'base64') as string
151
+ const recovered = Signature.fromCompact(b64Compact, 'base64')
152
+ expect(recovered.r.eq(sig.r)).toBe(true)
153
+ })
154
+ })
155
+
156
+ // --------------------------------------------------------------------------
157
+ // verify
158
+ // --------------------------------------------------------------------------
159
+ describe('verify', () => {
160
+ it('verifies a valid signature against a string message', () => {
161
+ const privKey = PrivateKey.fromRandom()
162
+ const pubKey = PublicKey.fromPrivateKey(privKey)
163
+ const sig = privKey.sign('hello world')
164
+ expect(sig.verify('hello world', pubKey)).toBe(true)
165
+ })
166
+
167
+ it('returns false for a wrong signature', () => {
168
+ const privKey = PrivateKey.fromRandom()
169
+ const pubKey = PublicKey.fromPrivateKey(privKey)
170
+ const otherKey = PrivateKey.fromRandom()
171
+ const wrongSig = otherKey.sign('hello world')
172
+ expect(wrongSig.verify('hello world', pubKey)).toBe(false)
173
+ })
174
+
175
+ it('verifies with hex encoding', () => {
176
+ const privKey = PrivateKey.fromRandom()
177
+ const pubKey = PublicKey.fromPrivateKey(privKey)
178
+ const sig = privKey.sign('deadbeef', 'hex')
179
+ expect(sig.verify('deadbeef', pubKey, 'hex')).toBe(true)
180
+ })
181
+ })
182
+
183
+ // --------------------------------------------------------------------------
184
+ // toString / toDER
185
+ // --------------------------------------------------------------------------
186
+ describe('toString / toDER', () => {
187
+ it('toString() with no args returns number array (same as toDER)', () => {
188
+ const sig = ECDSA.sign(msg, key)
189
+ const fromToString = sig.toString()
190
+ const fromToDER = sig.toDER()
191
+ expect(fromToString).toEqual(fromToDER)
192
+ })
193
+
194
+ it('toString(\"hex\") returns same as toDER(\"hex\")', () => {
195
+ const sig = ECDSA.sign(msg, key)
196
+ expect(sig.toString('hex')).toBe(sig.toDER('hex'))
197
+ })
198
+
199
+ it('toDER returns number[] by default', () => {
200
+ const sig = ECDSA.sign(msg, key)
201
+ expect(Array.isArray(sig.toDER())).toBe(true)
202
+ })
203
+
204
+ it('toDER(\"hex\") returns hex string', () => {
205
+ const sig = ECDSA.sign(msg, key)
206
+ const hex = sig.toDER('hex')
207
+ expect(typeof hex).toBe('string')
208
+ expect(hex as string).toMatch(/^[0-9a-f]+$/)
209
+ })
210
+
211
+ it('toDER(\"base64\") returns base64 string', () => {
212
+ const sig = ECDSA.sign(msg, key)
213
+ const b64 = sig.toDER('base64')
214
+ expect(typeof b64).toBe('string')
215
+ // base64 uses A-Z, a-z, 0-9, +, /, =
216
+ expect(b64 as string).toMatch(/^[A-Za-z0-9+/=]+$/)
217
+ })
218
+
219
+ it('round-trips through toDER → fromDER', () => {
220
+ const sig = ECDSA.sign(msg, key)
221
+ const der = sig.toDER() as number[]
222
+ const recovered = Signature.fromDER(der)
223
+ expect(recovered.r.eq(sig.r)).toBe(true)
224
+ expect(recovered.s.eq(sig.s)).toBe(true)
225
+ })
226
+ })
227
+
228
+ // --------------------------------------------------------------------------
229
+ // toCompact
230
+ // --------------------------------------------------------------------------
231
+ describe('toCompact', () => {
232
+ it('throws when recovery < 0', () => {
233
+ const sig = ECDSA.sign(msg, key)
234
+ expect(() => sig.toCompact(-1, true)).toThrow('Invalid recovery param')
235
+ })
236
+
237
+ it('throws when recovery > 3', () => {
238
+ const sig = ECDSA.sign(msg, key)
239
+ expect(() => sig.toCompact(4, true)).toThrow('Invalid recovery param')
240
+ })
241
+
242
+ it('throws when compressed is not a boolean', () => {
243
+ const sig = ECDSA.sign(msg, key)
244
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
245
+ expect(() => sig.toCompact(0, 'yes' as any)).toThrow('Invalid compressed param')
246
+ })
247
+
248
+ it('returns number[] by default', () => {
249
+ const sig = ECDSA.sign(msg, key)
250
+ const compact = sig.toCompact(0, true)
251
+ expect(Array.isArray(compact)).toBe(true)
252
+ expect((compact as number[]).length).toBe(65)
253
+ })
254
+
255
+ it('returns hex string when enc=\"hex\"', () => {
256
+ const sig = ECDSA.sign(msg, key)
257
+ const hex = sig.toCompact(0, true, 'hex')
258
+ expect(typeof hex).toBe('string')
259
+ expect((hex as string).length).toBe(130)
260
+ })
261
+
262
+ it('returns base64 string when enc=\"base64\"', () => {
263
+ const sig = ECDSA.sign(msg, key)
264
+ const b64 = sig.toCompact(0, true, 'base64')
265
+ expect(typeof b64).toBe('string')
266
+ })
267
+
268
+ it('compact byte = 27 + recovery for uncompressed (recovery=0)', () => {
269
+ const sig = ECDSA.sign(msg, key)
270
+ const compact = sig.toCompact(0, false) as number[]
271
+ expect(compact[0]).toBe(27)
272
+ })
273
+
274
+ it('compact byte = 31 + recovery for compressed (recovery=0)', () => {
275
+ const sig = ECDSA.sign(msg, key)
276
+ const compact = sig.toCompact(0, true) as number[]
277
+ expect(compact[0]).toBe(31)
278
+ })
279
+ })
280
+
281
+ // --------------------------------------------------------------------------
282
+ // RecoverPublicKey
283
+ // --------------------------------------------------------------------------
284
+ describe('RecoverPublicKey', () => {
285
+ it('recovers the public key from a signature', () => {
286
+ const privKey = PrivateKey.fromRandom()
287
+ const pubKey = PublicKey.fromPrivateKey(privKey)
288
+ const msgHash = new BigNumber('deadbeef', 16)
289
+ const sig = ECDSA.sign(msgHash, privKey)
290
+
291
+ let recovered: PublicKey | null = null
292
+ for (let r = 0; r <= 3; r++) {
293
+ try {
294
+ const candidate = sig.RecoverPublicKey(r, msgHash)
295
+ if (candidate.toString() === pubKey.toString()) {
296
+ recovered = candidate
297
+ break
298
+ }
299
+ } catch {
300
+ // try next
301
+ }
302
+ }
303
+ expect(recovered).not.toBeNull()
304
+ })
305
+ })
306
+
307
+ // --------------------------------------------------------------------------
308
+ // CalculateRecoveryFactor
309
+ // --------------------------------------------------------------------------
310
+ describe('CalculateRecoveryFactor', () => {
311
+ it('returns a valid recovery factor for a known key/msg pair', () => {
312
+ const privKey = PrivateKey.fromRandom()
313
+ const pubKey = PublicKey.fromPrivateKey(privKey)
314
+ const msgHash = new BigNumber('cafebabe', 16)
315
+ const sig = ECDSA.sign(msgHash, privKey)
316
+
317
+ const factor = sig.CalculateRecoveryFactor(pubKey, msgHash)
318
+ expect(factor).toBeGreaterThanOrEqual(0)
319
+ expect(factor).toBeLessThanOrEqual(3)
320
+ })
321
+
322
+ it('throws when no valid recovery factor can be found', () => {
323
+ const privKey = PrivateKey.fromRandom()
324
+ const wrongKey = PublicKey.fromPrivateKey(PrivateKey.fromRandom())
325
+ const msgHash = new BigNumber('cafebabe', 16)
326
+ const sig = ECDSA.sign(msgHash, privKey)
327
+
328
+ expect(() => sig.CalculateRecoveryFactor(wrongKey, msgHash)).toThrow(
329
+ 'Unable to find valid recovery factor'
330
+ )
331
+ })
332
+ })
333
+ })
@@ -0,0 +1,241 @@
1
+ import TransactionSignature, { SignatureHashCache } from '../../primitives/TransactionSignature'
2
+ import BigNumber from '../../primitives/BigNumber'
3
+ import LockingScript from '../../script/LockingScript'
4
+ import Script from '../../script/Script'
5
+ import Transaction from '../../transaction/Transaction'
6
+
7
+ const ZERO_TXID = '0'.repeat(64)
8
+
9
+ function makeParams (overrides: Partial<Parameters<typeof TransactionSignature.formatBip143>[0]> = {}): Parameters<typeof TransactionSignature.formatBip143>[0] {
10
+ const defaultScript = new LockingScript()
11
+ return {
12
+ sourceTXID: ZERO_TXID,
13
+ sourceOutputIndex: 0,
14
+ sourceSatoshis: 1000,
15
+ transactionVersion: 1,
16
+ otherInputs: [],
17
+ outputs: [{ lockingScript: defaultScript, satoshis: 900 }],
18
+ inputIndex: 0,
19
+ subscript: new Script(),
20
+ inputSequence: 0xffffffff,
21
+ lockTime: 0,
22
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID,
23
+ ...overrides
24
+ }
25
+ }
26
+
27
+ describe('TransactionSignature – additional coverage', () => {
28
+ describe('hasLowS', () => {
29
+ it('returns false when s < 1', () => {
30
+ const sig = new TransactionSignature(new BigNumber(1), new BigNumber(0), 1)
31
+ expect(sig.hasLowS()).toBe(false)
32
+ })
33
+
34
+ it('returns true for a normal low-s value', () => {
35
+ const sig = new TransactionSignature(new BigNumber(1), new BigNumber(100), 1)
36
+ expect(sig.hasLowS()).toBe(true)
37
+ })
38
+ })
39
+
40
+ describe('fromChecksigFormat', () => {
41
+ it('creates blank signature when buffer is empty', () => {
42
+ const sig = TransactionSignature.fromChecksigFormat([])
43
+ expect(sig.scope).toBe(1)
44
+ expect(sig.r.eqn(1)).toBe(true)
45
+ })
46
+ })
47
+
48
+ describe('formatBip143 – SIGHASH_NONE', () => {
49
+ it('produces output with empty hashSequence and hashOutputs for SIGHASH_NONE', () => {
50
+ const params = makeParams({
51
+ scope: TransactionSignature.SIGHASH_NONE | TransactionSignature.SIGHASH_FORKID
52
+ })
53
+ const result = TransactionSignature.formatBip143(params)
54
+ expect(result).toBeInstanceOf(Uint8Array)
55
+ expect(result.length).toBeGreaterThan(0)
56
+ })
57
+
58
+ it('SIGHASH_NONE with ANYONECANPAY skips prevouts hash', () => {
59
+ const params = makeParams({
60
+ scope: TransactionSignature.SIGHASH_NONE | TransactionSignature.SIGHASH_FORKID | TransactionSignature.SIGHASH_ANYONECANPAY
61
+ })
62
+ const result = TransactionSignature.formatBip143(params)
63
+ expect(result).toBeInstanceOf(Uint8Array)
64
+ })
65
+ })
66
+
67
+ describe('formatBip143 – SIGHASH_SINGLE', () => {
68
+ it('produces hashOutputs for the current input index', () => {
69
+ const params = makeParams({
70
+ scope: TransactionSignature.SIGHASH_SINGLE | TransactionSignature.SIGHASH_FORKID
71
+ })
72
+ const result = TransactionSignature.formatBip143(params)
73
+ expect(result).toBeInstanceOf(Uint8Array)
74
+ })
75
+
76
+ it('uses zero hashOutputs when inputIndex >= outputs.length', () => {
77
+ const params = makeParams({
78
+ scope: TransactionSignature.SIGHASH_SINGLE | TransactionSignature.SIGHASH_FORKID,
79
+ inputIndex: 0,
80
+ outputs: [] // no outputs → inputIndex (0) >= outputs.length (0)
81
+ })
82
+ const result = TransactionSignature.formatBip143(params)
83
+ expect(result).toBeInstanceOf(Uint8Array)
84
+ })
85
+ })
86
+
87
+ describe('formatBip143 – SIGHASH_ANYONECANPAY', () => {
88
+ it('skips prevouts and sequence hash for ANYONECANPAY | ALL', () => {
89
+ const params = makeParams({
90
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID | TransactionSignature.SIGHASH_ANYONECANPAY
91
+ })
92
+ const result = TransactionSignature.formatBip143(params)
93
+ expect(result).toBeInstanceOf(Uint8Array)
94
+ })
95
+ })
96
+
97
+ describe('formatBip143 – cache', () => {
98
+ it('uses cached hashPrevouts and hashSequence on second call', () => {
99
+ const params = makeParams()
100
+ const cache: SignatureHashCache = {}
101
+
102
+ const first = TransactionSignature.formatBip143({ ...params, cache })
103
+ expect(cache.hashPrevouts).toBeDefined()
104
+ expect(cache.hashSequence).toBeDefined()
105
+ expect(cache.hashOutputsAll).toBeDefined()
106
+
107
+ // Second call uses cached values
108
+ const second = TransactionSignature.formatBip143({ ...params, cache })
109
+ expect(first).toEqual(second)
110
+ })
111
+
112
+ it('caches hashOutputsSingle for SIGHASH_SINGLE and reuses it', () => {
113
+ const params = makeParams({
114
+ scope: TransactionSignature.SIGHASH_SINGLE | TransactionSignature.SIGHASH_FORKID
115
+ })
116
+ const cache: SignatureHashCache = {}
117
+
118
+ const first = TransactionSignature.formatBip143({ ...params, cache })
119
+ expect(cache.hashOutputsSingle).toBeDefined()
120
+
121
+ // Second call should hit the cached single output hash
122
+ const second = TransactionSignature.formatBip143({ ...params, cache })
123
+ expect(first).toEqual(second)
124
+ })
125
+ })
126
+
127
+ describe('formatBytes – routing', () => {
128
+ it('routes to formatOTDA when no SIGHASH_FORKID', () => {
129
+ const params = makeParams({
130
+ scope: TransactionSignature.SIGHASH_ALL // no FORKID
131
+ })
132
+ const result = TransactionSignature.formatBytes(params)
133
+ expect(result).toBeInstanceOf(Uint8Array)
134
+ })
135
+
136
+ it('routes to formatOTDA when SIGHASH_CHRONICLE is set', () => {
137
+ const params = makeParams({
138
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID | TransactionSignature.SIGHASH_CHRONICLE
139
+ })
140
+ const result = TransactionSignature.formatBytes(params)
141
+ expect(result).toBeInstanceOf(Uint8Array)
142
+ })
143
+
144
+ it('routes to formatBip143 for standard FORKID signing', () => {
145
+ const params = makeParams()
146
+ const result = TransactionSignature.formatBytes(params)
147
+ expect(result).toBeInstanceOf(Uint8Array)
148
+ })
149
+ })
150
+
151
+ describe('formatOTDA – SIGHASH variants', () => {
152
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
153
+ const makeOTDA = (overrides = {}) => ({
154
+ scope: TransactionSignature.SIGHASH_ALL,
155
+ sourceTXID: ZERO_TXID,
156
+ sourceOutputIndex: 0,
157
+ sourceSatoshis: 1000,
158
+ transactionVersion: 1,
159
+ otherInputs: [
160
+ {
161
+ sourceTXID: '1'.repeat(64),
162
+ sourceOutputIndex: 1,
163
+ sequence: 0xffffffff
164
+ }
165
+ ],
166
+ outputs: [
167
+ { lockingScript: new LockingScript(), satoshis: 900 },
168
+ { lockingScript: new LockingScript(), satoshis: 50 }
169
+ ],
170
+ inputIndex: 0,
171
+ subscript: new Script(),
172
+ inputSequence: 0xffffffff,
173
+ lockTime: 0,
174
+ ...overrides
175
+ })
176
+
177
+ it('handles SIGHASH_ALL', () => {
178
+ const result = TransactionSignature.formatOTDA(makeOTDA({ scope: TransactionSignature.SIGHASH_ALL }))
179
+ expect(result).toBeInstanceOf(Uint8Array)
180
+ })
181
+
182
+ it('handles SIGHASH_NONE', () => {
183
+ const result = TransactionSignature.formatOTDA(makeOTDA({ scope: TransactionSignature.SIGHASH_NONE }))
184
+ expect(result).toBeInstanceOf(Uint8Array)
185
+ })
186
+
187
+ it('handles SIGHASH_SINGLE with output at inputIndex', () => {
188
+ const result = TransactionSignature.formatOTDA(makeOTDA({ scope: TransactionSignature.SIGHASH_SINGLE }))
189
+ expect(result).toBeInstanceOf(Uint8Array)
190
+ })
191
+
192
+ it('handles SIGHASH_ANYONECANPAY | SIGHASH_ALL', () => {
193
+ const result = TransactionSignature.formatOTDA(makeOTDA({
194
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_ANYONECANPAY
195
+ }))
196
+ expect(result).toBeInstanceOf(Uint8Array)
197
+ })
198
+
199
+ it('handles SIGHASH_SINGLE when inputIndex >= outputs.length', () => {
200
+ const result = TransactionSignature.formatOTDA(makeOTDA({
201
+ scope: TransactionSignature.SIGHASH_SINGLE,
202
+ inputIndex: 5,
203
+ otherInputs: []
204
+ }))
205
+ expect(result).toBeInstanceOf(Uint8Array)
206
+ })
207
+ })
208
+
209
+ describe('formatBip143 – sourceTransaction path', () => {
210
+ it('uses sourceTransaction.hash() when sourceTXID is undefined', () => {
211
+ const sourceTx = new Transaction(1, [], [{ lockingScript: new LockingScript(), satoshis: 1000 }], 0)
212
+ const params = makeParams({
213
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID,
214
+ otherInputs: [
215
+ {
216
+ sourceTransaction: sourceTx,
217
+ sourceOutputIndex: 0,
218
+ sequence: 0xffffffff
219
+ // no sourceTXID → forces the sourceTransaction branch
220
+ }
221
+ ]
222
+ })
223
+ const result = TransactionSignature.formatBip143(params)
224
+ expect(result).toBeInstanceOf(Uint8Array)
225
+ })
226
+
227
+ it('throws when sourceTXID is undefined and sourceTransaction is null', () => {
228
+ const params = makeParams({
229
+ scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID,
230
+ otherInputs: [
231
+ {
232
+ sourceOutputIndex: 0,
233
+ sequence: 0xffffffff
234
+ // no sourceTXID, no sourceTransaction → should throw
235
+ }
236
+ ]
237
+ })
238
+ expect(() => TransactionSignature.formatBip143(params)).toThrow('Missing sourceTransaction for input')
239
+ })
240
+ })
241
+ })