@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.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +827 -0
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +654 -0
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
- package/dist/cjs/src/transaction/MerklePath.js +132 -0
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +825 -0
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +1 -0
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +619 -0
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +1 -0
- package/dist/esm/src/transaction/MerklePath.js +132 -0
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +21 -0
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +1 -0
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +2 -0
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +1 -0
- package/dist/types/src/transaction/MerklePath.d.ts +27 -0
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/dist/umd/bundle.js.map +1 -1
- package/docs/reference/storage.md +1 -1
- package/docs/reference/transaction.md +40 -0
- package/package.json +1 -1
- package/src/auth/clients/__tests__/AuthFetch.additional.test.ts +1131 -0
- package/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.ts +770 -0
- package/src/compat/__tests/Mnemonic.additional.test.ts +64 -0
- package/src/identity/__tests/IdentityClient.additional.test.ts +767 -0
- package/src/kvstore/__tests/LocalKVStore.additional.test.ts +611 -0
- package/src/kvstore/__tests/kvStoreInterpreter.test.ts +327 -0
- package/src/overlay-tools/__tests/HostReputationTracker.additional.test.ts +561 -0
- package/src/overlay-tools/__tests/LookupResolver.additional.test.ts +612 -0
- package/src/overlay-tools/__tests/withDoubleSpendRetry.test.ts +278 -0
- package/src/primitives/__tests/BigNumber.additional.test.ts +79 -0
- package/src/primitives/__tests/Curve.additional.test.ts +208 -0
- package/src/primitives/__tests/ECDSA.additional.test.ts +122 -0
- package/src/primitives/__tests/Hash.additional.test.ts +59 -0
- package/src/primitives/__tests/JacobianPoint.test.ts +308 -0
- package/src/primitives/__tests/Point.additional.test.ts +503 -0
- package/src/primitives/__tests/PublicKey.additional.test.ts +383 -0
- package/src/primitives/__tests/Random.additional.test.ts +262 -0
- package/src/primitives/__tests/Signature.test.ts +333 -0
- package/src/primitives/__tests/TransactionSignature.additional.test.ts +241 -0
- package/src/registry/__tests/RegistryClient.additional.test.ts +750 -0
- package/src/remittance/__tests/BasicBRC29.additional.test.ts +657 -0
- package/src/remittance/__tests/RemittanceManager.additional.test.ts +1272 -0
- package/src/script/__tests/LockingUnlockingScript.test.ts +79 -0
- package/src/script/__tests/Script.additional.test.ts +100 -0
- package/src/script/__tests/ScriptEvaluationError.test.ts +98 -0
- package/src/script/__tests/Spend.additional.test.ts +837 -0
- package/src/script/templates/__tests/RPuzzle.test.ts +134 -0
- package/src/transaction/MerklePath.ts +155 -0
- package/src/transaction/__tests/BeefParty.additional.test.ts +22 -0
- package/src/transaction/__tests/Broadcaster.test.ts +159 -0
- package/src/transaction/__tests/MerklePath.bench.test.ts +105 -0
- package/src/transaction/__tests/MerklePath.test.ts +80 -0
- package/src/transaction/__tests/Transaction.additional.test.ts +225 -0
- package/src/transaction/broadcasters/__tests/ARC.additional.test.ts +585 -0
- package/src/transaction/broadcasters/__tests/Teranode.test.ts +349 -0
- package/src/transaction/chaintrackers/__tests/BlockHeadersService.test.ts +253 -0
- package/src/transaction/chaintrackers/__tests/DefaultChainTracker.test.ts +44 -0
- package/src/transaction/chaintrackers/__tests/WhatsOnChain.additional.test.ts +193 -0
- package/src/transaction/fee-models/__tests/SatoshisPerKilobyte.test.ts +262 -0
- package/src/transaction/http/__tests/BinaryFetchClient.test.ts +212 -0
- package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +192 -0
- package/src/transaction/http/__tests/DefaultHttpClient.test.ts +71 -0
- package/src/wallet/__tests/ProtoWallet.additional.test.ts +134 -0
- package/src/wallet/__tests/WERR.test.ts +212 -0
- package/src/wallet/__tests/WalletClient.additional.test.ts +699 -0
- package/src/wallet/__tests/WalletClient.substrate.test.ts +759 -0
- package/src/wallet/__tests/WalletError.test.ts +290 -0
- package/src/wallet/__tests/validationHelpers.test.ts +1218 -0
- package/src/wallet/substrates/__tests/HTTPWalletJSON.test.ts +496 -0
- 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
|
+
})
|