"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __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 maxMultisigKeyCountBigInt = BigInt(maxMultisigKeyCount);
// --- 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 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.
 * @property {number} memoryLimit - Control over script interpreter memory usage.
 * @property {boolean} isRelaxed - Optional. If true, disables all the unlocking script maleability restrictions consitent with Chronicle release. Maleability restrictions are neve appliced to locking scripts.
 */
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.
     * @param {number} params.memoryLimit - Optional control over script interpreter memory usage.
     * @param {boolean} params.isRelaxed - Optional. If true, disables all the unlocking script maleability restrictions consitent with Chronicle release. Maleability restrictions are neve appliced to locking scripts.
     *
     * @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.isRelaxedOverride = params.isRelaxed === true;
        this.stack = [];
        this.altStack = [];
        this.ifStack = [];
        this.stackMem = 0;
        this.altStackMem = 0;
        this.sigHashCache = { hashOutputsSingle: new Map() };
        this.reset();
    }
    isRelaxed() {
        return this.isRelaxedOverride ||
            (this.transactionVersion > 1);
    }
    reset() {
        this.context = 'UnlockingScript';
        this.programCounter = 0;
        this.lastCodeSeparator = null;
        this.stack = [];
        this.altStack = [];
        this.ifStack = [];
        this.stackMem = 0;
        this.altStackMem = 0;
        this.sigHashCache = { hashOutputsSingle: new Map() };
    }
    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 (!this.isRelaxed() && !sig.hasLowS()) {
                this.scriptEvaluationError('The signature must have a low S value.');
                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.formatBytes({
            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,
            cache: this.sigHashCache
        });
        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 && currentOpcode >= 0 && currentOpcode <= OP_js_1.default.OP_PUSHDATA4) {
            if (!this.isRelaxed() && !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_VER:
                    this.pushStackCopy(new BigNumber_js_1.default(this.transactionVersion).toScriptNum());
                    break;
                case OP_js_1.default.OP_SUBSTR: {
                    if (this.stack.length < 3)
                        this.scriptEvaluationError('OP_SUBSTR requires at least three items to be on the stack.');
                    const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
                    const offset = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
                    buf = this.popStack();
                    const size = buf.length;
                    if (offset < 0 || offset >= size || len < 0 || len > size - offset) {
                        this.scriptEvaluationError(`OP_SUBSTR offset (${offset}) must be in range [0, ${size}) and length (${len}) must be in range [0, ${size - offset}]`);
                    }
                    this.pushStack(buf.slice(offset, offset + len));
                    break;
                }
                case OP_js_1.default.OP_LEFT: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_LEFT requires at least two items to be on the stack.');
                    const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
                    buf = this.popStack();
                    const size = buf.length;
                    if (len < 0 || len > size) {
                        this.scriptEvaluationError(`OP_LEFT length (${len}) must be in range [0, ${size}]`);
                    }
                    this.pushStack(buf.slice(0, len));
                    break;
                }
                case OP_js_1.default.OP_RIGHT: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_RIGHT requires at least two items to be on the stack.');
                    const len = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toNumber();
                    buf = this.popStack();
                    const size = buf.length;
                    if (len < 0 || len > size) {
                        this.scriptEvaluationError(`OP_RIGHT length (${len}) must be in range [0, ${size}]`);
                    }
                    this.pushStack(buf.slice(size - len, len));
                    break;
                }
                case OP_js_1.default.OP_LSHIFTNUM: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_LSHIFTNUM requires at least two items to be on the stack.');
                    const bits = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
                    if (bits < 0) {
                        this.scriptEvaluationError('OP_LSHIFTNUM bits to shift must not be negative.');
                    }
                    const value = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
                    const resultBn = new BigNumber_js_1.default(value << bits);
                    this.pushStack(resultBn.toScriptNum());
                    break;
                }
                case OP_js_1.default.OP_RSHIFTNUM: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_RSHIFTNUM requires at least two items to be on the stack.');
                    const bits = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
                    if (bits < 0) {
                        this.scriptEvaluationError('OP_RSHIFTNUM bits to shift must not be negative.');
                    }
                    const value = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
                    let resultBn;
                    if (value < 0) {
                        resultBn = new BigNumber_js_1.default(-(-value >> bits));
                    }
                    else {
                        resultBn = new BigNumber_js_1.default(value >> bits);
                    }
                    this.pushStack(resultBn.toScriptNum());
                    break;
                }
                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_NOP1:
                case OP_js_1.default.OP_NOP2:
                case OP_js_1.default.OP_NOP3:
                // case OP.OP_NOP4: // Allocated to OP_SUBSTR
                // case OP.OP_NOP5: // Allocated to OP_LEFT
                // case OP.OP_NOP6: // Allocated to OP_RIGHT
                // eslint-disable-next-line no-fallthrough
                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:
                // 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_VERIF:
                case OP_js_1.default.OP_VERNOTIF:
                    fValue = false;
                    if (isScriptExecuting) {
                        if (this.stack.length < 1)
                            this.scriptEvaluationError('OP_VERIF and OP_VERNOTIF require at least one item on the stack when they are used!');
                        buf2 = new BigNumber_js_1.default(this.transactionVersion).toScriptNum();
                        buf1 = this.popStack();
                        fValue = compareNumberArrays(buf1, buf2);
                        if (currentOpcode === OP_js_1.default.OP_VERNOTIF)
                            fValue = !fValue;
                    }
                    this.ifStack.push(fValue);
                    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(), !this.isRelaxed());
                    const nBigInt = bn.toBigInt();
                    if (nBigInt < 0n || nBigInt >= BigInt(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 nIndex = Number(nBigInt);
                    const itemToMoveOrCopy = this.stack[this.stack.length - 1 - nIndex];
                    if (currentOpcode === OP_js_1.default.OP_ROLL) {
                        this.stack.splice(this.stack.length - 1 - nIndex, 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(), !this.isRelaxed()); // n (shift amount)
                    buf1 = this.popStack(); // value to shift
                    const shiftBits = bn2.toBigInt();
                    if (shiftBits < 0n)
                        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(shiftBits);
                        // Truncate to original byte length by masking off the overflow MSBs
                        const mask = new BigNumber_js_1.default(1).ushln(buf1.length * 8).isubn(1);
                        shiftedBn = shiftedBn.iand(mask);
                    }
                    else {
                        shiftedBn = bn1.ushrn(shiftBits);
                    }
                    const shiftedArr = shiftedBn.toArray('be', 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_2MUL:
                case OP_js_1.default.OP_2DIV:
                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(), !this.isRelaxed());
                    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_2MUL:
                            bn = bn.mul(new BigNumber_js_1.default(2));
                            break;
                        case OP_js_1.default.OP_2DIV:
                            bn = bn.div(new BigNumber_js_1.default(2));
                            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, !this.isRelaxed());
                    bn1 = BigNumber_js_1.default.fromScriptNum(buf1, !this.isRelaxed());
                    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(), !this.isRelaxed()); // max
                    bn2 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // min
                    bn1 = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()); // 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
                    }
                    fSuccess = false;
                    if (bufSig.length > 0) {
                        try {
                            sig = TransactionSignature_js_1.default.fromChecksigFormat(bufSig);
                            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));
                            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.`);
                    }
                    const nKeysCountBN = BigNumber_js_1.default.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
                    const nKeysCountBigInt = nKeysCountBN.toBigInt();
                    if (nKeysCountBigInt < 0n || nKeysCountBigInt > maxMultisigKeyCountBigInt) {
                        this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires a key count between 0 and ${maxMultisigKeyCount}.`);
                    }
                    nKeysCount = Number(nKeysCountBigInt);
                    const declaredKeyCount = nKeysCount;
                    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}.`);
                    }
                    const nSigsCountBN = BigNumber_js_1.default.fromScriptNum(this.stackTop(-i), !this.isRelaxed());
                    const nSigsCountBigInt = nSigsCountBN.toBigInt();
                    if (nSigsCountBigInt < 0n || nSigsCountBigInt > BigInt(nKeysCount)) {
                        this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the number of signatures to be no greater than the number of keys.`);
                    }
                    nSigsCount = Number(nSigsCountBigInt);
                    const declaredSigCount = nSigsCount;
                    isig = ++i;
                    i += nSigsCount;
                    if (this.stack.length < i) {
                        this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} stack too small for N, keys, M, sigs, and dummy. Need ${i}, have ${this.stack.length}.`);
                    }
                    const baseScriptCMS = this.context === 'UnlockingScript' ? this.unlockingScript : this.lockingScript;
                    const subscriptChunksCMS = baseScriptCMS.chunks.slice(this.lastCodeSeparator === null ? 0 : this.lastCodeSeparator + 1);
                    subscript = new Script_js_1.default(subscriptChunksCMS);
                    for (let k = 0; k < nSigsCount; k++) {
                        bufSig = this.stackTop(-isig - k); // Sigs are closer to top than keys
                        subscript.findAndDelete(new Script_js_1.default().writeBin(bufSig));
                    }
                    fSuccess = true;
                    while (fSuccess && nSigsCount > 0) {
                        if (nKeysCount === 0) { // No more keys to check against but still sigs left
                            fSuccess = false;
                            break;
                        }
                        bufSig = this.stackTop(-isig);
                        bufPubkey = this.stackTop(-ikey);
                        if (!this.checkSignatureEncoding(bufSig) || !this.checkPublicKeyEncoding(bufPubkey)) {
                            this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires correct encoding for the public key and signature.`);
                        }
                        fOk = false;
                        if (bufSig.length > 0) {
                            try {
                                sig = TransactionSignature_js_1.default.fromChecksigFormat(bufSig);
                                pubkey = PublicKey_js_1.default.fromDER(bufPubkey);
                                fOk = this.verifySignature(sig, pubkey, subscript);
                            }
                            catch (e) {
                                fOk = false;
                            }
                        }
                        if (fOk) {
                            isig++;
                            nSigsCount--;
                        }
                        ikey++;
                        nKeysCount--;
                        if (nSigsCount > nKeysCount) {
                            fSuccess = false;
                        }
                    }
                    // Correct total items consumed by op (N_val, keys, M_val, sigs, dummy)
                    const itemsConsumedByOp = 1 + // N_val
                        declaredKeyCount + // keys
                        1 + // M_val
                        declaredSigCount + // sigs
                        1; // dummy
                    let popCount = itemsConsumedByOp - 1; // Pop all except dummy
                    while (popCount > 0) {
                        this.popStack();
                        popCount--;
                    }
                    // Check and pop dummy
                    if (this.stack.length < 1) {
                        this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires an extra item (dummy) to be on the stack.`);
                    }
                    const dummyBuf = this.popStack();
                    if (!this.isRelaxed() && dummyBuf.length > 0) { // SCRIPT_VERIFY_NULLDUMMY
                        this.scriptEvaluationError(`${OP_js_1.default[currentOpcode]} requires the extra stack item (dummy) to be empty.`);
                    }
                    this.pushStack(fSuccess ? [1] : []);
                    if (currentOpcode === OP_js_1.default.OP_CHECKMULTISIGVERIFY) {
                        if (!fSuccess)
                            this.scriptEvaluationError('OP_CHECKMULTISIGVERIFY requires that a sufficient number of valid signatures are provided.');
                        this.popStack();
                    }
                    break;
                }
                case OP_js_1.default.OP_CAT: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_CAT requires at least two items to be on the stack.');
                    buf2 = this.popStack();
                    buf1 = this.popStack();
                    const catResult = (buf1).concat(buf2);
                    if (catResult.length > maxScriptElementSize)
                        this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes.`);
                    this.pushStack(catResult);
                    break;
                }
                case OP_js_1.default.OP_SPLIT: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_SPLIT requires at least two items to be on the stack.');
                    const posBuf = this.popStack();
                    const dataToSplit = this.popStack();
                    const splitIndexBigInt = BigNumber_js_1.default.fromScriptNum(posBuf, !this.isRelaxed()).toBigInt();
                    if (splitIndexBigInt < 0n || splitIndexBigInt > BigInt(dataToSplit.length)) {
                        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.');
                    }
                    const splitIndex = Number(splitIndexBigInt);
                    this.pushStack(dataToSplit.slice(0, splitIndex));
                    this.pushStack(dataToSplit.slice(splitIndex));
                    break;
                }
                case OP_js_1.default.OP_NUM2BIN: {
                    if (this.stack.length < 2)
                        this.scriptEvaluationError('OP_NUM2BIN requires at least two items to be on the stack.');
                    const sizeBigInt = BigNumber_js_1.default.fromScriptNum(this.popStack(), !this.isRelaxed()).toBigInt();
                    if (sizeBigInt > BigInt(maxScriptElementSize) || sizeBigInt < 0n) { // size can be 0
                        this.scriptEvaluationError(`It's not currently possible to push data larger than ${maxScriptElementSize} bytes or negative size.`);
                    }
                    size = Number(sizeBigInt);
                    let rawnum = this.popStack(); // This is the number to convert
                    rawnum = (0, utils_js_1.minimallyEncode)(rawnum); // Get its minimal scriptnum form
                    if (rawnum.length > size) {
                        this.scriptEvaluationError('OP_NUM2BIN requires that the size expressed in the top stack item is large enough to hold the value expressed in the second-from-top stack item.');
                    }
                    if (rawnum.length === size) {
                        this.pushStack(rawnum);
                        break;
                    }
                    const resultN2B = new Array(size).fill(0x00);
                    let signbit = 0x00;
                    if (rawnum.length > 0) {
                        signbit = rawnum[rawnum.length - 1] & 0x80; // Store sign bit
                        rawnum[rawnum.length - 1] &= 0x7f; // Remove sign bit for padding
                    }
                    // Copy rawnum (now positive magnitude) into the result
                    for (let k = 0; k < rawnum.length; k++) {
                        resultN2B[k] = rawnum[k];
                    }
                    // If the original number was negative, the sign bit must be set on the new MSB
                    if (signbit !== 0) {
                        resultN2B[size - 1] |= 0x80;
                    }
                    this.pushStack(resultN2B);
                    break;
                }
                case OP_js_1.default.OP_BIN2NUM: {
                    if (this.stack.length < 1)
                        this.scriptEvaluationError('OP_BIN2NUM requires at least one item to be on the stack.');
                    buf1 = this.popStack();
                    const b2nResult = (0, utils_js_1.minimallyEncode)(buf1);
                    if (!isMinimallyEncodedHelper(b2nResult)) {
                        this.scriptEvaluationError('OP_BIN2NUM requires that the resulting number is valid.');
                    }
                    this.pushStack(b2nResult);
                    break;
                }
                default:
                    this.scriptEvaluationError(`Invalid opcode ${currentOpcode} (pc=${this.programCounter}).`);
            }
        }
        this.programCounter++;
        return true;
    }
    /**
     * @method validate
     * Validates the spend action by interpreting the locking and unlocking scripts.
     * @returns {boolean} Returns true if the scripts are valid and the spend is legitimate, otherwise false.
     * @example
     * if (spend.validate()) {
     *   console.log("Spend is valid!");
     * } else {
     *   console.log("Invalid spend!");
     * }
     */
    validate() {
        if (!this.isRelaxed() && !this.unlockingScript.isPushOnly()) {
            this.scriptEvaluationError('Unlocking scripts can only contain push operations, and no other opcodes.');
        }
        while (this.step()) {
            if (this.context === 'LockingScript' &&
                this.programCounter >= this.lockingScript.chunks.length) {
                break;
            }
        }
        if (this.ifStack.length > 0) {
            this.scriptEvaluationError('Every OP_IF, OP_NOTIF, or OP_ELSE must be terminated with OP_ENDIF prior to the end of the script.');
        }
        if (!this.isRelaxed()) {
            if (this.stack.length !== 1) {
                this.scriptEvaluationError(`The clean stack rule requires exactly one item to be on the stack after script execution, found ${this.stack.length}.`);
            }
        }
        if (this.stack.length === 0) {
            this.scriptEvaluationError('The top stack element must be truthy after script evaluation (stack is empty).');
        }
        else if (!this.castToBool(this.stackTop())) {
            this.scriptEvaluationError('The top stack element must be truthy after script evaluation.');
        }
        return true;
    }
    castToBool(val) {
        if (val.length === 0)
            return false;
        for (let i = 0; i < val.length; i++) {
            if (val[i] !== 0) {
                return !(i === val.length - 1 && val[i] === 0x80);
            }
        }
        return false;
    }
    scriptEvaluationError(str) {
        throw new ScriptEvaluationError_js_1.default({
            message: str,
            txid: this.sourceTXID,
            outputIndex: this.sourceOutputIndex,
            context: this.context,
            programCounter: this.programCounter,
            stackState: this.stack,
            altStackState: this.altStack,
            ifStackState: this.ifStack,
            stackMem: this.stackMem,
            altStackMem: this.altStackMem
        });
    }
}
exports.default = Spend;
//# sourceMappingURL=Spend.js.map