@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.
@@ -31,6 +31,7 @@ const requireCleanStack = true;
31
31
  * @property {number} inputIndex - The index of this input in the current transaction.
32
32
  * @property {UnlockingScript} unlockingScript - The unlocking script that unlocks the UTXO for spending.
33
33
  * @property {number} inputSequence - The sequence number of this input.
34
+ * @property {number} lockTime - The lock time of the transaction.
34
35
  */
35
36
  export default class Spend {
36
37
  sourceTXID;
@@ -50,6 +51,9 @@ export default class Spend {
50
51
  stack;
51
52
  altStack;
52
53
  ifStack;
54
+ memoryLimit;
55
+ stackMem;
56
+ altStackMem;
53
57
  /**
54
58
  * @constructor
55
59
  * Constructs the Spend object with necessary transaction details.
@@ -79,6 +83,7 @@ export default class Spend {
79
83
  * inputIndex: 0, // inputIndex
80
84
  * unlockingScript: UnlockingScript.fromASM("3045... 02ab..."),
81
85
  * inputSequence: 0xffffffff // inputSequence
86
+ * memoryLimit: 100000 // memoryLimit
82
87
  * });
83
88
  */
84
89
  constructor(params) {
@@ -93,6 +98,9 @@ export default class Spend {
93
98
  this.unlockingScript = params.unlockingScript;
94
99
  this.inputSequence = params.inputSequence;
95
100
  this.lockTime = params.lockTime;
101
+ 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.
102
+ this.stackMem = 0;
103
+ this.altStackMem = 0;
96
104
  this.reset();
97
105
  }
98
106
  reset() {
@@ -102,8 +110,24 @@ export default class Spend {
102
110
  this.stack = [];
103
111
  this.altStack = [];
104
112
  this.ifStack = [];
113
+ this.stackMem = 0;
114
+ this.altStackMem = 0;
105
115
  }
106
116
  step() {
117
+ let poppedValue;
118
+ // If the stack (or alt stack) is over the memory limit, evaluation has failed.
119
+ if (this.stackMem > this.memoryLimit) {
120
+ this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
121
+ return false;
122
+ }
123
+ if (this.altStackMem > this.memoryLimit) {
124
+ this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
125
+ return false;
126
+ }
127
+ // Clear popped values after use to free memory
128
+ const clearPoppedValue = () => {
129
+ poppedValue = undefined;
130
+ };
107
131
  // If the context is UnlockingScript and we have reached the end,
108
132
  // set the context to LockingScript and zero the program counter
109
133
  if (this.context === 'UnlockingScript' &&
@@ -233,6 +257,10 @@ export default class Spend {
233
257
  // Non-canonical signature: R value type mismatch
234
258
  return false;
235
259
  }
260
+ if (buf[4 - 1] !== nLEnR) {
261
+ // Non-canonical signature: R length mismatch
262
+ return false;
263
+ }
236
264
  if (nLEnR === 0) {
237
265
  // Non-canonical signature: R length is zero
238
266
  return false;
@@ -250,6 +278,10 @@ export default class Spend {
250
278
  // Non-canonical signature: S value type mismatch
251
279
  return false;
252
280
  }
281
+ if (buf[6 + nLEnR - 1] !== nLEnS) {
282
+ // Non-canonical signature: S length mismatch
283
+ return false;
284
+ }
253
285
  if (nLEnS === 0) {
254
286
  // Non-canonical signature: S length is zero
255
287
  return false;
@@ -343,9 +375,11 @@ export default class Spend {
343
375
  }
344
376
  if (!Array.isArray(operation.data)) {
345
377
  this.stack.push([]);
378
+ this.stackMem += 0;
346
379
  }
347
380
  else {
348
381
  this.stack.push(operation.data);
382
+ this.stackMem += operation.data.length;
349
383
  }
350
384
  }
351
385
  else if (isScriptExecuting ||
@@ -371,6 +405,7 @@ export default class Spend {
371
405
  n = currentOpcode - (OP.OP_1 - 1);
372
406
  buf = new BigNumber(n).toScriptNum();
373
407
  this.stack.push(buf);
408
+ this.stackMem += buf.length;
374
409
  break;
375
410
  case OP.OP_NOP:
376
411
  case OP.OP_NOP2:
@@ -460,7 +495,11 @@ export default class Spend {
460
495
  if (currentOpcode === OP.OP_NOTIF) {
461
496
  fValue = !fValue;
462
497
  }
463
- this.stack.pop();
498
+ poppedValue = this.stack.pop();
499
+ if (poppedValue != null) {
500
+ this.stackMem -= poppedValue.length;
501
+ }
502
+ clearPoppedValue();
464
503
  }
465
504
  this.ifStack.push(fValue);
466
505
  break;
@@ -483,10 +522,12 @@ export default class Spend {
483
522
  }
484
523
  buf = this.stacktop(-1);
485
524
  fValue = this.castToBool(buf);
486
- if (fValue) {
487
- this.stack.pop();
525
+ poppedValue = this.stack.pop();
526
+ if (poppedValue != null) {
527
+ this.stackMem -= poppedValue.length;
488
528
  }
489
- else {
529
+ clearPoppedValue();
530
+ if (!fValue) {
490
531
  this.scriptEvaluationError('OP_VERIFY requires the top stack value to be truthy.');
491
532
  }
492
533
  break;
@@ -503,20 +544,40 @@ export default class Spend {
503
544
  if (this.stack.length < 1) {
504
545
  this.scriptEvaluationError('OP_TOALTSTACK requires at oeast one item to be on the stack.');
505
546
  }
506
- this.altStack.push(this.stack.pop() ?? []);
547
+ poppedValue = this.stack.pop();
548
+ if (poppedValue != null) {
549
+ this.altStack.push(poppedValue);
550
+ this.altStackMem += poppedValue.length;
551
+ this.stackMem -= poppedValue.length;
552
+ }
553
+ clearPoppedValue();
507
554
  break;
508
555
  case OP.OP_FROMALTSTACK:
509
556
  if (this.altStack.length < 1) {
510
557
  this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.');
511
558
  }
512
- this.stack.push(this.altStack.pop() ?? []);
559
+ poppedValue = this.altStack.pop();
560
+ if (poppedValue != null) {
561
+ this.stack.push(poppedValue);
562
+ this.stackMem += poppedValue.length;
563
+ this.altStackMem -= poppedValue.length;
564
+ }
565
+ clearPoppedValue();
513
566
  break;
514
567
  case OP.OP_2DROP:
515
568
  if (this.stack.length < 2) {
516
569
  this.scriptEvaluationError('OP_2DROP requires at least two items to be on the stack.');
517
570
  }
518
- this.stack.pop();
519
- this.stack.pop();
571
+ poppedValue = this.stack.pop();
572
+ if (poppedValue != null) {
573
+ this.stackMem -= poppedValue.length;
574
+ }
575
+ clearPoppedValue();
576
+ poppedValue = this.stack.pop();
577
+ if (poppedValue != null) {
578
+ this.stackMem -= poppedValue.length;
579
+ }
580
+ clearPoppedValue();
520
581
  break;
521
582
  case OP.OP_2DUP:
522
583
  if (this.stack.length < 2) {
@@ -525,7 +586,9 @@ export default class Spend {
525
586
  buf1 = this.stacktop(-2);
526
587
  buf2 = this.stacktop(-1);
527
588
  this.stack.push([...buf1]);
589
+ this.stackMem += buf1.length;
528
590
  this.stack.push([...buf2]);
591
+ this.stackMem += buf2.length;
529
592
  break;
530
593
  case OP.OP_3DUP:
531
594
  if (this.stack.length < 3) {
@@ -535,8 +598,11 @@ export default class Spend {
535
598
  buf2 = this.stacktop(-2);
536
599
  buf3 = this.stacktop(-1);
537
600
  this.stack.push([...buf1]);
601
+ this.stackMem += buf1.length;
538
602
  this.stack.push([...buf2]);
603
+ this.stackMem += buf2.length;
539
604
  this.stack.push([...buf3]);
605
+ this.stackMem += buf3.length;
540
606
  break;
541
607
  case OP.OP_2OVER:
542
608
  if (this.stack.length < 4) {
@@ -545,7 +611,9 @@ export default class Spend {
545
611
  buf1 = this.stacktop(-4);
546
612
  buf2 = this.stacktop(-3);
547
613
  this.stack.push([...buf1]);
614
+ this.stackMem += buf1.length;
548
615
  this.stack.push([...buf2]);
616
+ this.stackMem += buf2.length;
549
617
  break;
550
618
  case OP.OP_2ROT:
551
619
  if (this.stack.length < 6) {
@@ -553,7 +621,9 @@ export default class Spend {
553
621
  }
554
622
  spliced = this.stack.splice(this.stack.length - 6, 2);
555
623
  this.stack.push(spliced[0]);
624
+ this.stackMem += spliced[0].length;
556
625
  this.stack.push(spliced[1]);
626
+ this.stackMem += spliced[1].length;
557
627
  break;
558
628
  case OP.OP_2SWAP:
559
629
  if (this.stack.length < 4) {
@@ -561,7 +631,9 @@ export default class Spend {
561
631
  }
562
632
  spliced = this.stack.splice(this.stack.length - 4, 2);
563
633
  this.stack.push(spliced[0]);
634
+ this.stackMem += spliced[0].length;
564
635
  this.stack.push(spliced[1]);
636
+ this.stackMem += spliced[1].length;
565
637
  break;
566
638
  case OP.OP_IFDUP:
567
639
  if (this.stack.length < 1) {
@@ -571,35 +643,44 @@ export default class Spend {
571
643
  fValue = this.castToBool(buf);
572
644
  if (fValue) {
573
645
  this.stack.push([...buf]);
646
+ this.stackMem += buf.length;
574
647
  }
575
648
  break;
576
649
  case OP.OP_DEPTH:
577
650
  buf = new BigNumber(this.stack.length).toScriptNum();
578
651
  this.stack.push(buf);
652
+ this.stackMem += buf.length;
579
653
  break;
580
654
  case OP.OP_DROP:
581
655
  if (this.stack.length < 1) {
582
656
  this.scriptEvaluationError('OP_DROP requires at least one item to be on the stack.');
583
657
  }
584
- this.stack.pop();
658
+ poppedValue = this.stack.pop();
659
+ if (poppedValue != null) {
660
+ this.stackMem -= poppedValue.length;
661
+ }
662
+ clearPoppedValue();
585
663
  break;
586
664
  case OP.OP_DUP:
587
665
  if (this.stack.length < 1) {
588
666
  this.scriptEvaluationError('OP_DUP requires at least one item to be on the stack.');
589
667
  }
590
668
  this.stack.push([...this.stacktop(-1)]);
669
+ this.stackMem += this.stacktop(-1).length;
591
670
  break;
592
671
  case OP.OP_NIP:
593
672
  if (this.stack.length < 2) {
594
673
  this.scriptEvaluationError('OP_NIP requires at least two items to be on the stack.');
595
674
  }
596
675
  this.stack.splice(this.stack.length - 2, 1);
676
+ this.stackMem -= this.stacktop(-1).length;
597
677
  break;
598
678
  case OP.OP_OVER:
599
679
  if (this.stack.length < 2) {
600
680
  this.scriptEvaluationError('OP_OVER requires at least two items to be on the stack.');
601
681
  }
602
682
  this.stack.push([...this.stacktop(-2)]);
683
+ this.stackMem += this.stacktop(-2).length;
603
684
  break;
604
685
  case OP.OP_PICK:
605
686
  case OP.OP_ROLL:
@@ -609,15 +690,21 @@ export default class Spend {
609
690
  buf = this.stacktop(-1);
610
691
  bn = BigNumber.fromScriptNum(buf, requireMinimalPush);
611
692
  n = bn.toNumber();
612
- this.stack.pop();
693
+ poppedValue = this.stack.pop();
694
+ if (poppedValue != null) {
695
+ this.stackMem -= poppedValue.length;
696
+ }
697
+ clearPoppedValue();
613
698
  if (n < 0 || n >= this.stack.length) {
614
699
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
615
700
  }
616
701
  buf = this.stacktop(-n - 1);
617
702
  if (currentOpcode === OP.OP_ROLL) {
618
703
  this.stack.splice(this.stack.length - n - 1, 1);
704
+ this.stackMem -= buf.length;
619
705
  }
620
706
  this.stack.push([...buf]);
707
+ this.stackMem += buf.length;
621
708
  break;
622
709
  case OP.OP_ROT:
623
710
  if (this.stack.length < 3) {
@@ -644,6 +731,7 @@ export default class Spend {
644
731
  this.scriptEvaluationError('OP_TUCK requires at least two items to be on the stack.');
645
732
  }
646
733
  this.stack.splice(this.stack.length - 2, 0, [...this.stacktop(-1)]);
734
+ this.stackMem += this.stacktop(-1).length;
647
735
  break;
648
736
  case OP.OP_SIZE:
649
737
  if (this.stack.length < 1) {
@@ -651,6 +739,7 @@ export default class Spend {
651
739
  }
652
740
  bn = new BigNumber(this.stacktop(-1).length);
653
741
  this.stack.push(bn.toScriptNum());
742
+ this.stackMem += bn.toScriptNum().length;
654
743
  break;
655
744
  case OP.OP_AND:
656
745
  case OP.OP_OR:
@@ -681,7 +770,11 @@ export default class Spend {
681
770
  break;
682
771
  }
683
772
  // And pop vch2.
684
- this.stack.pop();
773
+ poppedValue = this.stack.pop();
774
+ if (poppedValue != null) {
775
+ this.stackMem -= poppedValue.length;
776
+ }
777
+ clearPoppedValue();
685
778
  break;
686
779
  case OP.OP_INVERT:
687
780
  if (this.stack.length < 1) {
@@ -699,7 +792,11 @@ export default class Spend {
699
792
  }
700
793
  buf1 = this.stacktop(-2);
701
794
  if (buf1.length === 0) {
702
- this.stack.pop();
795
+ poppedValue = this.stack.pop();
796
+ if (poppedValue != null) {
797
+ this.stackMem -= poppedValue.length;
798
+ }
799
+ clearPoppedValue();
703
800
  }
704
801
  else {
705
802
  bn1 = new BigNumber(buf1);
@@ -708,8 +805,16 @@ export default class Spend {
708
805
  if (n < 0) {
709
806
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the top item on the stack not to be negative.`);
710
807
  }
711
- this.stack.pop();
712
- this.stack.pop();
808
+ poppedValue = this.stack.pop();
809
+ if (poppedValue != null) {
810
+ this.stackMem -= poppedValue.length;
811
+ }
812
+ clearPoppedValue();
813
+ poppedValue = this.stack.pop();
814
+ if (poppedValue != null) {
815
+ this.stackMem -= poppedValue.length;
816
+ }
817
+ clearPoppedValue();
713
818
  let shifted;
714
819
  if (currentOpcode === OP.OP_LSHIFT) {
715
820
  shifted = bn1.ushln(n);
@@ -719,6 +824,7 @@ export default class Spend {
719
824
  }
720
825
  const bufShifted = padDataToSize(shifted.toArray().slice(buf1.length * -1), buf1.length);
721
826
  this.stack.push(bufShifted);
827
+ this.stackMem += bufShifted.length;
722
828
  }
723
829
  break;
724
830
  case OP.OP_EQUAL:
@@ -729,12 +835,25 @@ export default class Spend {
729
835
  buf1 = this.stacktop(-2);
730
836
  buf2 = this.stacktop(-1);
731
837
  fEqual = toHex(buf1) === toHex(buf2);
732
- this.stack.pop();
733
- this.stack.pop();
838
+ poppedValue = this.stack.pop();
839
+ if (poppedValue != null) {
840
+ this.stackMem -= poppedValue.length;
841
+ }
842
+ clearPoppedValue();
843
+ poppedValue = this.stack.pop();
844
+ if (poppedValue != null) {
845
+ this.stackMem -= poppedValue.length;
846
+ }
847
+ clearPoppedValue();
734
848
  this.stack.push(fEqual ? [1] : []);
849
+ this.stackMem += (fEqual ? 1 : 0);
735
850
  if (currentOpcode === OP.OP_EQUALVERIFY) {
736
- if (fEqual) {
737
- this.stack.pop();
851
+ if (this.castToBool(this.stacktop(-1))) {
852
+ poppedValue = this.stack.pop();
853
+ if (poppedValue != null) {
854
+ this.stackMem -= poppedValue.length;
855
+ }
856
+ clearPoppedValue();
738
857
  }
739
858
  else {
740
859
  this.scriptEvaluationError('OP_EQUALVERIFY requires the top two stack items to be equal.');
@@ -774,8 +893,13 @@ export default class Spend {
774
893
  bn = new BigNumber(bn.cmpn(0) !== 0 ? 1 : 0 + 0);
775
894
  break;
776
895
  }
777
- this.stack.pop();
896
+ poppedValue = this.stack.pop();
897
+ if (poppedValue != null) {
898
+ this.stackMem -= poppedValue.length;
899
+ }
900
+ clearPoppedValue();
778
901
  this.stack.push(bn.toScriptNum());
902
+ this.stackMem += bn.toScriptNum().length;
779
903
  break;
780
904
  case OP.OP_ADD:
781
905
  case OP.OP_SUB:
@@ -855,12 +979,25 @@ export default class Spend {
855
979
  bn = bn1.cmp(bn2) > 0 ? bn1 : bn2;
856
980
  break;
857
981
  }
858
- this.stack.pop();
859
- this.stack.pop();
982
+ poppedValue = this.stack.pop();
983
+ if (poppedValue != null) {
984
+ this.stackMem -= poppedValue.length;
985
+ }
986
+ clearPoppedValue();
987
+ poppedValue = this.stack.pop();
988
+ if (poppedValue != null) {
989
+ this.stackMem -= poppedValue.length;
990
+ }
991
+ clearPoppedValue();
860
992
  this.stack.push(bn.toScriptNum());
993
+ this.stackMem += bn.toScriptNum().length;
861
994
  if (currentOpcode === OP.OP_NUMEQUALVERIFY) {
862
995
  if (this.castToBool(this.stacktop(-1))) {
863
- this.stack.pop();
996
+ poppedValue = this.stack.pop();
997
+ if (poppedValue != null) {
998
+ this.stackMem -= poppedValue.length;
999
+ }
1000
+ clearPoppedValue();
864
1001
  }
865
1002
  else {
866
1003
  this.scriptEvaluationError('OP_NUMEQUALVERIFY requires the top stack item to be truthy.');
@@ -875,10 +1012,23 @@ export default class Spend {
875
1012
  bn2 = BigNumber.fromScriptNum(this.stacktop(-2), requireMinimalPush);
876
1013
  bn3 = BigNumber.fromScriptNum(this.stacktop(-1), requireMinimalPush);
877
1014
  fValue = bn2.cmp(bn1) <= 0 && bn1.cmp(bn3) < 0;
878
- this.stack.pop();
879
- this.stack.pop();
880
- this.stack.pop();
1015
+ poppedValue = this.stack.pop();
1016
+ if (poppedValue != null) {
1017
+ this.stackMem -= poppedValue.length;
1018
+ }
1019
+ clearPoppedValue();
1020
+ poppedValue = this.stack.pop();
1021
+ if (poppedValue != null) {
1022
+ this.stackMem -= poppedValue.length;
1023
+ }
1024
+ clearPoppedValue();
1025
+ poppedValue = this.stack.pop();
1026
+ if (poppedValue != null) {
1027
+ this.stackMem -= poppedValue.length;
1028
+ }
1029
+ clearPoppedValue();
881
1030
  this.stack.push(fValue ? [1] : []);
1031
+ this.stackMem += (fValue ? 1 : 0);
882
1032
  break;
883
1033
  case OP.OP_RIPEMD160:
884
1034
  case OP.OP_SHA1:
@@ -888,7 +1038,7 @@ export default class Spend {
888
1038
  if (this.stack.length < 1) {
889
1039
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one item to be on the stack.`);
890
1040
  }
891
- let bufHash = []; // Initialize bufHash to an empty array
1041
+ let bufHash = []; // Initialize bufHash to an empty array
892
1042
  buf = this.stacktop(-1);
893
1043
  if (currentOpcode === OP.OP_RIPEMD160) {
894
1044
  bufHash = Hash.ripemd160(buf);
@@ -905,8 +1055,13 @@ export default class Spend {
905
1055
  else if (currentOpcode === OP.OP_HASH256) {
906
1056
  bufHash = Hash.hash256(buf);
907
1057
  }
908
- this.stack.pop();
1058
+ poppedValue = this.stack.pop();
1059
+ if (poppedValue != null) {
1060
+ this.stackMem -= poppedValue.length;
1061
+ }
1062
+ clearPoppedValue();
909
1063
  this.stack.push(bufHash);
1064
+ this.stackMem += bufHash.length;
910
1065
  break;
911
1066
  }
912
1067
  case OP.OP_CODESEPARATOR:
@@ -945,13 +1100,26 @@ export default class Spend {
945
1100
  if (!fSuccess && bufSig.length > 0) {
946
1101
  this.scriptEvaluationError(`${OP[currentOpcode]} failed to verify the signature, and requires an empty signature when verification fails.`);
947
1102
  }
948
- this.stack.pop();
949
- this.stack.pop();
1103
+ poppedValue = this.stack.pop();
1104
+ if (poppedValue != null) {
1105
+ this.stackMem -= poppedValue.length;
1106
+ }
1107
+ clearPoppedValue();
1108
+ poppedValue = this.stack.pop();
1109
+ if (poppedValue != null) {
1110
+ this.stackMem -= poppedValue.length;
1111
+ }
1112
+ clearPoppedValue();
950
1113
  // stack.push_back(fSuccess ? vchTrue : vchFalse);
951
1114
  this.stack.push(fSuccess ? [1] : []);
1115
+ this.stackMem += (fSuccess ? 1 : 0);
952
1116
  if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
953
1117
  if (fSuccess) {
954
- this.stack.pop();
1118
+ poppedValue = this.stack.pop();
1119
+ if (poppedValue != null) {
1120
+ this.stackMem -= poppedValue.length;
1121
+ }
1122
+ clearPoppedValue();
955
1123
  }
956
1124
  else {
957
1125
  this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.');
@@ -1039,7 +1207,11 @@ export default class Spend {
1039
1207
  if (ikey2 > 0) {
1040
1208
  ikey2--;
1041
1209
  }
1042
- this.stack.pop();
1210
+ poppedValue = this.stack.pop();
1211
+ if (poppedValue != null) {
1212
+ this.stackMem -= poppedValue.length;
1213
+ }
1214
+ clearPoppedValue();
1043
1215
  }
1044
1216
  // A bug causes CHECKMULTISIG to consume one extra argument
1045
1217
  // whose contents were not checked in any way.
@@ -1054,11 +1226,20 @@ export default class Spend {
1054
1226
  // NOTE: Is this necessary? We don't care about malleability.
1055
1227
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the extra stack item to be empty.`);
1056
1228
  }
1057
- this.stack.pop();
1229
+ poppedValue = this.stack.pop();
1230
+ if (poppedValue != null) {
1231
+ this.stackMem -= poppedValue.length;
1232
+ }
1233
+ clearPoppedValue();
1058
1234
  this.stack.push(fSuccess ? [1] : []);
1235
+ this.stackMem += (fSuccess ? 1 : 0);
1059
1236
  if (currentOpcode === OP.OP_CHECKMULTISIGVERIFY) {
1060
1237
  if (fSuccess) {
1061
- this.stack.pop();
1238
+ poppedValue = this.stack.pop();
1239
+ if (poppedValue != null) {
1240
+ this.stackMem -= poppedValue.length;
1241
+ }
1242
+ clearPoppedValue();
1062
1243
  }
1063
1244
  else {
1064
1245
  this.scriptEvaluationError('OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.');
@@ -1075,7 +1256,14 @@ export default class Spend {
1075
1256
  this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
1076
1257
  }
1077
1258
  this.stack[this.stack.length - 2] = [...buf1, ...buf2];
1078
- this.stack.pop();
1259
+ this.stackMem -= buf1.length;
1260
+ this.stackMem -= buf2.length;
1261
+ this.stackMem += buf1.length + buf2.length;
1262
+ poppedValue = this.stack.pop();
1263
+ if (poppedValue != null) {
1264
+ this.stackMem -= poppedValue.length;
1265
+ }
1266
+ clearPoppedValue();
1079
1267
  break;
1080
1268
  case OP.OP_SPLIT:
1081
1269
  if (this.stack.length < 2) {
@@ -1093,7 +1281,10 @@ export default class Spend {
1093
1281
  buf2 = [...buf1];
1094
1282
  // Replace existing stack values by the new values.
1095
1283
  this.stack[this.stack.length - 2] = buf2.slice(0, n);
1284
+ this.stackMem -= buf1.length;
1285
+ this.stackMem += n;
1096
1286
  this.stack[this.stack.length - 1] = buf2.slice(n);
1287
+ this.stackMem += buf1.length - n;
1097
1288
  break;
1098
1289
  case OP.OP_NUM2BIN:
1099
1290
  if (this.stack.length < 2) {
@@ -1103,7 +1294,11 @@ export default class Spend {
1103
1294
  if (size > maxScriptElementSize) {
1104
1295
  this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
1105
1296
  }
1106
- this.stack.pop();
1297
+ poppedValue = this.stack.pop();
1298
+ if (poppedValue != null) {
1299
+ this.stackMem -= poppedValue.length;
1300
+ }
1301
+ clearPoppedValue();
1107
1302
  rawnum = this.stacktop(-1);
1108
1303
  // Try to see if we can fit that number in the number of
1109
1304
  // byte requested.
@@ -1133,6 +1328,8 @@ export default class Spend {
1133
1328
  }
1134
1329
  num[n] = signbit;
1135
1330
  this.stack[this.stack.length - 1] = num;
1331
+ this.stackMem -= rawnum.length;
1332
+ this.stackMem += size;
1136
1333
  break;
1137
1334
  case OP.OP_BIN2NUM:
1138
1335
  if (this.stack.length < 1) {
@@ -1141,6 +1338,8 @@ export default class Spend {
1141
1338
  buf1 = this.stacktop(-1);
1142
1339
  buf2 = minimallyEncode(buf1);
1143
1340
  this.stack[this.stack.length - 1] = buf2;
1341
+ this.stackMem -= buf1.length;
1342
+ this.stackMem += buf2.length;
1144
1343
  // The resulting number must be a valid number.
1145
1344
  if (!isMinimallyEncoded(buf2)) {
1146
1345
  this.scriptEvaluationError('OP_BIN2NUM requires that the resulting number is valid.');
@@ -1152,6 +1351,7 @@ export default class Spend {
1152
1351
  }
1153
1352
  // Finally, increment the program counter
1154
1353
  this.programCounter++;
1354
+ return true;
1155
1355
  }
1156
1356
  /**
1157
1357
  * @method validate
@@ -1168,8 +1368,7 @@ export default class Spend {
1168
1368
  if (requirePushOnlyUnlockingScripts && !this.unlockingScript.isPushOnly()) {
1169
1369
  this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
1170
1370
  }
1171
- while (true) {
1172
- this.step();
1371
+ while (this.step()) {
1173
1372
  if (this.context === 'LockingScript' &&
1174
1373
  this.programCounter >= this.lockingScript.chunks.length) {
1175
1374
  break;