@bsv/sdk 2.0.14 → 2.0.15

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 (90) hide show
  1. package/dist/cjs/package.json +14 -14
  2. package/dist/cjs/src/primitives/Hash.js +1 -1
  3. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  4. package/dist/cjs/src/primitives/TransactionSignature.js +10 -3
  5. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  6. package/dist/cjs/src/script/Script.js +60 -13
  7. package/dist/cjs/src/script/Script.js.map +1 -1
  8. package/dist/cjs/src/script/Spend.js +434 -59
  9. package/dist/cjs/src/script/Spend.js.map +1 -1
  10. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +6 -2
  11. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  12. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +8 -4
  13. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  14. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  15. package/dist/esm/src/primitives/Hash.js +1 -1
  16. package/dist/esm/src/primitives/Hash.js.map +1 -1
  17. package/dist/esm/src/primitives/TransactionSignature.js +10 -3
  18. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  19. package/dist/esm/src/script/Script.js +60 -13
  20. package/dist/esm/src/script/Script.js.map +1 -1
  21. package/dist/esm/src/script/Spend.js +438 -59
  22. package/dist/esm/src/script/Spend.js.map +1 -1
  23. package/dist/esm/src/transaction/http/BinaryFetchClient.js +6 -2
  24. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  25. package/dist/esm/src/transaction/http/DefaultHttpClient.js +8 -4
  26. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  27. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  28. package/dist/types/src/primitives/TransactionSignature.d.ts +1 -0
  29. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  30. package/dist/types/src/script/Script.d.ts +1 -0
  31. package/dist/types/src/script/Script.d.ts.map +1 -1
  32. package/dist/types/src/script/ScriptChunk.d.ts +1 -0
  33. package/dist/types/src/script/ScriptChunk.d.ts.map +1 -1
  34. package/dist/types/src/script/Spend.d.ts +29 -0
  35. package/dist/types/src/script/Spend.d.ts.map +1 -1
  36. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
  37. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +2 -2
  38. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -1
  39. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  40. package/dist/umd/bundle.js +3 -4
  41. package/docs/reference/primitives.md +1 -0
  42. package/docs/reference/script.md +7 -0
  43. package/docs/reference/transaction.md +2 -2
  44. package/package.json +14 -14
  45. package/src/primitives/Hash.ts +1 -1
  46. package/src/primitives/TransactionSignature.ts +11 -3
  47. package/src/script/Script.ts +59 -13
  48. package/src/script/ScriptChunk.ts +1 -0
  49. package/src/script/Spend.ts +483 -61
  50. package/src/script/__tests/NormativeVectors.test.ts +465 -0
  51. package/src/script/__tests/fixtures/SOURCES.md +25 -0
  52. package/src/script/__tests/fixtures/bitcoin-sv/script_tests.json +2591 -0
  53. package/src/script/__tests/fixtures/bitcoin-sv/sighash.json +1003 -0
  54. package/src/script/__tests/fixtures/bitcoin-sv/tx_invalid.json +285 -0
  55. package/src/script/__tests/fixtures/bitcoin-sv/tx_valid.json +367 -0
  56. package/src/script/__tests/fixtures/teranode/script_tests.json +2432 -0
  57. package/src/script/__tests/fixtures/teranode/sighash.json +1003 -0
  58. package/src/script/__tests/fixtures/teranode/tx_invalid.json +285 -0
  59. package/src/script/__tests/fixtures/teranode/tx_valid.json +367 -0
  60. package/src/transaction/broadcasters/__tests/ARC.test.ts +26 -4
  61. package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +26 -4
  62. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -10
  63. package/src/transaction/http/BinaryFetchClient.ts +5 -2
  64. package/src/transaction/http/DefaultHttpClient.ts +7 -4
  65. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +19 -1
  66. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -827
  67. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  68. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +0 -266
  69. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  70. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -654
  71. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  72. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -144
  73. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  74. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -825
  75. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  76. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +0 -264
  77. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  78. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -619
  79. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  80. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -109
  81. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  82. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +0 -21
  83. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +0 -1
  84. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +0 -2
  85. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +0 -1
  86. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +0 -2
  87. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +0 -1
  88. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +0 -2
  89. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +0 -1
  90. package/dist/umd/bundle.js.map +0 -1
@@ -0,0 +1,465 @@
1
+ import { createHash } from 'crypto'
2
+ import { readFileSync } from 'fs'
3
+ import { join } from 'path'
4
+ import BigNumber from '../../primitives/BigNumber'
5
+ import { hash160, hash256 } from '../../primitives/Hash'
6
+ import TransactionSignature from '../../primitives/TransactionSignature'
7
+ import { toArray, toHex } from '../../primitives/utils'
8
+ import Transaction from '../../transaction/Transaction'
9
+ import LockingScript from '../LockingScript'
10
+ import OP from '../OP'
11
+ import Script from '../Script'
12
+ import ScriptChunk from '../ScriptChunk'
13
+ import Spend from '../Spend'
14
+ import UnlockingScript from '../UnlockingScript'
15
+
16
+ type JsonValue = string | number | JsonValue[]
17
+
18
+ const errorSummary = (e: unknown): string => String((e as Error).message ?? e).split('\n')[0]
19
+ const amountFromJSON = (amount: JsonValue): number => Math.round(Number(amount) * 100000000)
20
+
21
+ interface ScriptVector {
22
+ source: string
23
+ index: number
24
+ amount: number
25
+ txVersion: number
26
+ scriptSig: string
27
+ scriptPubKey: string
28
+ flags: string
29
+ expected: string
30
+ comment: string
31
+ }
32
+
33
+ interface TxVector {
34
+ source: string
35
+ index: number
36
+ prevouts: Array<[string, number, string, number?]>
37
+ txHex: string
38
+ flags: string[]
39
+ }
40
+
41
+ interface SighashVector {
42
+ source: string
43
+ index: number
44
+ txHex: string
45
+ scriptHex: string
46
+ inputIndex: number
47
+ hashType: number
48
+ regularHash: string
49
+ originalHash: string
50
+ }
51
+
52
+ const ZERO_TXID = '0'.repeat(64)
53
+ const FIXTURE_ROOT = join(process.cwd(), 'src/script/__tests/fixtures')
54
+
55
+ const fixtureSources = [
56
+ {
57
+ name: 'bitcoin-sv',
58
+ path: 'bitcoin-sv',
59
+ shas: {
60
+ 'script_tests.json': 'a77f8b94412ef61e9ee59980ebc682a64212b47a16f06d87f809d91770ba496d',
61
+ 'sighash.json': '9c1afcaf81e8482f818345efa8a3f0610f6541b975023b58550d50ad2a557f63',
62
+ 'tx_invalid.json': '536a533f00714374d15fb24f601b989a87f9447a7a414a6532380e41c09c4fbe',
63
+ 'tx_valid.json': '20e42308ead38db645454c4def763288e8cef2e5c47a4d9088e53dc7fc01419d'
64
+ }
65
+ },
66
+ {
67
+ name: 'teranode',
68
+ path: 'teranode',
69
+ shas: {
70
+ 'script_tests.json': '3577a2e6e71e3356c5ec06eaa5fffb243e563eeef3946bd9b0285a54f0ad87f9',
71
+ 'sighash.json': 'ca2d6449c7cb98b8a83f3e18dc994d8227001d87adedd907b5e9a235114e283f',
72
+ 'tx_invalid.json': '536a533f00714374d15fb24f601b989a87f9447a7a414a6532380e41c09c4fbe',
73
+ 'tx_valid.json': '20e42308ead38db645454c4def763288e8cef2e5c47a4d9088e53dc7fc01419d'
74
+ }
75
+ }
76
+ ]
77
+
78
+ function readFixture<T = JsonValue[]> (source: string, file: string): T {
79
+ return JSON.parse(readFileSync(join(FIXTURE_ROOT, source, file), 'utf8')) as T
80
+ }
81
+
82
+ function sha256File (source: string, file: string): string {
83
+ return createHash('sha256')
84
+ .update(readFileSync(join(FIXTURE_ROOT, source, file)))
85
+ .digest('hex')
86
+ }
87
+
88
+ function scriptNumBytes (value: bigint): number[] {
89
+ if (value === 0n) return []
90
+ const negative = value < 0n
91
+ let absValue = negative ? -value : value
92
+ const bytes: number[] = []
93
+ while (absValue > 0n) {
94
+ bytes.push(Number(absValue & 0xffn))
95
+ absValue >>= 8n
96
+ }
97
+ if ((bytes[bytes.length - 1] & 0x80) !== 0) {
98
+ bytes.push(negative ? 0x80 : 0)
99
+ } else if (negative) {
100
+ bytes[bytes.length - 1] |= 0x80
101
+ }
102
+ return bytes
103
+ }
104
+
105
+ function writePush (bytes: number[], out: number[]): void {
106
+ if (bytes.length === 0) {
107
+ out.push(OP.OP_0)
108
+ } else if (bytes.length === 1 && bytes[0] >= 1 && bytes[0] <= 16) {
109
+ out.push(OP.OP_1 + bytes[0] - 1)
110
+ } else if (bytes.length === 1 && bytes[0] === 0x81) {
111
+ out.push(OP.OP_1NEGATE)
112
+ } else if (bytes.length < OP.OP_PUSHDATA1) {
113
+ out.push(bytes.length, ...bytes)
114
+ } else if (bytes.length <= 0xff) {
115
+ out.push(OP.OP_PUSHDATA1, bytes.length, ...bytes)
116
+ } else if (bytes.length <= 0xffff) {
117
+ out.push(OP.OP_PUSHDATA2, bytes.length & 0xff, (bytes.length >> 8) & 0xff, ...bytes)
118
+ } else {
119
+ out.push(
120
+ OP.OP_PUSHDATA4,
121
+ bytes.length & 0xff,
122
+ (bytes.length >> 8) & 0xff,
123
+ (bytes.length >> 16) & 0xff,
124
+ (bytes.length >> 24) & 0xff,
125
+ ...bytes
126
+ )
127
+ }
128
+ }
129
+
130
+ function tokenToOpcode (token: string): number | undefined {
131
+ if (token === 'TRUE') return OP.OP_TRUE
132
+ if (token === 'FALSE') return OP.OP_FALSE
133
+ const opToken = token.startsWith('OP_') ? token : `OP_${token}`
134
+ const opcode = (OP as Record<string, number>)[opToken]
135
+ return typeof opcode === 'number' ? opcode : undefined
136
+ }
137
+
138
+ function tokenizeAsm (asm: string): string[] {
139
+ return asm.match(/'[^']*'|\S+/g) ?? []
140
+ }
141
+
142
+ function parseNodeAsm (asm: string): number[] {
143
+ const out: number[] = []
144
+ for (const token of tokenizeAsm(asm)) {
145
+ if (token.startsWith('0x')) {
146
+ out.push(...toArray(token.slice(2), 'hex'))
147
+ continue
148
+ }
149
+
150
+ if (token.startsWith("'") && token.endsWith("'")) {
151
+ writePush(Array.from(Buffer.from(token.slice(1, -1), 'utf8')), out)
152
+ continue
153
+ }
154
+
155
+ if (/^-?\d+$/.test(token)) {
156
+ writePush(scriptNumBytes(BigInt(token)), out)
157
+ continue
158
+ }
159
+
160
+ const opcode = tokenToOpcode(token)
161
+ if (opcode === undefined) throw new Error(`Unsupported script token: ${token}`)
162
+ out.push(opcode)
163
+ }
164
+ return out
165
+ }
166
+
167
+ function toLockingScript (asm: string): LockingScript {
168
+ return new LockingScript(Script.fromBinary(parseNodeAsm(asm)).chunks.map(cloneChunk))
169
+ }
170
+
171
+ function toUnlockingScript (asm: string): UnlockingScript {
172
+ return new UnlockingScript(Script.fromBinary(parseNodeAsm(asm)).chunks.map(cloneChunk))
173
+ }
174
+
175
+ function cloneChunk (chunk: ScriptChunk): ScriptChunk {
176
+ return {
177
+ op: chunk.op,
178
+ data: Array.isArray(chunk.data) ? chunk.data.slice() : undefined,
179
+ invalidLength: chunk.invalidLength
180
+ }
181
+ }
182
+
183
+ function buildCreditingTransaction (lockingScript: LockingScript, amount: number): Transaction {
184
+ return new Transaction(
185
+ 1,
186
+ [{
187
+ sourceTXID: ZERO_TXID,
188
+ sourceOutputIndex: 0xffffffff,
189
+ unlockingScript: new UnlockingScript([{ op: OP.OP_0 }, { op: OP.OP_0 }]),
190
+ sequence: 0xffffffff
191
+ }],
192
+ [{ lockingScript, satoshis: amount }],
193
+ 0
194
+ )
195
+ }
196
+
197
+ function buildScriptSpend (vector: ScriptVector): Spend {
198
+ const lockingScript = toLockingScript(vector.scriptPubKey)
199
+ const creditTx = buildCreditingTransaction(lockingScript, vector.amount)
200
+ return new Spend({
201
+ sourceTXID: creditTx.id('hex'),
202
+ sourceOutputIndex: 0,
203
+ sourceSatoshis: vector.amount,
204
+ lockingScript,
205
+ transactionVersion: vector.txVersion,
206
+ otherInputs: [],
207
+ outputs: [{ lockingScript: new LockingScript(), satoshis: vector.amount }],
208
+ inputIndex: 0,
209
+ unlockingScript: toUnlockingScript(vector.scriptSig),
210
+ inputSequence: 0xffffffff,
211
+ lockTime: 0,
212
+ verifyFlags: vector.flags
213
+ })
214
+ }
215
+
216
+ function parseScriptVectors (source: string): ScriptVector[] {
217
+ const raw = readFixture<JsonValue[]>(source, 'script_tests.json')
218
+ const vectors: ScriptVector[] = []
219
+ raw.forEach((entry, index) => {
220
+ if (!Array.isArray(entry) || entry.length <= 1) return
221
+ if (source === 'bitcoin-sv') {
222
+ let offset = 0
223
+ let amount = 0
224
+ if (Array.isArray(entry[0])) {
225
+ amount = amountFromJSON(entry[0][0])
226
+ offset = 1
227
+ }
228
+ vectors.push({
229
+ source,
230
+ index,
231
+ amount,
232
+ txVersion: Number(entry[offset]),
233
+ scriptSig: String(entry[offset + 1]),
234
+ scriptPubKey: String(entry[offset + 2]),
235
+ flags: String(entry[offset + 3]),
236
+ expected: String(entry[offset + 4]),
237
+ comment: entry[offset + 5] === undefined ? '' : String(entry[offset + 5])
238
+ })
239
+ return
240
+ }
241
+
242
+ let offset = 0
243
+ let amount = 0
244
+ if (Array.isArray(entry[0])) {
245
+ amount = amountFromJSON(entry[0][0])
246
+ offset = 1
247
+ }
248
+ vectors.push({
249
+ source,
250
+ index,
251
+ amount,
252
+ txVersion: 1,
253
+ scriptSig: String(entry[offset]),
254
+ scriptPubKey: String(entry[offset + 1]),
255
+ flags: String(entry[offset + 2]),
256
+ expected: String(entry[offset + 3]),
257
+ comment: entry[offset + 4] === undefined ? '' : String(entry[offset + 4])
258
+ })
259
+ })
260
+ return vectors
261
+ }
262
+
263
+ function parseTxVectors (source: string, file: string): TxVector[] {
264
+ const raw = readFixture<JsonValue[]>(source, file)
265
+ const vectors: TxVector[] = []
266
+ raw.forEach((entry, index) => {
267
+ if (!Array.isArray(entry) || entry.length <= 1) return
268
+ const flags = Array.isArray(entry[2]) ? entry[2].map(String) : [String(entry[2])]
269
+ vectors.push({
270
+ source,
271
+ index,
272
+ prevouts: entry[0] as Array<[string, number, string, number?]>,
273
+ txHex: String(entry[1]),
274
+ flags
275
+ })
276
+ })
277
+ return vectors
278
+ }
279
+
280
+ function parseSighashVectors (source: string): SighashVector[] {
281
+ const raw = readFixture<JsonValue[]>(source, 'sighash.json')
282
+ const vectors: SighashVector[] = []
283
+ raw.forEach((entry, index) => {
284
+ if (!Array.isArray(entry) || entry.length <= 1) return
285
+ vectors.push({
286
+ source,
287
+ index,
288
+ txHex: String(entry[0]),
289
+ scriptHex: String(entry[1]),
290
+ inputIndex: Number(entry[2]),
291
+ hashType: Number(entry[3]),
292
+ regularHash: String(entry[4]),
293
+ originalHash: String(entry[5])
294
+ })
295
+ })
296
+ return vectors
297
+ }
298
+
299
+ function validateTxVectorInput (vector: TxVector, flags: string, inputIndex: number): boolean {
300
+ const tx = Transaction.fromHex(vector.txHex)
301
+ const input = tx.inputs[inputIndex]
302
+ const prevout = vector.prevouts.find(([txid, vout]) =>
303
+ txid === input.sourceTXID && (vout >>> 0) === input.sourceOutputIndex
304
+ )
305
+ if (prevout === undefined || input.unlockingScript === undefined) {
306
+ throw new Error(`Missing prevout fixture for input ${inputIndex}`)
307
+ }
308
+
309
+ const otherInputs = [...tx.inputs]
310
+ otherInputs.splice(inputIndex, 1)
311
+ const spend = new Spend({
312
+ sourceTXID: input.sourceTXID ?? '',
313
+ sourceOutputIndex: input.sourceOutputIndex,
314
+ sourceSatoshis: prevout[3] ?? 0,
315
+ lockingScript: toLockingScript(prevout[2]),
316
+ transactionVersion: tx.version,
317
+ otherInputs,
318
+ outputs: tx.outputs,
319
+ inputIndex,
320
+ unlockingScript: input.unlockingScript,
321
+ inputSequence: input.sequence ?? 0xffffffff,
322
+ lockTime: tx.lockTime,
323
+ verifyFlags: flags
324
+ })
325
+ return spend.validate()
326
+ }
327
+
328
+ function computeSignatureHashes (vector: SighashVector): { regular: string, original: string } {
329
+ const tx = Transaction.fromHex(vector.txHex)
330
+ const input = tx.inputs[vector.inputIndex]
331
+ const otherInputs = [...tx.inputs]
332
+ otherInputs.splice(vector.inputIndex, 1)
333
+ const params = {
334
+ sourceTXID: input.sourceTXID ?? '',
335
+ sourceOutputIndex: input.sourceOutputIndex,
336
+ sourceSatoshis: 0,
337
+ transactionVersion: tx.version,
338
+ otherInputs,
339
+ outputs: tx.outputs,
340
+ inputIndex: vector.inputIndex,
341
+ subscript: Script.fromHex(vector.scriptHex),
342
+ inputSequence: input.sequence ?? 0xffffffff,
343
+ lockTime: tx.lockTime,
344
+ scope: vector.hashType
345
+ }
346
+ // Teranode's go-bt-derived vectors route FORKID signatures through the
347
+ // forkid digest even when the Chronicle bit is present. bitcoin-sv keeps
348
+ // the Chronicle bit as an OTDA selector, which remains the SDK default.
349
+ const regular = hash256(TransactionSignature.format({
350
+ ...params,
351
+ ignoreChronicle: vector.source === 'teranode'
352
+ })).reverse()
353
+ const original = hash256(TransactionSignature.formatOTDA(params)).reverse()
354
+ return {
355
+ regular: toHex(regular),
356
+ original: toHex(original)
357
+ }
358
+ }
359
+
360
+ describe('Normative BSV node script fixtures', () => {
361
+ it('authenticates fixture checksums', () => {
362
+ for (const source of fixtureSources) {
363
+ for (const [file, sha] of Object.entries(source.shas)) {
364
+ expect(sha256File(source.path, file)).toBe(sha)
365
+ }
366
+ }
367
+ })
368
+
369
+ for (const source of fixtureSources) {
370
+ describe(`${source.name} script_tests.json`, () => {
371
+ const vectors = parseScriptVectors(source.path)
372
+ it(`executes all ${vectors.length} script vectors with expected boolean result`, () => {
373
+ const failures: string[] = []
374
+ for (const vector of vectors) {
375
+ const run = (): boolean => buildScriptSpend(vector).validate()
376
+ const label = `${vector.source}#${vector.index} ${vector.expected} ${vector.flags} ${vector.comment}`
377
+ try {
378
+ if (vector.expected === 'OK') {
379
+ if (run() !== true) failures.push(`${label}: returned false`)
380
+ } else {
381
+ run()
382
+ failures.push(`${label}: accepted invalid script`)
383
+ }
384
+ } catch (e) {
385
+ if (vector.expected === 'OK') {
386
+ failures.push(`${label}: ${errorSummary(e)}`)
387
+ }
388
+ }
389
+ }
390
+ expect(failures).toEqual([])
391
+ })
392
+ })
393
+
394
+ describe(`${source.name} tx_valid.json`, () => {
395
+ const vectors = parseTxVectors(source.path, 'tx_valid.json')
396
+ it(`validates all script spends in ${vectors.length} valid transaction vectors`, () => {
397
+ const failures: string[] = []
398
+ for (const vector of vectors) {
399
+ const tx = Transaction.fromHex(vector.txHex)
400
+ expect(toHex(tx.toBinary())).toBe(vector.txHex)
401
+ for (const flags of vector.flags) {
402
+ for (let inputIndex = 0; inputIndex < tx.inputs.length; inputIndex++) {
403
+ const label = `${vector.source} tx_valid#${vector.index} input ${inputIndex} flags ${flags}`
404
+ try {
405
+ if (validateTxVectorInput(vector, flags, inputIndex) !== true) failures.push(`${label}: returned false`)
406
+ } catch (e) {
407
+ failures.push(`${label}: ${errorSummary(e)}`)
408
+ }
409
+ }
410
+ }
411
+ }
412
+ expect(failures).toEqual([])
413
+ })
414
+ })
415
+
416
+ describe(`${source.name} tx_invalid.json`, () => {
417
+ const vectors = parseTxVectors(source.path, 'tx_invalid.json')
418
+ it(`executes all parseable invalid transaction script spends across ${vectors.length} entries`, () => {
419
+ let evaluatedSpendCases = 0
420
+ let rejectedSpendCases = 0
421
+ for (const vector of vectors) {
422
+ let tx: Transaction
423
+ try {
424
+ tx = Transaction.fromHex(vector.txHex)
425
+ expect(toHex(tx.toBinary())).toBe(vector.txHex)
426
+ } catch (e) {
427
+ expect(e).toBeInstanceOf(Error)
428
+ continue
429
+ }
430
+
431
+ for (const flags of vector.flags) {
432
+ for (let inputIndex = 0; inputIndex < tx.inputs.length; inputIndex++) {
433
+ evaluatedSpendCases++
434
+ try {
435
+ if (validateTxVectorInput(vector, flags, inputIndex) !== true) rejectedSpendCases++
436
+ } catch (e) {
437
+ rejectedSpendCases++
438
+ }
439
+ }
440
+ }
441
+ }
442
+ expect(evaluatedSpendCases).toBeGreaterThan(0)
443
+ expect(rejectedSpendCases).toBeGreaterThan(0)
444
+ })
445
+ })
446
+
447
+ describe(`${source.name} sighash.json`, () => {
448
+ const vectors = parseSighashVectors(source.path)
449
+ it(`matches all ${vectors.length} regular and original signature hash vectors`, () => {
450
+ const failures: string[] = []
451
+ for (const vector of vectors) {
452
+ const actual = computeSignatureHashes(vector)
453
+ const label = `${vector.source} sighash#${vector.index}`
454
+ if (actual.regular !== vector.regularHash) {
455
+ failures.push(`${label} regular: expected ${vector.regularHash}, received ${actual.regular}`)
456
+ }
457
+ if (actual.original !== vector.originalHash) {
458
+ failures.push(`${label} original: expected ${vector.originalHash}, received ${actual.original}`)
459
+ }
460
+ }
461
+ expect(failures).toEqual([])
462
+ })
463
+ })
464
+ }
465
+ })
@@ -0,0 +1,25 @@
1
+ # Normative BSV Script Fixtures
2
+
3
+ These fixtures are copied verbatim from the current upstream node repositories used by this test suite.
4
+
5
+ ## bitcoin-sv/bitcoin-sv
6
+
7
+ - Repository: https://github.com/bitcoin-sv/bitcoin-sv
8
+ - Commit: `90a1a00e62b93c095b9ead39bbbd922873e1e6a9`
9
+ - Source directory: `src/test/data`
10
+ - SHA-256:
11
+ - `script_tests.json`: `a77f8b94412ef61e9ee59980ebc682a64212b47a16f06d87f809d91770ba496d`
12
+ - `sighash.json`: `9c1afcaf81e8482f818345efa8a3f0610f6541b975023b58550d50ad2a557f63`
13
+ - `tx_invalid.json`: `536a533f00714374d15fb24f601b989a87f9447a7a414a6532380e41c09c4fbe`
14
+ - `tx_valid.json`: `20e42308ead38db645454c4def763288e8cef2e5c47a4d9088e53dc7fc01419d`
15
+
16
+ ## bsv-blockchain/teranode
17
+
18
+ - Repository: https://github.com/bsv-blockchain/teranode
19
+ - Commit: `2355e57b80af962327930ea32568a1b322361542`
20
+ - Source directory: `test/consensus/testdata`
21
+ - SHA-256:
22
+ - `script_tests.json`: `3577a2e6e71e3356c5ec06eaa5fffb243e563eeef3946bd9b0285a54f0ad87f9`
23
+ - `sighash.json`: `ca2d6449c7cb98b8a83f3e18dc994d8227001d87adedd907b5e9a235114e283f`
24
+ - `tx_invalid.json`: `536a533f00714374d15fb24f601b989a87f9447a7a414a6532380e41c09c4fbe`
25
+ - `tx_valid.json`: `20e42308ead38db645454c4def763288e8cef2e5c47a4d9088e53dc7fc01419d`