@bsv/sdk 2.0.14 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/cjs/package.json +14 -14
  2. package/dist/cjs/src/primitives/Hash.js +1 -1
  3. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  4. package/dist/cjs/src/primitives/TransactionSignature.js +10 -3
  5. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  6. package/dist/cjs/src/script/Script.js +60 -13
  7. package/dist/cjs/src/script/Script.js.map +1 -1
  8. package/dist/cjs/src/script/Spend.js +434 -59
  9. package/dist/cjs/src/script/Spend.js.map +1 -1
  10. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +6 -2
  11. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  12. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +8 -4
  13. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  14. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  15. package/dist/esm/src/primitives/Hash.js +1 -1
  16. package/dist/esm/src/primitives/Hash.js.map +1 -1
  17. package/dist/esm/src/primitives/TransactionSignature.js +10 -3
  18. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  19. package/dist/esm/src/script/Script.js +60 -13
  20. package/dist/esm/src/script/Script.js.map +1 -1
  21. package/dist/esm/src/script/Spend.js +438 -59
  22. package/dist/esm/src/script/Spend.js.map +1 -1
  23. package/dist/esm/src/transaction/http/BinaryFetchClient.js +6 -2
  24. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  25. package/dist/esm/src/transaction/http/DefaultHttpClient.js +8 -4
  26. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  27. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  28. package/dist/types/src/primitives/TransactionSignature.d.ts +1 -0
  29. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  30. package/dist/types/src/script/Script.d.ts +1 -0
  31. package/dist/types/src/script/Script.d.ts.map +1 -1
  32. package/dist/types/src/script/ScriptChunk.d.ts +1 -0
  33. package/dist/types/src/script/ScriptChunk.d.ts.map +1 -1
  34. package/dist/types/src/script/Spend.d.ts +29 -0
  35. package/dist/types/src/script/Spend.d.ts.map +1 -1
  36. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
  37. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +2 -2
  38. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -1
  39. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  40. package/dist/umd/bundle.js +3 -4
  41. package/docs/reference/primitives.md +1 -0
  42. package/docs/reference/script.md +7 -0
  43. package/docs/reference/transaction.md +2 -2
  44. package/package.json +14 -14
  45. package/src/primitives/Hash.ts +1 -1
  46. package/src/primitives/TransactionSignature.ts +11 -3
  47. package/src/script/Script.ts +59 -13
  48. package/src/script/ScriptChunk.ts +1 -0
  49. package/src/script/Spend.ts +483 -61
  50. package/src/script/__tests/NormativeVectors.test.ts +465 -0
  51. package/src/script/__tests/fixtures/SOURCES.md +25 -0
  52. package/src/script/__tests/fixtures/bitcoin-sv/script_tests.json +2591 -0
  53. package/src/script/__tests/fixtures/bitcoin-sv/sighash.json +1003 -0
  54. package/src/script/__tests/fixtures/bitcoin-sv/tx_invalid.json +285 -0
  55. package/src/script/__tests/fixtures/bitcoin-sv/tx_valid.json +367 -0
  56. package/src/script/__tests/fixtures/teranode/script_tests.json +2432 -0
  57. package/src/script/__tests/fixtures/teranode/sighash.json +1003 -0
  58. package/src/script/__tests/fixtures/teranode/tx_invalid.json +285 -0
  59. package/src/script/__tests/fixtures/teranode/tx_valid.json +367 -0
  60. package/src/transaction/broadcasters/__tests/ARC.test.ts +26 -4
  61. package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +26 -4
  62. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -10
  63. package/src/transaction/http/BinaryFetchClient.ts +5 -2
  64. package/src/transaction/http/DefaultHttpClient.ts +7 -4
  65. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +19 -1
  66. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -827
  67. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  68. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +0 -266
  69. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  70. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -654
  71. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  72. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -144
  73. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  74. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -825
  75. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  76. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +0 -264
  77. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  78. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -619
  79. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  80. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -109
  81. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  82. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +0 -21
  83. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +0 -1
  84. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +0 -2
  85. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +0 -1
  86. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +0 -2
  87. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +0 -1
  88. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +0 -2
  89. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +0 -1
  90. package/dist/umd/bundle.js.map +0 -1
@@ -15,8 +15,14 @@ import TransactionOutput from '../transaction/TransactionOutput.js'
15
15
 
16
16
  // These constants control the current behavior of the interpreter.
17
17
  const maxScriptElementSize = 1024 * 1024 * 1024
18
+ const maxScriptElementSizeBeforeGenesis = 520
19
+ const maxScriptSizeBeforeGenesis = 10000
20
+ const maxOpsBeforeGenesis = 500
21
+ const maxStackItemsBeforeGenesis = 1000
18
22
  const maxMultisigKeyCount = Math.pow(2, 31) - 1
19
23
  const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount)
24
+ const maxMultisigKeyCountBeforeGenesis = 20
25
+ const sequenceLocktimeDisableFlag = 0x80000000
20
26
 
21
27
  // --- Optimization: Pre-computed script numbers ---
22
28
  const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum())
@@ -138,10 +144,14 @@ export default class Spend {
138
144
  stack: number[][]
139
145
  altStack: number[][]
140
146
  ifStack: boolean[]
147
+ elseStack: boolean[]
141
148
  memoryLimit: number
142
149
  stackMem: number
143
150
  altStackMem: number
144
151
  isRelaxedOverride: boolean
152
+ verifyFlags?: Set<string>
153
+ executedOpCount: number
154
+ returningFromConditional: boolean
145
155
 
146
156
  private sigHashCache: SignatureHashCache
147
157
 
@@ -193,6 +203,7 @@ export default class Spend {
193
203
  lockTime: number
194
204
  memoryLimit?: number
195
205
  isRelaxed?: boolean
206
+ verifyFlags?: string | string[]
196
207
  }) {
197
208
  this.sourceTXID = params.sourceTXID
198
209
  this.sourceOutputIndex = params.sourceOutputIndex
@@ -207,11 +218,23 @@ export default class Spend {
207
218
  this.lockTime = params.lockTime
208
219
  this.memoryLimit = params.memoryLimit ?? 32000000
209
220
  this.isRelaxedOverride = params.isRelaxed === true
221
+ this.verifyFlags = params.verifyFlags === undefined
222
+ ? undefined
223
+ : new Set(
224
+ (Array.isArray(params.verifyFlags)
225
+ ? params.verifyFlags
226
+ : params.verifyFlags.split(','))
227
+ .map(flag => flag.trim())
228
+ .filter(flag => flag.length > 0)
229
+ )
210
230
  this.stack = []
211
231
  this.altStack = []
212
232
  this.ifStack = []
233
+ this.elseStack = []
213
234
  this.stackMem = 0
214
235
  this.altStackMem = 0
236
+ this.executedOpCount = 0
237
+ this.returningFromConditional = false
215
238
  this.sigHashCache = { hashOutputsSingle: new Map() }
216
239
  this.reset()
217
240
  }
@@ -221,6 +244,80 @@ export default class Spend {
221
244
  (this.transactionVersion > 1)
222
245
  }
223
246
 
247
+ private hasExplicitFlags (): boolean {
248
+ return this.verifyFlags !== undefined
249
+ }
250
+
251
+ private hasFlag (flag: string): boolean {
252
+ return this.verifyFlags?.has(flag) === true
253
+ }
254
+
255
+ private isAfterGenesis (): boolean {
256
+ if (this.hasExplicitFlags()) {
257
+ return this.hasFlag('GENESIS') ||
258
+ this.hasFlag('UTXO_AFTER_GENESIS') ||
259
+ this.hasFlag('UTXO_AFTER_CHRONICLE')
260
+ }
261
+ return this.isRelaxed()
262
+ }
263
+
264
+ private isAfterChronicle (): boolean {
265
+ if (this.hasExplicitFlags()) return this.hasFlag('UTXO_AFTER_CHRONICLE')
266
+ return this.isRelaxed()
267
+ }
268
+
269
+ private shouldEnforceMinimalData (): boolean {
270
+ if (this.hasExplicitFlags()) return this.hasFlag('MINIMALDATA')
271
+ return !this.isRelaxed()
272
+ }
273
+
274
+ private shouldEnforceLowS (): boolean {
275
+ if (this.hasExplicitFlags()) return this.hasFlag('LOW_S')
276
+ return !this.isRelaxed()
277
+ }
278
+
279
+ private shouldEnforceNullDummy (): boolean {
280
+ if (this.hasExplicitFlags()) return this.hasFlag('NULLDUMMY')
281
+ return !this.isRelaxed()
282
+ }
283
+
284
+ private shouldEnforceSigPushOnly (): boolean {
285
+ if (this.hasExplicitFlags()) return this.hasFlag('SIGPUSHONLY')
286
+ return !this.isRelaxed()
287
+ }
288
+
289
+ private shouldEnforceCleanStack (): boolean {
290
+ if (this.hasExplicitFlags()) return this.hasFlag('CLEANSTACK')
291
+ return !this.isRelaxed()
292
+ }
293
+
294
+ private shouldEnforceDerSignatures (): boolean {
295
+ if (this.hasExplicitFlags()) {
296
+ return this.hasFlag('DERSIG') ||
297
+ this.hasFlag('STRICTENC') ||
298
+ this.hasFlag('LOW_S') ||
299
+ this.hasFlag('SIGHASH_FORKID')
300
+ }
301
+ return true
302
+ }
303
+
304
+ private shouldEnforceStrictEncoding (): boolean {
305
+ if (this.hasExplicitFlags()) {
306
+ return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID')
307
+ }
308
+ return true
309
+ }
310
+
311
+ private scriptNumMaxSize (): number | undefined {
312
+ if (this.hasExplicitFlags() && !this.isAfterGenesis()) return 4
313
+ return undefined
314
+ }
315
+
316
+ private maxPushSize (): number {
317
+ if (this.hasExplicitFlags() && !this.isAfterGenesis()) return maxScriptElementSizeBeforeGenesis
318
+ return maxScriptElementSize
319
+ }
320
+
224
321
  reset (): void {
225
322
  this.context = 'UnlockingScript'
226
323
  this.programCounter = 0
@@ -228,8 +325,11 @@ export default class Spend {
228
325
  this.stack = []
229
326
  this.altStack = []
230
327
  this.ifStack = []
328
+ this.elseStack = []
231
329
  this.stackMem = 0
232
330
  this.altStackMem = 0
331
+ this.executedOpCount = 0
332
+ this.returningFromConditional = false
233
333
  this.sigHashCache = { hashOutputsSingle: new Map() }
234
334
  }
235
335
 
@@ -284,6 +384,16 @@ export default class Spend {
284
384
  return this.stack[this.stack.length + index]
285
385
  }
286
386
 
387
+ private setStack (items: number[][]): void {
388
+ this.stack = items.map(item => item.slice())
389
+ this.stackMem = this.stack.reduce((total, item) => total + item.length, 0)
390
+ }
391
+
392
+ private clearAltStack (): void {
393
+ this.altStack = []
394
+ this.altStackMem = 0
395
+ }
396
+
287
397
  private pushAltStack (item: number[]): void {
288
398
  this.ensureAltStackMem(item.length)
289
399
  this.altStack.push(item)
@@ -303,27 +413,130 @@ export default class Spend {
303
413
  return item
304
414
  }
305
415
 
416
+ private readScriptNumber (buf: number[]): BigNumber {
417
+ try {
418
+ return BigNumber.fromScriptNum(
419
+ buf,
420
+ this.shouldEnforceMinimalData(),
421
+ this.scriptNumMaxSize()
422
+ )
423
+ } catch (e) {
424
+ const message = e instanceof Error ? e.message : String(e)
425
+ this.scriptEvaluationError(message)
426
+ }
427
+ return new BigNumber(0)
428
+ }
429
+
430
+ private isDefinedHashType (scope: number): boolean {
431
+ const baseType = scope & 0x1f
432
+ return baseType >= TransactionSignature.SIGHASH_ALL &&
433
+ baseType <= TransactionSignature.SIGHASH_SINGLE
434
+ }
435
+
306
436
  private checkSignatureEncoding (buf: Readonly<number[]>): boolean {
307
437
  if (buf.length === 0) return true
308
438
 
309
- if (!isChecksigFormatHelper(buf)) {
439
+ const enforceDer = this.shouldEnforceDerSignatures()
440
+ if (enforceDer && !isChecksigFormatHelper(buf)) {
310
441
  this.scriptEvaluationError('The signature format is invalid.') // Generic message like original
311
442
  return false
312
443
  }
313
444
  try {
314
445
  const sig = TransactionSignature.fromChecksigFormat(buf as number[]) // This can throw for stricter DER rules
315
- if (!this.isRelaxed() && !sig.hasLowS()) {
446
+ if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
447
+ this.scriptEvaluationError('The signature hash type is invalid.')
448
+ return false
449
+ }
450
+ if (
451
+ this.shouldEnforceStrictEncoding() &&
452
+ (sig.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0 &&
453
+ !this.isAfterChronicle()
454
+ ) {
455
+ this.scriptEvaluationError('The signature hash type is invalid before Chronicle.')
456
+ return false
457
+ }
458
+ const hasForkId = (sig.scope & TransactionSignature.SIGHASH_FORKID) !== 0
459
+ if (this.hasExplicitFlags()) {
460
+ if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
461
+ this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
462
+ return false
463
+ }
464
+ if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
465
+ this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.')
466
+ return false
467
+ }
468
+ }
469
+ if (this.shouldEnforceLowS() && !sig.hasLowS()) {
316
470
  this.scriptEvaluationError('The signature must have a low S value.')
317
471
  return false
318
472
  }
319
473
  } catch {
320
- this.scriptEvaluationError('The signature format is invalid.')
321
- return false
474
+ if (enforceDer) {
475
+ this.scriptEvaluationError('The signature format is invalid.')
476
+ return false
477
+ }
322
478
  }
323
479
  return true
324
480
  }
325
481
 
482
+ private parseChecksigSignature (buf: number[]): TransactionSignature {
483
+ try {
484
+ return TransactionSignature.fromChecksigFormat(buf)
485
+ } catch (e) {
486
+ if (this.shouldEnforceDerSignatures()) throw e
487
+ return this.parseLaxChecksigSignature(buf)
488
+ }
489
+ }
490
+
491
+ private readLaxDERLength (buf: number[], position: { value: number }): number {
492
+ const first = buf[position.value++]
493
+ if (first === undefined) throw new Error('Invalid DER length')
494
+ if ((first & 0x80) === 0) return first
495
+
496
+ const lengthBytes = first & 0x7f
497
+ if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
498
+ throw new Error('Invalid DER length')
499
+ }
500
+
501
+ let length = 0
502
+ for (let i = 0; i < lengthBytes; i++) {
503
+ length = (length << 8) | (buf[position.value++] ?? 0)
504
+ }
505
+ return length
506
+ }
507
+
508
+ private parseLaxDERInteger (buf: number[], position: { value: number }, sequenceEnd: number): BigNumber {
509
+ if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
510
+ throw new Error('Invalid DER integer')
511
+ }
512
+ const length = this.readLaxDERLength(buf, position)
513
+ if (position.value + length > sequenceEnd) {
514
+ throw new Error('Invalid DER integer length')
515
+ }
516
+ let bytes = buf.slice(position.value, position.value + length)
517
+ position.value += length
518
+
519
+ while (bytes.length > 1 && bytes[0] === 0) bytes = bytes.slice(1)
520
+ if (bytes.length === 0) bytes = [0]
521
+ return new BigNumber(bytes)
522
+ }
523
+
524
+ private parseLaxChecksigSignature (buf: number[]): TransactionSignature {
525
+ if (buf.length === 0) return TransactionSignature.fromChecksigFormat(buf)
526
+
527
+ const scope = buf[buf.length - 1]
528
+ const der = buf.slice(0, -1)
529
+ const position = { value: 0 }
530
+ if (der[position.value++] !== 0x30) throw new Error('Signature DER must start with 0x30')
531
+ const sequenceLength = this.readLaxDERLength(der, position)
532
+ const sequenceEnd = Math.min(position.value + sequenceLength, der.length)
533
+ const r = this.parseLaxDERInteger(der, position, sequenceEnd)
534
+ const s = this.parseLaxDERInteger(der, position, sequenceEnd)
535
+ return new TransactionSignature(r, s, scope)
536
+ }
537
+
326
538
  private checkPublicKeyEncoding (buf: Readonly<number[]>): boolean {
539
+ if (!this.shouldEnforceStrictEncoding()) return true
327
540
  if (buf.length === 0) {
328
541
  this.scriptEvaluationError('Public key is empty.')
329
542
  return false
@@ -360,7 +573,7 @@ export default class Spend {
360
573
  pubkey: PublicKey,
361
574
  subscript: Script
362
575
  ): boolean {
363
- const preimage = TransactionSignature.formatBytes({
576
+ const params = {
364
577
  sourceTXID: this.sourceTXID,
365
578
  sourceOutputIndex: this.sourceOutputIndex,
366
579
  sourceSatoshis: this.sourceSatoshis,
@@ -373,8 +586,10 @@ export default class Spend {
373
586
  lockTime: this.lockTime,
374
587
  scope: sig.scope,
375
588
  cache: this.sigHashCache
376
- })
377
- const hash = new BigNumber(Hash.hash256(preimage))
589
+ }
590
+ const hash = TransactionSignature.usesOtdaSingleBug(params)
591
+ ? new BigNumber([1, ...new Array(31).fill(0)])
592
+ : new BigNumber(Hash.hash256(TransactionSignature.formatBytes(params)))
378
593
  return verify(hash, sig, pubkey)
379
594
  }
380
595
 
@@ -392,6 +607,16 @@ export default class Spend {
392
607
  this.context === 'UnlockingScript' &&
393
608
  this.programCounter >= this.unlockingScript.chunks.length
394
609
  ) {
610
+ if (this.ifStack.length > 0) {
611
+ this.scriptEvaluationError(
612
+ 'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.'
613
+ )
614
+ }
615
+ this.clearAltStack()
616
+ this.ifStack = []
617
+ this.elseStack = []
618
+ this.returningFromConditional = false
619
+ this.lastCodeSeparator = null
395
620
  this.context = 'LockingScript'
396
621
  this.programCounter = 0
397
622
  }
@@ -406,14 +631,31 @@ export default class Spend {
406
631
  if (currentOpcode === undefined) {
407
632
  this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`) // Error thrown
408
633
  }
409
- if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
410
- this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`) // Error thrown
634
+ if (operation.invalidLength === true) {
635
+ this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`)
636
+ }
637
+ if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
638
+ this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`) // Error thrown
411
639
  }
412
640
 
413
- const isScriptExecuting = !this.ifStack.includes(false)
641
+ const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false)
642
+
643
+ if (
644
+ this.hasExplicitFlags() &&
645
+ !this.isAfterGenesis() &&
646
+ !this.isAfterChronicle() &&
647
+ (
648
+ currentOpcode === OP.OP_2MUL ||
649
+ currentOpcode === OP.OP_2DIV ||
650
+ currentOpcode === OP.OP_VERIF ||
651
+ currentOpcode === OP.OP_VERNOTIF
652
+ )
653
+ ) {
654
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
655
+ }
414
656
 
415
657
  if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
416
- if (!this.isRelaxed() && !isChunkMinimalPushHelper(operation)) {
658
+ if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
417
659
  this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`) // Error thrown
418
660
  }
419
661
  this.pushStack(Array.isArray(operation.data) ? operation.data : [])
@@ -426,6 +668,70 @@ export default class Spend {
426
668
  let sig: TransactionSignature, pubkey: PublicKey
427
669
  let i: number, ikey: number, isig: number, nKeysCount: number, nSigsCount: number, fOk: boolean
428
670
 
671
+ if (isScriptExecuting && currentOpcode > OP.OP_16) {
672
+ this.executedOpCount++
673
+ if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
674
+ this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`)
675
+ }
676
+ }
677
+
678
+ if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
679
+ if (
680
+ isScriptExecuting &&
681
+ (
682
+ currentOpcode === OP.OP_SUBSTR ||
683
+ currentOpcode === OP.OP_LEFT ||
684
+ currentOpcode === OP.OP_RIGHT ||
685
+ currentOpcode === OP.OP_LSHIFTNUM ||
686
+ currentOpcode === OP.OP_RSHIFTNUM
687
+ )
688
+ ) {
689
+ if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
690
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} is discouraged by verification flags.`)
691
+ }
692
+ this.programCounter++
693
+ return true
694
+ }
695
+ if (
696
+ (isScriptExecuting || !this.isAfterGenesis()) &&
697
+ (currentOpcode === OP.OP_2MUL || currentOpcode === OP.OP_2DIV)
698
+ ) {
699
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
700
+ }
701
+ if (
702
+ (isScriptExecuting || !this.isAfterGenesis()) &&
703
+ (
704
+ currentOpcode === OP.OP_VER ||
705
+ currentOpcode === OP.OP_VERIF ||
706
+ currentOpcode === OP.OP_VERNOTIF
707
+ )
708
+ ) {
709
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} is disabled until Chronicle.`)
710
+ }
711
+ if (
712
+ !isScriptExecuting &&
713
+ this.isAfterGenesis() &&
714
+ (currentOpcode === OP.OP_VERIF || currentOpcode === OP.OP_VERNOTIF)
715
+ ) {
716
+ this.programCounter++
717
+ return true
718
+ }
719
+ }
720
+
721
+ if (
722
+ isScriptExecuting &&
723
+ this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
724
+ (
725
+ currentOpcode === OP.OP_NOP1 ||
726
+ currentOpcode === OP.OP_CHECKLOCKTIMEVERIFY ||
727
+ currentOpcode === OP.OP_CHECKSEQUENCEVERIFY ||
728
+ currentOpcode === OP.OP_NOP9 ||
729
+ currentOpcode === OP.OP_NOP10
730
+ )
731
+ ) {
732
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} is discouraged by verification flags.`)
733
+ }
734
+
429
735
  switch (currentOpcode) {
430
736
  case OP.OP_VER: {
431
737
  // Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
@@ -435,8 +741,8 @@ export default class Spend {
435
741
  }
436
742
  case OP.OP_SUBSTR: {
437
743
  if (this.stack.length < 3) this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.')
438
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
439
- const offset = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
744
+ const len = this.readScriptNumber(this.popStack()).toNumber()
745
+ const offset = this.readScriptNumber(this.popStack()).toNumber()
440
746
  buf = this.popStack()
441
747
  const size = buf.length
442
748
 
@@ -449,7 +755,7 @@ export default class Spend {
449
755
  }
450
756
  case OP.OP_LEFT: {
451
757
  if (this.stack.length < 2) this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.')
452
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
758
+ const len = this.readScriptNumber(this.popStack()).toNumber()
453
759
  buf = this.popStack()
454
760
  const size = buf.length
455
761
 
@@ -462,7 +768,7 @@ export default class Spend {
462
768
  }
463
769
  case OP.OP_RIGHT: {
464
770
  if (this.stack.length < 2) this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.')
465
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber()
771
+ const len = this.readScriptNumber(this.popStack()).toNumber()
466
772
  buf = this.popStack()
467
773
  const size = buf.length
468
774
 
@@ -475,22 +781,22 @@ export default class Spend {
475
781
  }
476
782
  case OP.OP_LSHIFTNUM: {
477
783
  if (this.stack.length < 2) this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.')
478
- const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
784
+ const bits = this.readScriptNumber(this.popStack()).toBigInt()
479
785
  if (bits < 0) {
480
786
  this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.')
481
787
  }
482
- const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
788
+ const value = this.readScriptNumber(this.popStack()).toBigInt()
483
789
  const resultBn = new BigNumber(value << bits)
484
790
  this.pushStack(resultBn.toScriptNum())
485
791
  break
486
792
  }
487
793
  case OP.OP_RSHIFTNUM: {
488
794
  if (this.stack.length < 2) this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.')
489
- const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
795
+ const bits = this.readScriptNumber(this.popStack()).toBigInt()
490
796
  if (bits < 0) {
491
797
  this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.')
492
798
  }
493
- const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
799
+ const value = this.readScriptNumber(this.popStack()).toBigInt()
494
800
  let resultBn: BigNumber
495
801
  if (value < 0) {
496
802
  resultBn = new BigNumber(-(-value >> bits))
@@ -519,9 +825,24 @@ export default class Spend {
519
825
  // OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
520
826
  // falls through
521
827
  case OP.OP_CHECKLOCKTIMEVERIFY:
828
+ break
522
829
  // OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
523
- // falls through
524
830
  case OP.OP_CHECKSEQUENCEVERIFY:
831
+ if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
832
+ if (this.stack.length < 1) this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.')
833
+ let sequenceLock = 0n
834
+ try {
835
+ // BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
836
+ sequenceLock = BigNumber.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt()
837
+ } catch {
838
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.')
839
+ }
840
+ if (sequenceLock < 0n) this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.')
841
+ if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
842
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.')
843
+ }
844
+ }
845
+ break
525
846
  case OP.OP_NOP9:
526
847
  case OP.OP_NOP10:
527
848
  break
@@ -541,6 +862,7 @@ export default class Spend {
541
862
  if (currentOpcode === OP.OP_VERNOTIF) fValue = !fValue
542
863
  }
543
864
  this.ifStack.push(fValue)
865
+ this.elseStack.push(false)
544
866
  break
545
867
  case OP.OP_IF:
546
868
  case OP.OP_NOTIF:
@@ -548,18 +870,27 @@ export default class Spend {
548
870
  if (isScriptExecuting) {
549
871
  if (this.stack.length < 1) this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!')
550
872
  buf = this.popStack()
873
+ if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
874
+ this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.')
875
+ }
551
876
  fValue = this.castToBool(buf)
552
877
  if (currentOpcode === OP.OP_NOTIF) fValue = !fValue
553
878
  }
554
879
  this.ifStack.push(fValue)
880
+ this.elseStack.push(false)
555
881
  break
556
882
  case OP.OP_ELSE:
557
883
  if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.')
884
+ if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
885
+ this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.')
886
+ }
887
+ this.elseStack[this.elseStack.length - 1] = true
558
888
  this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1])
559
889
  break
560
890
  case OP.OP_ENDIF:
561
891
  if (this.ifStack.length === 0) this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.')
562
892
  this.ifStack.pop()
893
+ this.elseStack.pop()
563
894
  break
564
895
  case OP.OP_VERIFY:
565
896
  if (this.stack.length < 1) this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.')
@@ -569,10 +900,16 @@ export default class Spend {
569
900
  this.popStack()
570
901
  break
571
902
  case OP.OP_RETURN:
572
- if (this.context === 'UnlockingScript') this.programCounter = this.unlockingScript.chunks.length
573
- else this.programCounter = this.lockingScript.chunks.length
574
- this.ifStack = []
575
- this.programCounter-- // To counteract the final increment and ensure loop termination
903
+ if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
904
+ this.scriptEvaluationError('OP_RETURN is invalid before Genesis.')
905
+ }
906
+ if (this.ifStack.length > 0) {
907
+ this.returningFromConditional = true
908
+ } else {
909
+ if (this.context === 'UnlockingScript') this.programCounter = this.unlockingScript.chunks.length
910
+ else this.programCounter = this.lockingScript.chunks.length
911
+ this.programCounter-- // To counteract the final increment and ensure loop termination
912
+ }
576
913
  break
577
914
 
578
915
  case OP.OP_TOALTSTACK:
@@ -655,7 +992,7 @@ export default class Spend {
655
992
  case OP.OP_PICK:
656
993
  case OP.OP_ROLL: {
657
994
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
658
- bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed())
995
+ bn = this.readScriptNumber(this.popStack())
659
996
  const nBigInt = bn.toBigInt()
660
997
  if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
661
998
  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.`)
@@ -728,7 +1065,7 @@ export default class Spend {
728
1065
  case OP.OP_LSHIFT:
729
1066
  case OP.OP_RSHIFT: {
730
1067
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
731
- bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // n (shift amount)
1068
+ bn2 = this.readScriptNumber(this.popStack()) // n (shift amount)
732
1069
  buf1 = this.popStack() // value to shift
733
1070
  const shiftBits = bn2.toBigInt()
734
1071
  if (shiftBits < 0n) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`)
@@ -768,7 +1105,7 @@ export default class Spend {
768
1105
  case OP.OP_NEGATE: case OP.OP_ABS:
769
1106
  case OP.OP_NOT: case OP.OP_0NOTEQUAL:
770
1107
  if (this.stack.length < 1) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least one item to be on the stack.`)
771
- bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed())
1108
+ bn = this.readScriptNumber(this.popStack())
772
1109
  switch (currentOpcode) {
773
1110
  case OP.OP_1ADD: bn = bn.add(new BigNumber(1)); break
774
1111
  case OP.OP_1SUB: bn = bn.sub(new BigNumber(1)); break
@@ -790,8 +1127,8 @@ export default class Spend {
790
1127
  if (this.stack.length < 2) this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least two items to be on the stack.`)
791
1128
  buf2 = this.popStack()
792
1129
  buf1 = this.popStack()
793
- bn2 = BigNumber.fromScriptNum(buf2, !this.isRelaxed())
794
- bn1 = BigNumber.fromScriptNum(buf1, !this.isRelaxed())
1130
+ bn2 = this.readScriptNumber(buf2)
1131
+ bn1 = this.readScriptNumber(buf1)
795
1132
  let predictedLen = 0
796
1133
  switch (currentOpcode) {
797
1134
  case OP.OP_MUL:
@@ -837,9 +1174,9 @@ export default class Spend {
837
1174
  }
838
1175
  case OP.OP_WITHIN:
839
1176
  if (this.stack.length < 3) this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.')
840
- bn3 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // max
841
- bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // min
842
- bn1 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()) // x
1177
+ bn3 = this.readScriptNumber(this.popStack()) // max
1178
+ bn2 = this.readScriptNumber(this.popStack()) // min
1179
+ bn1 = this.readScriptNumber(this.popStack()) // x
843
1180
  fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0
844
1181
  this.pushStack(fValue ? [1] : [])
845
1182
  break
@@ -874,7 +1211,7 @@ export default class Spend {
874
1211
  fSuccess = false
875
1212
  if (bufSig.length > 0) {
876
1213
  try {
877
- sig = TransactionSignature.fromChecksigFormat(bufSig)
1214
+ sig = this.parseChecksigSignature(bufSig)
878
1215
 
879
1216
  const scriptForChecksig: Script = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
880
1217
  const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
@@ -888,6 +1225,9 @@ export default class Spend {
888
1225
  }
889
1226
  }
890
1227
 
1228
+ if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
1229
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires failing signatures to be empty.`)
1230
+ }
891
1231
  this.pushStack(fSuccess ? [1] : [])
892
1232
  if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
893
1233
  if (!fSuccess) this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.')
@@ -902,10 +1242,13 @@ export default class Spend {
902
1242
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires at least 1 item for nKeys.`)
903
1243
  }
904
1244
 
905
- const nKeysCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed())
1245
+ const nKeysCountBN = this.readScriptNumber(this.stackTop(-i))
906
1246
  const nKeysCountBigInt = nKeysCountBN.toBigInt()
907
- if (nKeysCountBigInt < 0n || nKeysCountBigInt > maxMultisigKeyCountBigInt) {
908
- this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${maxMultisigKeyCount}.`)
1247
+ const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
1248
+ ? BigInt(maxMultisigKeyCountBeforeGenesis)
1249
+ : maxMultisigKeyCountBigInt
1250
+ if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
1251
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`)
909
1252
  }
910
1253
  nKeysCount = Number(nKeysCountBigInt)
911
1254
  const declaredKeyCount = nKeysCount
@@ -916,7 +1259,7 @@ export default class Spend {
916
1259
  this.scriptEvaluationError(`${OP[currentOpcode] as string} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`)
917
1260
  }
918
1261
 
919
- const nSigsCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed())
1262
+ const nSigsCountBN = this.readScriptNumber(this.stackTop(-i))
920
1263
  const nSigsCountBigInt = nSigsCountBN.toBigInt()
921
1264
  if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
922
1265
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the number of signatures to be no greater than the number of keys.`)
@@ -933,8 +1276,10 @@ export default class Spend {
933
1276
  const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1)
934
1277
  subscript = new Script(subscriptChunksCMS)
935
1278
 
1279
+ let hasNonEmptySignature = false
936
1280
  for (let k = 0; k < nSigsCount; k++) {
937
1281
  bufSig = this.stackTop(-isig - k) // Sigs are closer to top than keys
1282
+ if (bufSig.length > 0) hasNonEmptySignature = true
938
1283
  subscript.findAndDelete(new Script().writeBin(bufSig))
939
1284
  }
940
1285
 
@@ -954,7 +1299,7 @@ export default class Spend {
954
1299
  fOk = false
955
1300
  if (bufSig.length > 0) {
956
1301
  try {
957
- sig = TransactionSignature.fromChecksigFormat(bufSig)
1302
+ sig = this.parseChecksigSignature(bufSig)
958
1303
  pubkey = PublicKey.fromDER(bufPubkey)
959
1304
  fOk = this.verifySignature(sig, pubkey, subscript)
960
1305
  } catch {
@@ -972,6 +1317,10 @@ export default class Spend {
972
1317
  }
973
1318
  }
974
1319
 
1320
+ if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
1321
+ this.scriptEvaluationError(`${OP[currentOpcode] as string} requires failing signatures to be empty.`)
1322
+ }
1323
+
975
1324
  // Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
976
1325
  const itemsConsumedByOp = 1 + // N_val
977
1326
  declaredKeyCount + // keys
@@ -990,7 +1339,7 @@ export default class Spend {
990
1339
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires an extra item (dummy) to be on the stack.`)
991
1340
  }
992
1341
  const dummyBuf = this.popStack()
993
- if (!this.isRelaxed() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
1342
+ if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
994
1343
  this.scriptEvaluationError(`${OP[currentOpcode] as string} requires the extra stack item (dummy) to be empty.`)
995
1344
  }
996
1345
 
@@ -1007,7 +1356,7 @@ export default class Spend {
1007
1356
  buf2 = this.popStack()
1008
1357
  buf1 = this.popStack()
1009
1358
  const catResult = (buf1).concat(buf2)
1010
- if (catResult.length > maxScriptElementSize) this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`)
1359
+ if (catResult.length > this.maxPushSize()) this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`)
1011
1360
  this.pushStack(catResult)
1012
1361
  break
1013
1362
  }
@@ -1016,7 +1365,7 @@ export default class Spend {
1016
1365
  const posBuf = this.popStack()
1017
1366
  const dataToSplit = this.popStack()
1018
1367
 
1019
- const splitIndexBigInt = BigNumber.fromScriptNum(posBuf, !this.isRelaxed()).toBigInt()
1368
+ const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt()
1020
1369
  if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
1021
1370
  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.')
1022
1371
  }
@@ -1029,9 +1378,9 @@ export default class Spend {
1029
1378
  case OP.OP_NUM2BIN: {
1030
1379
  if (this.stack.length < 2) this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.')
1031
1380
 
1032
- const sizeBigInt = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt()
1033
- if (sizeBigInt > BigInt(maxScriptElementSize) || sizeBigInt < 0n) { // size can be 0
1034
- this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`)
1381
+ const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt()
1382
+ if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
1383
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`)
1035
1384
  }
1036
1385
  size = Number(sizeBigInt)
1037
1386
 
@@ -1083,7 +1432,18 @@ export default class Spend {
1083
1432
  }
1084
1433
  }
1085
1434
 
1086
- this.programCounter++
1435
+ if (this.returningFromConditional && this.ifStack.length === 0) {
1436
+ this.programCounter = currentScript.chunks.length
1437
+ } else {
1438
+ this.programCounter++
1439
+ }
1440
+ if (
1441
+ this.hasExplicitFlags() &&
1442
+ !this.isAfterGenesis() &&
1443
+ this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis
1444
+ ) {
1445
+ this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`)
1446
+ }
1087
1447
  return true
1088
1448
  }
1089
1449
 
@@ -1099,35 +1459,99 @@ export default class Spend {
1099
1459
  * }
1100
1460
  */
1101
1461
  validate (): boolean {
1102
- if (!this.isRelaxed() && !this.unlockingScript.isPushOnly()) {
1462
+ if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
1103
1463
  this.scriptEvaluationError(
1104
1464
  'Unlocking scripts can only contain push operations, and no other opcodes.'
1105
1465
  )
1106
1466
  }
1107
1467
 
1108
- while (this.step()) {
1109
- if (
1110
- this.context === 'LockingScript' &&
1111
- this.programCounter >= this.lockingScript.chunks.length
1112
- ) {
1113
- break
1468
+ const originalLockingScript = this.lockingScript
1469
+ const shouldEvaluateP2SH =
1470
+ this.hasFlag('P2SH') &&
1471
+ !this.isAfterGenesis() &&
1472
+ this.isP2SHLockingScript(this.lockingScript)
1473
+
1474
+ if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
1475
+ this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.')
1476
+ }
1477
+
1478
+ this.reset()
1479
+ this.runScript('UnlockingScript')
1480
+ const stackAfterUnlockingScript = this.stack.map(item => item.slice())
1481
+
1482
+ this.runScript('LockingScript')
1483
+ this.requireTruthyTopStack()
1484
+
1485
+ try {
1486
+ if (shouldEvaluateP2SH) {
1487
+ if (stackAfterUnlockingScript.length === 0) {
1488
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.')
1489
+ }
1490
+ const redeemScriptBytes = stackAfterUnlockingScript.pop()
1491
+ if (redeemScriptBytes === undefined) {
1492
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.')
1493
+ return false
1494
+ }
1495
+ this.setStack(stackAfterUnlockingScript)
1496
+ const redeemScript = Script.fromBinary(redeemScriptBytes)
1497
+ this.lockingScript = new LockingScript(redeemScript.chunks)
1498
+ this.runScript('LockingScript')
1114
1499
  }
1500
+ } finally {
1501
+ this.lockingScript = originalLockingScript
1115
1502
  }
1116
1503
 
1504
+ if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
1505
+ this.scriptEvaluationError(
1506
+ `The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
1507
+ )
1508
+ }
1509
+
1510
+ this.requireTruthyTopStack()
1511
+ return true
1512
+ }
1513
+
1514
+ private runScript (context: 'UnlockingScript' | 'LockingScript'): void {
1515
+ this.context = context
1516
+ this.programCounter = 0
1517
+ this.ifStack = []
1518
+ this.elseStack = []
1519
+ this.returningFromConditional = false
1520
+ this.clearAltStack()
1521
+ this.lastCodeSeparator = null
1522
+ const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript
1523
+ if (
1524
+ this.hasExplicitFlags() &&
1525
+ !this.isAfterGenesis() &&
1526
+ script.toUint8Array().length > maxScriptSizeBeforeGenesis
1527
+ ) {
1528
+ this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`)
1529
+ }
1530
+ while (this.programCounter < script.chunks.length) {
1531
+ this.step()
1532
+ }
1117
1533
  if (this.ifStack.length > 0) {
1118
1534
  this.scriptEvaluationError(
1119
1535
  'Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.'
1120
1536
  )
1121
1537
  }
1538
+ this.ifStack = []
1539
+ this.elseStack = []
1540
+ this.clearAltStack()
1541
+ this.lastCodeSeparator = null
1542
+ }
1122
1543
 
1123
- if (!this.isRelaxed()) {
1124
- if (this.stack.length !== 1) {
1125
- this.scriptEvaluationError(
1126
- `The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`
1127
- )
1128
- }
1129
- }
1544
+ private isP2SHLockingScript (script: LockingScript): boolean {
1545
+ const chunks = script.chunks
1546
+ return chunks.length === 3 &&
1547
+ chunks[0].op === OP.OP_HASH160 &&
1548
+ chunks[1].op === 20 &&
1549
+ Array.isArray(chunks[1].data) &&
1550
+ chunks[1].data.length === 20 &&
1551
+ chunks[2].op === OP.OP_EQUAL
1552
+ }
1130
1553
 
1554
+ private requireTruthyTopStack (): void {
1131
1555
  if (this.stack.length === 0) {
1132
1556
  this.scriptEvaluationError(
1133
1557
  'The top stack element must be truthy after script evaluation (stack is empty).'
@@ -1137,8 +1561,6 @@ export default class Spend {
1137
1561
  'The top stack element must be truthy after script evaluation.'
1138
1562
  )
1139
1563
  }
1140
-
1141
- return true
1142
1564
  }
1143
1565
 
1144
1566
  private castToBool (val: Readonly<number[]>): boolean {