@bsv/sdk 2.0.14 → 2.0.16
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 +14 -14
- package/dist/cjs/src/kvstore/GlobalKVStore.js +16 -3
- package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
- package/dist/cjs/src/kvstore/types.js.map +1 -1
- package/dist/cjs/src/primitives/Hash.js +1 -1
- package/dist/cjs/src/primitives/Hash.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +10 -3
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/script/Script.js +60 -13
- package/dist/cjs/src/script/Script.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +434 -59
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/transaction/http/BinaryFetchClient.js +6 -2
- package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js +8 -4
- package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/kvstore/GlobalKVStore.js +16 -3
- package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
- package/dist/esm/src/kvstore/types.js.map +1 -1
- package/dist/esm/src/primitives/Hash.js +1 -1
- package/dist/esm/src/primitives/Hash.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +10 -3
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/script/Script.js +60 -13
- package/dist/esm/src/script/Script.js.map +1 -1
- package/dist/esm/src/script/Spend.js +438 -59
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/transaction/http/BinaryFetchClient.js +6 -2
- package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
- package/dist/esm/src/transaction/http/DefaultHttpClient.js +8 -4
- package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/kvstore/GlobalKVStore.d.ts +7 -0
- package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -1
- package/dist/types/src/kvstore/types.d.ts +2 -1
- package/dist/types/src/kvstore/types.d.ts.map +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts +1 -0
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/script/Script.d.ts +1 -0
- package/dist/types/src/script/Script.d.ts.map +1 -1
- package/dist/types/src/script/ScriptChunk.d.ts +1 -0
- package/dist/types/src/script/ScriptChunk.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts +29 -0
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +2 -2
- package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +3 -4
- package/docs/reference/kvstore.md +2 -1
- package/docs/reference/primitives.md +1 -0
- package/docs/reference/script.md +7 -0
- package/docs/reference/transaction.md +2 -2
- package/package.json +14 -14
- package/src/kvstore/GlobalKVStore.ts +19 -3
- package/src/kvstore/__tests/GlobalKVStore.test.ts +24 -1
- package/src/kvstore/types.ts +2 -1
- package/src/primitives/Hash.ts +1 -1
- package/src/primitives/TransactionSignature.ts +11 -3
- package/src/script/Script.ts +59 -13
- package/src/script/ScriptChunk.ts +1 -0
- package/src/script/Spend.ts +483 -61
- package/src/script/__tests/NormativeVectors.test.ts +465 -0
- package/src/script/__tests/fixtures/SOURCES.md +25 -0
- package/src/script/__tests/fixtures/bitcoin-sv/script_tests.json +2591 -0
- package/src/script/__tests/fixtures/bitcoin-sv/sighash.json +1003 -0
- package/src/script/__tests/fixtures/bitcoin-sv/tx_invalid.json +285 -0
- package/src/script/__tests/fixtures/bitcoin-sv/tx_valid.json +367 -0
- package/src/script/__tests/fixtures/teranode/script_tests.json +2432 -0
- package/src/script/__tests/fixtures/teranode/sighash.json +1003 -0
- package/src/script/__tests/fixtures/teranode/tx_invalid.json +285 -0
- package/src/script/__tests/fixtures/teranode/tx_valid.json +367 -0
- package/src/transaction/broadcasters/__tests/ARC.test.ts +26 -4
- package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +26 -4
- package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -10
- package/src/transaction/http/BinaryFetchClient.ts +5 -2
- package/src/transaction/http/DefaultHttpClient.ts +7 -4
- package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +19 -1
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -827
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +0 -266
- package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -654
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -144
- package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -825
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +0 -264
- package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -619
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -109
- package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +0 -21
- package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +0 -1
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +0 -2
- package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +0 -1
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +0 -2
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +0 -1
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +0 -2
- package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +0 -1
- 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`
|