@bsv/sdk 2.0.14 → 2.0.16

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 (104) hide show
  1. package/dist/cjs/package.json +14 -14
  2. package/dist/cjs/src/kvstore/GlobalKVStore.js +16 -3
  3. package/dist/cjs/src/kvstore/GlobalKVStore.js.map +1 -1
  4. package/dist/cjs/src/kvstore/types.js.map +1 -1
  5. package/dist/cjs/src/primitives/Hash.js +1 -1
  6. package/dist/cjs/src/primitives/Hash.js.map +1 -1
  7. package/dist/cjs/src/primitives/TransactionSignature.js +10 -3
  8. package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
  9. package/dist/cjs/src/script/Script.js +60 -13
  10. package/dist/cjs/src/script/Script.js.map +1 -1
  11. package/dist/cjs/src/script/Spend.js +434 -59
  12. package/dist/cjs/src/script/Spend.js.map +1 -1
  13. package/dist/cjs/src/transaction/http/BinaryFetchClient.js +6 -2
  14. package/dist/cjs/src/transaction/http/BinaryFetchClient.js.map +1 -1
  15. package/dist/cjs/src/transaction/http/DefaultHttpClient.js +8 -4
  16. package/dist/cjs/src/transaction/http/DefaultHttpClient.js.map +1 -1
  17. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  18. package/dist/esm/src/kvstore/GlobalKVStore.js +16 -3
  19. package/dist/esm/src/kvstore/GlobalKVStore.js.map +1 -1
  20. package/dist/esm/src/kvstore/types.js.map +1 -1
  21. package/dist/esm/src/primitives/Hash.js +1 -1
  22. package/dist/esm/src/primitives/Hash.js.map +1 -1
  23. package/dist/esm/src/primitives/TransactionSignature.js +10 -3
  24. package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
  25. package/dist/esm/src/script/Script.js +60 -13
  26. package/dist/esm/src/script/Script.js.map +1 -1
  27. package/dist/esm/src/script/Spend.js +438 -59
  28. package/dist/esm/src/script/Spend.js.map +1 -1
  29. package/dist/esm/src/transaction/http/BinaryFetchClient.js +6 -2
  30. package/dist/esm/src/transaction/http/BinaryFetchClient.js.map +1 -1
  31. package/dist/esm/src/transaction/http/DefaultHttpClient.js +8 -4
  32. package/dist/esm/src/transaction/http/DefaultHttpClient.js.map +1 -1
  33. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  34. package/dist/types/src/kvstore/GlobalKVStore.d.ts +7 -0
  35. package/dist/types/src/kvstore/GlobalKVStore.d.ts.map +1 -1
  36. package/dist/types/src/kvstore/types.d.ts +2 -1
  37. package/dist/types/src/kvstore/types.d.ts.map +1 -1
  38. package/dist/types/src/primitives/TransactionSignature.d.ts +1 -0
  39. package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
  40. package/dist/types/src/script/Script.d.ts +1 -0
  41. package/dist/types/src/script/Script.d.ts.map +1 -1
  42. package/dist/types/src/script/ScriptChunk.d.ts +1 -0
  43. package/dist/types/src/script/ScriptChunk.d.ts.map +1 -1
  44. package/dist/types/src/script/Spend.d.ts +29 -0
  45. package/dist/types/src/script/Spend.d.ts.map +1 -1
  46. package/dist/types/src/transaction/http/BinaryFetchClient.d.ts.map +1 -1
  47. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts +2 -2
  48. package/dist/types/src/transaction/http/DefaultHttpClient.d.ts.map +1 -1
  49. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  50. package/dist/umd/bundle.js +3 -4
  51. package/docs/reference/kvstore.md +2 -1
  52. package/docs/reference/primitives.md +1 -0
  53. package/docs/reference/script.md +7 -0
  54. package/docs/reference/transaction.md +2 -2
  55. package/package.json +14 -14
  56. package/src/kvstore/GlobalKVStore.ts +19 -3
  57. package/src/kvstore/__tests/GlobalKVStore.test.ts +24 -1
  58. package/src/kvstore/types.ts +2 -1
  59. package/src/primitives/Hash.ts +1 -1
  60. package/src/primitives/TransactionSignature.ts +11 -3
  61. package/src/script/Script.ts +59 -13
  62. package/src/script/ScriptChunk.ts +1 -0
  63. package/src/script/Spend.ts +483 -61
  64. package/src/script/__tests/NormativeVectors.test.ts +465 -0
  65. package/src/script/__tests/fixtures/SOURCES.md +25 -0
  66. package/src/script/__tests/fixtures/bitcoin-sv/script_tests.json +2591 -0
  67. package/src/script/__tests/fixtures/bitcoin-sv/sighash.json +1003 -0
  68. package/src/script/__tests/fixtures/bitcoin-sv/tx_invalid.json +285 -0
  69. package/src/script/__tests/fixtures/bitcoin-sv/tx_valid.json +367 -0
  70. package/src/script/__tests/fixtures/teranode/script_tests.json +2432 -0
  71. package/src/script/__tests/fixtures/teranode/sighash.json +1003 -0
  72. package/src/script/__tests/fixtures/teranode/tx_invalid.json +285 -0
  73. package/src/script/__tests/fixtures/teranode/tx_valid.json +367 -0
  74. package/src/transaction/broadcasters/__tests/ARC.test.ts +26 -4
  75. package/src/transaction/broadcasters/__tests/WhatsOnChainBroadcaster.test.ts +26 -4
  76. package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -10
  77. package/src/transaction/http/BinaryFetchClient.ts +5 -2
  78. package/src/transaction/http/DefaultHttpClient.ts +7 -4
  79. package/src/transaction/http/__tests/DefaultHttpClient.additional.test.ts +19 -1
  80. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -827
  81. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  82. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js +0 -266
  83. package/dist/cjs/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  84. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -654
  85. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  86. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -144
  87. package/dist/cjs/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  88. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js +0 -825
  89. package/dist/esm/src/auth/clients/__tests__/AuthFetch.additional.test.js.map +0 -1
  90. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js +0 -264
  91. package/dist/esm/src/auth/clients/__tests__/AuthFetch.test.js.map +0 -1
  92. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js +0 -619
  93. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.js.map +0 -1
  94. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js +0 -109
  95. package/dist/esm/src/auth/transports/__tests__/SimplifiedFetchTransport.test.js.map +0 -1
  96. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts +0 -21
  97. package/dist/types/src/auth/clients/__tests__/AuthFetch.additional.test.d.ts.map +0 -1
  98. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts +0 -2
  99. package/dist/types/src/auth/clients/__tests__/AuthFetch.test.d.ts.map +0 -1
  100. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts +0 -2
  101. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.additional.test.d.ts.map +0 -1
  102. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts +0 -2
  103. package/dist/types/src/auth/transports/__tests__/SimplifiedFetchTransport.test.d.ts.map +0 -1
  104. package/dist/umd/bundle.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import LockingScript from './LockingScript.js';
1
2
  import Script from './Script.js';
2
3
  import BigNumber from '../primitives/BigNumber.js';
3
4
  import OP from './OP.js';
@@ -9,8 +10,14 @@ import PublicKey from '../primitives/PublicKey.js';
9
10
  import { verify } from '../primitives/ECDSA.js';
10
11
  // These constants control the current behavior of the interpreter.
11
12
  const maxScriptElementSize = 1024 * 1024 * 1024;
13
+ const maxScriptElementSizeBeforeGenesis = 520;
14
+ const maxScriptSizeBeforeGenesis = 10000;
15
+ const maxOpsBeforeGenesis = 500;
16
+ const maxStackItemsBeforeGenesis = 1000;
12
17
  const maxMultisigKeyCount = Math.pow(2, 31) - 1;
13
18
  const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount);
19
+ const maxMultisigKeyCountBeforeGenesis = 20;
20
+ const sequenceLocktimeDisableFlag = 0x80000000;
14
21
  // --- Optimization: Pre-computed script numbers ---
15
22
  const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber(-1).toScriptNum());
16
23
  const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber(i).toScriptNum())));
@@ -135,10 +142,14 @@ export default class Spend {
135
142
  stack;
136
143
  altStack;
137
144
  ifStack;
145
+ elseStack;
138
146
  memoryLimit;
139
147
  stackMem;
140
148
  altStackMem;
141
149
  isRelaxedOverride;
150
+ verifyFlags;
151
+ executedOpCount;
152
+ returningFromConditional;
142
153
  sigHashCache;
143
154
  /**
144
155
  * @constructor
@@ -188,11 +199,21 @@ export default class Spend {
188
199
  this.lockTime = params.lockTime;
189
200
  this.memoryLimit = params.memoryLimit ?? 32000000;
190
201
  this.isRelaxedOverride = params.isRelaxed === true;
202
+ this.verifyFlags = params.verifyFlags === undefined
203
+ ? undefined
204
+ : new Set((Array.isArray(params.verifyFlags)
205
+ ? params.verifyFlags
206
+ : params.verifyFlags.split(','))
207
+ .map(flag => flag.trim())
208
+ .filter(flag => flag.length > 0));
191
209
  this.stack = [];
192
210
  this.altStack = [];
193
211
  this.ifStack = [];
212
+ this.elseStack = [];
194
213
  this.stackMem = 0;
195
214
  this.altStackMem = 0;
215
+ this.executedOpCount = 0;
216
+ this.returningFromConditional = false;
196
217
  this.sigHashCache = { hashOutputsSingle: new Map() };
197
218
  this.reset();
198
219
  }
@@ -200,6 +221,75 @@ export default class Spend {
200
221
  return this.isRelaxedOverride ||
201
222
  (this.transactionVersion > 1);
202
223
  }
224
+ hasExplicitFlags() {
225
+ return this.verifyFlags !== undefined;
226
+ }
227
+ hasFlag(flag) {
228
+ return this.verifyFlags?.has(flag) === true;
229
+ }
230
+ isAfterGenesis() {
231
+ if (this.hasExplicitFlags()) {
232
+ return this.hasFlag('GENESIS') ||
233
+ this.hasFlag('UTXO_AFTER_GENESIS') ||
234
+ this.hasFlag('UTXO_AFTER_CHRONICLE');
235
+ }
236
+ return this.isRelaxed();
237
+ }
238
+ isAfterChronicle() {
239
+ if (this.hasExplicitFlags())
240
+ return this.hasFlag('UTXO_AFTER_CHRONICLE');
241
+ return this.isRelaxed();
242
+ }
243
+ shouldEnforceMinimalData() {
244
+ if (this.hasExplicitFlags())
245
+ return this.hasFlag('MINIMALDATA');
246
+ return !this.isRelaxed();
247
+ }
248
+ shouldEnforceLowS() {
249
+ if (this.hasExplicitFlags())
250
+ return this.hasFlag('LOW_S');
251
+ return !this.isRelaxed();
252
+ }
253
+ shouldEnforceNullDummy() {
254
+ if (this.hasExplicitFlags())
255
+ return this.hasFlag('NULLDUMMY');
256
+ return !this.isRelaxed();
257
+ }
258
+ shouldEnforceSigPushOnly() {
259
+ if (this.hasExplicitFlags())
260
+ return this.hasFlag('SIGPUSHONLY');
261
+ return !this.isRelaxed();
262
+ }
263
+ shouldEnforceCleanStack() {
264
+ if (this.hasExplicitFlags())
265
+ return this.hasFlag('CLEANSTACK');
266
+ return !this.isRelaxed();
267
+ }
268
+ shouldEnforceDerSignatures() {
269
+ if (this.hasExplicitFlags()) {
270
+ return this.hasFlag('DERSIG') ||
271
+ this.hasFlag('STRICTENC') ||
272
+ this.hasFlag('LOW_S') ||
273
+ this.hasFlag('SIGHASH_FORKID');
274
+ }
275
+ return true;
276
+ }
277
+ shouldEnforceStrictEncoding() {
278
+ if (this.hasExplicitFlags()) {
279
+ return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID');
280
+ }
281
+ return true;
282
+ }
283
+ scriptNumMaxSize() {
284
+ if (this.hasExplicitFlags() && !this.isAfterGenesis())
285
+ return 4;
286
+ return undefined;
287
+ }
288
+ maxPushSize() {
289
+ if (this.hasExplicitFlags() && !this.isAfterGenesis())
290
+ return maxScriptElementSizeBeforeGenesis;
291
+ return maxScriptElementSize;
292
+ }
203
293
  reset() {
204
294
  this.context = 'UnlockingScript';
205
295
  this.programCounter = 0;
@@ -207,8 +297,11 @@ export default class Spend {
207
297
  this.stack = [];
208
298
  this.altStack = [];
209
299
  this.ifStack = [];
300
+ this.elseStack = [];
210
301
  this.stackMem = 0;
211
302
  this.altStackMem = 0;
303
+ this.executedOpCount = 0;
304
+ this.returningFromConditional = false;
212
305
  this.sigHashCache = { hashOutputsSingle: new Map() };
213
306
  }
214
307
  ensureStackMem(additional) {
@@ -252,6 +345,14 @@ export default class Spend {
252
345
  }
253
346
  return this.stack[this.stack.length + index];
254
347
  }
348
+ setStack(items) {
349
+ this.stack = items.map(item => item.slice());
350
+ this.stackMem = this.stack.reduce((total, item) => total + item.length, 0);
351
+ }
352
+ clearAltStack() {
353
+ this.altStack = [];
354
+ this.altStackMem = 0;
355
+ }
255
356
  pushAltStack(item) {
256
357
  this.ensureAltStackMem(item.length);
257
358
  this.altStack.push(item);
@@ -269,27 +370,124 @@ export default class Spend {
269
370
  this.altStackMem -= item.length;
270
371
  return item;
271
372
  }
373
+ readScriptNumber(buf) {
374
+ try {
375
+ return BigNumber.fromScriptNum(buf, this.shouldEnforceMinimalData(), this.scriptNumMaxSize());
376
+ }
377
+ catch (e) {
378
+ const message = e instanceof Error ? e.message : String(e);
379
+ this.scriptEvaluationError(message);
380
+ }
381
+ return new BigNumber(0);
382
+ }
383
+ isDefinedHashType(scope) {
384
+ const baseType = scope & 0x1f;
385
+ return baseType >= TransactionSignature.SIGHASH_ALL &&
386
+ baseType <= TransactionSignature.SIGHASH_SINGLE;
387
+ }
272
388
  checkSignatureEncoding(buf) {
273
389
  if (buf.length === 0)
274
390
  return true;
275
- if (!isChecksigFormatHelper(buf)) {
391
+ const enforceDer = this.shouldEnforceDerSignatures();
392
+ if (enforceDer && !isChecksigFormatHelper(buf)) {
276
393
  this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
277
394
  return false;
278
395
  }
279
396
  try {
280
397
  const sig = TransactionSignature.fromChecksigFormat(buf); // This can throw for stricter DER rules
281
- if (!this.isRelaxed() && !sig.hasLowS()) {
398
+ if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
399
+ this.scriptEvaluationError('The signature hash type is invalid.');
400
+ return false;
401
+ }
402
+ if (this.shouldEnforceStrictEncoding() &&
403
+ (sig.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0 &&
404
+ !this.isAfterChronicle()) {
405
+ this.scriptEvaluationError('The signature hash type is invalid before Chronicle.');
406
+ return false;
407
+ }
408
+ const hasForkId = (sig.scope & TransactionSignature.SIGHASH_FORKID) !== 0;
409
+ if (this.hasExplicitFlags()) {
410
+ if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
411
+ this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
412
+ return false;
413
+ }
414
+ if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
415
+ this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.');
416
+ return false;
417
+ }
418
+ }
419
+ if (this.shouldEnforceLowS() && !sig.hasLowS()) {
282
420
  this.scriptEvaluationError('The signature must have a low S value.');
283
421
  return false;
284
422
  }
285
423
  }
286
424
  catch {
287
- this.scriptEvaluationError('The signature format is invalid.');
288
- return false;
425
+ if (enforceDer) {
426
+ this.scriptEvaluationError('The signature format is invalid.');
427
+ return false;
428
+ }
289
429
  }
290
430
  return true;
291
431
  }
432
+ parseChecksigSignature(buf) {
433
+ try {
434
+ return TransactionSignature.fromChecksigFormat(buf);
435
+ }
436
+ catch (e) {
437
+ if (this.shouldEnforceDerSignatures())
438
+ throw e;
439
+ return this.parseLaxChecksigSignature(buf);
440
+ }
441
+ }
442
+ readLaxDERLength(buf, position) {
443
+ const first = buf[position.value++];
444
+ if (first === undefined)
445
+ throw new Error('Invalid DER length');
446
+ if ((first & 0x80) === 0)
447
+ return first;
448
+ const lengthBytes = first & 0x7f;
449
+ if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
450
+ throw new Error('Invalid DER length');
451
+ }
452
+ let length = 0;
453
+ for (let i = 0; i < lengthBytes; i++) {
454
+ length = (length << 8) | (buf[position.value++] ?? 0);
455
+ }
456
+ return length;
457
+ }
458
+ parseLaxDERInteger(buf, position, sequenceEnd) {
459
+ if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
460
+ throw new Error('Invalid DER integer');
461
+ }
462
+ const length = this.readLaxDERLength(buf, position);
463
+ if (position.value + length > sequenceEnd) {
464
+ throw new Error('Invalid DER integer length');
465
+ }
466
+ let bytes = buf.slice(position.value, position.value + length);
467
+ position.value += length;
468
+ while (bytes.length > 1 && bytes[0] === 0)
469
+ bytes = bytes.slice(1);
470
+ if (bytes.length === 0)
471
+ bytes = [0];
472
+ return new BigNumber(bytes);
473
+ }
474
+ parseLaxChecksigSignature(buf) {
475
+ if (buf.length === 0)
476
+ return TransactionSignature.fromChecksigFormat(buf);
477
+ const scope = buf[buf.length - 1];
478
+ const der = buf.slice(0, -1);
479
+ const position = { value: 0 };
480
+ if (der[position.value++] !== 0x30)
481
+ throw new Error('Signature DER must start with 0x30');
482
+ const sequenceLength = this.readLaxDERLength(der, position);
483
+ const sequenceEnd = Math.min(position.value + sequenceLength, der.length);
484
+ const r = this.parseLaxDERInteger(der, position, sequenceEnd);
485
+ const s = this.parseLaxDERInteger(der, position, sequenceEnd);
486
+ return new TransactionSignature(r, s, scope);
487
+ }
292
488
  checkPublicKeyEncoding(buf) {
489
+ if (!this.shouldEnforceStrictEncoding())
490
+ return true;
293
491
  if (buf.length === 0) {
294
492
  this.scriptEvaluationError('Public key is empty.');
295
493
  return false;
@@ -324,7 +522,7 @@ export default class Spend {
324
522
  return true;
325
523
  }
326
524
  verifySignature(sig, pubkey, subscript) {
327
- const preimage = TransactionSignature.formatBytes({
525
+ const params = {
328
526
  sourceTXID: this.sourceTXID,
329
527
  sourceOutputIndex: this.sourceOutputIndex,
330
528
  sourceSatoshis: this.sourceSatoshis,
@@ -337,8 +535,10 @@ export default class Spend {
337
535
  lockTime: this.lockTime,
338
536
  scope: sig.scope,
339
537
  cache: this.sigHashCache
340
- });
341
- const hash = new BigNumber(Hash.hash256(preimage));
538
+ };
539
+ const hash = TransactionSignature.usesOtdaSingleBug(params)
540
+ ? new BigNumber([1, ...new Array(31).fill(0)])
541
+ : new BigNumber(Hash.hash256(TransactionSignature.formatBytes(params)));
342
542
  return verify(hash, sig, pubkey);
343
543
  }
344
544
  step() {
@@ -352,6 +552,14 @@ export default class Spend {
352
552
  }
353
553
  if (this.context === 'UnlockingScript' &&
354
554
  this.programCounter >= this.unlockingScript.chunks.length) {
555
+ if (this.ifStack.length > 0) {
556
+ this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.');
557
+ }
558
+ this.clearAltStack();
559
+ this.ifStack = [];
560
+ this.elseStack = [];
561
+ this.returningFromConditional = false;
562
+ this.lastCodeSeparator = null;
355
563
  this.context = 'LockingScript';
356
564
  this.programCounter = 0;
357
565
  }
@@ -364,12 +572,24 @@ export default class Spend {
364
572
  if (currentOpcode === undefined) {
365
573
  this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
366
574
  }
367
- if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
368
- this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`); // Error thrown
575
+ if (operation.invalidLength === true) {
576
+ this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`);
577
+ }
578
+ if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
579
+ this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`); // Error thrown
580
+ }
581
+ const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false);
582
+ if (this.hasExplicitFlags() &&
583
+ !this.isAfterGenesis() &&
584
+ !this.isAfterChronicle() &&
585
+ (currentOpcode === OP.OP_2MUL ||
586
+ currentOpcode === OP.OP_2DIV ||
587
+ currentOpcode === OP.OP_VERIF ||
588
+ currentOpcode === OP.OP_VERNOTIF)) {
589
+ this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
369
590
  }
370
- const isScriptExecuting = !this.ifStack.includes(false);
371
591
  if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP.OP_PUSHDATA4) {
372
- if (!this.isRelaxed() && !isChunkMinimalPushHelper(operation)) {
592
+ if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
373
593
  this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
374
594
  }
375
595
  this.pushStack(Array.isArray(operation.data) ? operation.data : []);
@@ -382,6 +602,51 @@ export default class Spend {
382
602
  let bufSig, bufPubkey;
383
603
  let sig, pubkey;
384
604
  let i, ikey, isig, nKeysCount, nSigsCount, fOk;
605
+ if (isScriptExecuting && currentOpcode > OP.OP_16) {
606
+ this.executedOpCount++;
607
+ if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
608
+ this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`);
609
+ }
610
+ }
611
+ if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
612
+ if (isScriptExecuting &&
613
+ (currentOpcode === OP.OP_SUBSTR ||
614
+ currentOpcode === OP.OP_LEFT ||
615
+ currentOpcode === OP.OP_RIGHT ||
616
+ currentOpcode === OP.OP_LSHIFTNUM ||
617
+ currentOpcode === OP.OP_RSHIFTNUM)) {
618
+ if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
619
+ this.scriptEvaluationError(`${OP[currentOpcode]} is discouraged by verification flags.`);
620
+ }
621
+ this.programCounter++;
622
+ return true;
623
+ }
624
+ if ((isScriptExecuting || !this.isAfterGenesis()) &&
625
+ (currentOpcode === OP.OP_2MUL || currentOpcode === OP.OP_2DIV)) {
626
+ this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
627
+ }
628
+ if ((isScriptExecuting || !this.isAfterGenesis()) &&
629
+ (currentOpcode === OP.OP_VER ||
630
+ currentOpcode === OP.OP_VERIF ||
631
+ currentOpcode === OP.OP_VERNOTIF)) {
632
+ this.scriptEvaluationError(`${OP[currentOpcode]} is disabled until Chronicle.`);
633
+ }
634
+ if (!isScriptExecuting &&
635
+ this.isAfterGenesis() &&
636
+ (currentOpcode === OP.OP_VERIF || currentOpcode === OP.OP_VERNOTIF)) {
637
+ this.programCounter++;
638
+ return true;
639
+ }
640
+ }
641
+ if (isScriptExecuting &&
642
+ this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
643
+ (currentOpcode === OP.OP_NOP1 ||
644
+ currentOpcode === OP.OP_CHECKLOCKTIMEVERIFY ||
645
+ currentOpcode === OP.OP_CHECKSEQUENCEVERIFY ||
646
+ currentOpcode === OP.OP_NOP9 ||
647
+ currentOpcode === OP.OP_NOP10)) {
648
+ this.scriptEvaluationError(`${OP[currentOpcode]} is discouraged by verification flags.`);
649
+ }
385
650
  switch (currentOpcode) {
386
651
  case OP.OP_VER: {
387
652
  // Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
@@ -392,8 +657,8 @@ export default class Spend {
392
657
  case OP.OP_SUBSTR: {
393
658
  if (this.stack.length < 3)
394
659
  this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.');
395
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
396
- const offset = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
660
+ const len = this.readScriptNumber(this.popStack()).toNumber();
661
+ const offset = this.readScriptNumber(this.popStack()).toNumber();
397
662
  buf = this.popStack();
398
663
  const size = buf.length;
399
664
  if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
@@ -405,7 +670,7 @@ export default class Spend {
405
670
  case OP.OP_LEFT: {
406
671
  if (this.stack.length < 2)
407
672
  this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.');
408
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
673
+ const len = this.readScriptNumber(this.popStack()).toNumber();
409
674
  buf = this.popStack();
410
675
  const size = buf.length;
411
676
  if (len < 0 || len > size) {
@@ -417,7 +682,7 @@ export default class Spend {
417
682
  case OP.OP_RIGHT: {
418
683
  if (this.stack.length < 2)
419
684
  this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.');
420
- const len = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
685
+ const len = this.readScriptNumber(this.popStack()).toNumber();
421
686
  buf = this.popStack();
422
687
  const size = buf.length;
423
688
  if (len < 0 || len > size) {
@@ -429,11 +694,11 @@ export default class Spend {
429
694
  case OP.OP_LSHIFTNUM: {
430
695
  if (this.stack.length < 2)
431
696
  this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.');
432
- const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
697
+ const bits = this.readScriptNumber(this.popStack()).toBigInt();
433
698
  if (bits < 0) {
434
699
  this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.');
435
700
  }
436
- const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
701
+ const value = this.readScriptNumber(this.popStack()).toBigInt();
437
702
  const resultBn = new BigNumber(value << bits);
438
703
  this.pushStack(resultBn.toScriptNum());
439
704
  break;
@@ -441,11 +706,11 @@ export default class Spend {
441
706
  case OP.OP_RSHIFTNUM: {
442
707
  if (this.stack.length < 2)
443
708
  this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.');
444
- const bits = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
709
+ const bits = this.readScriptNumber(this.popStack()).toBigInt();
445
710
  if (bits < 0) {
446
711
  this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.');
447
712
  }
448
- const value = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
713
+ const value = this.readScriptNumber(this.popStack()).toBigInt();
449
714
  let resultBn;
450
715
  if (value < 0) {
451
716
  resultBn = new BigNumber(-(-value >> bits));
@@ -489,9 +754,27 @@ export default class Spend {
489
754
  // OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
490
755
  // falls through
491
756
  case OP.OP_CHECKLOCKTIMEVERIFY:
757
+ break;
492
758
  // OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
493
- // falls through
494
759
  case OP.OP_CHECKSEQUENCEVERIFY:
760
+ if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
761
+ if (this.stack.length < 1)
762
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.');
763
+ let sequenceLock = 0n;
764
+ try {
765
+ // BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
766
+ sequenceLock = BigNumber.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt();
767
+ }
768
+ catch {
769
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.');
770
+ }
771
+ if (sequenceLock < 0n)
772
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.');
773
+ if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
774
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.');
775
+ }
776
+ }
777
+ break;
495
778
  case OP.OP_NOP9:
496
779
  case OP.OP_NOP10:
497
780
  break;
@@ -512,6 +795,7 @@ export default class Spend {
512
795
  fValue = !fValue;
513
796
  }
514
797
  this.ifStack.push(fValue);
798
+ this.elseStack.push(false);
515
799
  break;
516
800
  case OP.OP_IF:
517
801
  case OP.OP_NOTIF:
@@ -520,21 +804,30 @@ export default class Spend {
520
804
  if (this.stack.length < 1)
521
805
  this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
522
806
  buf = this.popStack();
807
+ if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
808
+ this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.');
809
+ }
523
810
  fValue = this.castToBool(buf);
524
811
  if (currentOpcode === OP.OP_NOTIF)
525
812
  fValue = !fValue;
526
813
  }
527
814
  this.ifStack.push(fValue);
815
+ this.elseStack.push(false);
528
816
  break;
529
817
  case OP.OP_ELSE:
530
818
  if (this.ifStack.length === 0)
531
819
  this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
820
+ if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
821
+ this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.');
822
+ }
823
+ this.elseStack[this.elseStack.length - 1] = true;
532
824
  this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1]);
533
825
  break;
534
826
  case OP.OP_ENDIF:
535
827
  if (this.ifStack.length === 0)
536
828
  this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
537
829
  this.ifStack.pop();
830
+ this.elseStack.pop();
538
831
  break;
539
832
  case OP.OP_VERIFY:
540
833
  if (this.stack.length < 1)
@@ -546,12 +839,19 @@ export default class Spend {
546
839
  this.popStack();
547
840
  break;
548
841
  case OP.OP_RETURN:
549
- if (this.context === 'UnlockingScript')
550
- this.programCounter = this.unlockingScript.chunks.length;
551
- else
552
- this.programCounter = this.lockingScript.chunks.length;
553
- this.ifStack = [];
554
- this.programCounter--; // To counteract the final increment and ensure loop termination
842
+ if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
843
+ this.scriptEvaluationError('OP_RETURN is invalid before Genesis.');
844
+ }
845
+ if (this.ifStack.length > 0) {
846
+ this.returningFromConditional = true;
847
+ }
848
+ else {
849
+ if (this.context === 'UnlockingScript')
850
+ this.programCounter = this.unlockingScript.chunks.length;
851
+ else
852
+ this.programCounter = this.lockingScript.chunks.length;
853
+ this.programCounter--; // To counteract the final increment and ensure loop termination
854
+ }
555
855
  break;
556
856
  case OP.OP_TOALTSTACK:
557
857
  if (this.stack.length < 1)
@@ -662,7 +962,7 @@ export default class Spend {
662
962
  case OP.OP_ROLL: {
663
963
  if (this.stack.length < 2)
664
964
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
665
- bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed());
965
+ bn = this.readScriptNumber(this.popStack());
666
966
  const nBigInt = bn.toBigInt();
667
967
  if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
668
968
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
@@ -748,7 +1048,7 @@ export default class Spend {
748
1048
  case OP.OP_RSHIFT: {
749
1049
  if (this.stack.length < 2)
750
1050
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
751
- bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()); // n (shift amount)
1051
+ bn2 = this.readScriptNumber(this.popStack()); // n (shift amount)
752
1052
  buf1 = this.popStack(); // value to shift
753
1053
  const shiftBits = bn2.toBigInt();
754
1054
  if (shiftBits < 0n)
@@ -796,7 +1096,7 @@ export default class Spend {
796
1096
  case OP.OP_0NOTEQUAL:
797
1097
  if (this.stack.length < 1)
798
1098
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least one item to be on the stack.`);
799
- bn = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed());
1099
+ bn = this.readScriptNumber(this.popStack());
800
1100
  switch (currentOpcode) {
801
1101
  case OP.OP_1ADD:
802
1102
  bn = bn.add(new BigNumber(1));
@@ -846,8 +1146,8 @@ export default class Spend {
846
1146
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least two items to be on the stack.`);
847
1147
  buf2 = this.popStack();
848
1148
  buf1 = this.popStack();
849
- bn2 = BigNumber.fromScriptNum(buf2, !this.isRelaxed());
850
- bn1 = BigNumber.fromScriptNum(buf1, !this.isRelaxed());
1149
+ bn2 = this.readScriptNumber(buf2);
1150
+ bn1 = this.readScriptNumber(buf1);
851
1151
  let predictedLen = 0;
852
1152
  switch (currentOpcode) {
853
1153
  case OP.OP_MUL:
@@ -927,9 +1227,9 @@ export default class Spend {
927
1227
  case OP.OP_WITHIN:
928
1228
  if (this.stack.length < 3)
929
1229
  this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
930
- bn3 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()); // max
931
- bn2 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()); // min
932
- bn1 = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()); // x
1230
+ bn3 = this.readScriptNumber(this.popStack()); // max
1231
+ bn2 = this.readScriptNumber(this.popStack()); // min
1232
+ bn1 = this.readScriptNumber(this.popStack()); // x
933
1233
  fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
934
1234
  this.pushStack(fValue ? [1] : []);
935
1235
  break;
@@ -971,7 +1271,7 @@ export default class Spend {
971
1271
  fSuccess = false;
972
1272
  if (bufSig.length > 0) {
973
1273
  try {
974
- sig = TransactionSignature.fromChecksigFormat(bufSig);
1274
+ sig = this.parseChecksigSignature(bufSig);
975
1275
  const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
976
1276
  const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
977
1277
  subscript = new Script(scriptCodeChunks);
@@ -983,6 +1283,9 @@ export default class Spend {
983
1283
  fSuccess = false;
984
1284
  }
985
1285
  }
1286
+ if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
1287
+ this.scriptEvaluationError(`${OP[currentOpcode]} requires failing signatures to be empty.`);
1288
+ }
986
1289
  this.pushStack(fSuccess ? [1] : []);
987
1290
  if (currentOpcode === OP.OP_CHECKSIGVERIFY) {
988
1291
  if (!fSuccess)
@@ -997,10 +1300,13 @@ export default class Spend {
997
1300
  if (this.stack.length < i) {
998
1301
  this.scriptEvaluationError(`${OP[currentOpcode]} requires at least 1 item for nKeys.`);
999
1302
  }
1000
- const nKeysCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
1303
+ const nKeysCountBN = this.readScriptNumber(this.stackTop(-i));
1001
1304
  const nKeysCountBigInt = nKeysCountBN.toBigInt();
1002
- if (nKeysCountBigInt < 0n || nKeysCountBigInt > maxMultisigKeyCountBigInt) {
1003
- this.scriptEvaluationError(`${OP[currentOpcode]} requires a key count between 0 and ${maxMultisigKeyCount}.`);
1305
+ const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
1306
+ ? BigInt(maxMultisigKeyCountBeforeGenesis)
1307
+ : maxMultisigKeyCountBigInt;
1308
+ if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
1309
+ this.scriptEvaluationError(`${OP[currentOpcode]} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`);
1004
1310
  }
1005
1311
  nKeysCount = Number(nKeysCountBigInt);
1006
1312
  const declaredKeyCount = nKeysCount;
@@ -1009,7 +1315,7 @@ export default class Spend {
1009
1315
  if (this.stack.length < i) {
1010
1316
  this.scriptEvaluationError(`${OP[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);
1011
1317
  }
1012
- const nSigsCountBN = BigNumber.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
1318
+ const nSigsCountBN = this.readScriptNumber(this.stackTop(-i));
1013
1319
  const nSigsCountBigInt = nSigsCountBN.toBigInt();
1014
1320
  if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
1015
1321
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
@@ -1024,8 +1330,11 @@ export default class Spend {
1024
1330
  const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
1025
1331
  const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
1026
1332
  subscript = new Script(subscriptChunksCMS);
1333
+ let hasNonEmptySignature = false;
1027
1334
  for (let k = 0; k < nSigsCount; k++) {
1028
1335
  bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
1336
+ if (bufSig.length > 0)
1337
+ hasNonEmptySignature = true;
1029
1338
  subscript.findAndDelete(new Script().writeBin(bufSig));
1030
1339
  }
1031
1340
  fSuccess = true;
@@ -1042,7 +1351,7 @@ export default class Spend {
1042
1351
  fOk = false;
1043
1352
  if (bufSig.length > 0) {
1044
1353
  try {
1045
- sig = TransactionSignature.fromChecksigFormat(bufSig);
1354
+ sig = this.parseChecksigSignature(bufSig);
1046
1355
  pubkey = PublicKey.fromDER(bufPubkey);
1047
1356
  fOk = this.verifySignature(sig, pubkey, subscript);
1048
1357
  }
@@ -1060,6 +1369,9 @@ export default class Spend {
1060
1369
  fSuccess = false;
1061
1370
  }
1062
1371
  }
1372
+ if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
1373
+ this.scriptEvaluationError(`${OP[currentOpcode]} requires failing signatures to be empty.`);
1374
+ }
1063
1375
  // Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
1064
1376
  const itemsConsumedByOp = 1 + // N_val
1065
1377
  declaredKeyCount + // keys
@@ -1076,7 +1388,7 @@ export default class Spend {
1076
1388
  this.scriptEvaluationError(`${OP[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
1077
1389
  }
1078
1390
  const dummyBuf = this.popStack();
1079
- if (!this.isRelaxed() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
1391
+ if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
1080
1392
  this.scriptEvaluationError(`${OP[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
1081
1393
  }
1082
1394
  this.pushStack(fSuccess ? [1] : []);
@@ -1093,8 +1405,8 @@ export default class Spend {
1093
1405
  buf2 = this.popStack();
1094
1406
  buf1 = this.popStack();
1095
1407
  const catResult = (buf1).concat(buf2);
1096
- if (catResult.length > maxScriptElementSize)
1097
- this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
1408
+ if (catResult.length > this.maxPushSize())
1409
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`);
1098
1410
  this.pushStack(catResult);
1099
1411
  break;
1100
1412
  }
@@ -1103,7 +1415,7 @@ export default class Spend {
1103
1415
  this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
1104
1416
  const posBuf = this.popStack();
1105
1417
  const dataToSplit = this.popStack();
1106
- const splitIndexBigInt = BigNumber.fromScriptNum(posBuf, !this.isRelaxed()).toBigInt();
1418
+ const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt();
1107
1419
  if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
1108
1420
  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.');
1109
1421
  }
@@ -1115,9 +1427,9 @@ export default class Spend {
1115
1427
  case OP.OP_NUM2BIN: {
1116
1428
  if (this.stack.length < 2)
1117
1429
  this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
1118
- const sizeBigInt = BigNumber.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
1119
- if (sizeBigInt > BigInt(maxScriptElementSize) || sizeBigInt < 0n) { // size can be 0
1120
- this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`);
1430
+ const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt();
1431
+ if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
1432
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`);
1121
1433
  }
1122
1434
  size = Number(sizeBigInt);
1123
1435
  let rawnum = this.popStack(); // This is the number to convert
@@ -1161,7 +1473,17 @@ export default class Spend {
1161
1473
  this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
1162
1474
  }
1163
1475
  }
1164
- this.programCounter++;
1476
+ if (this.returningFromConditional && this.ifStack.length === 0) {
1477
+ this.programCounter = currentScript.chunks.length;
1478
+ }
1479
+ else {
1480
+ this.programCounter++;
1481
+ }
1482
+ if (this.hasExplicitFlags() &&
1483
+ !this.isAfterGenesis() &&
1484
+ this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis) {
1485
+ this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`);
1486
+ }
1165
1487
  return true;
1166
1488
  }
1167
1489
  /**
@@ -1176,30 +1498,87 @@ export default class Spend {
1176
1498
  * }
1177
1499
  */
1178
1500
  validate() {
1179
- if (!this.isRelaxed() && !this.unlockingScript.isPushOnly()) {
1501
+ if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
1180
1502
  this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
1181
1503
  }
1182
- while (this.step()) {
1183
- if (this.context === 'LockingScript' &&
1184
- this.programCounter >= this.lockingScript.chunks.length) {
1185
- break;
1504
+ const originalLockingScript = this.lockingScript;
1505
+ const shouldEvaluateP2SH = this.hasFlag('P2SH') &&
1506
+ !this.isAfterGenesis() &&
1507
+ this.isP2SHLockingScript(this.lockingScript);
1508
+ if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
1509
+ this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.');
1510
+ }
1511
+ this.reset();
1512
+ this.runScript('UnlockingScript');
1513
+ const stackAfterUnlockingScript = this.stack.map(item => item.slice());
1514
+ this.runScript('LockingScript');
1515
+ this.requireTruthyTopStack();
1516
+ try {
1517
+ if (shouldEvaluateP2SH) {
1518
+ if (stackAfterUnlockingScript.length === 0) {
1519
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
1520
+ }
1521
+ const redeemScriptBytes = stackAfterUnlockingScript.pop();
1522
+ if (redeemScriptBytes === undefined) {
1523
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
1524
+ return false;
1525
+ }
1526
+ this.setStack(stackAfterUnlockingScript);
1527
+ const redeemScript = Script.fromBinary(redeemScriptBytes);
1528
+ this.lockingScript = new LockingScript(redeemScript.chunks);
1529
+ this.runScript('LockingScript');
1186
1530
  }
1187
1531
  }
1532
+ finally {
1533
+ this.lockingScript = originalLockingScript;
1534
+ }
1535
+ if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
1536
+ this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
1537
+ }
1538
+ this.requireTruthyTopStack();
1539
+ return true;
1540
+ }
1541
+ runScript(context) {
1542
+ this.context = context;
1543
+ this.programCounter = 0;
1544
+ this.ifStack = [];
1545
+ this.elseStack = [];
1546
+ this.returningFromConditional = false;
1547
+ this.clearAltStack();
1548
+ this.lastCodeSeparator = null;
1549
+ const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
1550
+ if (this.hasExplicitFlags() &&
1551
+ !this.isAfterGenesis() &&
1552
+ script.toUint8Array().length > maxScriptSizeBeforeGenesis) {
1553
+ this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`);
1554
+ }
1555
+ while (this.programCounter < script.chunks.length) {
1556
+ this.step();
1557
+ }
1188
1558
  if (this.ifStack.length > 0) {
1189
1559
  this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
1190
1560
  }
1191
- if (!this.isRelaxed()) {
1192
- if (this.stack.length !== 1) {
1193
- this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
1194
- }
1195
- }
1561
+ this.ifStack = [];
1562
+ this.elseStack = [];
1563
+ this.clearAltStack();
1564
+ this.lastCodeSeparator = null;
1565
+ }
1566
+ isP2SHLockingScript(script) {
1567
+ const chunks = script.chunks;
1568
+ return chunks.length === 3 &&
1569
+ chunks[0].op === OP.OP_HASH160 &&
1570
+ chunks[1].op === 20 &&
1571
+ Array.isArray(chunks[1].data) &&
1572
+ chunks[1].data.length === 20 &&
1573
+ chunks[2].op === OP.OP_EQUAL;
1574
+ }
1575
+ requireTruthyTopStack() {
1196
1576
  if (this.stack.length === 0) {
1197
1577
  this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
1198
1578
  }
1199
1579
  else if (!this.castToBool(this.stackTop())) {
1200
1580
  this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
1201
1581
  }
1202
- return true;
1203
1582
  }
1204
1583
  castToBool(val) {
1205
1584
  if (val.length === 0)