@bsv/sdk 1.5.3 → 1.6.0

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