@bsv/sdk 1.5.3 → 1.6.0

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 (57) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/primitives/AESGCM.js +113 -137
  3. package/dist/cjs/src/primitives/AESGCM.js.map +1 -1
  4. package/dist/cjs/src/primitives/BigNumber.js +1019 -3947
  5. package/dist/cjs/src/primitives/BigNumber.js.map +1 -1
  6. package/dist/cjs/src/primitives/K256.js +53 -37
  7. package/dist/cjs/src/primitives/K256.js.map +1 -1
  8. package/dist/cjs/src/primitives/Mersenne.js +16 -21
  9. package/dist/cjs/src/primitives/Mersenne.js.map +1 -1
  10. package/dist/cjs/src/primitives/MontgomoryMethod.js.map +1 -1
  11. package/dist/cjs/src/primitives/utils.js +27 -17
  12. package/dist/cjs/src/primitives/utils.js.map +1 -1
  13. package/dist/cjs/src/script/Spend.js +618 -858
  14. package/dist/cjs/src/script/Spend.js.map +1 -1
  15. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  16. package/dist/esm/src/primitives/AESGCM.js +112 -137
  17. package/dist/esm/src/primitives/AESGCM.js.map +1 -1
  18. package/dist/esm/src/primitives/BigNumber.js +1011 -3969
  19. package/dist/esm/src/primitives/BigNumber.js.map +1 -1
  20. package/dist/esm/src/primitives/K256.js +53 -37
  21. package/dist/esm/src/primitives/K256.js.map +1 -1
  22. package/dist/esm/src/primitives/Mersenne.js +16 -21
  23. package/dist/esm/src/primitives/Mersenne.js.map +1 -1
  24. package/dist/esm/src/primitives/MontgomoryMethod.js.map +1 -1
  25. package/dist/esm/src/primitives/utils.js +29 -17
  26. package/dist/esm/src/primitives/utils.js.map +1 -1
  27. package/dist/esm/src/script/Spend.js +618 -857
  28. package/dist/esm/src/script/Spend.js.map +1 -1
  29. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  30. package/dist/types/src/primitives/AESGCM.d.ts.map +1 -1
  31. package/dist/types/src/primitives/BigNumber.d.ts +238 -1705
  32. package/dist/types/src/primitives/BigNumber.d.ts.map +1 -1
  33. package/dist/types/src/primitives/K256.d.ts.map +1 -1
  34. package/dist/types/src/primitives/Mersenne.d.ts +2 -2
  35. package/dist/types/src/primitives/Mersenne.d.ts.map +1 -1
  36. package/dist/types/src/primitives/utils.d.ts +2 -0
  37. package/dist/types/src/primitives/utils.d.ts.map +1 -1
  38. package/dist/types/src/script/Spend.d.ts +11 -1
  39. package/dist/types/src/script/Spend.d.ts.map +1 -1
  40. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  41. package/dist/umd/bundle.js +1 -1
  42. package/docs/performance.md +70 -0
  43. package/docs/primitives.md +262 -3049
  44. package/package.json +1 -1
  45. package/src/auth/__tests/Peer.test.ts +38 -23
  46. package/src/auth/certificates/__tests/MasterCertificate.test.ts +27 -20
  47. package/src/auth/certificates/__tests/VerifiableCertificate.test.ts +24 -24
  48. package/src/primitives/AESGCM.ts +118 -164
  49. package/src/primitives/BigNumber.ts +867 -4180
  50. package/src/primitives/K256.ts +57 -37
  51. package/src/primitives/Mersenne.ts +16 -20
  52. package/src/primitives/MontgomoryMethod.ts +2 -2
  53. package/src/primitives/__tests/ReductionContext.test.ts +6 -1
  54. package/src/primitives/utils.ts +28 -17
  55. package/src/script/Spend.ts +634 -1309
  56. package/src/transaction/__tests/Transaction.test.ts +14 -16
  57. package/src/transaction/__tests/Transaction.benchmarks.test.ts +0 -237
@@ -13,7 +13,6 @@ import TransactionInput from '../transaction/TransactionInput.js'
13
13
  import TransactionOutput from '../transaction/TransactionOutput.js'
14
14
 
15
15
  // These constants control the current behavior of the interpreter.
16
- // In the future, all of them will go away.
17
16
  const maxScriptElementSize = 1024 * 1024 * 1024
18
17
  const maxMultisigKeyCount = Math.pow(2, 31) - 1
19
18
  const requireMinimalPush = true
@@ -21,6 +20,96 @@ const requirePushOnlyUnlockingScripts = true
21
20
  const requireLowSSignatures = true
22
21
  const requireCleanStack = true
23
22
 
23
+ // --- Optimization: Pre-computed script numbers ---
24
+ const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum())
25
+ const SCRIPTNUMS_0_TO_16: ReadonlyArray<Readonly<number[]>> = Object.freeze(
26
+ Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber(i).toScriptNum()))
27
+ )
28
+
29
+ // --- Helper functions ---
30
+
31
+ function compareNumberArrays (a: Readonly<number[]>, b: Readonly<number[]>): boolean {
32
+ if (a.length !== b.length) return false
33
+ for (let i = 0; i < a.length; i++) {
34
+ if (a[i] !== b[i]) return false
35
+ }
36
+ return true
37
+ }
38
+
39
+ function isMinimallyEncodedHelper (
40
+ buf: Readonly<number[]>,
41
+ maxNumSize: number = Number.MAX_SAFE_INTEGER
42
+ ): boolean {
43
+ if (buf.length > maxNumSize) {
44
+ return false
45
+ }
46
+ if (buf.length > 0) {
47
+ if ((buf[buf.length - 1] & 0x7f) === 0) {
48
+ if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
49
+ return false
50
+ }
51
+ }
52
+ }
53
+ return true
54
+ }
55
+
56
+ function isChecksigFormatHelper (buf: Readonly<number[]>): boolean {
57
+ // This is a simplified check. The full DER check is more complex and typically
58
+ // done by TransactionSignature.fromChecksigFormat which can throw.
59
+ // This helper is mostly for early bailout or non-throwing checks if needed.
60
+ if (buf.length < 9 || buf.length > 73) return false
61
+ if (buf[0] !== 0x30) return false // DER SEQUENCE
62
+ if (buf[1] !== buf.length - 3) return false // Total length (excluding type and length byte for sequence, and hash type)
63
+
64
+ const rMarker = buf[2]
65
+ const rLen = buf[3]
66
+ if (rMarker !== 0x02) return false // DER INTEGER
67
+ if (rLen === 0) return false // R length is zero
68
+ if (5 + rLen >= buf.length) return false // S length misplaced or R too long
69
+
70
+ const sMarkerOffset = 4 + rLen
71
+ const sMarker = buf[sMarkerOffset]
72
+ const sLen = buf[sMarkerOffset + 1]
73
+ if (sMarker !== 0x02) return false // DER INTEGER
74
+ if (sLen === 0) return false // S length is zero
75
+
76
+ // Check R value negative or excessively padded
77
+ if ((buf[4] & 0x80) !== 0) return false // R value negative
78
+ if (rLen > 1 && buf[4] === 0x00 && (buf[5] & 0x80) === 0) return false // R value excessively padded
79
+
80
+ // Check S value negative or excessively padded
81
+ const sValueOffset = sMarkerOffset + 2
82
+ if ((buf[sValueOffset] & 0x80) !== 0) return false // S value negative
83
+ if (sLen > 1 && buf[sValueOffset] === 0x00 && (buf[sValueOffset + 1] & 0x80) === 0) return false // S value excessively padded
84
+
85
+ if (rLen + sLen + 7 !== buf.length) return false // Final length check including hash type
86
+
87
+ return true
88
+ }
89
+
90
+ function isOpcodeDisabledHelper (op: number): boolean {
91
+ return (
92
+ op === OP.OP_2MUL ||
93
+ op === OP.OP_2DIV ||
94
+ op === OP.OP_VERIF ||
95
+ op === OP.OP_VERNOTIF ||
96
+ op === OP.OP_VER
97
+ )
98
+ }
99
+
100
+ function isChunkMinimalPushHelper (chunk: ScriptChunk): boolean {
101
+ const data = chunk.data
102
+ const op = chunk.op
103
+ if (!Array.isArray(data)) return true
104
+ if (data.length === 0) return op === OP.OP_0
105
+ if (data.length === 1 && data[0] >= 1 && data[0] <= 16) return op === OP.OP_1 + (data[0] - 1)
106
+ if (data.length === 1 && data[0] === 0x81) return op === OP.OP_1NEGATE
107
+ if (data.length <= 75) return op === data.length
108
+ if (data.length <= 255) return op === OP.OP_PUSHDATA1
109
+ if (data.length <= 65535) return op === OP.OP_PUSHDATA2
110
+ return true
111
+ }
112
+
24
113
  /**
25
114
  * The Spend class represents a spend action within a Bitcoin SV transaction.
26
115
  * It encapsulates all the necessary data required for spending a UTXO (Unspent Transaction Output)
@@ -52,6 +141,7 @@ export default class Spend {
52
141
  unlockingScript: UnlockingScript
53
142
  inputSequence: number
54
143
  lockTime: number
144
+
55
145
  context: 'UnlockingScript' | 'LockingScript'
56
146
  programCounter: number
57
147
  lastCodeSeparator: number | null
@@ -119,7 +209,10 @@ export default class Spend {
119
209
  this.unlockingScript = params.unlockingScript
120
210
  this.inputSequence = params.inputSequence
121
211
  this.lockTime = params.lockTime
122
- this.memoryLimit = params.memoryLimit ?? 100000 // 100 MB is going to be processed by most miners by policy, but the default should protect apps against memory attacks.
212
+ this.memoryLimit = params.memoryLimit ?? 32000000
213
+ this.stack = []
214
+ this.altStack = []
215
+ this.ifStack = []
123
216
  this.stackMem = 0
124
217
  this.altStackMem = 0
125
218
  this.reset()
@@ -136,377 +229,212 @@ export default class Spend {
136
229
  this.altStackMem = 0
137
230
  }
138
231
 
139
- step (): boolean {
140
- let poppedValue: number[] | undefined
141
- // If the stack (or alt stack) is over the memory limit, evaluation has failed.
142
- if (this.stackMem > this.memoryLimit) {
143
- this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
144
- return false
145
- }
146
- if (this.altStackMem > this.memoryLimit) {
147
- this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
148
- return false
232
+ private ensureStackMem (additional: number): void {
233
+ if (this.stackMem + additional > this.memoryLimit) {
234
+ this.scriptEvaluationError(
235
+ 'Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes'
236
+ )
149
237
  }
238
+ }
150
239
 
151
- // Clear popped values after use to free memory
152
- const clearPoppedValue = (): void => {
153
- poppedValue = undefined
240
+ private ensureAltStackMem (additional: number): void {
241
+ if (this.altStackMem + additional > this.memoryLimit) {
242
+ this.scriptEvaluationError(
243
+ 'Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes'
244
+ )
154
245
  }
246
+ }
155
247
 
156
- // If the context is UnlockingScript and we have reached the end,
157
- // set the context to LockingScript and zero the program counter
158
- if (
159
- this.context === 'UnlockingScript' &&
160
- this.programCounter >= this.unlockingScript.chunks.length
161
- ) {
162
- this.context = 'LockingScript'
163
- this.programCounter = 0
164
- }
248
+ private pushStack (item: number[]): void {
249
+ this.ensureStackMem(item.length)
250
+ this.stack.push(item)
251
+ this.stackMem += item.length
252
+ }
165
253
 
166
- let operation: ScriptChunk
167
- if (this.context === 'UnlockingScript') {
168
- operation = this.unlockingScript.chunks[this.programCounter]
169
- } else {
170
- operation = this.lockingScript.chunks[this.programCounter]
171
- }
254
+ private pushStackCopy (item: Readonly<number[]>): void {
255
+ this.ensureStackMem(item.length)
256
+ const copy = item.slice()
257
+ this.stack.push(copy)
258
+ this.stackMem += copy.length
259
+ }
172
260
 
173
- const isOpcodeDisabled = (op: number): boolean => {
174
- return (
175
- op === OP.OP_2MUL ||
176
- op === OP.OP_2DIV ||
177
- op === OP.OP_VERIF ||
178
- op === OP.OP_VERNOTIF ||
179
- op === OP.OP_VER
180
- )
261
+ private popStack (): number[] {
262
+ if (this.stack.length === 0) {
263
+ this.scriptEvaluationError('Attempted to pop from an empty stack.')
181
264
  }
265
+ const item = this.stack.pop() as number[]
266
+ this.stackMem -= item.length
267
+ return item
268
+ }
182
269
 
183
- const isChunkMinimal = (chunk: ScriptChunk): boolean => {
184
- const data = chunk.data
185
- const op = chunk.op
186
- if (!Array.isArray(data)) {
187
- return true
188
- }
189
- if (data.length === 0) {
190
- // Could have used OP_0.
191
- return op === OP.OP_0
192
- } else if (data.length === 1 && data[0] >= 1 && data[0] <= 16) {
193
- // Could have used OP_1 .. OP_16.
194
- return op === OP.OP_1 + (data[0] - 1)
195
- } else if (data.length === 1 && data[0] === 0x81) {
196
- // Could have used OP_1NEGATE.
197
- return op === OP.OP_1NEGATE
198
- } else if (data.length <= 75) {
199
- // Could have used a direct push (opCode indicating number of bytes pushed + those bytes).
200
- return op === data.length
201
- } else if (data.length <= 255) {
202
- // Could have used OP_PUSHDATA.
203
- return op === OP.OP_PUSHDATA1
204
- } else if (data.length <= 65535) {
205
- // Could have used OP_PUSHDATA2.
206
- return op === OP.OP_PUSHDATA2
207
- }
208
- return true
270
+ private stackTop (index: number = -1): number[] {
271
+ // index = -1 for top, -2 for second top, etc.
272
+ // stack.length + index provides 0-based index from start
273
+ if (this.stack.length === 0 || this.stack.length < Math.abs(index) || (index >= 0 && index >= this.stack.length)) {
274
+ this.scriptEvaluationError(`Stack underflow accessing element at index ${index}. Stack length is ${this.stack.length}.`)
209
275
  }
276
+ return this.stack[this.stack.length + index]
277
+ }
210
278
 
211
- // Following example from sCrypt now using Number.MAX_SAFE_INTEGER (bsv/lib/transaction/input/input).
212
- const isMinimallyEncoded = (
213
- buf: number[],
214
- maxNumSize: number = Number.MAX_SAFE_INTEGER
215
- ): boolean => {
216
- if (buf.length > maxNumSize) {
217
- return false
218
- }
219
-
220
- if (buf.length > 0) {
221
- // Check that the number is encoded with the minimum possible number
222
- // of bytes.
223
- //
224
- // If the most-significant-byte - excluding the sign bit - is zero
225
- // then we're not minimal. Note how this test also rejects the
226
- // negative-zero encoding, 0x80.
227
- if ((buf[buf.length - 1] & 0x7f) === 0) {
228
- // One exception: if there's more than one byte and the most
229
- // significant bit of the second-most-significant-byte is set it
230
- // would conflict with the sign bit. An example of this case is
231
- // +-255, which encode to 0xff00 and 0xff80 respectively.
232
- // (big-endian).
233
- if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
234
- return false
235
- }
236
- }
237
- }
238
- return true
239
- }
279
+ private pushAltStack (item: number[]): void {
280
+ this.ensureAltStackMem(item.length)
281
+ this.altStack.push(item)
282
+ this.altStackMem += item.length
283
+ }
240
284
 
241
- const padDataToSize = (buf: number[], len: number): number[] => {
242
- const b = buf
243
- while (b.length < len) {
244
- b.unshift(0)
245
- }
246
- return b
285
+ private popAltStack (): number[] {
286
+ if (this.altStack.length === 0) {
287
+ this.scriptEvaluationError('Attempted to pop from an empty alt stack.')
247
288
  }
289
+ const item = this.altStack.pop() as number[]
290
+ this.altStackMem -= item.length
291
+ return item
292
+ }
248
293
 
249
- /**
250
- * This function is translated from bitcoind's IsDERSignature and is used in
251
- * the script interpreter. This "DER" format actually includes an extra byte,
252
- * the nHashType, at the end. It is really the tx format, not DER format.
253
- *
254
- * A canonical signature exists of: [30] [total len] [02] [len R] [R] [02] [len S] [S] [hashtype]
255
- * Where R and S are not negative (their first byte has its highest bit not set), and not
256
- * excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
257
- * in which case a single 0 byte is necessary and even required).
258
- *
259
- * See https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
260
- */
261
- const isChecksigFormat = (buf: number[]): boolean => {
262
- if (buf.length < 9) {
263
- // Non-canonical signature: too short
264
- return false
265
- }
266
- if (buf.length > 73) {
267
- // Non-canonical signature: too long
268
- return false
269
- }
270
- if (buf[0] !== 0x30) {
271
- // Non-canonical signature: wrong type
272
- return false
273
- }
274
- if (buf[1] !== buf.length - 3) {
275
- // Non-canonical signature: wrong length marker
276
- return false
277
- }
278
- const nLEnR = buf[3]
279
- if (5 + nLEnR >= buf.length) {
280
- // Non-canonical signature: S length misplaced
281
- return false
282
- }
283
- const nLEnS = buf[5 + nLEnR]
284
- if (nLEnR + nLEnS + 7 !== buf.length) {
285
- // Non-canonical signature: R+S length mismatch
286
- return false
287
- }
294
+ private checkSignatureEncoding (buf: Readonly<number[]>): boolean {
295
+ if (buf.length === 0) return true
288
296
 
289
- const R = buf.slice(4)
290
- if (buf[4 - 2] !== 0x02) {
291
- // Non-canonical signature: R value type mismatch
292
- return false
293
- }
294
- if (buf[4 - 1] !== nLEnR) {
295
- // Non-canonical signature: R length mismatch
296
- return false
297
- }
298
- if (nLEnR === 0) {
299
- // Non-canonical signature: R length is zero
300
- return false
301
- }
302
- if ((R[0] & 0x80) !== 0) {
303
- // Non-canonical signature: R value negative
297
+ if (!isChecksigFormatHelper(buf)) {
298
+ this.scriptEvaluationError('The signature format is invalid.') // Generic message like original
299
+ return false
300
+ }
301
+ try {
302
+ const sig = TransactionSignature.fromChecksigFormat(buf as number[]) // This can throw for stricter DER rules
303
+ if (requireLowSSignatures && !sig.hasLowS()) {
304
+ this.scriptEvaluationError('The signature must have a low S value.')
304
305
  return false
305
306
  }
306
- if (nLEnR > 1 && R[0] === 0x00 && (R[1] & 0x80) === 0) {
307
- // Non-canonical signature: R value excessively padded
307
+ if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
308
+ this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
308
309
  return false
309
310
  }
311
+ } catch (e) {
312
+ this.scriptEvaluationError('The signature format is invalid.')
313
+ return false
314
+ }
315
+ return true
316
+ }
310
317
 
311
- const S = buf.slice(6 + nLEnR)
312
- if (buf[6 + nLEnR - 2] !== 0x02) {
313
- // Non-canonical signature: S value type mismatch
314
- return false
315
- }
316
- if (buf[6 + nLEnR - 1] !== nLEnS) {
317
- // Non-canonical signature: S length mismatch
318
- return false
319
- }
320
- if (nLEnS === 0) {
321
- // Non-canonical signature: S length is zero
322
- return false
323
- }
324
- if ((S[0] & 0x80) !== 0) {
325
- // Non-canonical signature: S value negative
318
+ private checkPublicKeyEncoding (buf: Readonly<number[]>): boolean {
319
+ if (buf.length === 0) {
320
+ this.scriptEvaluationError('Public key is empty.')
321
+ return false
322
+ }
323
+ if (buf.length < 33) {
324
+ this.scriptEvaluationError('The public key is too short, it must be at least 33 bytes.')
325
+ return false
326
+ }
327
+ if (buf[0] === 0x04) {
328
+ if (buf.length !== 65) {
329
+ this.scriptEvaluationError('The non-compressed public key must be 65 bytes.')
326
330
  return false
327
331
  }
328
- if (nLEnS > 1 && S[0] === 0x00 && (S[1] & 0x80) === 0) {
329
- // Non-canonical signature: S value excessively padded
332
+ } else if (buf[0] === 0x02 || buf[0] === 0x03) {
333
+ if (buf.length !== 33) {
334
+ this.scriptEvaluationError('The compressed public key must be 33 bytes.')
330
335
  return false
331
336
  }
332
- return true
337
+ } else {
338
+ this.scriptEvaluationError('The public key is in an unknown format.')
339
+ return false
333
340
  }
341
+ try {
342
+ PublicKey.fromDER(buf as number[]) // This can throw for stricter DER rules
343
+ } catch (e) {
344
+ this.scriptEvaluationError('The public key is in an unknown format.')
345
+ return false
346
+ }
347
+ return true
348
+ }
334
349
 
335
- const checkSignatureEncoding = (buf: number[]): boolean => {
336
- // Empty signature. Not strictly DER encoded, but allowed to provide a
337
- // compact way to provide an invalid signature for use with CHECK(MULTI)SIG
338
- if (buf.length === 0) {
339
- return true
340
- }
341
-
342
- if (!isChecksigFormat(buf)) {
343
- this.scriptEvaluationError(
344
- 'The signature format is invalid.'
345
- )
346
- }
347
- const sig = TransactionSignature.fromChecksigFormat(buf)
348
- if (requireLowSSignatures && !sig.hasLowS()) {
349
- this.scriptEvaluationError(
350
- 'The signature must have a low S value.'
351
- )
352
- }
353
- if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
354
- this.scriptEvaluationError(
355
- 'The signature must use SIGHASH_FORKID.'
356
- )
357
- return false
358
- }
350
+ private verifySignature (
351
+ sig: TransactionSignature,
352
+ pubkey: PublicKey,
353
+ subscript: Script
354
+ ): boolean {
355
+ const preimage = TransactionSignature.format({
356
+ sourceTXID: this.sourceTXID,
357
+ sourceOutputIndex: this.sourceOutputIndex,
358
+ sourceSatoshis: this.sourceSatoshis,
359
+ transactionVersion: this.transactionVersion,
360
+ otherInputs: this.otherInputs,
361
+ outputs: this.outputs,
362
+ inputIndex: this.inputIndex,
363
+ subscript,
364
+ inputSequence: this.inputSequence,
365
+ lockTime: this.lockTime,
366
+ scope: sig.scope
367
+ })
368
+ const hash = new BigNumber(Hash.hash256(preimage))
369
+ return verify(hash, sig, pubkey)
370
+ }
359
371
 
360
- return true
372
+ step (): boolean {
373
+ if (this.stackMem > this.memoryLimit) {
374
+ this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
375
+ return false // Error thrown
376
+ }
377
+ if (this.altStackMem > this.memoryLimit) {
378
+ this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes')
379
+ return false // Error thrown
361
380
  }
362
381
 
363
- const checkPublicKeyEncoding = (buf: number[]): boolean => {
364
- if (buf.length < 33) {
365
- this.scriptEvaluationError(
366
- 'The public key is too short, it must be at least 33 bytes.'
367
- )
368
- }
369
- if (buf[0] === 0x04) {
370
- if (buf.length !== 65) {
371
- this.scriptEvaluationError(
372
- 'The non-compressed public key must be 65 bytes.'
373
- )
374
- }
375
- } else if (buf[0] === 0x02 || buf[0] === 0x03) {
376
- if (buf.length !== 33) {
377
- this.scriptEvaluationError(
378
- 'The compressed public key must be 33 bytes.'
379
- )
380
- }
381
- } else {
382
- this.scriptEvaluationError(
383
- 'The public key is in an unknown format.'
384
- )
385
- }
386
- return true
382
+ if (
383
+ this.context === 'UnlockingScript' &&
384
+ this.programCounter >= this.unlockingScript.chunks.length
385
+ ) {
386
+ this.context = 'LockingScript'
387
+ this.programCounter = 0
387
388
  }
388
389
 
389
- const verifySignature = (
390
- sig: TransactionSignature,
391
- pubkey: PublicKey,
392
- subscript: Script
393
- ): boolean => {
394
- const preimage = TransactionSignature.format({
395
- sourceTXID: this.sourceTXID,
396
- sourceOutputIndex: this.sourceOutputIndex,
397
- sourceSatoshis: this.sourceSatoshis,
398
- transactionVersion: this.transactionVersion,
399
- otherInputs: this.otherInputs,
400
- outputs: this.outputs,
401
- inputIndex: this.inputIndex,
402
- subscript,
403
- inputSequence: this.inputSequence,
404
- lockTime: this.lockTime,
405
- scope: sig.scope
406
- })
407
- const hash = new BigNumber(Hash.hash256(preimage))
408
- return verify(hash, sig, pubkey)
390
+ const currentScript = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
391
+ if (this.programCounter >= currentScript.chunks.length) {
392
+ return false
409
393
  }
394
+ const operation = currentScript.chunks[this.programCounter]
410
395
 
411
- const isScriptExecuting = !this.ifStack.includes(false)
412
- let buf: number[],
413
- buf1: number[],
414
- buf2: number[],
415
- buf3: number[],
416
- spliced: number[][],
417
- n: number,
418
- size: number,
419
- rawnum: number[],
420
- num: number[],
421
- signbit: number,
422
- x1: number[],
423
- x2: number[],
424
- x3: number[],
425
- bn: BigNumber,
426
- bn1: BigNumber,
427
- bn2: BigNumber,
428
- bn3: BigNumber,
429
- bufSig: number[],
430
- bufPubkey: number[],
431
- subscript
432
- let sig,
433
- pubkey,
434
- i: number,
435
- fOk: boolean,
436
- nKeysCount: number,
437
- ikey: number,
438
- ikey2: number,
439
- nSigsCount: number,
440
- isig: number
441
- let fValue: boolean, fEqual: boolean, fSuccess: boolean
442
-
443
- // Read instruction
444
396
  const currentOpcode = operation.op
445
397
  if (typeof currentOpcode === 'undefined') {
446
- this.scriptEvaluationError(
447
- `An opcode is missing in this chunk of the ${this.context}!`
448
- )
398
+ this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`) // Error thrown
449
399
  }
450
- if (
451
- Array.isArray(operation.data) &&
452
- operation.data.length > maxScriptElementSize
453
- ) {
454
- this.scriptEvaluationError(
455
- `It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
456
- )
400
+ if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
401
+ this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`) // Error thrown
457
402
  }
458
403
 
459
- if (isScriptExecuting && isOpcodeDisabled(currentOpcode)) {
460
- this.scriptEvaluationError('This opcode is currently disabled.')
404
+ const isScriptExecuting = !this.ifStack.includes(false)
405
+
406
+ if (isScriptExecuting && isOpcodeDisabledHelper(currentOpcode)) {
407
+ this.scriptEvaluationError(`This opcode is currently disabled. (Opcode: ${OP[currentOpcode] as string}, PC: ${this.programCounter})`) // Error thrown
461
408
  }
462
409
 
463
- if (
464
- isScriptExecuting &&
465
- currentOpcode >= 0 &&
466
- currentOpcode <= OP.OP_PUSHDATA4
467
- ) {
468
- if (requireMinimalPush && !isChunkMinimal(operation)) {
469
- this.scriptEvaluationError('This data is not minimally-encoded.')
410
+ if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
411
+ if (requireMinimalPush && !isChunkMinimalPushHelper(operation)) {
412
+ this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`) // Error thrown
470
413
  }
414
+ this.pushStack(Array.isArray(operation.data) ? operation.data : [])
415
+ } else if (isScriptExecuting || (currentOpcode >= OP.OP_IF && currentOpcode <= OP.OP_ENDIF)) {
416
+ let buf: number[], buf1: number[], buf2: number[], buf3: number[]
417
+ let x1: number[], x2: number[], x3: number[]
418
+ let bn: BigNumber, bn1: BigNumber, bn2: BigNumber, bn3: BigNumber
419
+ let n: number, size: number, fValue: boolean, fSuccess: boolean, subscript: Script
420
+ let bufSig: number[], bufPubkey: number[]
421
+ let sig: TransactionSignature, pubkey: PublicKey
422
+ let i: number, ikey: number, isig: number, nKeysCount: number, nSigsCount: number, fOk: boolean
471
423
 
472
- if (!Array.isArray(operation.data)) {
473
- this.stack.push([])
474
- this.stackMem += 0
475
- } else {
476
- this.stack.push(operation.data)
477
- this.stackMem += operation.data.length
478
- }
479
- } else if (
480
- isScriptExecuting ||
481
- (OP.OP_IF <= currentOpcode && currentOpcode <= OP.OP_ENDIF)
482
- ) {
483
424
  switch (currentOpcode) {
484
- case OP.OP_1NEGATE:
485
- case OP.OP_1:
486
- case OP.OP_2:
487
- case OP.OP_3:
488
- case OP.OP_4:
489
- case OP.OP_5:
490
- case OP.OP_6:
491
- case OP.OP_7:
492
- case OP.OP_8:
493
- case OP.OP_9:
494
- case OP.OP_10:
495
- case OP.OP_11:
496
- case OP.OP_12:
497
- case OP.OP_13:
498
- case OP.OP_14:
499
- case OP.OP_15:
500
- case OP.OP_16:
425
+ case OP.OP_1NEGATE: this.pushStackCopy(SCRIPTNUM_NEG_1); break
426
+ case OP.OP_0: this.pushStackCopy(SCRIPTNUMS_0_TO_16[0]); break
427
+ case OP.OP_1: case OP.OP_2: case OP.OP_3: case OP.OP_4:
428
+ case OP.OP_5: case OP.OP_6: case OP.OP_7: case OP.OP_8:
429
+ case OP.OP_9: case OP.OP_10: case OP.OP_11: case OP.OP_12:
430
+ case OP.OP_13: case OP.OP_14: case OP.OP_15: case OP.OP_16:
501
431
  n = currentOpcode - (OP.OP_1 - 1)
502
- buf = new BigNumber(n).toScriptNum()
503
- this.stack.push(buf)
504
- this.stackMem += buf.length
432
+ this.pushStackCopy(SCRIPTNUMS_0_TO_16[n])
505
433
  break
506
434
 
507
435
  case OP.OP_NOP:
508
- case OP.OP_NOP2:
509
- case OP.OP_NOP3:
436
+ case OP.OP_NOP2: // Formerly CHECKLOCKTIMEVERIFY
437
+ case OP.OP_NOP3: // Formerly CHECKSEQUENCEVERIFY
510
438
  case OP.OP_NOP1:
511
439
  case OP.OP_NOP4:
512
440
  case OP.OP_NOP5:
@@ -515,69 +443,22 @@ export default class Spend {
515
443
  case OP.OP_NOP8:
516
444
  case OP.OP_NOP9:
517
445
  case OP.OP_NOP10:
518
- case OP.OP_NOP11:
519
- case OP.OP_NOP12:
520
- case OP.OP_NOP13:
521
- case OP.OP_NOP14:
522
- case OP.OP_NOP15:
523
- case OP.OP_NOP16:
524
- case OP.OP_NOP17:
525
- case OP.OP_NOP18:
526
- case OP.OP_NOP19:
527
- case OP.OP_NOP20:
528
- case OP.OP_NOP21:
529
- case OP.OP_NOP22:
530
- case OP.OP_NOP23:
531
- case OP.OP_NOP24:
532
- case OP.OP_NOP25:
533
- case OP.OP_NOP26:
534
- case OP.OP_NOP27:
535
- case OP.OP_NOP28:
536
- case OP.OP_NOP29:
537
- case OP.OP_NOP30:
538
- case OP.OP_NOP31:
539
- case OP.OP_NOP32:
540
- case OP.OP_NOP33:
541
- case OP.OP_NOP34:
542
- case OP.OP_NOP35:
543
- case OP.OP_NOP36:
544
- case OP.OP_NOP37:
545
- case OP.OP_NOP38:
546
- case OP.OP_NOP39:
547
- case OP.OP_NOP40:
548
- case OP.OP_NOP41:
549
- case OP.OP_NOP42:
550
- case OP.OP_NOP43:
551
- case OP.OP_NOP44:
552
- case OP.OP_NOP45:
553
- case OP.OP_NOP46:
554
- case OP.OP_NOP47:
555
- case OP.OP_NOP48:
556
- case OP.OP_NOP49:
557
- case OP.OP_NOP50:
558
- case OP.OP_NOP51:
559
- case OP.OP_NOP52:
560
- case OP.OP_NOP53:
561
- case OP.OP_NOP54:
562
- case OP.OP_NOP55:
563
- case OP.OP_NOP56:
564
- case OP.OP_NOP57:
565
- case OP.OP_NOP58:
566
- case OP.OP_NOP59:
567
- case OP.OP_NOP60:
568
- case OP.OP_NOP61:
569
- case OP.OP_NOP62:
570
- case OP.OP_NOP63:
571
- case OP.OP_NOP64:
572
- case OP.OP_NOP65:
573
- case OP.OP_NOP66:
574
- case OP.OP_NOP67:
575
- case OP.OP_NOP68:
576
- case OP.OP_NOP69:
577
- case OP.OP_NOP70:
578
- case OP.OP_NOP71:
579
- case OP.OP_NOP72:
580
- case OP.OP_NOP73:
446
+ /* falls through */
447
+ // eslint-disable-next-line no-fallthrough
448
+ // eslint-disable-next-line no-fallthrough
449
+ case OP.OP_NOP11: case OP.OP_NOP12: case OP.OP_NOP13: case OP.OP_NOP14: case OP.OP_NOP15:
450
+ case OP.OP_NOP16: case OP.OP_NOP17: case OP.OP_NOP18: case OP.OP_NOP19: case OP.OP_NOP20:
451
+ case OP.OP_NOP21: case OP.OP_NOP22: case OP.OP_NOP23: case OP.OP_NOP24: case OP.OP_NOP25:
452
+ case OP.OP_NOP26: case OP.OP_NOP27: case OP.OP_NOP28: case OP.OP_NOP29: case OP.OP_NOP30:
453
+ case OP.OP_NOP31: case OP.OP_NOP32: case OP.OP_NOP33: case OP.OP_NOP34: case OP.OP_NOP35:
454
+ case OP.OP_NOP36: case OP.OP_NOP37: case OP.OP_NOP38: case OP.OP_NOP39: case OP.OP_NOP40:
455
+ case OP.OP_NOP41: case OP.OP_NOP42: case OP.OP_NOP43: case OP.OP_NOP44: case OP.OP_NOP45:
456
+ case OP.OP_NOP46: case OP.OP_NOP47: case OP.OP_NOP48: case OP.OP_NOP49: case OP.OP_NOP50:
457
+ case OP.OP_NOP51: case OP.OP_NOP52: case OP.OP_NOP53: case OP.OP_NOP54: case OP.OP_NOP55:
458
+ case OP.OP_NOP56: case OP.OP_NOP57: case OP.OP_NOP58: case OP.OP_NOP59: case OP.OP_NOP60:
459
+ case OP.OP_NOP61: case OP.OP_NOP62: case OP.OP_NOP63: case OP.OP_NOP64: case OP.OP_NOP65:
460
+ case OP.OP_NOP66: case OP.OP_NOP67: case OP.OP_NOP68: case OP.OP_NOP69: case OP.OP_NOP70:
461
+ case OP.OP_NOP71: case OP.OP_NOP72: case OP.OP_NOP73:
581
462
  case OP.OP_NOP77:
582
463
  break
583
464
 
@@ -585,1091 +466,525 @@ export default class Spend {
585
466
  case OP.OP_NOTIF:
586
467
  fValue = false
587
468
  if (isScriptExecuting) {
588
- if (this.stack.length < 1) {
589
- this.scriptEvaluationError(
590
- 'OP_IF and OP_NOTIF require at least one item on the stack when they are used!'
591
- )
592
- }
593
- buf = this.stacktop(-1)
594
-
469
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!')
470
+ buf = this.popStack()
595
471
  fValue = this.castToBool(buf)
596
- if (currentOpcode === OP.OP_NOTIF) {
597
- fValue = !fValue
598
- }
599
- poppedValue = this.stack.pop()
600
- if (poppedValue != null) {
601
- this.stackMem -= poppedValue.length
602
- }
603
- clearPoppedValue()
472
+ if (currentOpcode === OP.OP_NOTIF) fValue = !fValue
604
473
  }
605
474
  this.ifStack.push(fValue)
606
475
  break
607
-
608
476
  case OP.OP_ELSE:
609
- if (this.ifStack.length === 0) {
610
- this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
611
- }
612
- this.ifStack[this.ifStack.length - 1] =
613
- !this.ifStack[this.ifStack.length - 1]
477
+ if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
478
+ this.ifStack[this.ifStack.length - 1] = !this.ifStack[this.ifStack.length - 1]
614
479
  break
615
-
616
480
  case OP.OP_ENDIF:
617
- if (this.ifStack.length === 0) {
618
- this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
619
- }
481
+ if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
620
482
  this.ifStack.pop()
621
483
  break
622
-
623
484
  case OP.OP_VERIFY:
624
- if (this.stack.length < 1) {
625
- this.scriptEvaluationError(
626
- 'OP_VERIFY requires at least one item to be on the stack.'
627
- )
628
- }
629
- buf = this.stacktop(-1)
630
- fValue = this.castToBool(buf)
631
- poppedValue = this.stack.pop()
632
- if (poppedValue != null) {
633
- this.stackMem -= poppedValue.length
634
- }
635
- clearPoppedValue()
636
- if (!fValue) {
637
- this.scriptEvaluationError(
638
- 'OP_VERIFY requires the top stack value to be truthy.'
639
- )
640
- }
485
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.')
486
+ buf1 = this.stackTop()
487
+ fValue = this.castToBool(buf1)
488
+ if (!fValue) this.scriptEvaluationError('OP_VERIFY requires the top stack value to be truthy.')
489
+ this.popStack()
641
490
  break
642
-
643
491
  case OP.OP_RETURN:
644
- if (this.context === 'UnlockingScript') {
645
- this.programCounter = this.unlockingScript.chunks.length
646
- } else {
647
- this.programCounter = this.lockingScript.chunks.length
648
- }
492
+ if (this.context === 'UnlockingScript') this.programCounter = this.unlockingScript.chunks.length
493
+ else this.programCounter = this.lockingScript.chunks.length
649
494
  this.ifStack = []
495
+ this.programCounter-- // To counteract the final increment and ensure loop termination
650
496
  break
651
497
 
652
498
  case OP.OP_TOALTSTACK:
653
- if (this.stack.length < 1) {
654
- this.scriptEvaluationError(
655
- 'OP_TOALTSTACK requires at oeast one item to be on the stack.')
656
- }
657
- poppedValue = this.stack.pop()
658
- if (poppedValue != null) {
659
- this.altStack.push(poppedValue)
660
- this.altStackMem += poppedValue.length
661
- this.stackMem -= poppedValue.length
662
- }
663
- clearPoppedValue()
499
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_TOALTSTACK requires at oeast one item to be on the stack.')
500
+ this.pushAltStack(this.popStack())
664
501
  break
665
-
666
502
  case OP.OP_FROMALTSTACK:
667
- if (this.altStack.length < 1) {
668
- this.scriptEvaluationError(
669
- 'OP_FROMALTSTACK requires at least one item to be on the stack.'
670
- )
671
- }
672
- poppedValue = this.altStack.pop()
673
- if (poppedValue != null) {
674
- this.stack.push(poppedValue)
675
- this.stackMem += poppedValue.length
676
- this.altStackMem -= poppedValue.length
677
- }
678
- clearPoppedValue()
503
+ if (this.altStack.length < 1) this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.') // "stack" here means altstack
504
+ this.pushStack(this.popAltStack())
679
505
  break
680
-
681
506
  case OP.OP_2DROP:
682
- if (this.stack.length < 2) {
683
- this.scriptEvaluationError(
684
- 'OP_2DROP requires at least two items to be on the stack.'
685
- )
686
- }
687
- poppedValue = this.stack.pop()
688
- if (poppedValue != null) {
689
- this.stackMem -= poppedValue.length
690
- }
691
- clearPoppedValue()
692
- poppedValue = this.stack.pop()
693
- if (poppedValue != null) {
694
- this.stackMem -= poppedValue.length
695
- }
696
- clearPoppedValue()
507
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_2DROP requires at least two items to be on the stack.')
508
+ this.popStack(); this.popStack()
697
509
  break
698
-
699
510
  case OP.OP_2DUP:
700
- if (this.stack.length < 2) {
701
- this.scriptEvaluationError(
702
- 'OP_2DUP requires at least two items to be on the stack.'
703
- )
704
- }
705
- buf1 = this.stacktop(-2)
706
- buf2 = this.stacktop(-1)
707
- this.stack.push([...buf1])
708
- this.stackMem += buf1.length
709
- this.stack.push([...buf2])
710
- this.stackMem += buf2.length
511
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_2DUP requires at least two items to be on the stack.')
512
+ buf1 = this.stackTop(-2)
513
+ buf2 = this.stackTop(-1)
514
+ this.pushStackCopy(buf1); this.pushStackCopy(buf2)
711
515
  break
712
-
713
516
  case OP.OP_3DUP:
714
- if (this.stack.length < 3) {
715
- this.scriptEvaluationError(
716
- 'OP_3DUP requires at least three items to be on the stack.'
717
- )
718
- }
719
- buf1 = this.stacktop(-3)
720
- buf2 = this.stacktop(-2)
721
- buf3 = this.stacktop(-1)
722
- this.stack.push([...buf1])
723
- this.stackMem += buf1.length
724
- this.stack.push([...buf2])
725
- this.stackMem += buf2.length
726
- this.stack.push([...buf3])
727
- this.stackMem += buf3.length
517
+ if (this.stack.length < 3) this.scriptEvaluationError('OP_3DUP requires at least three items to be on the stack.')
518
+ buf1 = this.stackTop(-3)
519
+ buf2 = this.stackTop(-2)
520
+ buf3 = this.stackTop(-1)
521
+ this.pushStackCopy(buf1); this.pushStackCopy(buf2); this.pushStackCopy(buf3)
728
522
  break
729
-
730
523
  case OP.OP_2OVER:
731
- if (this.stack.length < 4) {
732
- this.scriptEvaluationError(
733
- 'OP_2OVER requires at least four items to be on the stack.'
734
- )
735
- }
736
- buf1 = this.stacktop(-4)
737
- buf2 = this.stacktop(-3)
738
- this.stack.push([...buf1])
739
- this.stackMem += buf1.length
740
- this.stack.push([...buf2])
741
- this.stackMem += buf2.length
524
+ if (this.stack.length < 4) this.scriptEvaluationError('OP_2OVER requires at least four items to be on the stack.')
525
+ buf1 = this.stackTop(-4)
526
+ buf2 = this.stackTop(-3)
527
+ this.pushStackCopy(buf1); this.pushStackCopy(buf2)
742
528
  break
743
-
744
- case OP.OP_2ROT:
745
- if (this.stack.length < 6) {
746
- this.scriptEvaluationError(
747
- 'OP_2ROT requires at least six items to be on the stack.'
748
- )
749
- }
750
- spliced = this.stack.splice(this.stack.length - 6, 2)
751
- this.stack.push(spliced[0])
752
- this.stackMem += spliced[0].length
753
- this.stack.push(spliced[1])
754
- this.stackMem += spliced[1].length
529
+ case OP.OP_2ROT: {
530
+ if (this.stack.length < 6) this.scriptEvaluationError('OP_2ROT requires at least six items to be on the stack.')
531
+ const rot6 = this.popStack(); const rot5 = this.popStack()
532
+ const rot4 = this.popStack(); const rot3 = this.popStack()
533
+ const rot2 = this.popStack(); const rot1 = this.popStack()
534
+ this.pushStack(rot3); this.pushStack(rot4)
535
+ this.pushStack(rot5); this.pushStack(rot6)
536
+ this.pushStack(rot1); this.pushStack(rot2)
755
537
  break
756
-
757
- case OP.OP_2SWAP:
758
- if (this.stack.length < 4) {
759
- this.scriptEvaluationError(
760
- 'OP_2SWAP requires at least four items to be on the stack.'
761
- )
762
- }
763
- spliced = this.stack.splice(this.stack.length - 4, 2)
764
- this.stack.push(spliced[0])
765
- this.stackMem += spliced[0].length
766
- this.stack.push(spliced[1])
767
- this.stackMem += spliced[1].length
538
+ }
539
+ case OP.OP_2SWAP: {
540
+ if (this.stack.length < 4) this.scriptEvaluationError('OP_2SWAP requires at least four items to be on the stack.')
541
+ const swap4 = this.popStack(); const swap3 = this.popStack()
542
+ const swap2 = this.popStack(); const swap1 = this.popStack()
543
+ this.pushStack(swap3); this.pushStack(swap4)
544
+ this.pushStack(swap1); this.pushStack(swap2)
768
545
  break
769
-
546
+ }
770
547
  case OP.OP_IFDUP:
771
- if (this.stack.length < 1) {
772
- this.scriptEvaluationError(
773
- 'OP_IFDUP requires at least one item to be on the stack.'
774
- )
775
- }
776
- buf = this.stacktop(-1)
777
- fValue = this.castToBool(buf)
778
- if (fValue) {
779
- this.stack.push([...buf])
780
- this.stackMem += buf.length
548
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_IFDUP requires at least one item to be on the stack.')
549
+ buf1 = this.stackTop()
550
+ if (this.castToBool(buf1)) {
551
+ this.pushStackCopy(buf1)
781
552
  }
782
553
  break
783
-
784
554
  case OP.OP_DEPTH:
785
- buf = new BigNumber(this.stack.length).toScriptNum()
786
- this.stack.push(buf)
787
- this.stackMem += buf.length
555
+ this.pushStack(new BigNumber(this.stack.length).toScriptNum())
788
556
  break
789
-
790
557
  case OP.OP_DROP:
791
- if (this.stack.length < 1) {
792
- this.scriptEvaluationError(
793
- 'OP_DROP requires at least one item to be on the stack.'
794
- )
795
- }
796
- poppedValue = this.stack.pop()
797
- if (poppedValue != null) {
798
- this.stackMem -= poppedValue.length
799
- }
800
- clearPoppedValue()
558
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_DROP requires at least one item to be on the stack.')
559
+ this.popStack()
801
560
  break
802
-
803
561
  case OP.OP_DUP:
804
- if (this.stack.length < 1) {
805
- this.scriptEvaluationError(
806
- 'OP_DUP requires at least one item to be on the stack.'
807
- )
808
- }
809
- this.stack.push([...this.stacktop(-1)])
810
- this.stackMem += this.stacktop(-1).length
562
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_DUP requires at least one item to be on the stack.')
563
+ this.pushStackCopy(this.stackTop())
811
564
  break
812
-
813
565
  case OP.OP_NIP:
814
- if (this.stack.length < 2) {
815
- this.scriptEvaluationError(
816
- 'OP_NIP requires at least two items to be on the stack.'
817
- )
818
- }
819
- this.stack.splice(this.stack.length - 2, 1)
820
- this.stackMem -= this.stacktop(-1).length
566
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_NIP requires at least two items to be on the stack.')
567
+ buf2 = this.popStack()
568
+ this.popStack()
569
+ this.pushStack(buf2)
821
570
  break
822
-
823
571
  case OP.OP_OVER:
824
- if (this.stack.length < 2) {
825
- this.scriptEvaluationError(
826
- 'OP_OVER requires at least two items to be on the stack.'
827
- )
828
- }
829
- this.stack.push([...this.stacktop(-2)])
830
- this.stackMem += this.stacktop(-2).length
572
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_OVER requires at least two items to be on the stack.')
573
+ this.pushStackCopy(this.stackTop(-2))
831
574
  break
832
-
833
575
  case OP.OP_PICK:
834
- case OP.OP_ROLL:
835
- if (this.stack.length < 2) {
836
- this.scriptEvaluationError(
837
- `${OP[currentOpcode] as string} requires at least two items to be on the stack.`
838
- )
839
- }
840
- buf = this.stacktop(-1)
841
- bn = BigNumber.fromScriptNum(buf, requireMinimalPush)
576
+ case OP.OP_ROLL: {
577
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
578
+ bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
842
579
  n = bn.toNumber()
843
- poppedValue = this.stack.pop()
844
- if (poppedValue != null) {
845
- this.stackMem -= poppedValue.length
846
- }
847
- clearPoppedValue()
848
580
  if (n < 0 || n >= this.stack.length) {
849
- this.scriptEvaluationError(
850
- `${OP[currentOpcode] as string} requires the top stack element to be 0 or a positive number less than the current size of the stack.`
851
- )
581
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top stack element to be 0 or a positive number less than the current size of the stack.`)
852
582
  }
853
- buf = this.stacktop(-n - 1)
583
+ const itemToMoveOrCopy = this.stack[this.stack.length - 1 - n]
854
584
  if (currentOpcode === OP.OP_ROLL) {
855
- this.stack.splice(this.stack.length - n - 1, 1)
856
- this.stackMem -= buf.length
585
+ this.stack.splice(this.stack.length - 1 - n, 1)
586
+ this.stackMem -= itemToMoveOrCopy.length
587
+ this.pushStack(itemToMoveOrCopy)
588
+ } else { // OP_PICK
589
+ this.pushStackCopy(itemToMoveOrCopy)
857
590
  }
858
- this.stack.push([...buf])
859
- this.stackMem += buf.length
860
591
  break
861
-
592
+ }
862
593
  case OP.OP_ROT:
863
- if (this.stack.length < 3) {
864
- this.scriptEvaluationError(
865
- 'OP_ROT requires at least three items to be on the stack.'
866
- )
867
- }
868
- x1 = this.stacktop(-3)
869
- x2 = this.stacktop(-2)
870
- x3 = this.stacktop(-1)
871
- this.stack[this.stack.length - 3] = x2
872
- this.stack[this.stack.length - 2] = x3
873
- this.stack[this.stack.length - 1] = x1
594
+ if (this.stack.length < 3) this.scriptEvaluationError('OP_ROT requires at least three items to be on the stack.')
595
+ x3 = this.popStack()
596
+ x2 = this.popStack()
597
+ x1 = this.popStack()
598
+ this.pushStack(x2); this.pushStack(x3); this.pushStack(x1)
874
599
  break
875
-
876
600
  case OP.OP_SWAP:
877
- if (this.stack.length < 2) {
878
- this.scriptEvaluationError(
879
- 'OP_SWAP requires at least two items to be on the stack.'
880
- )
881
- }
882
- x1 = this.stacktop(-2)
883
- x2 = this.stacktop(-1)
884
- this.stack[this.stack.length - 2] = x2
885
- this.stack[this.stack.length - 1] = x1
601
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_SWAP requires at least two items to be on the stack.')
602
+ x2 = this.popStack()
603
+ x1 = this.popStack()
604
+ this.pushStack(x2); this.pushStack(x1)
886
605
  break
887
-
888
606
  case OP.OP_TUCK:
889
- if (this.stack.length < 2) {
890
- this.scriptEvaluationError(
891
- 'OP_TUCK requires at least two items to be on the stack.'
892
- )
893
- }
894
- this.stack.splice(this.stack.length - 2, 0, [...this.stacktop(-1)])
895
- this.stackMem += this.stacktop(-1).length
607
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_TUCK requires at least two items to be on the stack.')
608
+ buf1 = this.stackTop(-1) // Top element (x2)
609
+ // stack is [... rest, x1, x2]
610
+ // We want [... rest, x2_copy, x1, x2]
611
+ this.ensureStackMem(buf1.length)
612
+ this.stack.splice(this.stack.length - 2, 0, buf1.slice()) // Insert copy of x2 before x1
613
+ this.stackMem += buf1.length // Account for the new copy
896
614
  break
897
-
898
615
  case OP.OP_SIZE:
899
- if (this.stack.length < 1) {
900
- this.scriptEvaluationError(
901
- 'OP_SIZE requires at least one item to be on the stack.'
902
- )
903
- }
904
- bn = new BigNumber(this.stacktop(-1).length)
905
- this.stack.push(bn.toScriptNum())
906
- this.stackMem += bn.toScriptNum().length
616
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_SIZE requires at least one item to be on the stack.')
617
+ this.pushStack(new BigNumber(this.stackTop().length).toScriptNum())
907
618
  break
908
619
 
909
620
  case OP.OP_AND:
910
621
  case OP.OP_OR:
911
- case OP.OP_XOR:
912
- if (this.stack.length < 2) {
913
- this.scriptEvaluationError(
914
- `${OP[currentOpcode] as string} requires at least one item to be on the stack.`
915
- )
916
- }
917
- buf1 = this.stacktop(-2)
918
- buf2 = this.stacktop(-1)
919
-
920
- if (buf1.length !== buf2.length) {
921
- this.scriptEvaluationError(
922
- `${OP[currentOpcode] as string} requires the top two stack items to be the same size.`
923
- )
924
- }
925
-
926
- switch (currentOpcode) {
927
- case OP.OP_AND:
928
- for (let i = 0; i < buf1.length; i++) {
929
- buf1[i] &= buf2[i]
930
- }
931
- break
932
- case OP.OP_OR:
933
- for (let i = 0; i < buf1.length; i++) {
934
- buf1[i] |= buf2[i]
935
- }
936
- break
937
- case OP.OP_XOR:
938
- for (let i = 0; i < buf1.length; i++) {
939
- buf1[i] ^= buf2[i]
940
- }
941
- break
942
- }
943
-
944
- // And pop vch2.
945
- poppedValue = this.stack.pop()
946
- if (poppedValue != null) {
947
- this.stackMem -= poppedValue.length
948
- }
949
- clearPoppedValue()
622
+ case OP.OP_XOR: {
623
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items on the stack.`)
624
+ buf2 = this.popStack()
625
+ buf1 = this.popStack()
626
+ if (buf1.length !== buf2.length) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top two stack items to be the same size.`)
627
+
628
+ const resultBufBitwiseOp = new Array(buf1.length)
629
+ for (let k = 0; k < buf1.length; k++) {
630
+ if (currentOpcode === OP.OP_AND) resultBufBitwiseOp[k] = buf1[k] & buf2[k]
631
+ else if (currentOpcode === OP.OP_OR) resultBufBitwiseOp[k] = buf1[k] | buf2[k]
632
+ else resultBufBitwiseOp[k] = buf1[k] ^ buf2[k]
633
+ }
634
+ this.pushStack(resultBufBitwiseOp)
950
635
  break
951
-
952
- case OP.OP_INVERT:
953
- if (this.stack.length < 1) {
954
- this.scriptEvaluationError(
955
- 'OP_INVERT requires at least one item to be on the stack.'
956
- )
957
- }
958
- buf = this.stacktop(-1)
959
- for (let i = 0; i < buf.length; i++) {
960
- buf[i] = ~buf[i]
961
- }
636
+ }
637
+ case OP.OP_INVERT: {
638
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_INVERT requires at least one item to be on the stack.')
639
+ buf = this.popStack()
640
+ const invertedBufOp = new Array(buf.length)
641
+ for (let k = 0; k < buf.length; k++) {
642
+ invertedBufOp[k] = (~buf[k]) & 0xff
643
+ }
644
+ this.pushStack(invertedBufOp)
962
645
  break
963
-
646
+ }
964
647
  case OP.OP_LSHIFT:
965
- case OP.OP_RSHIFT:
966
- if (this.stack.length < 2) {
967
- this.scriptEvaluationError(
968
- `${OP[currentOpcode] as string} requires at least two items to be on the stack.`
969
- )
970
- }
971
- buf1 = this.stacktop(-2)
648
+ case OP.OP_RSHIFT: {
649
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
650
+ bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // n (shift amount)
651
+ buf1 = this.popStack() // value to shift
652
+ n = bn2.toNumber()
653
+ if (n < 0) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`)
972
654
  if (buf1.length === 0) {
973
- poppedValue = this.stack.pop()
974
- if (poppedValue != null) {
975
- this.stackMem -= poppedValue.length
976
- }
977
- clearPoppedValue()
978
- } else {
979
- bn1 = new BigNumber(buf1)
980
- bn2 = BigNumber.fromScriptNum(
981
- this.stacktop(-1),
982
- requireMinimalPush
983
- )
984
- n = bn2.toNumber()
985
- if (n < 0) {
986
- this.scriptEvaluationError(
987
- `${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`
988
- )
989
- }
990
- poppedValue = this.stack.pop()
991
- if (poppedValue != null) {
992
- this.stackMem -= poppedValue.length
993
- }
994
- clearPoppedValue()
995
- poppedValue = this.stack.pop()
996
- if (poppedValue != null) {
997
- this.stackMem -= poppedValue.length
998
- }
999
- clearPoppedValue()
1000
- let shifted
1001
- if (currentOpcode === OP.OP_LSHIFT) {
1002
- shifted = bn1.ushln(n)
1003
- }
1004
- if (currentOpcode === OP.OP_RSHIFT) {
1005
- shifted = bn1.ushrn(n)
1006
- }
1007
- const bufShifted = padDataToSize(
1008
- shifted.toArray().slice(buf1.length * -1),
1009
- buf1.length
1010
- )
1011
- this.stack.push(bufShifted)
1012
- this.stackMem += bufShifted.length
655
+ this.pushStack([])
656
+ break
1013
657
  }
1014
- break
658
+ bn1 = new BigNumber(buf1)
659
+ let shiftedBn: BigNumber
660
+ if (currentOpcode === OP.OP_LSHIFT) shiftedBn = bn1.ushln(n)
661
+ else shiftedBn = bn1.ushrn(n)
1015
662
 
663
+ const shiftedArr = shiftedBn.toArray('le', buf1.length)
664
+ this.pushStack(shiftedArr)
665
+ break
666
+ }
1016
667
  case OP.OP_EQUAL:
1017
668
  case OP.OP_EQUALVERIFY:
1018
- if (this.stack.length < 2) {
1019
- this.scriptEvaluationError(
1020
- `${OP[currentOpcode] as string} requires at least two items to be on the stack.`
1021
- )
1022
- }
1023
- buf1 = this.stacktop(-2)
1024
- buf2 = this.stacktop(-1)
1025
- fEqual = toHex(buf1) === toHex(buf2)
1026
- poppedValue = this.stack.pop()
1027
- if (poppedValue != null) {
1028
- this.stackMem -= poppedValue.length
1029
- }
1030
- clearPoppedValue()
1031
- poppedValue = this.stack.pop()
1032
- if (poppedValue != null) {
1033
- this.stackMem -= poppedValue.length
1034
- }
1035
- clearPoppedValue()
1036
- this.stack.push(fEqual ? [1] : [])
1037
- this.stackMem += (fEqual ? 1 : 0)
669
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
670
+ buf2 = this.popStack()
671
+ buf1 = this.popStack()
672
+ fValue = compareNumberArrays(buf1, buf2)
673
+ this.pushStack(fValue ? [1] : [])
1038
674
  if (currentOpcode === OP.OP_EQUALVERIFY) {
1039
- if (this.castToBool(this.stacktop(-1))) {
1040
- poppedValue = this.stack.pop()
1041
- if (poppedValue != null) {
1042
- this.stackMem -= poppedValue.length
1043
- }
1044
- clearPoppedValue()
1045
- } else {
1046
- this.scriptEvaluationError(
1047
- 'OP_EQUALVERIFY requires the top two stack items to be equal.'
1048
- )
1049
- }
675
+ if (!fValue) this.scriptEvaluationError('OP_EQUALVERIFY requires the top two stack items to be equal.')
676
+ this.popStack()
1050
677
  }
1051
678
  break
1052
679
 
1053
- case OP.OP_1ADD:
1054
- case OP.OP_1SUB:
1055
- case OP.OP_NEGATE:
1056
- case OP.OP_ABS:
1057
- case OP.OP_NOT:
1058
- case OP.OP_0NOTEQUAL:
1059
- if (this.stack.length < 1) {
1060
- this.scriptEvaluationError(
1061
- `${OP[currentOpcode] as string} requires at least one items to be on the stack.`
1062
- )
1063
- }
1064
- buf = this.stacktop(-1)
1065
- bn = BigNumber.fromScriptNum(buf, requireMinimalPush)
680
+ case OP.OP_1ADD: case OP.OP_1SUB:
681
+ case OP.OP_NEGATE: case OP.OP_ABS:
682
+ case OP.OP_NOT: case OP.OP_0NOTEQUAL:
683
+ if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
684
+ bn = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush)
1066
685
  switch (currentOpcode) {
1067
- case OP.OP_1ADD:
1068
- bn = bn.addn(1)
1069
- break
1070
- case OP.OP_1SUB:
1071
- bn = bn.subn(1)
1072
- break
1073
- case OP.OP_NEGATE:
1074
- bn = bn.neg()
1075
- break
1076
- case OP.OP_ABS:
1077
- if (bn.cmpn(0) < 0) {
1078
- bn = bn.neg()
1079
- }
1080
- break
1081
- case OP.OP_NOT:
1082
- bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0 + 0)
1083
- break
1084
- case OP.OP_0NOTEQUAL:
1085
- bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0 + 0)
1086
- break
1087
- }
1088
- poppedValue = this.stack.pop()
1089
- if (poppedValue != null) {
1090
- this.stackMem -= poppedValue.length
1091
- }
1092
- clearPoppedValue()
1093
- this.stack.push(bn.toScriptNum())
1094
- this.stackMem += bn.toScriptNum().length
686
+ case OP.OP_1ADD: bn = bn.add(new BigNumber(1)); break
687
+ case OP.OP_1SUB: bn = bn.sub(new BigNumber(1)); break
688
+ case OP.OP_NEGATE: bn = bn.neg(); break
689
+ case OP.OP_ABS: if (bn.isNeg()) bn = bn.neg(); break
690
+ case OP.OP_NOT: bn = new BigNumber(bn.cmpn(0) === 0 ? 1 : 0); break
691
+ case OP.OP_0NOTEQUAL: bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0); break
692
+ }
693
+ this.pushStack(bn.toScriptNum())
1095
694
  break
1096
-
1097
- case OP.OP_ADD:
1098
- case OP.OP_SUB:
1099
- case OP.OP_MUL:
1100
- case OP.OP_MOD:
1101
- case OP.OP_DIV:
1102
- case OP.OP_BOOLAND:
1103
- case OP.OP_BOOLOR:
1104
- case OP.OP_NUMEQUAL:
1105
- case OP.OP_NUMEQUALVERIFY:
1106
- case OP.OP_NUMNOTEQUAL:
1107
- case OP.OP_LESSTHAN:
1108
- case OP.OP_GREATERTHAN:
1109
- case OP.OP_LESSTHANOREQUAL:
1110
- case OP.OP_GREATERTHANOREQUAL:
1111
- case OP.OP_MIN:
1112
- case OP.OP_MAX:
1113
- if (this.stack.length < 2) {
1114
- this.scriptEvaluationError(
1115
- `${OP[currentOpcode] as string} requires at least two items to be on the stack.`
1116
- )
1117
- }
1118
- bn1 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush)
1119
- bn2 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush)
1120
- bn = new BigNumber(0)
1121
-
695
+ case OP.OP_ADD: case OP.OP_SUB: case OP.OP_MUL: case OP.OP_DIV: case OP.OP_MOD:
696
+ case OP.OP_BOOLAND: case OP.OP_BOOLOR:
697
+ case OP.OP_NUMEQUAL: case OP.OP_NUMEQUALVERIFY: case OP.OP_NUMNOTEQUAL:
698
+ case OP.OP_LESSTHAN: case OP.OP_GREATERTHAN:
699
+ case OP.OP_LESSTHANOREQUAL: case OP.OP_GREATERTHANOREQUAL:
700
+ case OP.OP_MIN: case OP.OP_MAX: {
701
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
702
+ buf2 = this.popStack()
703
+ buf1 = this.popStack()
704
+ bn2 = BigNumber.fromScriptNum(buf2, requireMinimalPush)
705
+ bn1 = BigNumber.fromScriptNum(buf1, requireMinimalPush)
706
+ let predictedLen = 0
1122
707
  switch (currentOpcode) {
1123
- case OP.OP_ADD:
1124
- bn = bn1.add(bn2)
708
+ case OP.OP_MUL:
709
+ predictedLen = bn1.byteLength() + bn2.byteLength()
1125
710
  break
711
+ case OP.OP_ADD:
1126
712
  case OP.OP_SUB:
1127
- bn = bn1.sub(bn2)
1128
- break
1129
- case OP.OP_MUL:
1130
- bn = bn1.mul(bn2)
713
+ predictedLen = Math.max(bn1.byteLength(), bn2.byteLength()) + 1
1131
714
  break
715
+ default:
716
+ predictedLen = Math.max(bn1.byteLength(), bn2.byteLength())
717
+ }
718
+ this.ensureStackMem(predictedLen)
719
+ let resultBnArithmetic: BigNumber = new BigNumber(0)
720
+ switch (currentOpcode) {
721
+ case OP.OP_ADD: resultBnArithmetic = bn1.add(bn2); break
722
+ case OP.OP_SUB: resultBnArithmetic = bn1.sub(bn2); break
723
+ case OP.OP_MUL: resultBnArithmetic = bn1.mul(bn2); break
1132
724
  case OP.OP_DIV:
1133
- if (bn2.cmpn(0) === 0) {
1134
- this.scriptEvaluationError('OP_DIV cannot divide by zero!')
1135
- }
1136
- bn = bn1.div(bn2)
1137
- break
725
+ if (bn2.cmpn(0) === 0) this.scriptEvaluationError('OP_DIV cannot divide by zero!')
726
+ resultBnArithmetic = bn1.div(bn2); break
1138
727
  case OP.OP_MOD:
1139
- if (bn2.cmpn(0) === 0) {
1140
- this.scriptEvaluationError('OP_MOD cannot divide by zero!')
1141
- }
1142
- bn = bn1.mod(bn2)
1143
- break
1144
- case OP.OP_BOOLAND:
1145
- bn = new BigNumber(
1146
- bn1.cmpn(0) !== 0 && bn2.cmpn(0) !== 0 ? 1 : 0 + 0
1147
- )
1148
- break
1149
- case OP.OP_BOOLOR:
1150
- bn = new BigNumber(
1151
- bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0 ? 1 : 0 + 0
1152
- )
1153
- break
1154
- case OP.OP_NUMEQUAL:
1155
- bn = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0 + 0)
1156
- break
1157
- case OP.OP_NUMEQUALVERIFY:
1158
- bn = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0 + 0)
1159
- break
1160
- case OP.OP_NUMNOTEQUAL:
1161
- bn = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0 + 0)
1162
- break
1163
- case OP.OP_LESSTHAN:
1164
- bn = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0 + 0)
1165
- break
1166
- case OP.OP_GREATERTHAN:
1167
- bn = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0 + 0)
1168
- break
1169
- case OP.OP_LESSTHANOREQUAL:
1170
- bn = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0 + 0)
1171
- break
1172
- case OP.OP_GREATERTHANOREQUAL:
1173
- bn = new BigNumber(bn1.cmp(bn2) >= 0 ? 1 : 0 + 0)
1174
- break
1175
- case OP.OP_MIN:
1176
- bn = bn1.cmp(bn2) < 0 ? bn1 : bn2
1177
- break
1178
- case OP.OP_MAX:
1179
- bn = bn1.cmp(bn2) > 0 ? bn1 : bn2
1180
- break
1181
- }
1182
- poppedValue = this.stack.pop()
1183
- if (poppedValue != null) {
1184
- this.stackMem -= poppedValue.length
1185
- }
1186
- clearPoppedValue()
1187
- poppedValue = this.stack.pop()
1188
- if (poppedValue != null) {
1189
- this.stackMem -= poppedValue.length
1190
- }
1191
- clearPoppedValue()
1192
- this.stack.push(bn.toScriptNum())
1193
- this.stackMem += bn.toScriptNum().length
1194
-
728
+ if (bn2.cmpn(0) === 0) this.scriptEvaluationError('OP_MOD cannot divide by zero!')
729
+ resultBnArithmetic = bn1.mod(bn2); break
730
+ case OP.OP_BOOLAND: resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 && bn2.cmpn(0) !== 0) ? 1 : 0); break
731
+ case OP.OP_BOOLOR: resultBnArithmetic = new BigNumber((bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0) ? 1 : 0); break
732
+ case OP.OP_NUMEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
733
+ case OP.OP_NUMEQUALVERIFY: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) === 0 ? 1 : 0); break
734
+ case OP.OP_NUMNOTEQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) !== 0 ? 1 : 0); break
735
+ case OP.OP_LESSTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) < 0 ? 1 : 0); break
736
+ case OP.OP_GREATERTHAN: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) > 0 ? 1 : 0); break
737
+ case OP.OP_LESSTHANOREQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) <= 0 ? 1 : 0); break
738
+ case OP.OP_GREATERTHANOREQUAL: resultBnArithmetic = new BigNumber(bn1.cmp(bn2) >= 0 ? 1 : 0); break
739
+ case OP.OP_MIN: resultBnArithmetic = bn1.cmp(bn2) < 0 ? bn1 : bn2; break
740
+ case OP.OP_MAX: resultBnArithmetic = bn1.cmp(bn2) > 0 ? bn1 : bn2; break
741
+ }
742
+ this.pushStack(resultBnArithmetic.toScriptNum())
1195
743
  if (currentOpcode === OP.OP_NUMEQUALVERIFY) {
1196
- if (this.castToBool(this.stacktop(-1))) {
1197
- poppedValue = this.stack.pop()
1198
- if (poppedValue != null) {
1199
- this.stackMem -= poppedValue.length
1200
- }
1201
- clearPoppedValue()
1202
- } else {
1203
- this.scriptEvaluationError(
1204
- 'OP_NUMEQUALVERIFY requires the top stack item to be truthy.'
1205
- )
1206
- }
744
+ if (!this.castToBool(this.stackTop())) this.scriptEvaluationError('OP_NUMEQUALVERIFY requires the top stack item to be truthy.')
745
+ this.popStack()
1207
746
  }
1208
747
  break
1209
-
748
+ }
1210
749
  case OP.OP_WITHIN:
1211
- if (this.stack.length < 3) {
1212
- this.scriptEvaluationError(
1213
- 'OP_WITHIN requires at least three items to be on the stack.'
1214
- )
1215
- }
1216
- bn1 = BigNumber.fromScriptNum(this.stacktop(-3), requireMinimalPush)
1217
- bn2 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush)
1218
- bn3 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush)
1219
- fValue = bn2.cmp(bn1) <= 0 && bn1.cmp(bn3) < 0
1220
- poppedValue = this.stack.pop()
1221
- if (poppedValue != null) {
1222
- this.stackMem -= poppedValue.length
1223
- }
1224
- clearPoppedValue()
1225
- poppedValue = this.stack.pop()
1226
- if (poppedValue != null) {
1227
- this.stackMem -= poppedValue.length
1228
- }
1229
- clearPoppedValue()
1230
- poppedValue = this.stack.pop()
1231
- if (poppedValue != null) {
1232
- this.stackMem -= poppedValue.length
1233
- }
1234
- clearPoppedValue()
1235
- this.stack.push(fValue ? [1] : [])
1236
- this.stackMem += (fValue ? 1 : 0)
750
+ if (this.stack.length < 3) this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.')
751
+ bn3 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // max
752
+ bn2 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // min
753
+ bn1 = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush) // x
754
+ fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0
755
+ this.pushStack(fValue ? [1] : [])
1237
756
  break
1238
757
 
1239
- case OP.OP_RIPEMD160:
1240
- case OP.OP_SHA1:
1241
- case OP.OP_SHA256:
1242
- case OP.OP_HASH160:
1243
- case OP.OP_HASH256: {
1244
- if (this.stack.length < 1) {
1245
- this.scriptEvaluationError(
1246
- `${OP[currentOpcode] as string} requires at least one item to be on the stack.`
1247
- )
1248
- }
1249
-
1250
- let bufHash: number[] = [] // Initialize bufHash to an empty array
1251
- buf = this.stacktop(-1)
1252
- if (currentOpcode === OP.OP_RIPEMD160) {
1253
- bufHash = Hash.ripemd160(buf)
1254
- } else if (currentOpcode === OP.OP_SHA1) {
1255
- bufHash = Hash.sha1(buf)
1256
- } else if (currentOpcode === OP.OP_SHA256) {
1257
- bufHash = Hash.sha256(buf)
1258
- } else if (currentOpcode === OP.OP_HASH160) {
1259
- bufHash = Hash.hash160(buf)
1260
- } else if (currentOpcode === OP.OP_HASH256) {
1261
- bufHash = Hash.hash256(buf)
1262
- }
1263
-
1264
- poppedValue = this.stack.pop()
1265
- if (poppedValue != null) {
1266
- this.stackMem -= poppedValue.length
1267
- }
1268
- clearPoppedValue()
1269
- this.stack.push(bufHash)
1270
- this.stackMem += bufHash.length
758
+ case OP.OP_RIPEMD160: case OP.OP_SHA1: case OP.OP_SHA256:
759
+ case OP.OP_HASH160: case OP.OP_HASH256: {
760
+ if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
761
+ buf = this.popStack()
762
+ let hashResult: number[] = [] // Initialize to empty, to satisfy TS compiler
763
+ if (currentOpcode === OP.OP_RIPEMD160) hashResult = Hash.ripemd160(buf)
764
+ else if (currentOpcode === OP.OP_SHA1) hashResult = Hash.sha1(buf)
765
+ else if (currentOpcode === OP.OP_SHA256) hashResult = Hash.sha256(buf)
766
+ else if (currentOpcode === OP.OP_HASH160) hashResult = Hash.hash160(buf)
767
+ else if (currentOpcode === OP.OP_HASH256) hashResult = Hash.hash256(buf)
768
+ this.pushStack(hashResult)
1271
769
  break
1272
770
  }
1273
-
1274
771
  case OP.OP_CODESEPARATOR:
1275
772
  this.lastCodeSeparator = this.programCounter
1276
773
  break
1277
-
1278
774
  case OP.OP_CHECKSIG:
1279
- case OP.OP_CHECKSIGVERIFY:
1280
- if (this.stack.length < 2) {
1281
- this.scriptEvaluationError(
1282
- `${OP[currentOpcode] as string} requires at least two items to be on the stack.`
1283
- )
1284
- }
775
+ case OP.OP_CHECKSIGVERIFY: {
776
+ if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
777
+ bufPubkey = this.popStack()
778
+ bufSig = this.popStack()
1285
779
 
1286
- bufSig = this.stacktop(-2)
1287
- bufPubkey = this.stacktop(-1)
1288
-
1289
- if (
1290
- !checkSignatureEncoding(bufSig) ||
1291
- !checkPublicKeyEncoding(bufPubkey)
1292
- ) {
1293
- this.scriptEvaluationError(
1294
- `${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`
1295
- )
1296
- }
1297
-
1298
- // Subset of script starting at the most recent codeseparator
1299
- // CScript scriptCode(pbegincodehash, pend);
1300
- if (this.context === 'UnlockingScript') {
1301
- subscript = new Script(
1302
- this.unlockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
1303
- )
1304
- } else {
1305
- subscript = new Script(
1306
- this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
1307
- )
780
+ if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
781
+ // Error already thrown by helpers
782
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`) // Fallback, should be unreachable
1308
783
  }
1309
784
 
1310
- // Drop the signature, since there's no way for a signature to sign itself
785
+ const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
786
+ const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
787
+ subscript = new Script(scriptCodeChunks)
1311
788
  subscript.findAndDelete(new Script().writeBin(bufSig))
1312
789
 
1313
- try {
1314
- sig = TransactionSignature.fromChecksigFormat(bufSig)
1315
- pubkey = PublicKey.fromDER(bufPubkey)
1316
- fSuccess = verifySignature(sig, pubkey, subscript)
1317
- } catch (e) {
1318
- // invalid sig or pubkey
1319
- fSuccess = false
1320
- }
1321
-
1322
- if (!fSuccess && bufSig.length > 0) {
1323
- this.scriptEvaluationError(
1324
- `${OP[currentOpcode] as string} failed to verify the signature, and requires an empty signature when verification fails.`
1325
- )
1326
- }
1327
-
1328
- poppedValue = this.stack.pop()
1329
- if (poppedValue != null) {
1330
- this.stackMem -= poppedValue.length
1331
- }
1332
- clearPoppedValue()
1333
- poppedValue = this.stack.pop()
1334
- if (poppedValue != null) {
1335
- this.stackMem -= poppedValue.length
790
+ fSuccess = false
791
+ if (bufSig.length > 0) {
792
+ try {
793
+ sig = TransactionSignature.fromChecksigFormat(bufSig)
794
+ pubkey = PublicKey.fromDER(bufPubkey)
795
+ fSuccess = this.verifySignature(sig, pubkey, subscript)
796
+ } catch (e) {
797
+ fSuccess = false
798
+ }
1336
799
  }
1337
- clearPoppedValue()
1338
800
 
1339
- // stack.push_back(fSuccess ? vchTrue : vchFalse);
1340
- this.stack.push(fSuccess ? [1] : [])
1341
- this.stackMem += (fSuccess ? 1 : 0)
801
+ this.pushStack(fSuccess ? [1] : [])
1342
802
  if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
1343
- if (fSuccess) {
1344
- poppedValue = this.stack.pop()
1345
- if (poppedValue != null) {
1346
- this.stackMem -= poppedValue.length
1347
- }
1348
- clearPoppedValue()
1349
- } else {
1350
- this.scriptEvaluationError(
1351
- 'OP_CHECKSIGVERIFY requires that a valid signature is provided.'
1352
- )
1353
- }
803
+ if (!fSuccess) this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.')
804
+ this.popStack()
1354
805
  }
1355
806
  break
1356
-
807
+ }
1357
808
  case OP.OP_CHECKMULTISIG:
1358
- case OP.OP_CHECKMULTISIGVERIFY:
809
+ case OP.OP_CHECKMULTISIGVERIFY: {
1359
810
  i = 1
1360
811
  if (this.stack.length < i) {
1361
- this.scriptEvaluationError(
1362
- `${OP[currentOpcode] as string} requires at least 1 item to be on the stack.`
1363
- )
812
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least 1 item for nKeys.`)
1364
813
  }
1365
814
 
1366
- nKeysCount = BigNumber.fromScriptNum(
1367
- this.stacktop(-i),
1368
- requireMinimalPush
1369
- ).toNumber()
1370
- // TODO: Keys and opcount are parameterized in client. No magic numbers!
815
+ nKeysCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber()
1371
816
  if (nKeysCount < 0 || nKeysCount > maxMultisigKeyCount) {
1372
- this.scriptEvaluationError(
1373
- `${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`
1374
- )
817
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`)
1375
818
  }
1376
819
  ikey = ++i
1377
820
  i += nKeysCount
1378
821
 
1379
- // ikey2 is the position of last non-signature item in
1380
- // the stack. Top stack item = 1. With
1381
- // SCRIPT_VERIFY_NULLFAIL, this is used for cleanup if
1382
- // operation fails.
1383
- ikey2 = nKeysCount + 2
1384
-
1385
822
  if (this.stack.length < i) {
1386
- this.scriptEvaluationError(
1387
- `${OP[currentOpcode] as string} requires the number of stack items not to be less than the number of keys used.`
1388
- )
823
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`)
1389
824
  }
1390
825
 
1391
- nSigsCount = BigNumber.fromScriptNum(
1392
- this.stacktop(-i),
1393
- requireMinimalPush
1394
- ).toNumber()
826
+ nSigsCount = BigNumber.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber()
1395
827
  if (nSigsCount < 0 || nSigsCount > nKeysCount) {
1396
- this.scriptEvaluationError(
1397
- `${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`
1398
- )
828
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`)
1399
829
  }
1400
830
  isig = ++i
1401
831
  i += nSigsCount
1402
832
  if (this.stack.length < i) {
1403
- this.scriptEvaluationError(
1404
- `${OP[currentOpcode] as string} requires the number of stack items not to be less than the number of signatures provided.`
1405
- )
833
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for N, keys, M, sigs, and dummy. Need ${i}, have ${this.stack.length}.`)
1406
834
  }
1407
835
 
1408
- // Subset of script starting at the most recent codeseparator
1409
- if (this.context === 'UnlockingScript') {
1410
- subscript = new Script(
1411
- this.unlockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
1412
- )
1413
- } else {
1414
- subscript = new Script(
1415
- this.lockingScript.chunks.slice(this.lastCodeSeparator ?? 0)
1416
- )
1417
- }
836
+ const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
837
+ const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
838
+ subscript = new Script(subscriptChunksCMS)
1418
839
 
1419
- // Drop the signatures, since there's no way for a signature to sign itself
1420
840
  for (let k = 0; k < nSigsCount; k++) {
1421
- bufSig = this.stacktop(-isig - k)
841
+ bufSig = this.stackTop(-isig - k) // Sigs are closer to top than keys
1422
842
  subscript.findAndDelete(new Script().writeBin(bufSig))
1423
843
  }
1424
844
 
1425
845
  fSuccess = true
1426
846
  while (fSuccess && nSigsCount > 0) {
1427
- // valtype& vchSig = this.stacktop(-isig);
1428
- bufSig = this.stacktop(-isig)
1429
- // valtype& vchPubKey = this.stacktop(-ikey);
1430
- bufPubkey = this.stacktop(-ikey)
1431
-
1432
- if (
1433
- !checkSignatureEncoding(bufSig) ||
1434
- !checkPublicKeyEncoding(bufPubkey)
1435
- ) {
1436
- this.scriptEvaluationError(
1437
- `${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`
1438
- )
847
+ if (nKeysCount === 0) { // No more keys to check against but still sigs left
848
+ fSuccess = false
849
+ break
1439
850
  }
851
+ bufSig = this.stackTop(-isig)
852
+ bufPubkey = this.stackTop(-ikey)
1440
853
 
1441
- try {
1442
- sig = TransactionSignature.fromChecksigFormat(bufSig)
1443
- pubkey = PublicKey.fromString(toHex(bufPubkey))
1444
- fOk = verifySignature(sig, pubkey, subscript)
1445
- } catch (e) {
1446
- // invalid sig or pubkey
1447
- fOk = false
854
+ if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
855
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires correct encoding for the public key and signature.`)
856
+ }
857
+
858
+ fOk = false
859
+ if (bufSig.length > 0) {
860
+ try {
861
+ sig = TransactionSignature.fromChecksigFormat(bufSig)
862
+ pubkey = PublicKey.fromDER(bufPubkey)
863
+ fOk = this.verifySignature(sig, pubkey, subscript)
864
+ } catch (e) {
865
+ fOk = false
866
+ }
1448
867
  }
1449
868
 
1450
869
  if (fOk) {
1451
- isig++
1452
- nSigsCount--
870
+ isig++; nSigsCount--
1453
871
  }
1454
- ikey++
1455
- nKeysCount--
872
+ ikey++; nKeysCount--
1456
873
 
1457
- // If there are more signatures left than keys left,
1458
- // then too many signatures have failed
1459
874
  if (nSigsCount > nKeysCount) {
1460
875
  fSuccess = false
1461
876
  }
1462
877
  }
1463
878
 
1464
- // Clean up stack of actual arguments
1465
- while (i-- > 1) {
1466
- if (!fSuccess && ikey2 === 0 && this.stacktop(-1).length > 0) {
1467
- this.scriptEvaluationError(
1468
- `${OP[currentOpcode] as string} failed to verify a signature, and requires an empty signature when verification fails.`
1469
- )
1470
- }
1471
-
1472
- if (ikey2 > 0) {
1473
- ikey2--
1474
- }
879
+ // Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
880
+ const itemsConsumedByOp = 1 + // N_val
881
+ BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + // keys
882
+ 1 + // M_val
883
+ BigNumber.fromScriptNum(this.stackTop(-(1 + BigNumber.fromScriptNum(this.stackTop(-1), false).toNumber() + 1)), false).toNumber() + // sigs
884
+ 1 // dummy
1475
885
 
1476
- poppedValue = this.stack.pop()
1477
- if (poppedValue != null) {
1478
- this.stackMem -= poppedValue.length
1479
- }
1480
- clearPoppedValue()
886
+ let popCount = itemsConsumedByOp - 1 // Pop all except dummy
887
+ while (popCount > 0) {
888
+ this.popStack()
889
+ popCount--
1481
890
  }
1482
891
 
1483
- // A bug causes CHECKMULTISIG to consume one extra argument
1484
- // whose contents were not checked in any way.
1485
- //
1486
- // Unfortunately this is a potential source of mutability,
1487
- // so optionally verify it is exactly equal to zero prior
1488
- // to removing it from the stack.
892
+ // Check and pop dummy
1489
893
  if (this.stack.length < 1) {
1490
- this.scriptEvaluationError(
1491
- `${OP[currentOpcode] as string} requires an extra item to be on the stack.`
1492
- )
1493
- }
1494
- if (this.stacktop(-1).length > 0) {
1495
- // NOTE: Is this necessary? We don't care about malleability.
1496
- this.scriptEvaluationError(
1497
- `${OP[currentOpcode] as string} requires the extra stack item to be empty.`
1498
- )
894
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires an extra item (dummy) to be on the stack.`)
1499
895
  }
1500
- poppedValue = this.stack.pop()
1501
- if (poppedValue != null) {
1502
- this.stackMem -= poppedValue.length
896
+ const dummyBuf = this.popStack()
897
+ if (dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
898
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the extra stack item (dummy) to be empty.`)
1503
899
  }
1504
- clearPoppedValue()
1505
-
1506
- this.stack.push(fSuccess ? [1] : [])
1507
- this.stackMem += (fSuccess ? 1 : 0)
1508
900
 
901
+ this.pushStack(fSuccess ? [1] : [])
1509
902
  if (currentOpcode === OP.OP_CHECKMULTISIGVERIFY) {
1510
- if (fSuccess) {
1511
- poppedValue = this.stack.pop()
1512
- if (poppedValue != null) {
1513
- this.stackMem -= poppedValue.length
1514
- }
1515
- clearPoppedValue()
1516
- } else {
1517
- this.scriptEvaluationError(
1518
- 'OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.'
1519
- )
1520
- }
903
+ if (!fSuccess) this.scriptEvaluationError('OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.')
904
+ this.popStack()
1521
905
  }
1522
906
  break
907
+ }
1523
908
 
1524
- case OP.OP_CAT:
1525
- if (this.stack.length < 2) {
1526
- this.scriptEvaluationError(
1527
- 'OP_CAT requires at least two items to be on the stack.'
1528
- )
1529
- }
1530
-
1531
- buf1 = this.stacktop(-2)
1532
- buf2 = this.stacktop(-1)
1533
- if (buf1.length + buf2.length > maxScriptElementSize) {
1534
- this.scriptEvaluationError(
1535
- `It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
1536
- )
1537
- }
1538
- this.stack[this.stack.length - 2] = [...buf1, ...buf2]
1539
- this.stackMem -= buf1.length
1540
- this.stackMem -= buf2.length
1541
- this.stackMem += buf1.length + buf2.length
1542
- poppedValue = this.stack.pop()
1543
- if (poppedValue != null) {
1544
- this.stackMem -= poppedValue.length
1545
- }
1546
- clearPoppedValue()
909
+ case OP.OP_CAT: {
910
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_CAT requires at least two items to be on the stack.')
911
+ buf2 = this.popStack()
912
+ buf1 = this.popStack()
913
+ const catResult = (buf1).concat(buf2)
914
+ if (catResult.length > maxScriptElementSize) this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`)
915
+ this.pushStack(catResult)
1547
916
  break
917
+ }
918
+ case OP.OP_SPLIT: {
919
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.')
920
+ const posBuf = this.popStack()
921
+ const dataToSplit = this.popStack()
1548
922
 
1549
- case OP.OP_SPLIT:
1550
- if (this.stack.length < 2) {
1551
- this.scriptEvaluationError(
1552
- 'OP_SPLIT requires at least two items to be on the stack.'
1553
- )
1554
- }
1555
- buf1 = this.stacktop(-2)
1556
-
1557
- // Make sure the split point is apropriate.
1558
- n = BigNumber.fromScriptNum(
1559
- this.stacktop(-1),
1560
- requireMinimalPush
1561
- ).toNumber()
1562
- if (n < 0 || n > buf1.length) {
1563
- this.scriptEvaluationError(
1564
- 'OP_SPLIT requires the first stack item to be a non-negative number less than or equal to the size of the second-from-top stack item.'
1565
- )
923
+ n = BigNumber.fromScriptNum(posBuf, requireMinimalPush).toNumber()
924
+ if (n < 0 || n > dataToSplit.length) {
925
+ this.scriptEvaluationError('OP_SPLIT requires the first stack item to be a non-negative number less than or equal to the size of the second-from-top stack item.')
1566
926
  }
1567
927
 
1568
- // Prepare the results in their own buffer as `data`
1569
- // will be invalidated.
1570
- // Copy buffer data, to slice it before
1571
- buf2 = [...buf1]
1572
-
1573
- // Replace existing stack values by the new values.
1574
- this.stack[this.stack.length - 2] = buf2.slice(0, n)
1575
- this.stackMem -= buf1.length
1576
- this.stackMem += n
1577
- this.stack[this.stack.length - 1] = buf2.slice(n)
1578
- this.stackMem += buf1.length - n
928
+ this.pushStack(dataToSplit.slice(0, n))
929
+ this.pushStack(dataToSplit.slice(n))
1579
930
  break
931
+ }
932
+ case OP.OP_NUM2BIN: {
933
+ if (this.stack.length < 2) this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.')
1580
934
 
1581
- case OP.OP_NUM2BIN:
1582
- if (this.stack.length < 2) {
1583
- this.scriptEvaluationError(
1584
- 'OP_NUM2BIN requires at least two items to be on the stack.'
1585
- )
1586
- }
1587
-
1588
- size = BigNumber.fromScriptNum(
1589
- this.stacktop(-1),
1590
- requireMinimalPush
1591
- ).toNumber()
1592
- if (size > maxScriptElementSize) {
1593
- this.scriptEvaluationError(
1594
- `It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`
1595
- )
1596
- }
1597
-
1598
- poppedValue = this.stack.pop()
1599
- if (poppedValue != null) {
1600
- this.stackMem -= poppedValue.length
935
+ size = BigNumber.fromScriptNum(this.popStack(), requireMinimalPush).toNumber()
936
+ if (size > maxScriptElementSize || size < 0) { // size can be 0
937
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`)
1601
938
  }
1602
- clearPoppedValue()
1603
- rawnum = this.stacktop(-1)
1604
939
 
1605
- // Try to see if we can fit that number in the number of
1606
- // byte requested.
1607
- rawnum = minimallyEncode(rawnum)
940
+ let rawnum = this.popStack() // This is the number to convert
941
+ rawnum = minimallyEncode(rawnum) // Get its minimal scriptnum form
1608
942
 
1609
943
  if (rawnum.length > size) {
1610
- this.scriptEvaluationError(
1611
- 'OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.'
1612
- )
944
+ this.scriptEvaluationError('OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.')
1613
945
  }
1614
946
 
1615
- // We already have an element of the right size, we
1616
- // don't need to do anything.
1617
947
  if (rawnum.length === size) {
1618
- this.stack[this.stack.length - 1] = rawnum
948
+ this.pushStack(rawnum)
1619
949
  break
1620
950
  }
1621
951
 
1622
- signbit = 0x00
1623
- if (rawnum.length > 0) {
1624
- signbit = rawnum[rawnum.length - 1] & 0x80
1625
- rawnum[rawnum.length - 1] &= 0x7f
1626
- }
952
+ const resultN2B = new Array(size).fill(0x00)
953
+ let signbit = 0x00
1627
954
 
1628
- num = new Array(size)
1629
- num.fill(0)
1630
- for (n = 0; n < size; n++) {
1631
- num[n] = rawnum[n]
1632
- }
1633
- n = rawnum.length - 1
1634
- while (n++ < size - 2) {
1635
- num[n] = 0x00
955
+ if (rawnum.length > 0) {
956
+ signbit = rawnum[rawnum.length - 1] & 0x80 // Store sign bit
957
+ rawnum[rawnum.length - 1] &= 0x7f // Remove sign bit for padding
1636
958
  }
1637
959
 
1638
- num[n] = signbit
1639
-
1640
- this.stack[this.stack.length - 1] = num
1641
- this.stackMem -= rawnum.length
1642
- this.stackMem += size
1643
- break
1644
-
1645
- case OP.OP_BIN2NUM:
1646
- if (this.stack.length < 1) {
1647
- this.scriptEvaluationError(
1648
- 'OP_BIN2NUM requires at least one item to be on the stack.'
1649
- )
960
+ // Copy rawnum (now positive magnitude) into the result
961
+ for (let k = 0; k < rawnum.length; k++) {
962
+ resultN2B[k] = rawnum[k]
1650
963
  }
1651
964
 
1652
- buf1 = this.stacktop(-1)
1653
- buf2 = minimallyEncode(buf1)
1654
-
1655
- this.stack[this.stack.length - 1] = buf2
1656
- this.stackMem -= buf1.length
1657
- this.stackMem += buf2.length
1658
-
1659
- // The resulting number must be a valid number.
1660
- if (!isMinimallyEncoded(buf2)) {
1661
- this.scriptEvaluationError(
1662
- 'OP_BIN2NUM requires that the resulting number is valid.'
1663
- )
965
+ // If the original number was negative, the sign bit must be set on the new MSB
966
+ if (signbit !== 0) {
967
+ resultN2B[size - 1] |= 0x80
1664
968
  }
969
+ this.pushStack(resultN2B)
1665
970
  break
971
+ }
972
+ case OP.OP_BIN2NUM: {
973
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_BIN2NUM requires at least one item to be on the stack.')
974
+ buf1 = this.popStack()
975
+ const b2nResult = minimallyEncode(buf1)
976
+ if (!isMinimallyEncodedHelper(b2nResult)) {
977
+ this.scriptEvaluationError('OP_BIN2NUM requires that the resulting number is valid.')
978
+ }
979
+ this.pushStack(b2nResult)
980
+ break
981
+ }
1666
982
 
1667
983
  default:
1668
- this.scriptEvaluationError('Invalid opcode!')
984
+ this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`)
1669
985
  }
1670
986
  }
1671
987
 
1672
- // Finally, increment the program counter
1673
988
  this.programCounter++
1674
989
  return true
1675
990
  }
@@ -1691,6 +1006,7 @@ export default class Spend {
1691
1006
  'Unlocking scripts can only contain push operations, and no other opcodes.'
1692
1007
  )
1693
1008
  }
1009
+
1694
1010
  while (this.step()) {
1695
1011
  if (
1696
1012
  this.context === 'LockingScript' &&
@@ -1699,46 +1015,55 @@ export default class Spend {
1699
1015
  break
1700
1016
  }
1701
1017
  }
1018
+
1702
1019
  if (this.ifStack.length > 0) {
1703
1020
  this.scriptEvaluationError(
1704
- 'Every OP_IF must be terminated prior to the end of the script.'
1021
+ 'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.'
1705
1022
  )
1706
1023
  }
1024
+
1707
1025
  if (requireCleanStack) {
1708
1026
  if (this.stack.length !== 1) {
1709
1027
  this.scriptEvaluationError(
1710
- 'The clean stack rule requires exactly one item to be on the stack after script execution.'
1028
+ `The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
1711
1029
  )
1712
1030
  }
1713
1031
  }
1714
- if (!this.castToBool(this.stacktop(-1))) {
1032
+
1033
+ if (this.stack.length === 0) {
1034
+ this.scriptEvaluationError(
1035
+ 'The top stack element must be truthy after script evaluation (stack is empty).'
1036
+ )
1037
+ } else if (!this.castToBool(this.stackTop())) {
1715
1038
  this.scriptEvaluationError(
1716
1039
  'The top stack element must be truthy after script evaluation.'
1717
1040
  )
1718
1041
  }
1719
- return true
1720
- }
1721
1042
 
1722
- private stacktop (i: number): number[] {
1723
- return this.stack[this.stack.length + i]
1043
+ return true
1724
1044
  }
1725
1045
 
1726
- private castToBool (val: number[]): boolean {
1046
+ private castToBool (val: Readonly<number[]>): boolean {
1047
+ if (val.length === 0) return false
1727
1048
  for (let i = 0; i < val.length; i++) {
1728
1049
  if (val[i] !== 0) {
1729
- // can be negative zero
1730
- if (i === val.length - 1 && val[i] === 0x80) {
1731
- return false
1732
- }
1733
- return true
1050
+ return !(i === val.length - 1 && val[i] === 0x80)
1734
1051
  }
1735
1052
  }
1736
1053
  return false
1737
1054
  }
1738
1055
 
1739
1056
  private scriptEvaluationError (str: string): void {
1057
+ const pcInfo = `Context: ${this.context}, PC: ${this.programCounter}`
1058
+ const stackHex = this.stack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
1059
+ const altStackHex = this.altStack.map(s => (s != null && typeof s.length !== 'undefined') ? toHex(s) : (s === null || s === undefined ? 'null/undef' : 'INVALID_STACK_ITEM')).join(', ')
1060
+
1061
+ const stackInfo = `Stack: [${stackHex}] (len: ${this.stack.length}, mem: ${this.stackMem})`
1062
+ const altStackInfo = `AltStack: [${altStackHex}] (len: ${this.altStack.length}, mem: ${this.altStackMem})`
1063
+ const ifStackInfo = `IfStack: [${this.ifStack.join(', ')}]`
1064
+
1740
1065
  throw new Error(
1741
- `Script evaluation error: ${str}\n\nSource TXID: ${this.sourceTXID}\nSource output index: ${this.sourceOutputIndex}\nContext: ${this.context}\nProgram counter: ${this.programCounter}\nStack size: ${this.stack.length}\nAlt stack size: ${this.altStack.length}`
1066
+ `Script evaluation error: ${str}\nTXID: ${this.sourceTXID}, OutputIdx: ${this.sourceOutputIndex}\n${pcInfo}\n${stackInfo}\n${altStackInfo}\n${ifStackInfo}`
1742
1067
  )
1743
1068
  }
1744
1069
  }