@bsv/sdk
Version:
BSV Blockchain Software Development Kit
971 lines (970 loc) • 61.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Script_js_1 = __importDefault(require("./Script.js"));
const BigNumber_js_1 = __importDefault(require("../primitives/BigNumber.js"));
const OP_js_1 = __importDefault(require("./OP.js"));
const utils_js_1 = require("../primitives/utils.js");
const ScriptEvaluationError_js_1 = __importDefault(require("./ScriptEvaluationError.js"));
const Hash = __importStar(require("../primitives/Hash.js"));
const TransactionSignature_js_1 = __importDefault(require("../primitives/TransactionSignature.js"));
const PublicKey_js_1 = __importDefault(require("../primitives/PublicKey.js"));
const ECDSA_js_1 = require("../primitives/ECDSA.js");
// These constants control the current behavior of the interpreter.
const maxScriptElementSize = 1024 * 1024 * 1024;
const maxMultisigKeyCount = Math.pow(2, 31) - 1;
const requireMinimalPush = true;
const requirePushOnlyUnlockingScripts = true;
const requireLowSSignatures = true;
const requireCleanStack = true;
// --- Optimization: Pre-computed script numbers ---
const SCRIPTNUM_NEG_1 = Object.freeze(new BigNumber_js_1.default(-1).toScriptNum());
const SCRIPTNUMS_0_TO_16 = Object.freeze(Array.from({ length: 17 }, (_, i) => Object.freeze(new BigNumber_js_1.default(i).toScriptNum())));
// --- Helper functions ---
function compareNumberArrays(a, b) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
function isMinimallyEncodedHelper(buf, maxNumSize = Number.MAX_SAFE_INTEGER) {
if (buf.length > maxNumSize) {
return false;
}
if (buf.length > 0) {
if ((buf[buf.length - 1] & 0x7f) === 0) {
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) {
return false;
}
}
}
return true;
}
function isChecksigFormatHelper(buf) {
// This is a simplified check. The full DER check is more complex and typically
// done by TransactionSignature.fromChecksigFormat which can throw.
// This helper is mostly for early bailout or non-throwing checks if needed.
if (buf.length < 9 || buf.length > 73)
return false;
if (buf[0] !== 0x30)
return false; // DER SEQUENCE
if (buf[1] !== buf.length - 3)
return false; // Total length (excluding type and length byte for sequence, and hash type)
const rMarker = buf[2];
const rLen = buf[3];
if (rMarker !== 0x02)
return false; // DER INTEGER
if (rLen === 0)
return false; // R length is zero
if (5 + rLen >= buf.length)
return false; // S length misplaced or R too long
const sMarkerOffset = 4 + rLen;
const sMarker = buf[sMarkerOffset];
const sLen = buf[sMarkerOffset + 1];
if (sMarker !== 0x02)
return false; // DER INTEGER
if (sLen === 0)
return false; // S length is zero
// Check R value negative or excessively padded
if ((buf[4] & 0x80) !== 0)
return false; // R value negative
if (rLen > 1 && buf[4] === 0x00 && (buf[5] & 0x80) === 0)
return false; // R value excessively padded
// Check S value negative or excessively padded
const sValueOffset = sMarkerOffset + 2;
if ((buf[sValueOffset] & 0x80) !== 0)
return false; // S value negative
if (sLen > 1 && buf[sValueOffset] === 0x00 && (buf[sValueOffset + 1] & 0x80) === 0)
return false; // S value excessively padded
if (rLen + sLen + 7 !== buf.length)
return false; // Final length check including hash type
return true;
}
function isOpcodeDisabledHelper(op) {
return (op === OP_js_1.default.OP_2MUL ||
op === OP_js_1.default.OP_2DIV ||
op === OP_js_1.default.OP_VERIF ||
op === OP_js_1.default.OP_VERNOTIF ||
op === OP_js_1.default.OP_VER);
}
function isChunkMinimalPushHelper(chunk) {
const data = chunk.data;
const op = chunk.op;
if (!Array.isArray(data))
return true;
if (data.length === 0)
return op === OP_js_1.default.OP_0;
if (data.length === 1 && data[0] >= 1 && data[0] <= 16)
return op === OP_js_1.default.OP_1 + (data[0] - 1);
if (data.length === 1 && data[0] === 0x81)
return op === OP_js_1.default.OP_1NEGATE;
if (data.length <= 75)
return op === data.length;
if (data.length <= 255)
return op === OP_js_1.default.OP_PUSHDATA1;
if (data.length <= 65535)
return op === OP_js_1.default.OP_PUSHDATA2;
return true;
}
/**
* The Spend class represents a spend action within a Bitcoin SV transaction.
* It encapsulates all the necessary data required for spending a UTXO (Unspent Transaction Output)
* and includes details about the source transaction, output, and the spending transaction itself.
*
* @property {string} sourceTXID - The transaction ID of the source UTXO.
* @property {number} sourceOutputIndex - The index of the output in the source transaction.
* @property {BigNumber} sourceSatoshis - The amount of satoshis in the source UTXO.
* @property {LockingScript} lockingScript - The locking script associated with the UTXO.
* @property {number} transactionVersion - The version of the current transaction.
* @property {Array<{ sourceTXID: string, sourceOutputIndex: number, sequence: number }>} otherInputs -
* An array of other inputs in the transaction, each with a txid, outputIndex, and sequence number.
* @property {Array<{ satoshis: BigNumber, lockingScript: LockingScript }>} outputs -
* An array of outputs of the current transaction, including the satoshi value and locking script for each.
* @property {number} inputIndex - The index of this input in the current transaction.
* @property {UnlockingScript} unlockingScript - The unlocking script that unlocks the UTXO for spending.
* @property {number} inputSequence - The sequence number of this input.
* @property {number} lockTime - The lock time of the transaction.
*/
class Spend {
/**
* @constructor
* Constructs the Spend object with necessary transaction details.
* @param {string} params.sourceTXID - The transaction ID of the source UTXO.
* @param {number} params.sourceOutputIndex - The index of the output in the source transaction.
* @param {BigNumber} params.sourceSatoshis - The amount of satoshis in the source UTXO.
* @param {LockingScript} params.lockingScript - The locking script associated with the UTXO.
* @param {number} params.transactionVersion - The version of the current transaction.
* @param {Array<{ sourceTXID: string, sourceOutputIndex: number, sequence: number }>} params.otherInputs -
* An array of other inputs in the transaction.
* @param {Array<{ satoshis: BigNumber, lockingScript: LockingScript }>} params.outputs -
* The outputs of the current transaction.
* @param {number} params.inputIndex - The index of this input in the current transaction.
* @param {UnlockingScript} params.unlockingScript - The unlocking script for this spend.
* @param {number} params.inputSequence - The sequence number of this input.
* @param {number} params.lockTime - The lock time of the transaction.
*
* @example
* const spend = new Spend({
* sourceTXID: "abcd1234", // sourceTXID
* sourceOutputIndex: 0, // sourceOutputIndex
* sourceSatoshis: new BigNumber(1000), // sourceSatoshis
* lockingScript: LockingScript.fromASM("OP_DUP OP_HASH160 abcd1234... OP_EQUALVERIFY OP_CHECKSIG"),
* transactionVersion: 1, // transactionVersion
* otherInputs: [{ sourceTXID: "abcd1234", sourceOutputIndex: 1, sequence: 0xffffffff }], // otherInputs
* outputs: [{ satoshis: new BigNumber(500), lockingScript: LockingScript.fromASM("OP_DUP...") }], // outputs
* inputIndex: 0, // inputIndex
* unlockingScript: UnlockingScript.fromASM("3045... 02ab..."),
* inputSequence: 0xffffffff // inputSequence
* memoryLimit: 100000 // memoryLimit
* });
*/
constructor(params) {
this.sourceTXID = params.sourceTXID;
this.sourceOutputIndex = params.sourceOutputIndex;
this.sourceSatoshis = params.sourceSatoshis;
this.lockingScript = params.lockingScript;
this.transactionVersion = params.transactionVersion;
this.otherInputs = params.otherInputs;
this.outputs = params.outputs;
this.inputIndex = params.inputIndex;
this.unlockingScript = params.unlockingScript;
this.inputSequence = params.inputSequence;
this.lockTime = params.lockTime;
this.memoryLimit = params.memoryLimit ?? 32000000;
this.stack = [];
this.altStack = [];
this.ifStack = [];
this.stackMem = 0;
this.altStackMem = 0;
this.reset();
}
reset() {
this.context = 'UnlockingScript';
this.programCounter = 0;
this.lastCodeSeparator = null;
this.stack = [];
this.altStack = [];
this.ifStack = [];
this.stackMem = 0;
this.altStackMem = 0;
}
ensureStackMem(additional) {
if (this.stackMem + additional > this.memoryLimit) {
this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
}
}
ensureAltStackMem(additional) {
if (this.altStackMem + additional > this.memoryLimit) {
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
}
}
pushStack(item) {
this.ensureStackMem(item.length);
this.stack.push(item);
this.stackMem += item.length;
}
pushStackCopy(item) {
this.ensureStackMem(item.length);
const copy = item.slice();
this.stack.push(copy);
this.stackMem += copy.length;
}
popStack() {
if (this.stack.length === 0) {
this.scriptEvaluationError('Attempted to pop from an empty stack.');
}
const item = this.stack.pop();
this.stackMem -= item.length;
return item;
}
stackTop(index = -1) {
// index = -1 for top, -2 for second top, etc.
// stack.length + index provides 0-based index from start
if (this.stack.length === 0 || this.stack.length < Math.abs(index) || (index >= 0 && index >= this.stack.length)) {
this.scriptEvaluationError(`Stack underflow accessing element at index ${index}. Stack length is ${this.stack.length}.`);
}
return this.stack[this.stack.length + index];
}
pushAltStack(item) {
this.ensureAltStackMem(item.length);
this.altStack.push(item);
this.altStackMem += item.length;
}
popAltStack() {
if (this.altStack.length === 0) {
this.scriptEvaluationError('Attempted to pop from an empty alt stack.');
}
const item = this.altStack.pop();
this.altStackMem -= item.length;
return item;
}
checkSignatureEncoding(buf) {
if (buf.length === 0)
return true;
if (!isChecksigFormatHelper(buf)) {
this.scriptEvaluationError('The signature format is invalid.'); // Generic message like original
return false;
}
try {
const sig = TransactionSignature_js_1.default.fromChecksigFormat(buf); // This can throw for stricter DER rules
if (requireLowSSignatures && !sig.hasLowS()) {
this.scriptEvaluationError('The signature must have a low S value.');
return false;
}
if ((sig.scope & TransactionSignature_js_1.default.SIGHASH_FORKID) === 0) {
this.scriptEvaluationError('The signature must use SIGHASH_FORKID.');
return false;
}
}
catch (e) {
this.scriptEvaluationError('The signature format is invalid.');
return false;
}
return true;
}
checkPublicKeyEncoding(buf) {
if (buf.length === 0) {
this.scriptEvaluationError('Public key is empty.');
return false;
}
if (buf.length < 33) {
this.scriptEvaluationError('The public key is too short, it must be at least 33 bytes.');
return false;
}
if (buf[0] === 0x04) {
if (buf.length !== 65) {
this.scriptEvaluationError('The non-compressed public key must be 65 bytes.');
return false;
}
}
else if (buf[0] === 0x02 || buf[0] === 0x03) {
if (buf.length !== 33) {
this.scriptEvaluationError('The compressed public key must be 33 bytes.');
return false;
}
}
else {
this.scriptEvaluationError('The public key is in an unknown format.');
return false;
}
try {
PublicKey_js_1.default.fromDER(buf); // This can throw for stricter DER rules
}
catch (e) {
this.scriptEvaluationError('The public key is in an unknown format.');
return false;
}
return true;
}
verifySignature(sig, pubkey, subscript) {
const preimage = TransactionSignature_js_1.default.format({
sourceTXID: this.sourceTXID,
sourceOutputIndex: this.sourceOutputIndex,
sourceSatoshis: this.sourceSatoshis,
transactionVersion: this.transactionVersion,
otherInputs: this.otherInputs,
outputs: this.outputs,
inputIndex: this.inputIndex,
subscript,
inputSequence: this.inputSequence,
lockTime: this.lockTime,
scope: sig.scope
});
const hash = new BigNumber_js_1.default(Hash.hash256(preimage));
return (0, ECDSA_js_1.verify)(hash, sig, pubkey);
}
step() {
if (this.stackMem > this.memoryLimit) {
this.scriptEvaluationError('Stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
return false; // Error thrown
}
if (this.altStackMem > this.memoryLimit) {
this.scriptEvaluationError('Alt stack memory usage has exceeded ' + String(this.memoryLimit) + ' bytes');
return false; // Error thrown
}
if (this.context === 'UnlockingScript' &&
this.programCounter >= this.unlockingScript.chunks.length) {
this.context = 'LockingScript';
this.programCounter = 0;
}
const currentScript = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
if (this.programCounter >= currentScript.chunks.length) {
return false;
}
const operation = currentScript.chunks[this.programCounter];
const currentOpcode = operation.op;
if (typeof currentOpcode === 'undefined') {
this.scriptEvaluationError(`Missing opcode in ${this.context} at pc=${this.programCounter}.`); // Error thrown
}
if (Array.isArray(operation.data) && operation.data.length > maxScriptElementSize) {
this.scriptEvaluationError(`Data push > ${maxScriptElementSize} bytes (pc=${this.programCounter}).`); // Error thrown
}
const isScriptExecuting = !this.ifStack.includes(false);
if (isScriptExecuting && isOpcodeDisabledHelper(currentOpcode)) {
this.scriptEvaluationError(`This opcode is currently disabled. (Opcode: ${OP_js_1.default[currentOpcode]}, PC: ${this.programCounter})`); // Error thrown
}
if (isScriptExecuting && currentOpcode >= 0 && currentOpcode <= OP_js_1.default.OP_PUSHDATA4) {
if (requireMinimalPush && !isChunkMinimalPushHelper(operation)) {
this.scriptEvaluationError(`This data is not minimally-encoded. (PC: ${this.programCounter})`); // Error thrown
}
this.pushStack(Array.isArray(operation.data) ? operation.data : []);
}
else if (isScriptExecuting || (currentOpcode >= OP_js_1.default.OP_IF && currentOpcode <= OP_js_1.default.OP_ENDIF)) {
let buf, buf1, buf2, buf3;
let x1, x2, x3;
let bn, bn1, bn2, bn3;
let n, size, fValue, fSuccess, subscript;
let bufSig, bufPubkey;
let sig, pubkey;
let i, ikey, isig, nKeysCount, nSigsCount, fOk;
switch (currentOpcode) {
case OP_js_1.default.OP_1NEGATE:
this.pushStackCopy(SCRIPTNUM_NEG_1);
break;
case OP_js_1.default.OP_0:
this.pushStackCopy(SCRIPTNUMS_0_TO_16[0]);
break;
case OP_js_1.default.OP_1:
case OP_js_1.default.OP_2:
case OP_js_1.default.OP_3:
case OP_js_1.default.OP_4:
case OP_js_1.default.OP_5:
case OP_js_1.default.OP_6:
case OP_js_1.default.OP_7:
case OP_js_1.default.OP_8:
case OP_js_1.default.OP_9:
case OP_js_1.default.OP_10:
case OP_js_1.default.OP_11:
case OP_js_1.default.OP_12:
case OP_js_1.default.OP_13:
case OP_js_1.default.OP_14:
case OP_js_1.default.OP_15:
case OP_js_1.default.OP_16:
n = currentOpcode - (OP_js_1.default.OP_1 - 1);
this.pushStackCopy(SCRIPTNUMS_0_TO_16[n]);
break;
case OP_js_1.default.OP_NOP:
case OP_js_1.default.OP_NOP2: // Formerly CHECKLOCKTIMEVERIFY
case OP_js_1.default.OP_NOP3: // Formerly CHECKSEQUENCEVERIFY
case OP_js_1.default.OP_NOP1:
case OP_js_1.default.OP_NOP4:
case OP_js_1.default.OP_NOP5:
case OP_js_1.default.OP_NOP6:
case OP_js_1.default.OP_NOP7:
case OP_js_1.default.OP_NOP8:
case OP_js_1.default.OP_NOP9:
case OP_js_1.default.OP_NOP10:
/* falls through */
// eslint-disable-next-line no-fallthrough
// eslint-disable-next-line no-fallthrough
case OP_js_1.default.OP_NOP11:
case OP_js_1.default.OP_NOP12:
case OP_js_1.default.OP_NOP13:
case OP_js_1.default.OP_NOP14:
case OP_js_1.default.OP_NOP15:
case OP_js_1.default.OP_NOP16:
case OP_js_1.default.OP_NOP17:
case OP_js_1.default.OP_NOP18:
case OP_js_1.default.OP_NOP19:
case OP_js_1.default.OP_NOP20:
case OP_js_1.default.OP_NOP21:
case OP_js_1.default.OP_NOP22:
case OP_js_1.default.OP_NOP23:
case OP_js_1.default.OP_NOP24:
case OP_js_1.default.OP_NOP25:
case OP_js_1.default.OP_NOP26:
case OP_js_1.default.OP_NOP27:
case OP_js_1.default.OP_NOP28:
case OP_js_1.default.OP_NOP29:
case OP_js_1.default.OP_NOP30:
case OP_js_1.default.OP_NOP31:
case OP_js_1.default.OP_NOP32:
case OP_js_1.default.OP_NOP33:
case OP_js_1.default.OP_NOP34:
case OP_js_1.default.OP_NOP35:
case OP_js_1.default.OP_NOP36:
case OP_js_1.default.OP_NOP37:
case OP_js_1.default.OP_NOP38:
case OP_js_1.default.OP_NOP39:
case OP_js_1.default.OP_NOP40:
case OP_js_1.default.OP_NOP41:
case OP_js_1.default.OP_NOP42:
case OP_js_1.default.OP_NOP43:
case OP_js_1.default.OP_NOP44:
case OP_js_1.default.OP_NOP45:
case OP_js_1.default.OP_NOP46:
case OP_js_1.default.OP_NOP47:
case OP_js_1.default.OP_NOP48:
case OP_js_1.default.OP_NOP49:
case OP_js_1.default.OP_NOP50:
case OP_js_1.default.OP_NOP51:
case OP_js_1.default.OP_NOP52:
case OP_js_1.default.OP_NOP53:
case OP_js_1.default.OP_NOP54:
case OP_js_1.default.OP_NOP55:
case OP_js_1.default.OP_NOP56:
case OP_js_1.default.OP_NOP57:
case OP_js_1.default.OP_NOP58:
case OP_js_1.default.OP_NOP59:
case OP_js_1.default.OP_NOP60:
case OP_js_1.default.OP_NOP61:
case OP_js_1.default.OP_NOP62:
case OP_js_1.default.OP_NOP63:
case OP_js_1.default.OP_NOP64:
case OP_js_1.default.OP_NOP65:
case OP_js_1.default.OP_NOP66:
case OP_js_1.default.OP_NOP67:
case OP_js_1.default.OP_NOP68:
case OP_js_1.default.OP_NOP69:
case OP_js_1.default.OP_NOP70:
case OP_js_1.default.OP_NOP71:
case OP_js_1.default.OP_NOP72:
case OP_js_1.default.OP_NOP73:
case OP_js_1.default.OP_NOP77:
break;
case OP_js_1.default.OP_IF:
case OP_js_1.default.OP_NOTIF:
fValue = false;
if (isScriptExecuting) {
if (this.stack.length < 1)
this.scriptEvaluationError('OP_IF and OP_NOTIF require at least one item on the stack when they are used!');
buf = this.popStack();
fValue = this.castToBool(buf);
if (currentOpcode === OP_js_1.default.OP_NOTIF)
fValue = !fValue;
}
this.ifStack.push(fValue);
break;
case OP_js_1.default.OP_ELSE:
if (this.ifStack.length === 0)
this.scriptEvaluationError('OP_ELSE requires a preceeding OP_IF.');
this.ifStack[this.ifStack.length - 1] = !this.ifStack[this.ifStack.length - 1];
break;
case OP_js_1.default.OP_ENDIF:
if (this.ifStack.length === 0)
this.scriptEvaluationError('OP_ENDIF requires a preceeding OP_IF.');
this.ifStack.pop();
break;
case OP_js_1.default.OP_VERIFY:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_VERIFY requires at least one item to be on the stack.');
buf1 = this.stackTop();
fValue = this.castToBool(buf1);
if (!fValue)
this.scriptEvaluationError('OP_VERIFY requires the top stack value to be truthy.');
this.popStack();
break;
case OP_js_1.default.OP_RETURN:
if (this.context === 'UnlockingScript')
this.programCounter = this.unlockingScript.chunks.length;
else
this.programCounter = this.lockingScript.chunks.length;
this.ifStack = [];
this.programCounter--; // To counteract the final increment and ensure loop termination
break;
case OP_js_1.default.OP_TOALTSTACK:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_TOALTSTACK requires at oeast one item to be on the stack.');
this.pushAltStack(this.popStack());
break;
case OP_js_1.default.OP_FROMALTSTACK:
if (this.altStack.length < 1)
this.scriptEvaluationError('OP_FROMALTSTACK requires at least one item to be on the stack.'); // "stack" here means altstack
this.pushStack(this.popAltStack());
break;
case OP_js_1.default.OP_2DROP:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_2DROP requires at least two items to be on the stack.');
this.popStack();
this.popStack();
break;
case OP_js_1.default.OP_2DUP:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_2DUP requires at least two items to be on the stack.');
buf1 = this.stackTop(-2);
buf2 = this.stackTop(-1);
this.pushStackCopy(buf1);
this.pushStackCopy(buf2);
break;
case OP_js_1.default.OP_3DUP:
if (this.stack.length < 3)
this.scriptEvaluationError('OP_3DUP requires at least three items to be on the stack.');
buf1 = this.stackTop(-3);
buf2 = this.stackTop(-2);
buf3 = this.stackTop(-1);
this.pushStackCopy(buf1);
this.pushStackCopy(buf2);
this.pushStackCopy(buf3);
break;
case OP_js_1.default.OP_2OVER:
if (this.stack.length < 4)
this.scriptEvaluationError('OP_2OVER requires at least four items to be on the stack.');
buf1 = this.stackTop(-4);
buf2 = this.stackTop(-3);
this.pushStackCopy(buf1);
this.pushStackCopy(buf2);
break;
case OP_js_1.default.OP_2ROT: {
if (this.stack.length < 6)
this.scriptEvaluationError('OP_2ROT requires at least six items to be on the stack.');
const rot6 = this.popStack();
const rot5 = this.popStack();
const rot4 = this.popStack();
const rot3 = this.popStack();
const rot2 = this.popStack();
const rot1 = this.popStack();
this.pushStack(rot3);
this.pushStack(rot4);
this.pushStack(rot5);
this.pushStack(rot6);
this.pushStack(rot1);
this.pushStack(rot2);
break;
}
case OP_js_1.default.OP_2SWAP: {
if (this.stack.length < 4)
this.scriptEvaluationError('OP_2SWAP requires at least four items to be on the stack.');
const swap4 = this.popStack();
const swap3 = this.popStack();
const swap2 = this.popStack();
const swap1 = this.popStack();
this.pushStack(swap3);
this.pushStack(swap4);
this.pushStack(swap1);
this.pushStack(swap2);
break;
}
case OP_js_1.default.OP_IFDUP:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_IFDUP requires at least one item to be on the stack.');
buf1 = this.stackTop();
if (this.castToBool(buf1)) {
this.pushStackCopy(buf1);
}
break;
case OP_js_1.default.OP_DEPTH:
this.pushStack(new BigNumber_js_1.default(this.stack.length).toScriptNum());
break;
case OP_js_1.default.OP_DROP:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_DROP requires at least one item to be on the stack.');
this.popStack();
break;
case OP_js_1.default.OP_DUP:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_DUP requires at least one item to be on the stack.');
this.pushStackCopy(this.stackTop());
break;
case OP_js_1.default.OP_NIP:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_NIP requires at least two items to be on the stack.');
buf2 = this.popStack();
this.popStack();
this.pushStack(buf2);
break;
case OP_js_1.default.OP_OVER:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_OVER requires at least two items to be on the stack.');
this.pushStackCopy(this.stackTop(-2));
break;
case OP_js_1.default.OP_PICK:
case OP_js_1.default.OP_ROLL: {
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
bn = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush);
n = bn.toNumber();
if (n < 0 || n >= this.stack.length) {
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.`);
}
const itemToMoveOrCopy = this.stack[this.stack.length - 1 - n];
if (currentOpcode === OP_js_1.default.OP_ROLL) {
this.stack.splice(this.stack.length - 1 - n, 1);
this.stackMem -= itemToMoveOrCopy.length;
this.pushStack(itemToMoveOrCopy);
}
else { // OP_PICK
this.pushStackCopy(itemToMoveOrCopy);
}
break;
}
case OP_js_1.default.OP_ROT:
if (this.stack.length < 3)
this.scriptEvaluationError('OP_ROT requires at least three items to be on the stack.');
x3 = this.popStack();
x2 = this.popStack();
x1 = this.popStack();
this.pushStack(x2);
this.pushStack(x3);
this.pushStack(x1);
break;
case OP_js_1.default.OP_SWAP:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_SWAP requires at least two items to be on the stack.');
x2 = this.popStack();
x1 = this.popStack();
this.pushStack(x2);
this.pushStack(x1);
break;
case OP_js_1.default.OP_TUCK:
if (this.stack.length < 2)
this.scriptEvaluationError('OP_TUCK requires at least two items to be on the stack.');
buf1 = this.stackTop(-1); // Top element (x2)
// stack is [... rest, x1, x2]
// We want [... rest, x2_copy, x1, x2]
this.ensureStackMem(buf1.length);
this.stack.splice(this.stack.length - 2, 0, buf1.slice()); // Insert copy of x2 before x1
this.stackMem += buf1.length; // Account for the new copy
break;
case OP_js_1.default.OP_SIZE:
if (this.stack.length < 1)
this.scriptEvaluationError('OP_SIZE requires at least one item to be on the stack.');
this.pushStack(new BigNumber_js_1.default(this.stackTop().length).toScriptNum());
break;
case OP_js_1.default.OP_AND:
case OP_js_1.default.OP_OR:
case OP_js_1.default.OP_XOR: {
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items on the stack.`);
buf2 = this.popStack();
buf1 = this.popStack();
if (buf1.length !== buf2.length)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the top two stack items to be the same size.`);
const resultBufBitwiseOp = new Array(buf1.length);
for (let k = 0; k < buf1.length; k++) {
if (currentOpcode === OP_js_1.default.OP_AND)
resultBufBitwiseOp[k] = buf1[k] & buf2[k];
else if (currentOpcode === OP_js_1.default.OP_OR)
resultBufBitwiseOp[k] = buf1[k] | buf2[k];
else
resultBufBitwiseOp[k] = buf1[k] ^ buf2[k];
}
this.pushStack(resultBufBitwiseOp);
break;
}
case OP_js_1.default.OP_INVERT: {
if (this.stack.length < 1)
this.scriptEvaluationError('OP_INVERT requires at least one item to be on the stack.');
buf = this.popStack();
const invertedBufOp = new Array(buf.length);
for (let k = 0; k < buf.length; k++) {
invertedBufOp[k] = (~buf[k]) & 0xff;
}
this.pushStack(invertedBufOp);
break;
}
case OP_js_1.default.OP_LSHIFT:
case OP_js_1.default.OP_RSHIFT: {
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
bn2 = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush); // n (shift amount)
buf1 = this.popStack(); // value to shift
n = bn2.toNumber();
if (n < 0)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the top item on the stack not to be negative.`);
if (buf1.length === 0) {
this.pushStack([]);
break;
}
bn1 = new BigNumber_js_1.default(buf1);
let shiftedBn;
if (currentOpcode === OP_js_1.default.OP_LSHIFT)
shiftedBn = bn1.ushln(n);
else
shiftedBn = bn1.ushrn(n);
const shiftedArr = shiftedBn.toArray('le', buf1.length);
this.pushStack(shiftedArr);
break;
}
case OP_js_1.default.OP_EQUAL:
case OP_js_1.default.OP_EQUALVERIFY:
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
buf2 = this.popStack();
buf1 = this.popStack();
fValue = compareNumberArrays(buf1, buf2);
this.pushStack(fValue ? [1] : []);
if (currentOpcode === OP_js_1.default.OP_EQUALVERIFY) {
if (!fValue)
this.scriptEvaluationError('OP_EQUALVERIFY requires the top two stack items to be equal.');
this.popStack();
}
break;
case OP_js_1.default.OP_1ADD:
case OP_js_1.default.OP_1SUB:
case OP_js_1.default.OP_NEGATE:
case OP_js_1.default.OP_ABS:
case OP_js_1.default.OP_NOT:
case OP_js_1.default.OP_0NOTEQUAL:
if (this.stack.length < 1)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least one item to be on the stack.`);
bn = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush);
switch (currentOpcode) {
case OP_js_1.default.OP_1ADD:
bn = bn.add(new BigNumber_js_1.default(1));
break;
case OP_js_1.default.OP_1SUB:
bn = bn.sub(new BigNumber_js_1.default(1));
break;
case OP_js_1.default.OP_NEGATE:
bn = bn.neg();
break;
case OP_js_1.default.OP_ABS:
if (bn.isNeg())
bn = bn.neg();
break;
case OP_js_1.default.OP_NOT:
bn = new BigNumber_js_1.default(bn.cmpn(0) === 0 ? 1 : 0);
break;
case OP_js_1.default.OP_0NOTEQUAL:
bn = new BigNumber_js_1.default(bn.cmpn(0) !== 0 ? 1 : 0);
break;
}
this.pushStack(bn.toScriptNum());
break;
case OP_js_1.default.OP_ADD:
case OP_js_1.default.OP_SUB:
case OP_js_1.default.OP_MUL:
case OP_js_1.default.OP_DIV:
case OP_js_1.default.OP_MOD:
case OP_js_1.default.OP_BOOLAND:
case OP_js_1.default.OP_BOOLOR:
case OP_js_1.default.OP_NUMEQUAL:
case OP_js_1.default.OP_NUMEQUALVERIFY:
case OP_js_1.default.OP_NUMNOTEQUAL:
case OP_js_1.default.OP_LESSTHAN:
case OP_js_1.default.OP_GREATERTHAN:
case OP_js_1.default.OP_LESSTHANOREQUAL:
case OP_js_1.default.OP_GREATERTHANOREQUAL:
case OP_js_1.default.OP_MIN:
case OP_js_1.default.OP_MAX: {
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
buf2 = this.popStack();
buf1 = this.popStack();
bn2 = BigNumber_js_1.default.fromScriptNum(buf2, requireMinimalPush);
bn1 = BigNumber_js_1.default.fromScriptNum(buf1, requireMinimalPush);
let predictedLen = 0;
switch (currentOpcode) {
case OP_js_1.default.OP_MUL:
predictedLen = bn1.byteLength() + bn2.byteLength();
break;
case OP_js_1.default.OP_ADD:
case OP_js_1.default.OP_SUB:
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength()) + 1;
break;
default:
predictedLen = Math.max(bn1.byteLength(), bn2.byteLength());
}
this.ensureStackMem(predictedLen);
let resultBnArithmetic = new BigNumber_js_1.default(0);
switch (currentOpcode) {
case OP_js_1.default.OP_ADD:
resultBnArithmetic = bn1.add(bn2);
break;
case OP_js_1.default.OP_SUB:
resultBnArithmetic = bn1.sub(bn2);
break;
case OP_js_1.default.OP_MUL:
resultBnArithmetic = bn1.mul(bn2);
break;
case OP_js_1.default.OP_DIV:
if (bn2.cmpn(0) === 0)
this.scriptEvaluationError('OP_DIV cannot divide by zero!');
resultBnArithmetic = bn1.div(bn2);
break;
case OP_js_1.default.OP_MOD:
if (bn2.cmpn(0) === 0)
this.scriptEvaluationError('OP_MOD cannot divide by zero!');
resultBnArithmetic = bn1.mod(bn2);
break;
case OP_js_1.default.OP_BOOLAND:
resultBnArithmetic = new BigNumber_js_1.default((bn1.cmpn(0) !== 0 && bn2.cmpn(0) !== 0) ? 1 : 0);
break;
case OP_js_1.default.OP_BOOLOR:
resultBnArithmetic = new BigNumber_js_1.default((bn1.cmpn(0) !== 0 || bn2.cmpn(0) !== 0) ? 1 : 0);
break;
case OP_js_1.default.OP_NUMEQUAL:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) === 0 ? 1 : 0);
break;
case OP_js_1.default.OP_NUMEQUALVERIFY:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) === 0 ? 1 : 0);
break;
case OP_js_1.default.OP_NUMNOTEQUAL:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) !== 0 ? 1 : 0);
break;
case OP_js_1.default.OP_LESSTHAN:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) < 0 ? 1 : 0);
break;
case OP_js_1.default.OP_GREATERTHAN:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) > 0 ? 1 : 0);
break;
case OP_js_1.default.OP_LESSTHANOREQUAL:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) <= 0 ? 1 : 0);
break;
case OP_js_1.default.OP_GREATERTHANOREQUAL:
resultBnArithmetic = new BigNumber_js_1.default(bn1.cmp(bn2) >= 0 ? 1 : 0);
break;
case OP_js_1.default.OP_MIN:
resultBnArithmetic = bn1.cmp(bn2) < 0 ? bn1 : bn2;
break;
case OP_js_1.default.OP_MAX:
resultBnArithmetic = bn1.cmp(bn2) > 0 ? bn1 : bn2;
break;
}
this.pushStack(resultBnArithmetic.toScriptNum());
if (currentOpcode === OP_js_1.default.OP_NUMEQUALVERIFY) {
if (!this.castToBool(this.stackTop()))
this.scriptEvaluationError('OP_NUMEQUALVERIFY requires the top stack item to be truthy.');
this.popStack();
}
break;
}
case OP_js_1.default.OP_WITHIN:
if (this.stack.length < 3)
this.scriptEvaluationError('OP_WITHIN requires at least three items to be on the stack.');
bn3 = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush); // max
bn2 = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush); // min
bn1 = BigNumber_js_1.default.fromScriptNum(this.popStack(), requireMinimalPush); // x
fValue = bn1.cmp(bn2) >= 0 && bn1.cmp(bn3) < 0;
this.pushStack(fValue ? [1] : []);
break;
case OP_js_1.default.OP_RIPEMD160:
case OP_js_1.default.OP_SHA1:
case OP_js_1.default.OP_SHA256:
case OP_js_1.default.OP_HASH160:
case OP_js_1.default.OP_HASH256: {
if (this.stack.length < 1)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least one item to be on the stack.`);
buf = this.popStack();
let hashResult = []; // Initialize to empty, to satisfy TS compiler
if (currentOpcode === OP_js_1.default.OP_RIPEMD160)
hashResult = Hash.ripemd160(buf);
else if (currentOpcode === OP_js_1.default.OP_SHA1)
hashResult = Hash.sha1(buf);
else if (currentOpcode === OP_js_1.default.OP_SHA256)
hashResult = Hash.sha256(buf);
else if (currentOpcode === OP_js_1.default.OP_HASH160)
hashResult = Hash.hash160(buf);
else if (currentOpcode === OP_js_1.default.OP_HASH256)
hashResult = Hash.hash256(buf);
this.pushStack(hashResult);
break;
}
case OP_js_1.default.OP_CODESEPARATOR:
this.lastCodeSeparator = this.programCounter;
break;
case OP_js_1.default.OP_CHECKSIG:
case OP_js_1.default.OP_CHECKSIGVERIFY: {
if (this.stack.length < 2)
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least two items to be on the stack.`);
bufPubkey = this.popStack();
bufSig = this.popStack();
if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
// Error already thrown by helpers
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires correct encoding for the public key and signature.`); // Fallback, should be unreachable
}
const scriptForChecksig = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
const scriptCodeChunks = scriptForChecksig.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
subscript = new Script_js_1.default(scriptCodeChunks);
subscript.findAndDelete(new Script_js_1.default().writeBin(bufSig));
fSuccess = false;
if (bufSig.length > 0) {
try {
sig = TransactionSignature_js_1.default.fromChecksigFormat(bufSig);
pubkey = PublicKey_js_1.default.fromDER(bufPubkey);
fSuccess = this.verifySignature(sig, pubkey, subscript);
}
catch (e) {
fSuccess = false;
}
}
this.pushStack(fSuccess ? [1] : []);
if (currentOpcode === OP_js_1.default.OP_CHECKSIGVERIFY) {
if (!fSuccess)
this.scriptEvaluationError('OP_CHECKSIGVERIFY requires that a valid signature is provided.');
this.popStack();
}
break;
}
case OP_js_1.default.OP_CHECKMULTISIG:
case OP_js_1.default.OP_CHECKMULTISIGVERIFY: {
i = 1;
if (this.stack.length < i) {
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires at least 1 item for nKeys.`);
}
nKeysCount = BigNumber_js_1.default.fromScriptNum(this.stackTop(-i), requireMinimalPush).toNumber();
if (nKeysCount < 0 || nKeysCount > maxMultisigKeyCount) {
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires a key count between 0 and ${maxMultisigKeyCount}.`);
}
ikey = ++i;
i += nKeysCount;
if (this.stack.length < i) {
this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} stack too small for nKeys and keys. Need ${i}, have ${this.stack.length}.`);