@bsv/sdk 1.4.20 → 1.4.22

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.
@@ -1974,7 +1974,7 @@ Argument Details
1974
1974
  Verifies the legitimacy of the Bitcoin transaction according to the rules of SPV by ensuring all the input transactions link back to valid block headers, the chain of spends for all inputs are valid, and the sum of inputs is not less than the sum of outputs.
1975
1975
 
1976
1976
  ```ts
1977
- async verify(chainTracker: ChainTracker | "scripts only" = defaultChainTracker(), feeModel?: FeeModel): Promise<boolean>
1977
+ async verify(chainTracker: ChainTracker | "scripts only" = defaultChainTracker(), feeModel?: FeeModel, memoryLimit?: number): Promise<boolean>
1978
1978
  ```
1979
1979
  See also: [ChainTracker](./transaction.md#interface-chaintracker), [FeeModel](./transaction.md#interface-feemodel), [defaultChainTracker](./transaction.md#function-defaultchaintracker)
1980
1980
 
@@ -1986,6 +1986,10 @@ Argument Details
1986
1986
 
1987
1987
  + **chainTracker**
1988
1988
  + An instance of ChainTracker, a Bitcoin block header tracker. If the value is set to 'scripts only', headers will not be verified. If not provided then the default chain tracker will be used.
1989
+ + **feeModel**
1990
+ + An instance of FeeModel, a fee model to use for fee calculation. If not provided then the default fee model will be used.
1991
+ + **memoryLimit**
1992
+ + The maximum memory in bytes usage allowed for script evaluation. If not provided then the default memory limit will be used.
1989
1993
 
1990
1994
  Example
1991
1995
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.4.20",
3
+ "version": "1.4.22",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -38,6 +38,7 @@ const requireCleanStack = true
38
38
  * @property {number} inputIndex - The index of this input in the current transaction.
39
39
  * @property {UnlockingScript} unlockingScript - The unlocking script that unlocks the UTXO for spending.
40
40
  * @property {number} inputSequence - The sequence number of this input.
41
+ * @property {number} lockTime - The lock time of the transaction.
41
42
  */
42
43
  export default class Spend {
43
44
  sourceTXID: string
@@ -57,6 +58,9 @@ export default class Spend {
57
58
  stack: number[][]
58
59
  altStack: number[][]
59
60
  ifStack: boolean[]
61
+ memoryLimit: number
62
+ stackMem: number
63
+ altStackMem: number
60
64
 
61
65
  /**
62
66
  * @constructor
@@ -87,6 +91,7 @@ export default class Spend {
87
91
  * inputIndex: 0, // inputIndex
88
92
  * unlockingScript: UnlockingScript.fromASM("3045... 02ab..."),
89
93
  * inputSequence: 0xffffffff // inputSequence
94
+ * memoryLimit: 100000 // memoryLimit
90
95
  * });
91
96
  */
92
97
  constructor (params: {
@@ -101,6 +106,7 @@ export default class Spend {
101
106
  inputSequence: number
102
107
  inputIndex: number
103
108
  lockTime: number
109
+ memoryLimit?: number
104
110
  }) {
105
111
  this.sourceTXID = params.sourceTXID
106
112
  this.sourceOutputIndex = params.sourceOutputIndex
@@ -113,6 +119,9 @@ export default class Spend {
113
119
  this.unlockingScript = params.unlockingScript
114
120
  this.inputSequence = params.inputSequence
115
121
  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.
123
+ this.stackMem = 0
124
+ this.altStackMem = 0
116
125
  this.reset()
117
126
  }
118
127
 
@@ -123,9 +132,27 @@ export default class Spend {
123
132
  this.stack = []
124
133
  this.altStack = []
125
134
  this.ifStack = []
135
+ this.stackMem = 0
136
+ this.altStackMem = 0
126
137
  }
127
138
 
128
- step (): void {
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
149
+ }
150
+
151
+ // Clear popped values after use to free memory
152
+ const clearPoppedValue = (): void => {
153
+ poppedValue = undefined
154
+ }
155
+
129
156
  // If the context is UnlockingScript and we have reached the end,
130
157
  // set the context to LockingScript and zero the program counter
131
158
  if (
@@ -264,6 +291,10 @@ export default class Spend {
264
291
  // Non-canonical signature: R value type mismatch
265
292
  return false
266
293
  }
294
+ if (buf[4 - 1] !== nLEnR) {
295
+ // Non-canonical signature: R length mismatch
296
+ return false
297
+ }
267
298
  if (nLEnR === 0) {
268
299
  // Non-canonical signature: R length is zero
269
300
  return false
@@ -282,6 +313,10 @@ export default class Spend {
282
313
  // Non-canonical signature: S value type mismatch
283
314
  return false
284
315
  }
316
+ if (buf[6 + nLEnR - 1] !== nLEnS) {
317
+ // Non-canonical signature: S length mismatch
318
+ return false
319
+ }
285
320
  if (nLEnS === 0) {
286
321
  // Non-canonical signature: S length is zero
287
322
  return false
@@ -305,14 +340,20 @@ export default class Spend {
305
340
  }
306
341
 
307
342
  if (!isChecksigFormat(buf)) {
308
- this.scriptEvaluationError('The signature format is invalid.')
343
+ this.scriptEvaluationError(
344
+ 'The signature format is invalid.'
345
+ )
309
346
  }
310
347
  const sig = TransactionSignature.fromChecksigFormat(buf)
311
348
  if (requireLowSSignatures && !sig.hasLowS()) {
312
- this.scriptEvaluationError('The signature must have a low S value.')
349
+ this.scriptEvaluationError(
350
+ 'The signature must have a low S value.'
351
+ )
313
352
  }
314
353
  if ((sig.scope & TransactionSignature.SIGHASH_FORKID) === 0) {
315
- this.scriptEvaluationError('The signature must use SIGHASH_FORKID.')
354
+ this.scriptEvaluationError(
355
+ 'The signature must use SIGHASH_FORKID.'
356
+ )
316
357
  return false
317
358
  }
318
359
 
@@ -338,7 +379,9 @@ export default class Spend {
338
379
  )
339
380
  }
340
381
  } else {
341
- this.scriptEvaluationError('The public key is in an unknown format.')
382
+ this.scriptEvaluationError(
383
+ 'The public key is in an unknown format.'
384
+ )
342
385
  }
343
386
  return true
344
387
  }
@@ -428,8 +471,10 @@ export default class Spend {
428
471
 
429
472
  if (!Array.isArray(operation.data)) {
430
473
  this.stack.push([])
474
+ this.stackMem += 0
431
475
  } else {
432
476
  this.stack.push(operation.data)
477
+ this.stackMem += operation.data.length
433
478
  }
434
479
  } else if (
435
480
  isScriptExecuting ||
@@ -456,6 +501,7 @@ export default class Spend {
456
501
  n = currentOpcode - (OP.OP_1 - 1)
457
502
  buf = new BigNumber(n).toScriptNum()
458
503
  this.stack.push(buf)
504
+ this.stackMem += buf.length
459
505
  break
460
506
 
461
507
  case OP.OP_NOP:
@@ -550,7 +596,11 @@ export default class Spend {
550
596
  if (currentOpcode === OP.OP_NOTIF) {
551
597
  fValue = !fValue
552
598
  }
553
- this.stack.pop()
599
+ poppedValue = this.stack.pop()
600
+ if (poppedValue != null) {
601
+ this.stackMem -= poppedValue.length
602
+ }
603
+ clearPoppedValue()
554
604
  }
555
605
  this.ifStack.push(fValue)
556
606
  break
@@ -578,9 +628,12 @@ export default class Spend {
578
628
  }
579
629
  buf = this.stacktop(-1)
580
630
  fValue = this.castToBool(buf)
581
- if (fValue) {
582
- this.stack.pop()
583
- } else {
631
+ poppedValue = this.stack.pop()
632
+ if (poppedValue != null) {
633
+ this.stackMem -= poppedValue.length
634
+ }
635
+ clearPoppedValue()
636
+ if (!fValue) {
584
637
  this.scriptEvaluationError(
585
638
  'OP_VERIFY requires the top stack value to be truthy.'
586
639
  )
@@ -601,7 +654,13 @@ export default class Spend {
601
654
  this.scriptEvaluationError(
602
655
  'OP_TOALTSTACK requires at oeast one item to be on the stack.')
603
656
  }
604
- this.altStack.push(this.stack.pop() ?? [])
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()
605
664
  break
606
665
 
607
666
  case OP.OP_FROMALTSTACK:
@@ -610,7 +669,13 @@ export default class Spend {
610
669
  'OP_FROMALTSTACK requires at least one item to be on the stack.'
611
670
  )
612
671
  }
613
- this.stack.push(this.altStack.pop() ?? [])
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()
614
679
  break
615
680
 
616
681
  case OP.OP_2DROP:
@@ -619,8 +684,16 @@ export default class Spend {
619
684
  'OP_2DROP requires at least two items to be on the stack.'
620
685
  )
621
686
  }
622
- this.stack.pop()
623
- this.stack.pop()
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()
624
697
  break
625
698
 
626
699
  case OP.OP_2DUP:
@@ -632,7 +705,9 @@ export default class Spend {
632
705
  buf1 = this.stacktop(-2)
633
706
  buf2 = this.stacktop(-1)
634
707
  this.stack.push([...buf1])
708
+ this.stackMem += buf1.length
635
709
  this.stack.push([...buf2])
710
+ this.stackMem += buf2.length
636
711
  break
637
712
 
638
713
  case OP.OP_3DUP:
@@ -645,8 +720,11 @@ export default class Spend {
645
720
  buf2 = this.stacktop(-2)
646
721
  buf3 = this.stacktop(-1)
647
722
  this.stack.push([...buf1])
723
+ this.stackMem += buf1.length
648
724
  this.stack.push([...buf2])
725
+ this.stackMem += buf2.length
649
726
  this.stack.push([...buf3])
727
+ this.stackMem += buf3.length
650
728
  break
651
729
 
652
730
  case OP.OP_2OVER:
@@ -658,7 +736,9 @@ export default class Spend {
658
736
  buf1 = this.stacktop(-4)
659
737
  buf2 = this.stacktop(-3)
660
738
  this.stack.push([...buf1])
739
+ this.stackMem += buf1.length
661
740
  this.stack.push([...buf2])
741
+ this.stackMem += buf2.length
662
742
  break
663
743
 
664
744
  case OP.OP_2ROT:
@@ -669,7 +749,9 @@ export default class Spend {
669
749
  }
670
750
  spliced = this.stack.splice(this.stack.length - 6, 2)
671
751
  this.stack.push(spliced[0])
752
+ this.stackMem += spliced[0].length
672
753
  this.stack.push(spliced[1])
754
+ this.stackMem += spliced[1].length
673
755
  break
674
756
 
675
757
  case OP.OP_2SWAP:
@@ -680,7 +762,9 @@ export default class Spend {
680
762
  }
681
763
  spliced = this.stack.splice(this.stack.length - 4, 2)
682
764
  this.stack.push(spliced[0])
765
+ this.stackMem += spliced[0].length
683
766
  this.stack.push(spliced[1])
767
+ this.stackMem += spliced[1].length
684
768
  break
685
769
 
686
770
  case OP.OP_IFDUP:
@@ -693,12 +777,14 @@ export default class Spend {
693
777
  fValue = this.castToBool(buf)
694
778
  if (fValue) {
695
779
  this.stack.push([...buf])
780
+ this.stackMem += buf.length
696
781
  }
697
782
  break
698
783
 
699
784
  case OP.OP_DEPTH:
700
785
  buf = new BigNumber(this.stack.length).toScriptNum()
701
786
  this.stack.push(buf)
787
+ this.stackMem += buf.length
702
788
  break
703
789
 
704
790
  case OP.OP_DROP:
@@ -707,7 +793,11 @@ export default class Spend {
707
793
  'OP_DROP requires at least one item to be on the stack.'
708
794
  )
709
795
  }
710
- this.stack.pop()
796
+ poppedValue = this.stack.pop()
797
+ if (poppedValue != null) {
798
+ this.stackMem -= poppedValue.length
799
+ }
800
+ clearPoppedValue()
711
801
  break
712
802
 
713
803
  case OP.OP_DUP:
@@ -717,6 +807,7 @@ export default class Spend {
717
807
  )
718
808
  }
719
809
  this.stack.push([...this.stacktop(-1)])
810
+ this.stackMem += this.stacktop(-1).length
720
811
  break
721
812
 
722
813
  case OP.OP_NIP:
@@ -726,6 +817,7 @@ export default class Spend {
726
817
  )
727
818
  }
728
819
  this.stack.splice(this.stack.length - 2, 1)
820
+ this.stackMem -= this.stacktop(-1).length
729
821
  break
730
822
 
731
823
  case OP.OP_OVER:
@@ -735,6 +827,7 @@ export default class Spend {
735
827
  )
736
828
  }
737
829
  this.stack.push([...this.stacktop(-2)])
830
+ this.stackMem += this.stacktop(-2).length
738
831
  break
739
832
 
740
833
  case OP.OP_PICK:
@@ -747,7 +840,11 @@ export default class Spend {
747
840
  buf = this.stacktop(-1)
748
841
  bn = BigNumber.fromScriptNum(buf, requireMinimalPush)
749
842
  n = bn.toNumber()
750
- this.stack.pop()
843
+ poppedValue = this.stack.pop()
844
+ if (poppedValue != null) {
845
+ this.stackMem -= poppedValue.length
846
+ }
847
+ clearPoppedValue()
751
848
  if (n < 0 || n >= this.stack.length) {
752
849
  this.scriptEvaluationError(
753
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.`
@@ -756,8 +853,10 @@ export default class Spend {
756
853
  buf = this.stacktop(-n - 1)
757
854
  if (currentOpcode === OP.OP_ROLL) {
758
855
  this.stack.splice(this.stack.length - n - 1, 1)
856
+ this.stackMem -= buf.length
759
857
  }
760
858
  this.stack.push([...buf])
859
+ this.stackMem += buf.length
761
860
  break
762
861
 
763
862
  case OP.OP_ROT:
@@ -793,6 +892,7 @@ export default class Spend {
793
892
  )
794
893
  }
795
894
  this.stack.splice(this.stack.length - 2, 0, [...this.stacktop(-1)])
895
+ this.stackMem += this.stacktop(-1).length
796
896
  break
797
897
 
798
898
  case OP.OP_SIZE:
@@ -803,6 +903,7 @@ export default class Spend {
803
903
  }
804
904
  bn = new BigNumber(this.stacktop(-1).length)
805
905
  this.stack.push(bn.toScriptNum())
906
+ this.stackMem += bn.toScriptNum().length
806
907
  break
807
908
 
808
909
  case OP.OP_AND:
@@ -841,7 +942,11 @@ export default class Spend {
841
942
  }
842
943
 
843
944
  // And pop vch2.
844
- this.stack.pop()
945
+ poppedValue = this.stack.pop()
946
+ if (poppedValue != null) {
947
+ this.stackMem -= poppedValue.length
948
+ }
949
+ clearPoppedValue()
845
950
  break
846
951
 
847
952
  case OP.OP_INVERT:
@@ -865,7 +970,11 @@ export default class Spend {
865
970
  }
866
971
  buf1 = this.stacktop(-2)
867
972
  if (buf1.length === 0) {
868
- this.stack.pop()
973
+ poppedValue = this.stack.pop()
974
+ if (poppedValue != null) {
975
+ this.stackMem -= poppedValue.length
976
+ }
977
+ clearPoppedValue()
869
978
  } else {
870
979
  bn1 = new BigNumber(buf1)
871
980
  bn2 = BigNumber.fromScriptNum(
@@ -878,8 +987,16 @@ export default class Spend {
878
987
  `${OP[currentOpcode] as string} requires the top item on the stack not to be negative.`
879
988
  )
880
989
  }
881
- this.stack.pop()
882
- this.stack.pop()
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()
883
1000
  let shifted
884
1001
  if (currentOpcode === OP.OP_LSHIFT) {
885
1002
  shifted = bn1.ushln(n)
@@ -892,6 +1009,7 @@ export default class Spend {
892
1009
  buf1.length
893
1010
  )
894
1011
  this.stack.push(bufShifted)
1012
+ this.stackMem += bufShifted.length
895
1013
  }
896
1014
  break
897
1015
 
@@ -905,12 +1023,25 @@ export default class Spend {
905
1023
  buf1 = this.stacktop(-2)
906
1024
  buf2 = this.stacktop(-1)
907
1025
  fEqual = toHex(buf1) === toHex(buf2)
908
- this.stack.pop()
909
- this.stack.pop()
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()
910
1036
  this.stack.push(fEqual ? [1] : [])
1037
+ this.stackMem += (fEqual ? 1 : 0)
911
1038
  if (currentOpcode === OP.OP_EQUALVERIFY) {
912
- if (fEqual) {
913
- this.stack.pop()
1039
+ if (this.castToBool(this.stacktop(-1))) {
1040
+ poppedValue = this.stack.pop()
1041
+ if (poppedValue != null) {
1042
+ this.stackMem -= poppedValue.length
1043
+ }
1044
+ clearPoppedValue()
914
1045
  } else {
915
1046
  this.scriptEvaluationError(
916
1047
  'OP_EQUALVERIFY requires the top two stack items to be equal.'
@@ -954,8 +1085,13 @@ export default class Spend {
954
1085
  bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0 + 0)
955
1086
  break
956
1087
  }
957
- this.stack.pop()
1088
+ poppedValue = this.stack.pop()
1089
+ if (poppedValue != null) {
1090
+ this.stackMem -= poppedValue.length
1091
+ }
1092
+ clearPoppedValue()
958
1093
  this.stack.push(bn.toScriptNum())
1094
+ this.stackMem += bn.toScriptNum().length
959
1095
  break
960
1096
 
961
1097
  case OP.OP_ADD:
@@ -1043,13 +1179,26 @@ export default class Spend {
1043
1179
  bn = bn1.cmp(bn2) > 0 ? bn1 : bn2
1044
1180
  break
1045
1181
  }
1046
- this.stack.pop()
1047
- this.stack.pop()
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()
1048
1192
  this.stack.push(bn.toScriptNum())
1193
+ this.stackMem += bn.toScriptNum().length
1049
1194
 
1050
1195
  if (currentOpcode === OP.OP_NUMEQUALVERIFY) {
1051
1196
  if (this.castToBool(this.stacktop(-1))) {
1052
- this.stack.pop()
1197
+ poppedValue = this.stack.pop()
1198
+ if (poppedValue != null) {
1199
+ this.stackMem -= poppedValue.length
1200
+ }
1201
+ clearPoppedValue()
1053
1202
  } else {
1054
1203
  this.scriptEvaluationError(
1055
1204
  'OP_NUMEQUALVERIFY requires the top stack item to be truthy.'
@@ -1068,10 +1217,23 @@ export default class Spend {
1068
1217
  bn2 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush)
1069
1218
  bn3 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush)
1070
1219
  fValue = bn2.cmp(bn1) <= 0 && bn1.cmp(bn3) < 0
1071
- this.stack.pop()
1072
- this.stack.pop()
1073
- this.stack.pop()
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()
1074
1235
  this.stack.push(fValue ? [1] : [])
1236
+ this.stackMem += (fValue ? 1 : 0)
1075
1237
  break
1076
1238
 
1077
1239
  case OP.OP_RIPEMD160:
@@ -1085,7 +1247,7 @@ export default class Spend {
1085
1247
  )
1086
1248
  }
1087
1249
 
1088
- let bufHash: number[] = [] // Initialize bufHash to an empty array
1250
+ let bufHash: number[] = [] // Initialize bufHash to an empty array
1089
1251
  buf = this.stacktop(-1)
1090
1252
  if (currentOpcode === OP.OP_RIPEMD160) {
1091
1253
  bufHash = Hash.ripemd160(buf)
@@ -1099,8 +1261,13 @@ export default class Spend {
1099
1261
  bufHash = Hash.hash256(buf)
1100
1262
  }
1101
1263
 
1102
- this.stack.pop()
1264
+ poppedValue = this.stack.pop()
1265
+ if (poppedValue != null) {
1266
+ this.stackMem -= poppedValue.length
1267
+ }
1268
+ clearPoppedValue()
1103
1269
  this.stack.push(bufHash)
1270
+ this.stackMem += bufHash.length
1104
1271
  break
1105
1272
  }
1106
1273
 
@@ -1158,14 +1325,27 @@ export default class Spend {
1158
1325
  )
1159
1326
  }
1160
1327
 
1161
- this.stack.pop()
1162
- this.stack.pop()
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
1336
+ }
1337
+ clearPoppedValue()
1163
1338
 
1164
1339
  // stack.push_back(fSuccess ? vchTrue : vchFalse);
1165
1340
  this.stack.push(fSuccess ? [1] : [])
1341
+ this.stackMem += (fSuccess ? 1 : 0)
1166
1342
  if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
1167
1343
  if (fSuccess) {
1168
- this.stack.pop()
1344
+ poppedValue = this.stack.pop()
1345
+ if (poppedValue != null) {
1346
+ this.stackMem -= poppedValue.length
1347
+ }
1348
+ clearPoppedValue()
1169
1349
  } else {
1170
1350
  this.scriptEvaluationError(
1171
1351
  'OP_CHECKSIGVERIFY requires that a valid signature is provided.'
@@ -1293,7 +1473,11 @@ export default class Spend {
1293
1473
  ikey2--
1294
1474
  }
1295
1475
 
1296
- this.stack.pop()
1476
+ poppedValue = this.stack.pop()
1477
+ if (poppedValue != null) {
1478
+ this.stackMem -= poppedValue.length
1479
+ }
1480
+ clearPoppedValue()
1297
1481
  }
1298
1482
 
1299
1483
  // A bug causes CHECKMULTISIG to consume one extra argument
@@ -1313,13 +1497,22 @@ export default class Spend {
1313
1497
  `${OP[currentOpcode] as string} requires the extra stack item to be empty.`
1314
1498
  )
1315
1499
  }
1316
- this.stack.pop()
1500
+ poppedValue = this.stack.pop()
1501
+ if (poppedValue != null) {
1502
+ this.stackMem -= poppedValue.length
1503
+ }
1504
+ clearPoppedValue()
1317
1505
 
1318
1506
  this.stack.push(fSuccess ? [1] : [])
1507
+ this.stackMem += (fSuccess ? 1 : 0)
1319
1508
 
1320
1509
  if (currentOpcode === OP.OP_CHECKMULTISIGVERIFY) {
1321
1510
  if (fSuccess) {
1322
- this.stack.pop()
1511
+ poppedValue = this.stack.pop()
1512
+ if (poppedValue != null) {
1513
+ this.stackMem -= poppedValue.length
1514
+ }
1515
+ clearPoppedValue()
1323
1516
  } else {
1324
1517
  this.scriptEvaluationError(
1325
1518
  'OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.'
@@ -1343,7 +1536,14 @@ export default class Spend {
1343
1536
  )
1344
1537
  }
1345
1538
  this.stack[this.stack.length - 2] = [...buf1, ...buf2]
1346
- this.stack.pop()
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()
1347
1547
  break
1348
1548
 
1349
1549
  case OP.OP_SPLIT:
@@ -1372,7 +1572,10 @@ export default class Spend {
1372
1572
 
1373
1573
  // Replace existing stack values by the new values.
1374
1574
  this.stack[this.stack.length - 2] = buf2.slice(0, n)
1575
+ this.stackMem -= buf1.length
1576
+ this.stackMem += n
1375
1577
  this.stack[this.stack.length - 1] = buf2.slice(n)
1578
+ this.stackMem += buf1.length - n
1376
1579
  break
1377
1580
 
1378
1581
  case OP.OP_NUM2BIN:
@@ -1392,7 +1595,11 @@ export default class Spend {
1392
1595
  )
1393
1596
  }
1394
1597
 
1395
- this.stack.pop()
1598
+ poppedValue = this.stack.pop()
1599
+ if (poppedValue != null) {
1600
+ this.stackMem -= poppedValue.length
1601
+ }
1602
+ clearPoppedValue()
1396
1603
  rawnum = this.stacktop(-1)
1397
1604
 
1398
1605
  // Try to see if we can fit that number in the number of
@@ -1431,6 +1638,8 @@ export default class Spend {
1431
1638
  num[n] = signbit
1432
1639
 
1433
1640
  this.stack[this.stack.length - 1] = num
1641
+ this.stackMem -= rawnum.length
1642
+ this.stackMem += size
1434
1643
  break
1435
1644
 
1436
1645
  case OP.OP_BIN2NUM:
@@ -1444,6 +1653,8 @@ export default class Spend {
1444
1653
  buf2 = minimallyEncode(buf1)
1445
1654
 
1446
1655
  this.stack[this.stack.length - 1] = buf2
1656
+ this.stackMem -= buf1.length
1657
+ this.stackMem += buf2.length
1447
1658
 
1448
1659
  // The resulting number must be a valid number.
1449
1660
  if (!isMinimallyEncoded(buf2)) {
@@ -1460,6 +1671,7 @@ export default class Spend {
1460
1671
 
1461
1672
  // Finally, increment the program counter
1462
1673
  this.programCounter++
1674
+ return true
1463
1675
  }
1464
1676
 
1465
1677
  /**
@@ -1479,8 +1691,7 @@ export default class Spend {
1479
1691
  'Unlocking scripts can only contain push operations, and no other opcodes.'
1480
1692
  )
1481
1693
  }
1482
- while (true) {
1483
- this.step()
1694
+ while (this.step()) {
1484
1695
  if (
1485
1696
  this.context === 'LockingScript' &&
1486
1697
  this.programCounter >= this.lockingScript.chunks.length