@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
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const LockingScript_js_1 = __importDefault(require("./LockingScript.js"));
39
40
  const Script_js_1 = __importDefault(require("./Script.js"));
40
41
  const BigNumber_js_1 = __importDefault(require("../primitives/BigNumber.js"));
41
42
  const OP_js_1 = __importDefault(require("./OP.js"));
@@ -47,8 +48,14 @@ const PublicKey_js_1 = __importDefault(require("../primitives/PublicKey.js"));
47
48
  const ECDSA_js_1 = require("../primitives/ECDSA.js");
48
49
  // These constants control the current behavior of the interpreter.
49
50
  const maxScriptElementSize = 1024 * 1024 * 1024;
51
+ const maxScriptElementSizeBeforeGenesis = 520;
52
+ const maxScriptSizeBeforeGenesis = 10000;
53
+ const maxOpsBeforeGenesis = 500;
54
+ const maxStackItemsBeforeGenesis = 1000;
50
55
  const maxMultisigKeyCount = Math.pow(2, 31) - 1;
51
56
  const maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount);
57
+ const maxMultisigKeyCountBeforeGenesis = 20;
58
+ const sequenceLocktimeDisableFlag = 0x80000000;
52
59
  // --- Optimization: Pre-computed script numbers ---
53
60
  const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber_js_1.default(-1).toScriptNum());
54
61
  const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber_js_1.default(i).toScriptNum())));
@@ -204,11 +211,21 @@ class Spend {
204
211
  this.lockTime = params.lockTime;
205
212
  this.memoryLimit = params.memoryLimit ?? 32000000;
206
213
  this.isRelaxedOverride = params.isRelaxed === true;
214
+ this.verifyFlags = params.verifyFlags === undefined
215
+ ? undefined
216
+ : new Set((Array.isArray(params.verifyFlags)
217
+ ? params.verifyFlags
218
+ : params.verifyFlags.split(','))
219
+ .map(flag => flag.trim())
220
+ .filter(flag => flag.length > 0));
207
221
  this.stack = [];
208
222
  this.altStack = [];
209
223
  this.ifStack = [];
224
+ this.elseStack = [];
210
225
  this.stackMem = 0;
211
226
  this.altStackMem = 0;
227
+ this.executedOpCount = 0;
228
+ this.returningFromConditional = false;
212
229
  this.sigHashCache = { hashOutputsSingle: new Map() };
213
230
  this.reset();
214
231
  }
@@ -216,6 +233,75 @@ class Spend {
216
233
  return this.isRelaxedOverride ||
217
234
  (this.transactionVersion > 1);
218
235
  }
236
+ hasExplicitFlags() {
237
+ return this.verifyFlags !== undefined;
238
+ }
239
+ hasFlag(flag) {
240
+ return this.verifyFlags?.has(flag) === true;
241
+ }
242
+ isAfterGenesis() {
243
+ if (this.hasExplicitFlags()) {
244
+ return this.hasFlag('GENESIS') ||
245
+ this.hasFlag('UTXO_AFTER_GENESIS') ||
246
+ this.hasFlag('UTXO_AFTER_CHRONICLE');
247
+ }
248
+ return this.isRelaxed();
249
+ }
250
+ isAfterChronicle() {
251
+ if (this.hasExplicitFlags())
252
+ return this.hasFlag('UTXO_AFTER_CHRONICLE');
253
+ return this.isRelaxed();
254
+ }
255
+ shouldEnforceMinimalData() {
256
+ if (this.hasExplicitFlags())
257
+ return this.hasFlag('MINIMALDATA');
258
+ return !this.isRelaxed();
259
+ }
260
+ shouldEnforceLowS() {
261
+ if (this.hasExplicitFlags())
262
+ return this.hasFlag('LOW_S');
263
+ return !this.isRelaxed();
264
+ }
265
+ shouldEnforceNullDummy() {
266
+ if (this.hasExplicitFlags())
267
+ return this.hasFlag('NULLDUMMY');
268
+ return !this.isRelaxed();
269
+ }
270
+ shouldEnforceSigPushOnly() {
271
+ if (this.hasExplicitFlags())
272
+ return this.hasFlag('SIGPUSHONLY');
273
+ return !this.isRelaxed();
274
+ }
275
+ shouldEnforceCleanStack() {
276
+ if (this.hasExplicitFlags())
277
+ return this.hasFlag('CLEANSTACK');
278
+ return !this.isRelaxed();
279
+ }
280
+ shouldEnforceDerSignatures() {
281
+ if (this.hasExplicitFlags()) {
282
+ return this.hasFlag('DERSIG') ||
283
+ this.hasFlag('STRICTENC') ||
284
+ this.hasFlag('LOW_S') ||
285
+ this.hasFlag('SIGHASH_FORKID');
286
+ }
287
+ return true;
288
+ }
289
+ shouldEnforceStrictEncoding() {
290
+ if (this.hasExplicitFlags()) {
291
+ return this.hasFlag('STRICTENC') || this.hasFlag('SIGHASH_FORKID');
292
+ }
293
+ return true;
294
+ }
295
+ scriptNumMaxSize() {
296
+ if (this.hasExplicitFlags() && !this.isAfterGenesis())
297
+ return 4;
298
+ return undefined;
299
+ }
300
+ maxPushSize() {
301
+ if (this.hasExplicitFlags() && !this.isAfterGenesis())
302
+ return maxScriptElementSizeBeforeGenesis;
303
+ return maxScriptElementSize;
304
+ }
219
305
  reset() {
220
306
  this.context = 'UnlockingScript';
221
307
  this.programCounter = 0;
@@ -223,8 +309,11 @@ class Spend {
223
309
  this.stack = [];
224
310
  this.altStack = [];
225
311
  this.ifStack = [];
312
+ this.elseStack = [];
226
313
  this.stackMem = 0;
227
314
  this.altStackMem = 0;
315
+ this.executedOpCount = 0;
316
+ this.returningFromConditional = false;
228
317
  this.sigHashCache = { hashOutputsSingle: new Map() };
229
318
  }
230
319
  ensureStackMem(additional) {
@@ -268,6 +357,14 @@ class Spend {
268
357
  }
269
358
  return this.stack[this.stack.length + index];
270
359
  }
360
+ setStack(items) {
361
+ this.stack = items.map(item => item.slice());
362
+ this.stackMem = this.stack.reduce((total, item) => total + item.length, 0);
363
+ }
364
+ clearAltStack() {
365
+ this.altStack = [];
366
+ this.altStackMem = 0;
367
+ }
271
368
  pushAltStack(item) {
272
369
  this.ensureAltStackMem(item.length);
273
370
  this.altStack.push(item);
@@ -285,27 +382,124 @@ class Spend {
285
382
  this.altStackMem -= item.length;
286
383
  return item;
287
384
  }
385
+ readScriptNumber(buf) {
386
+ try {
387
+ return BigNumber_js_1.default.fromScriptNum(buf, this.shouldEnforceMinimalData(), this.scriptNumMaxSize());
388
+ }
389
+ catch (e) {
390
+ const message = e instanceof Error ? e.message : String(e);
391
+ this.scriptEvaluationError(message);
392
+ }
393
+ return new BigNumber_js_1.default(0);
394
+ }
395
+ isDefinedHashType(scope) {
396
+ const baseType = scope & 0x1f;
397
+ return baseType >= TransactionSignature_js_1.default.SIGHASH_ALL &&
398
+ baseType <= TransactionSignature_js_1.default.SIGHASH_SINGLE;
399
+ }
288
400
  checkSignatureEncoding(buf) {
289
401
  if (buf.length === 0)
290
402
  return true;
291
- if (!isChecksigFormatHelper(buf)) {
403
+ const enforceDer = this.shouldEnforceDerSignatures();
404
+ if (enforceDer && !isChecksigFormatHelper(buf)) {
292
405
  this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
293
406
  return false;
294
407
  }
295
408
  try {
296
409
  const sig = TransactionSignature_js_1.default.fromChecksigFormat(buf); // This can throw for stricter DER rules
297
- if (!this.isRelaxed() && !sig.hasLowS()) {
410
+ if (this.shouldEnforceStrictEncoding() && !this.isDefinedHashType(sig.scope)) {
411
+ this.scriptEvaluationError('The signature hash type is invalid.');
412
+ return false;
413
+ }
414
+ if (this.shouldEnforceStrictEncoding() &&
415
+ (sig.scope & TransactionSignature_js_1.default.SIGHASH_CHRONICLE) !== 0 &&
416
+ !this.isAfterChronicle()) {
417
+ this.scriptEvaluationError('The signature hash type is invalid before Chronicle.');
418
+ return false;
419
+ }
420
+ const hasForkId = (sig.scope & TransactionSignature_js_1.default.SIGHASH_FORKID) !== 0;
421
+ if (this.hasExplicitFlags()) {
422
+ if (this.hasFlag('SIGHASH_FORKID') && !hasForkId) {
423
+ this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
424
+ return false;
425
+ }
426
+ if (!this.hasFlag('SIGHASH_FORKID') && !this.isAfterGenesis() && hasForkId) {
427
+ this.scriptEvaluationError('The signature must not use SIGHASH_FORKID.');
428
+ return false;
429
+ }
430
+ }
431
+ if (this.shouldEnforceLowS() && !sig.hasLowS()) {
298
432
  this.scriptEvaluationError('The signature must have a low S value.');
299
433
  return false;
300
434
  }
301
435
  }
302
436
  catch {
303
- this.scriptEvaluationError('The signature format is invalid.');
304
- return false;
437
+ if (enforceDer) {
438
+ this.scriptEvaluationError('The signature format is invalid.');
439
+ return false;
440
+ }
305
441
  }
306
442
  return true;
307
443
  }
444
+ parseChecksigSignature(buf) {
445
+ try {
446
+ return TransactionSignature_js_1.default.fromChecksigFormat(buf);
447
+ }
448
+ catch (e) {
449
+ if (this.shouldEnforceDerSignatures())
450
+ throw e;
451
+ return this.parseLaxChecksigSignature(buf);
452
+ }
453
+ }
454
+ readLaxDERLength(buf, position) {
455
+ const first = buf[position.value++];
456
+ if (first === undefined)
457
+ throw new Error('Invalid DER length');
458
+ if ((first & 0x80) === 0)
459
+ return first;
460
+ const lengthBytes = first & 0x7f;
461
+ if (lengthBytes === 0 || position.value + lengthBytes > buf.length) {
462
+ throw new Error('Invalid DER length');
463
+ }
464
+ let length = 0;
465
+ for (let i = 0; i < lengthBytes; i++) {
466
+ length = (length << 8) | (buf[position.value++] ?? 0);
467
+ }
468
+ return length;
469
+ }
470
+ parseLaxDERInteger(buf, position, sequenceEnd) {
471
+ if (position.value >= sequenceEnd || buf[position.value++] !== 0x02) {
472
+ throw new Error('Invalid DER integer');
473
+ }
474
+ const length = this.readLaxDERLength(buf, position);
475
+ if (position.value + length > sequenceEnd) {
476
+ throw new Error('Invalid DER integer length');
477
+ }
478
+ let bytes = buf.slice(position.value, position.value + length);
479
+ position.value += length;
480
+ while (bytes.length > 1 && bytes[0] === 0)
481
+ bytes = bytes.slice(1);
482
+ if (bytes.length === 0)
483
+ bytes = [0];
484
+ return new BigNumber_js_1.default(bytes);
485
+ }
486
+ parseLaxChecksigSignature(buf) {
487
+ if (buf.length === 0)
488
+ return TransactionSignature_js_1.default.fromChecksigFormat(buf);
489
+ const scope = buf[buf.length - 1];
490
+ const der = buf.slice(0, -1);
491
+ const position = { value: 0 };
492
+ if (der[position.value++] !== 0x30)
493
+ throw new Error('Signature DER must start with 0x30');
494
+ const sequenceLength = this.readLaxDERLength(der, position);
495
+ const sequenceEnd = Math.min(position.value + sequenceLength, der.length);
496
+ const r = this.parseLaxDERInteger(der, position, sequenceEnd);
497
+ const s = this.parseLaxDERInteger(der, position, sequenceEnd);
498
+ return new TransactionSignature_js_1.default(r, s, scope);
499
+ }
308
500
  checkPublicKeyEncoding(buf) {
501
+ if (!this.shouldEnforceStrictEncoding())
502
+ return true;
309
503
  if (buf.length === 0) {
310
504
  this.scriptEvaluationError('Public key is empty.');
311
505
  return false;
@@ -340,7 +534,7 @@ class Spend {
340
534
  return true;
341
535
  }
342
536
  verifySignature(sig, pubkey, subscript) {
343
- const preimage = TransactionSignature_js_1.default.formatBytes({
537
+ const params = {
344
538
  sourceTXID: this.sourceTXID,
345
539
  sourceOutputIndex: this.sourceOutputIndex,
346
540
  sourceSatoshis: this.sourceSatoshis,
@@ -353,8 +547,10 @@ class Spend {
353
547
  lockTime: this.lockTime,
354
548
  scope: sig.scope,
355
549
  cache: this.sigHashCache
356
- });
357
- const hash = new BigNumber_js_1.default(Hash.hash256(preimage));
550
+ };
551
+ const hash = TransactionSignature_js_1.default.usesOtdaSingleBug(params)
552
+ ? new BigNumber_js_1.default([1, ...new Array(31).fill(0)])
553
+ : new BigNumber_js_1.default(Hash.hash256(TransactionSignature_js_1.default.formatBytes(params)));
358
554
  return (0, ECDSA_js_1.verify)(hash, sig, pubkey);
359
555
  }
360
556
  step() {
@@ -368,6 +564,14 @@ class Spend {
368
564
  }
369
565
  if (this.context === 'UnlockingScript' &&
370
566
  this.programCounter >= this.unlockingScript.chunks.length) {
567
+ if (this.ifStack.length > 0) {
568
+ this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the unlocking script.');
569
+ }
570
+ this.clearAltStack();
571
+ this.ifStack = [];
572
+ this.elseStack = [];
573
+ this.returningFromConditional = false;
574
+ this.lastCodeSeparator = null;
371
575
  this.context = 'LockingScript';
372
576
  this.programCounter = 0;
373
577
  }
@@ -380,12 +584,24 @@ class Spend {
380
584
  if (currentOpcode === undefined) {
381
585
  this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
382
586
  }
383
- if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
384
- this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`); // Error thrown
587
+ if (operation.invalidLength === true) {
588
+ this.scriptEvaluationError(`Malformed push data in ${this.context} at pc=${this.programCounter}.`);
589
+ }
590
+ if (Array.isArray(operation.data) && operation.data.length > this.maxPushSize()) {
591
+ this.scriptEvaluationError(`Data push > ${this.maxPushSize()} bytes (pc=${this.programCounter}).`); // Error thrown
592
+ }
593
+ const isScriptExecuting = !this.returningFromConditional && !this.ifStack.includes(false);
594
+ if (this.hasExplicitFlags() &&
595
+ !this.isAfterGenesis() &&
596
+ !this.isAfterChronicle() &&
597
+ (currentOpcode === OP_js_1.default.OP_2MUL ||
598
+ currentOpcode === OP_js_1.default.OP_2DIV ||
599
+ currentOpcode === OP_js_1.default.OP_VERIF ||
600
+ currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
601
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
385
602
  }
386
- const isScriptExecuting = !this.ifStack.includes(false);
387
603
  if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP_js_1.default.OP_PUSHDATA4) {
388
- if (!this.isRelaxed() && !isChunkMinimalPushHelper(operation)) {
604
+ if (this.shouldEnforceMinimalData() && !isChunkMinimalPushHelper(operation)) {
389
605
  this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
390
606
  }
391
607
  this.pushStack(Array.isArray(operation.data) ? operation.data : []);
@@ -398,6 +614,51 @@ class Spend {
398
614
  let bufSig, bufPubkey;
399
615
  let sig, pubkey;
400
616
  let i, ikey, isig, nKeysCount, nSigsCount, fOk;
617
+ if (isScriptExecuting && currentOpcode > OP_js_1.default.OP_16) {
618
+ this.executedOpCount++;
619
+ if (this.hasExplicitFlags() && !this.isAfterGenesis() && this.executedOpCount > maxOpsBeforeGenesis) {
620
+ this.scriptEvaluationError(`Script executed more than ${maxOpsBeforeGenesis} opcodes.`);
621
+ }
622
+ }
623
+ if (this.hasExplicitFlags() && !this.isAfterChronicle()) {
624
+ if (isScriptExecuting &&
625
+ (currentOpcode === OP_js_1.default.OP_SUBSTR ||
626
+ currentOpcode === OP_js_1.default.OP_LEFT ||
627
+ currentOpcode === OP_js_1.default.OP_RIGHT ||
628
+ currentOpcode === OP_js_1.default.OP_LSHIFTNUM ||
629
+ currentOpcode === OP_js_1.default.OP_RSHIFTNUM)) {
630
+ if (this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS')) {
631
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is discouraged by verification flags.`);
632
+ }
633
+ this.programCounter++;
634
+ return true;
635
+ }
636
+ if ((isScriptExecuting || !this.isAfterGenesis()) &&
637
+ (currentOpcode === OP_js_1.default.OP_2MUL || currentOpcode === OP_js_1.default.OP_2DIV)) {
638
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
639
+ }
640
+ if ((isScriptExecuting || !this.isAfterGenesis()) &&
641
+ (currentOpcode === OP_js_1.default.OP_VER ||
642
+ currentOpcode === OP_js_1.default.OP_VERIF ||
643
+ currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
644
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is disabled until Chronicle.`);
645
+ }
646
+ if (!isScriptExecuting &&
647
+ this.isAfterGenesis() &&
648
+ (currentOpcode === OP_js_1.default.OP_VERIF || currentOpcode === OP_js_1.default.OP_VERNOTIF)) {
649
+ this.programCounter++;
650
+ return true;
651
+ }
652
+ }
653
+ if (isScriptExecuting &&
654
+ this.hasFlag('DISCOURAGE_UPGRADABLE_NOPS') &&
655
+ (currentOpcode === OP_js_1.default.OP_NOP1 ||
656
+ currentOpcode === OP_js_1.default.OP_CHECKLOCKTIMEVERIFY ||
657
+ currentOpcode === OP_js_1.default.OP_CHECKSEQUENCEVERIFY ||
658
+ currentOpcode === OP_js_1.default.OP_NOP9 ||
659
+ currentOpcode === OP_js_1.default.OP_NOP10)) {
660
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} is discouraged by verification flags.`);
661
+ }
401
662
  switch (currentOpcode) {
402
663
  case OP_js_1.default.OP_VER: {
403
664
  // Node v1.2.0: pushes tx_version as a 4-byte little-endian integer (to_le encoding)
@@ -408,8 +669,8 @@ class Spend {
408
669
  case OP_js_1.default.OP_SUBSTR: {
409
670
  if (this.stack.length < 3)
410
671
  this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.');
411
- const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
412
- const offset = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
672
+ const len = this.readScriptNumber(this.popStack()).toNumber();
673
+ const offset = this.readScriptNumber(this.popStack()).toNumber();
413
674
  buf = this.popStack();
414
675
  const size = buf.length;
415
676
  if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
@@ -421,7 +682,7 @@ class Spend {
421
682
  case OP_js_1.default.OP_LEFT: {
422
683
  if (this.stack.length < 2)
423
684
  this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.');
424
- const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
685
+ const len = this.readScriptNumber(this.popStack()).toNumber();
425
686
  buf = this.popStack();
426
687
  const size = buf.length;
427
688
  if (len < 0 || len > size) {
@@ -433,7 +694,7 @@ class Spend {
433
694
  case OP_js_1.default.OP_RIGHT: {
434
695
  if (this.stack.length < 2)
435
696
  this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.');
436
- const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
697
+ const len = this.readScriptNumber(this.popStack()).toNumber();
437
698
  buf = this.popStack();
438
699
  const size = buf.length;
439
700
  if (len < 0 || len > size) {
@@ -445,11 +706,11 @@ class Spend {
445
706
  case OP_js_1.default.OP_LSHIFTNUM: {
446
707
  if (this.stack.length < 2)
447
708
  this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.');
448
- const bits = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
709
+ const bits = this.readScriptNumber(this.popStack()).toBigInt();
449
710
  if (bits < 0) {
450
711
  this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.');
451
712
  }
452
- const value = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
713
+ const value = this.readScriptNumber(this.popStack()).toBigInt();
453
714
  const resultBn = new BigNumber_js_1.default(value << bits);
454
715
  this.pushStack(resultBn.toScriptNum());
455
716
  break;
@@ -457,11 +718,11 @@ class Spend {
457
718
  case OP_js_1.default.OP_RSHIFTNUM: {
458
719
  if (this.stack.length < 2)
459
720
  this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.');
460
- const bits = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
721
+ const bits = this.readScriptNumber(this.popStack()).toBigInt();
461
722
  if (bits < 0) {
462
723
  this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.');
463
724
  }
464
- const value = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
725
+ const value = this.readScriptNumber(this.popStack()).toBigInt();
465
726
  let resultBn;
466
727
  if (value < 0) {
467
728
  resultBn = new BigNumber_js_1.default(-(-value >> bits));
@@ -505,9 +766,27 @@ class Spend {
505
766
  // OP_NOP2 (0xb1) = OP_CHECKLOCKTIMEVERIFY: on BSV post-genesis treated as NOP
506
767
  // falls through
507
768
  case OP_js_1.default.OP_CHECKLOCKTIMEVERIFY:
769
+ break;
508
770
  // OP_NOP3 (0xb2) = OP_CHECKSEQUENCEVERIFY: on BSV post-genesis treated as NOP
509
- // falls through
510
771
  case OP_js_1.default.OP_CHECKSEQUENCEVERIFY:
772
+ if (this.hasFlag('CHECKSEQUENCEVERIFY')) {
773
+ if (this.stack.length < 1)
774
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires at least one item to be on the stack.');
775
+ let sequenceLock = 0n;
776
+ try {
777
+ // BIP112 explicitly permits 5-byte script numbers so the disable flag can be represented.
778
+ sequenceLock = BigNumber_js_1.default.fromScriptNum(this.stackTop(), this.shouldEnforceMinimalData(), 5).toBigInt();
779
+ }
780
+ catch {
781
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a minimally-encoded numeric lock time.');
782
+ }
783
+ if (sequenceLock < 0n)
784
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY requires a non-negative lock time.');
785
+ if ((Number(sequenceLock & BigInt(sequenceLocktimeDisableFlag)) === 0) && this.transactionVersion < 2) {
786
+ this.scriptEvaluationError('OP_CHECKSEQUENCEVERIFY lock time is unsatisfied.');
787
+ }
788
+ }
789
+ break;
511
790
  case OP_js_1.default.OP_NOP9:
512
791
  case OP_js_1.default.OP_NOP10:
513
792
  break;
@@ -528,6 +807,7 @@ class Spend {
528
807
  fValue = !fValue;
529
808
  }
530
809
  this.ifStack.push(fValue);
810
+ this.elseStack.push(false);
531
811
  break;
532
812
  case OP_js_1.default.OP_IF:
533
813
  case OP_js_1.default.OP_NOTIF:
@@ -536,21 +816,30 @@ class Spend {
536
816
  if (this.stack.length < 1)
537
817
  this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
538
818
  buf = this.popStack();
819
+ if (this.hasFlag('MINIMALIF') && buf.length > 0 && !(buf.length === 1 && buf[0] === 1)) {
820
+ this.scriptEvaluationError('OP_IF and OP_NOTIF require minimal truth values.');
821
+ }
539
822
  fValue = this.castToBool(buf);
540
823
  if (currentOpcode === OP_js_1.default.OP_NOTIF)
541
824
  fValue = !fValue;
542
825
  }
543
826
  this.ifStack.push(fValue);
827
+ this.elseStack.push(false);
544
828
  break;
545
829
  case OP_js_1.default.OP_ELSE:
546
830
  if (this.ifStack.length === 0)
547
831
  this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
832
+ if (this.hasExplicitFlags() && this.isAfterGenesis() && this.elseStack[this.elseStack.length - 1]) {
833
+ this.scriptEvaluationError('OP_ELSE may only be used once for each OP_IF or OP_NOTIF after Genesis.');
834
+ }
835
+ this.elseStack[this.elseStack.length - 1] = true;
548
836
  this.ifStack[this.ifStack.length - 1] = !(this.ifStack[this.ifStack.length - 1]);
549
837
  break;
550
838
  case OP_js_1.default.OP_ENDIF:
551
839
  if (this.ifStack.length === 0)
552
840
  this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
553
841
  this.ifStack.pop();
842
+ this.elseStack.pop();
554
843
  break;
555
844
  case OP_js_1.default.OP_VERIFY:
556
845
  if (this.stack.length < 1)
@@ -562,12 +851,19 @@ class Spend {
562
851
  this.popStack();
563
852
  break;
564
853
  case OP_js_1.default.OP_RETURN:
565
- if (this.context === 'UnlockingScript')
566
- this.programCounter = this.unlockingScript.chunks.length;
567
- else
568
- this.programCounter = this.lockingScript.chunks.length;
569
- this.ifStack = [];
570
- this.programCounter--; // To counteract the final increment and ensure loop termination
854
+ if (this.hasExplicitFlags() && !this.isAfterGenesis()) {
855
+ this.scriptEvaluationError('OP_RETURN is invalid before Genesis.');
856
+ }
857
+ if (this.ifStack.length > 0) {
858
+ this.returningFromConditional = true;
859
+ }
860
+ else {
861
+ if (this.context === 'UnlockingScript')
862
+ this.programCounter = this.unlockingScript.chunks.length;
863
+ else
864
+ this.programCounter = this.lockingScript.chunks.length;
865
+ this.programCounter--; // To counteract the final increment and ensure loop termination
866
+ }
571
867
  break;
572
868
  case OP_js_1.default.OP_TOALTSTACK:
573
869
  if (this.stack.length < 1)
@@ -678,7 +974,7 @@ class Spend {
678
974
  case OP_js_1.default.OP_ROLL: {
679
975
  if (this.stack.length < 2)
680
976
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
681
- bn = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed());
977
+ bn = this.readScriptNumber(this.popStack());
682
978
  const nBigInt = bn.toBigInt();
683
979
  if (nBigInt < 0n || nBigInt >= BigInt(this.stack.length)) {
684
980
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the top stack element to be 0 or a positive number less than the current size of the stack.`);
@@ -764,7 +1060,7 @@ class Spend {
764
1060
  case OP_js_1.default.OP_RSHIFT: {
765
1061
  if (this.stack.length < 2)
766
1062
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
767
- bn2 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // n (shift amount)
1063
+ bn2 = this.readScriptNumber(this.popStack()); // n (shift amount)
768
1064
  buf1 = this.popStack(); // value to shift
769
1065
  const shiftBits = bn2.toBigInt();
770
1066
  if (shiftBits < 0n)
@@ -812,7 +1108,7 @@ class Spend {
812
1108
  case OP_js_1.default.OP_0NOTEQUAL:
813
1109
  if (this.stack.length < 1)
814
1110
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least one item to be on the stack.`);
815
- bn = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed());
1111
+ bn = this.readScriptNumber(this.popStack());
816
1112
  switch (currentOpcode) {
817
1113
  case OP_js_1.default.OP_1ADD:
818
1114
  bn = bn.add(new BigNumber_js_1.default(1));
@@ -862,8 +1158,8 @@ class Spend {
862
1158
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
863
1159
  buf2 = this.popStack();
864
1160
  buf1 = this.popStack();
865
- bn2 = BigNumber_js_1.default.fromScriptNum(buf2, !this.isRelaxed());
866
- bn1 = BigNumber_js_1.default.fromScriptNum(buf1, !this.isRelaxed());
1161
+ bn2 = this.readScriptNumber(buf2);
1162
+ bn1 = this.readScriptNumber(buf1);
867
1163
  let predictedLen = 0;
868
1164
  switch (currentOpcode) {
869
1165
  case OP_js_1.default.OP_MUL:
@@ -943,9 +1239,9 @@ class Spend {
943
1239
  case OP_js_1.default.OP_WITHIN:
944
1240
  if (this.stack.length < 3)
945
1241
  this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
946
- bn3 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // max
947
- bn2 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // min
948
- bn1 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // x
1242
+ bn3 = this.readScriptNumber(this.popStack()); // max
1243
+ bn2 = this.readScriptNumber(this.popStack()); // min
1244
+ bn1 = this.readScriptNumber(this.popStack()); // x
949
1245
  fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
950
1246
  this.pushStack(fValue ? [1] : []);
951
1247
  break;
@@ -987,7 +1283,7 @@ class Spend {
987
1283
  fSuccess = false;
988
1284
  if (bufSig.length > 0) {
989
1285
  try {
990
- sig = TransactionSignature_js_1.default.fromChecksigFormat(bufSig);
1286
+ sig = this.parseChecksigSignature(bufSig);
991
1287
  const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
992
1288
  const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
993
1289
  subscript = new Script_js_1.default(scriptCodeChunks);
@@ -999,6 +1295,9 @@ class Spend {
999
1295
  fSuccess = false;
1000
1296
  }
1001
1297
  }
1298
+ if (!fSuccess && this.hasFlag('NULLFAIL') && bufSig.length > 0) {
1299
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires failing signatures to be empty.`);
1300
+ }
1002
1301
  this.pushStack(fSuccess ? [1] : []);
1003
1302
  if (currentOpcode === OP_js_1.default.OP_CHECKSIGVERIFY) {
1004
1303
  if (!fSuccess)
@@ -1013,10 +1312,13 @@ class Spend {
1013
1312
  if (this.stack.length < i) {
1014
1313
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least 1 item for nKeys.`);
1015
1314
  }
1016
- const nKeysCountBN = BigNumber_js_1.default.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
1315
+ const nKeysCountBN = this.readScriptNumber(this.stackTop(-i));
1017
1316
  const nKeysCountBigInt = nKeysCountBN.toBigInt();
1018
- if (nKeysCountBigInt < 0n || nKeysCountBigInt > maxMultisigKeyCountBigInt) {
1019
- this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires a key count between 0 and ${maxMultisigKeyCount}.`);
1317
+ const multisigKeyLimitBigInt = this.hasExplicitFlags() && !this.isAfterGenesis()
1318
+ ? BigInt(maxMultisigKeyCountBeforeGenesis)
1319
+ : maxMultisigKeyCountBigInt;
1320
+ if (nKeysCountBigInt < 0n || nKeysCountBigInt > multisigKeyLimitBigInt) {
1321
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires a key count between 0 and ${multisigKeyLimitBigInt.toString()}.`);
1020
1322
  }
1021
1323
  nKeysCount = Number(nKeysCountBigInt);
1022
1324
  const declaredKeyCount = nKeysCount;
@@ -1025,7 +1327,7 @@ class Spend {
1025
1327
  if (this.stack.length < i) {
1026
1328
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);
1027
1329
  }
1028
- const nSigsCountBN = BigNumber_js_1.default.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
1330
+ const nSigsCountBN = this.readScriptNumber(this.stackTop(-i));
1029
1331
  const nSigsCountBigInt = nSigsCountBN.toBigInt();
1030
1332
  if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
1031
1333
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
@@ -1040,8 +1342,11 @@ class Spend {
1040
1342
  const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
1041
1343
  const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
1042
1344
  subscript = new Script_js_1.default(subscriptChunksCMS);
1345
+ let hasNonEmptySignature = false;
1043
1346
  for (let k = 0; k < nSigsCount; k++) {
1044
1347
  bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
1348
+ if (bufSig.length > 0)
1349
+ hasNonEmptySignature = true;
1045
1350
  subscript.findAndDelete(new Script_js_1.default().writeBin(bufSig));
1046
1351
  }
1047
1352
  fSuccess = true;
@@ -1058,7 +1363,7 @@ class Spend {
1058
1363
  fOk = false;
1059
1364
  if (bufSig.length > 0) {
1060
1365
  try {
1061
- sig = TransactionSignature_js_1.default.fromChecksigFormat(bufSig);
1366
+ sig = this.parseChecksigSignature(bufSig);
1062
1367
  pubkey = PublicKey_js_1.default.fromDER(bufPubkey);
1063
1368
  fOk = this.verifySignature(sig, pubkey, subscript);
1064
1369
  }
@@ -1076,6 +1381,9 @@ class Spend {
1076
1381
  fSuccess = false;
1077
1382
  }
1078
1383
  }
1384
+ if (!fSuccess && this.hasFlag('NULLFAIL') && hasNonEmptySignature) {
1385
+ this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires failing signatures to be empty.`);
1386
+ }
1079
1387
  // Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
1080
1388
  const itemsConsumedByOp = 1 + // N_val
1081
1389
  declaredKeyCount + // keys
@@ -1092,7 +1400,7 @@ class Spend {
1092
1400
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
1093
1401
  }
1094
1402
  const dummyBuf = this.popStack();
1095
- if (!this.isRelaxed() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
1403
+ if (this.shouldEnforceNullDummy() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
1096
1404
  this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
1097
1405
  }
1098
1406
  this.pushStack(fSuccess ? [1] : []);
@@ -1109,8 +1417,8 @@ class Spend {
1109
1417
  buf2 = this.popStack();
1110
1418
  buf1 = this.popStack();
1111
1419
  const catResult = (buf1).concat(buf2);
1112
- if (catResult.length > maxScriptElementSize)
1113
- this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
1420
+ if (catResult.length > this.maxPushSize())
1421
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes.`);
1114
1422
  this.pushStack(catResult);
1115
1423
  break;
1116
1424
  }
@@ -1119,7 +1427,7 @@ class Spend {
1119
1427
  this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
1120
1428
  const posBuf = this.popStack();
1121
1429
  const dataToSplit = this.popStack();
1122
- const splitIndexBigInt = BigNumber_js_1.default.fromScriptNum(posBuf, !this.isRelaxed()).toBigInt();
1430
+ const splitIndexBigInt = this.readScriptNumber(posBuf).toBigInt();
1123
1431
  if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
1124
1432
  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.');
1125
1433
  }
@@ -1131,9 +1439,9 @@ class Spend {
1131
1439
  case OP_js_1.default.OP_NUM2BIN: {
1132
1440
  if (this.stack.length < 2)
1133
1441
  this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
1134
- const sizeBigInt = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
1135
- if (sizeBigInt > BigInt(maxScriptElementSize) || sizeBigInt < 0n) { // size can be 0
1136
- this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`);
1442
+ const sizeBigInt = this.readScriptNumber(this.popStack()).toBigInt();
1443
+ if (sizeBigInt > BigInt(this.maxPushSize()) || sizeBigInt < 0n) { // size can be 0
1444
+ this.scriptEvaluationError(`It's not currently possible to push data larger than ${this.maxPushSize()} bytes or negative size.`);
1137
1445
  }
1138
1446
  size = Number(sizeBigInt);
1139
1447
  let rawnum = this.popStack(); // This is the number to convert
@@ -1177,7 +1485,17 @@ class Spend {
1177
1485
  this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
1178
1486
  }
1179
1487
  }
1180
- this.programCounter++;
1488
+ if (this.returningFromConditional && this.ifStack.length === 0) {
1489
+ this.programCounter = currentScript.chunks.length;
1490
+ }
1491
+ else {
1492
+ this.programCounter++;
1493
+ }
1494
+ if (this.hasExplicitFlags() &&
1495
+ !this.isAfterGenesis() &&
1496
+ this.stack.length + this.altStack.length > maxStackItemsBeforeGenesis) {
1497
+ this.scriptEvaluationError(`Stack item count has exceeded ${maxStackItemsBeforeGenesis}.`);
1498
+ }
1181
1499
  return true;
1182
1500
  }
1183
1501
  /**
@@ -1192,30 +1510,87 @@ class Spend {
1192
1510
  * }
1193
1511
  */
1194
1512
  validate() {
1195
- if (!this.isRelaxed() && !this.unlockingScript.isPushOnly()) {
1513
+ if (this.shouldEnforceSigPushOnly() && !this.unlockingScript.isPushOnly()) {
1196
1514
  this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
1197
1515
  }
1198
- while (this.step()) {
1199
- if (this.context === 'LockingScript' &&
1200
- this.programCounter >= this.lockingScript.chunks.length) {
1201
- break;
1516
+ const originalLockingScript = this.lockingScript;
1517
+ const shouldEvaluateP2SH = this.hasFlag('P2SH') &&
1518
+ !this.isAfterGenesis() &&
1519
+ this.isP2SHLockingScript(this.lockingScript);
1520
+ if (shouldEvaluateP2SH && !this.unlockingScript.isPushOnly()) {
1521
+ this.scriptEvaluationError('P2SH unlocking scripts can only contain push operations.');
1522
+ }
1523
+ this.reset();
1524
+ this.runScript('UnlockingScript');
1525
+ const stackAfterUnlockingScript = this.stack.map(item => item.slice());
1526
+ this.runScript('LockingScript');
1527
+ this.requireTruthyTopStack();
1528
+ try {
1529
+ if (shouldEvaluateP2SH) {
1530
+ if (stackAfterUnlockingScript.length === 0) {
1531
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
1532
+ }
1533
+ const redeemScriptBytes = stackAfterUnlockingScript.pop();
1534
+ if (redeemScriptBytes === undefined) {
1535
+ this.scriptEvaluationError('P2SH evaluation requires a redeem script on the stack.');
1536
+ return false;
1537
+ }
1538
+ this.setStack(stackAfterUnlockingScript);
1539
+ const redeemScript = Script_js_1.default.fromBinary(redeemScriptBytes);
1540
+ this.lockingScript = new LockingScript_js_1.default(redeemScript.chunks);
1541
+ this.runScript('LockingScript');
1202
1542
  }
1203
1543
  }
1544
+ finally {
1545
+ this.lockingScript = originalLockingScript;
1546
+ }
1547
+ if (this.shouldEnforceCleanStack() && this.stack.length !== 1) {
1548
+ this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
1549
+ }
1550
+ this.requireTruthyTopStack();
1551
+ return true;
1552
+ }
1553
+ runScript(context) {
1554
+ this.context = context;
1555
+ this.programCounter = 0;
1556
+ this.ifStack = [];
1557
+ this.elseStack = [];
1558
+ this.returningFromConditional = false;
1559
+ this.clearAltStack();
1560
+ this.lastCodeSeparator = null;
1561
+ const script = context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
1562
+ if (this.hasExplicitFlags() &&
1563
+ !this.isAfterGenesis() &&
1564
+ script.toUint8Array().length > maxScriptSizeBeforeGenesis) {
1565
+ this.scriptEvaluationError(`Script size exceeds ${maxScriptSizeBeforeGenesis} bytes.`);
1566
+ }
1567
+ while (this.programCounter < script.chunks.length) {
1568
+ this.step();
1569
+ }
1204
1570
  if (this.ifStack.length > 0) {
1205
1571
  this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
1206
1572
  }
1207
- if (!this.isRelaxed()) {
1208
- if (this.stack.length !== 1) {
1209
- this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
1210
- }
1211
- }
1573
+ this.ifStack = [];
1574
+ this.elseStack = [];
1575
+ this.clearAltStack();
1576
+ this.lastCodeSeparator = null;
1577
+ }
1578
+ isP2SHLockingScript(script) {
1579
+ const chunks = script.chunks;
1580
+ return chunks.length === 3 &&
1581
+ chunks[0].op === OP_js_1.default.OP_HASH160 &&
1582
+ chunks[1].op === 20 &&
1583
+ Array.isArray(chunks[1].data) &&
1584
+ chunks[1].data.length === 20 &&
1585
+ chunks[2].op === OP_js_1.default.OP_EQUAL;
1586
+ }
1587
+ requireTruthyTopStack() {
1212
1588
  if (this.stack.length === 0) {
1213
1589
  this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
1214
1590
  }
1215
1591
  else if (!this.castToBool(this.stackTop())) {
1216
1592
  this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
1217
1593
  }
1218
- return true;
1219
1594
  }
1220
1595
  castToBool(val) {
1221
1596
  if (val.length === 0)