UNPKG

bitcore-lib

Version:

A pure and powerful JavaScript Bitcoin library.

1,455 lines (1,263 loc) 70.5 kB
'use strict'; const _ = require('lodash'); const Script = require('./script'); const Opcode = require('../opcode'); const BN = require('../crypto/bn'); const Hash = require('../crypto/hash'); const Signature = require('../crypto/signature'); const PublicKey = require('../publickey'); const $ = require('../util/preconditions'); const SighashWitness = require('../transaction/sighashwitness'); const SighashSchnorr = require('../transaction/sighashschnorr'); const BufferWriter = require('../encoding/bufferwriter'); const TaggedHash = require('../crypto/taggedhash'); /** * Bitcoin transactions contain scripts. Each input has a script called the * scriptSig, and each output has a script called the scriptPubkey. To validate * an input, the input's script is concatenated with the referenced output script, * and the result is executed. If at the end of execution the stack contains a * "true" value, then the transaction is valid. * * The primary way to use this class is via the verify function. * e.g., Interpreter().verify( ... ); */ var Interpreter = function Interpreter(obj) { if (!(this instanceof Interpreter)) { return new Interpreter(obj); } if (obj) { this.initialize(); this.set(obj); } else { this.initialize(); } }; Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, satoshis, flags, isP2SH) { var scriptPubKey = new Script(); var stack = []; if (version === 0) { if (program.length === Interpreter.WITNESS_V0_SCRIPTHASH_SIZE) { if (witness.length === 0) { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'; return false; } var scriptPubKeyBuffer = witness[witness.length - 1]; scriptPubKey = new Script(scriptPubKeyBuffer); var hash = Hash.sha256(scriptPubKeyBuffer); if (hash.toString('hex') !== program.toString('hex')) { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; return false; } stack = witness.slice(0, -1); return this.executeWitnessScript(scriptPubKey, stack, Signature.Version.WITNESS_V0, satoshis, flags); } else if (program.length === Interpreter.WITNESS_V0_KEYHASH_SIZE) { if (witness.length !== 2) { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; return false; } scriptPubKey.add(Opcode.OP_DUP); scriptPubKey.add(Opcode.OP_HASH160); scriptPubKey.add(program); scriptPubKey.add(Opcode.OP_EQUALVERIFY); scriptPubKey.add(Opcode.OP_CHECKSIG); stack = witness; return this.executeWitnessScript(scriptPubKey, stack, Signature.Version.WITNESS_V0, satoshis, flags); } else { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH'; return false; } } else if (version === 1 && program.length == Interpreter.WITNESS_V1_TAPROOT_SIZE && !isP2SH) { const execdata = { annexPresent: false }; // BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey) if (!(flags & Interpreter.SCRIPT_VERIFY_TAPROOT)) { return true; } stack = Array.from(witness); if (stack.length == 0) { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY'; return false; } if (stack.length >= 2 && stack[stack.length - 1].length && stack[stack.length - 1][0] === Script.ANNEX_TAG) { // Drop annex (this is non-standard; see IsWitnessStandard) const annex = stack.pop(); const annexWriter = new BufferWriter(); annexWriter.writeVarintNum(annex.length); annexWriter.write(annex); execdata.annexHash = Hash.sha256(annexWriter.toBuffer()); execdata.annexPresent = true; } execdata.annexInit = true; if (stack.length === 1) { // Key path spending (stack size is 1 after removing optional annex) return this.checkSchnorrSignature(stack[0], program, Signature.Version.TAPROOT, execdata); } else { // Script path spending (stack size is >1 after removing optional annex) const control = stack.pop(); const scriptPubKeyBuf = stack.pop(); if ( control.length < Interpreter.TAPROOT_CONTROL_BASE_SIZE || control.length > Interpreter.TAPROOT_CONTROL_MAX_SIZE || ((control.length - Interpreter.TAPROOT_CONTROL_BASE_SIZE) % Interpreter.TAPROOT_CONTROL_NODE_SIZE) != 0 ) { this.errstr = 'SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE'; return false; } execdata.tapleafHash = Interpreter.computeTapleafHash(control[0] & Interpreter.TAPROOT_LEAF_MASK, scriptPubKeyBuf); if (!Interpreter.verifyTaprootCommitment(control, program, execdata.tapleafHash)) { this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH'; return false; } execdata.tapleafHashInit = true; if ((control[0] & Interpreter.TAPROOT_LEAF_MASK) === Interpreter.TAPROOT_LEAF_TAPSCRIPT) { // Tapscript (leaf version 0xc0) let witnessSize; { const bw = new BufferWriter(); bw.writeVarintNum(witness.length); for (let element of witness) { bw.writeVarintNum(element.length); bw.write(element); } witnessSize = bw.toBuffer().length; } try { scriptPubKey = new Script(scriptPubKeyBuf); } catch (err) { // Note how this condition would not be reached if an unknown OP_SUCCESSx was found this.errstr = 'SCRIPT_ERR_BAD_OPCODE'; return false; } execdata.validationWeightLeft = witnessSize + Script.VALIDATION_WEIGHT_OFFSET; execdata.validationWeightLeftInit = true; return this.executeWitnessScript(scriptPubKey, stack, Signature.Version.TAPSCRIPT, satoshis, flags, execdata); } // If none of the above conditions are met then this must be an upgraded taproot version. if (flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION'; return false; } // Future softfork compatibility return true; } } else if ((flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM'; return false; } // Other version/size/p2sh combinations return true for future softfork compatibility return true; }; Interpreter.prototype.executeWitnessScript = function(scriptPubKey, stack, sigversion, satoshis, flags, execdata) { if (sigversion === Signature.Version.TAPSCRIPT) { for (let chunk of scriptPubKey.chunks) { // New opcodes will be listed here. May use a different sigversion to modify existing opcodes. if (Opcode.isOpSuccess(chunk.opcodenum)) { if (flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_OP_SUCCESS'; return false; } return true; } } // Tapscript enforces initial stack size limits (altstack is empty here) if (stack.length > Interpreter.MAX_STACK_SIZE) { this.errstr = 'SCRIPT_ERR_STACK_SIZE'; return false; } } // Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack if (stack.length && stack.some(elem => elem.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE)) { this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; return false; } this.initialize(); this.set({ script: scriptPubKey, stack: stack, sigversion: sigversion, satoshis: satoshis, flags: flags, execdata: execdata }); if (!this.evaluate()) { return false; } if (this.stack.length !== 1) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; return false; } var buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; return false; } return true; }; /** * Verifies a Script by executing it and returns true if it is valid. * This function needs to be provided with the scriptSig and the scriptPubkey * separately. * @param {Script} scriptSig - the script's first part (corresponding to the tx input) * @param {Script} scriptPubkey - the script's last part (corresponding to the tx output) * @param {Transaction=} tx - the Transaction containing the scriptSig in one input (used * to check signature validity for some opcodes like OP_CHECKSIG) * @param {number} nin - index of the transaction input containing the scriptSig verified. * @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants * @param {number} witness - array of witness data * @param {number} satoshis - number of satoshis created by this output * * Translated from bitcoind's VerifyScript */ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) { var Transaction = require('../transaction'); if (_.isUndefined(tx)) { tx = new Transaction(); } if (_.isUndefined(nin)) { nin = 0; } if (_.isUndefined(flags)) { flags = 0; } if (_.isUndefined(witness)) { witness = null; } if (_.isUndefined(satoshis)) { satoshis = 0; } this.set({ script: scriptSig, tx: tx, nin: nin, sigversion: Signature.Version.BASE, satoshis: 0, flags: flags }); var stackCopy; if ((flags & Interpreter.SCRIPT_VERIFY_SIGPUSHONLY) !== 0 && !scriptSig.isPushOnly()) { this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; return false; } // evaluate scriptSig if (!this.evaluate()) { return false; } if (flags & Interpreter.SCRIPT_VERIFY_P2SH) { stackCopy = this.stack.slice(); } var stack = this.stack; this.initialize(); this.set({ script: scriptPubkey, stack: stack, tx: tx, nin: nin, flags: flags }); // evaluate scriptPubkey if (!this.evaluate()) { return false; } if (this.stack.length === 0) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT'; return false; } var buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; return false; } var hadWitness = false; if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { var witnessValues = {}; if (scriptPubkey.isWitnessProgram(witnessValues)) { hadWitness = true; if (scriptSig.toBuffer().length !== 0) { this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED'; return false; } if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, this.flags, /* isP2SH */ false)) { return false; } } } // Additional validation for spend-to-script-hash transactions: if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) { // scriptSig must be literals-only or validation fails if (!scriptSig.isPushOnly()) { this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; return false; } // stackCopy cannot be empty here, because if it was the // P2SH HASH <> EQUAL scriptPubKey would be evaluated with // an empty stack and the EvalScript above would return false. if (stackCopy.length === 0) { throw new Error('internal error - stack copy empty'); } var redeemScriptSerialized = stackCopy[stackCopy.length - 1]; var redeemScript = Script.fromBuffer(redeemScriptSerialized); stackCopy.pop(); this.initialize(); this.set({ script: redeemScript, stack: stackCopy, tx: tx, nin: nin, flags: flags }); // evaluate redeemScript if (!this.evaluate()) { return false; } if (stackCopy.length === 0) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK'; return false; } if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; return false; } if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { var p2shWitnessValues = {}; if (redeemScript.isWitnessProgram(p2shWitnessValues)) { hadWitness = true; var redeemScriptPush = new Script(); redeemScriptPush.add(redeemScript.toBuffer()); if (scriptSig.toHex() !== redeemScriptPush.toHex()) { this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED_P2SH'; return false; } if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, this.flags, /* isP2SH */ true)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean // for witness programs. stack = [stack[0]]; } } } // The CLEANSTACK check is only performed after potential P2SH evaluation, // as the non-P2SH evaluation of a P2SH script will obviously not result in // a clean stack (the P2SH inputs remain). The same holds for witness // evaluation. if ((this.flags & Interpreter.SCRIPT_VERIFY_CLEANSTACK) != 0) { // Disallow CLEANSTACK without P2SH, as otherwise a switch // CLEANSTACK->P2SH+CLEANSTACK would be possible, which is not a // softfork (and P2SH should be one). if ( (this.flags & Interpreter.SCRIPT_VERIFY_P2SH) == 0 || (this.flags & Interpreter.SCRIPT_VERIFY_WITNESS) == 0 ) { throw 'flags & SCRIPT_VERIFY_P2SH'; } if (stackCopy.length != 1) { this.errstr = 'SCRIPT_ERR_CLEANSTACK'; return false; } } if ((this.flags & Interpreter.SCRIPT_VERIFY_WITNESS)) { if (!hadWitness && witness.length > 0) { this.errstr = 'SCRIPT_ERR_WITNESS_UNEXPECTED'; return false; } } return true; }; module.exports = Interpreter; Interpreter.prototype.initialize = function(obj) { this.stack = []; this.altstack = []; this.pc = 0; this.satoshis = 0; this.sigversion = Signature.Version.BASE; this.pbegincodehash = 0; this.nOpCount = 0; this.vfExec = []; this.errstr = ''; this.flags = 0; this.execdata = {}; }; Interpreter.prototype.set = function(obj) { this.script = obj.script || this.script; this.tx = obj.tx || this.tx; this.nin = typeof obj.nin === 'undefined' ? this.nin : parseInt(obj.nin); this.stack = obj.stack || this.stack; this.altstack = obj.altstack || this.altstack; this.pc = typeof obj.pc === 'undefined' ? this.pc : obj.pc; this.pbegincodehash = typeof obj.pbegincodehash === 'undefined' ? this.pbegincodehash : obj.pbegincodehash; this.sigversion = typeof obj.sigversion === 'undefined' ? this.sigversion : obj.sigversion; this.satoshis = typeof obj.satoshis === 'undefined' ? this.satoshis : obj.satoshis; this.nOpCount = typeof obj.nOpCount === 'undefined' ? this.nOpCount : obj.nOpCount; this.vfExec = obj.vfExec || this.vfExec; this.errstr = obj.errstr || this.errstr; this.flags = typeof obj.flags === 'undefined' ? this.flags : obj.flags; this.execdata = typeof obj.execdata === 'undefined' ? this.execdata : (obj.execdata || {}); }; Interpreter.true = Buffer.from([1]); Interpreter.false = Buffer.from([]); Interpreter.MAX_SCRIPT_SIZE = 10000; Interpreter.MAX_STACK_SIZE = 1000; Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520; Interpreter.LOCKTIME_THRESHOLD = 500000000; Interpreter.LOCKTIME_THRESHOLD_BN = new BN(Interpreter.LOCKTIME_THRESHOLD); // flags taken from bitcoind // bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 Interpreter.SCRIPT_VERIFY_NONE = 0; // Evaluate P2SH subscripts (softfork safe, BIP16). Interpreter.SCRIPT_VERIFY_P2SH = (1 << 0); // Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure. // Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be // skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT). Interpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1); // Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) Interpreter.SCRIPT_VERIFY_DERSIG = (1 << 2); // Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure // (softfork safe, BIP62 rule 5). Interpreter.SCRIPT_VERIFY_LOW_S = (1 << 3); // verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). Interpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4); // Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). Interpreter.SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5); // Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct // pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating // any other push causes the script to fail (BIP62 rule 3). // In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4). // (softfork safe) Interpreter.SCRIPT_VERIFY_MINIMALDATA = (1 << 6); // Discourage use of NOPs reserved for upgrades (NOP1-10) // // Provided so that nodes can avoid accepting or mining transactions // containing executed NOP's whose meaning may change after a soft-fork, // thus rendering the script invalid; with this flag set executing // discouraged NOPs fails the script. This verification flag will never be // a mandatory flag applied to scripts in a block. NOPs that are not // executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); // Require that only a single stack element remains after evaluation. This // changes the success criterion from "At least one stack element must // remain, and when interpreted as a boolean, it must be true" to "Exactly // one stack element must remain, and when interpreted as a boolean, it must // be true". // (softfork safe, BIP62 rule 6) // Note: CLEANSTACK should never be used without P2SH or WITNESS. Interpreter.SCRIPT_VERIFY_CLEANSTACK = (1 << 8), // Verify CHECKLOCKTIMEVERIFY // // See BIP65 for details. Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9); // support CHECKSEQUENCEVERIFY opcode // // See BIP112 for details Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1 << 10); // Support segregated witness // Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 11); // Making v1-v16 witness program non-standard // Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1 << 12); // // Segwit script only: Require the argument of OP_IF/NOTIF to be exactly // 0x01 or empty vector // Interpreter.SCRIPT_VERIFY_MINIMALIF = (1 << 13); // Signature(s) must be empty vector if an CHECK(MULTI)SIG operation failed // Interpreter.SCRIPT_VERIFY_NULLFAIL = (1 << 14); // Public keys in scripts must be compressed // Interpreter.SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1 << 15); // Do we accept signature using SIGHASH_FORKID // Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID = (1 << 16); // Do we accept activate replay protection using a different fork id. // Interpreter.SCRIPT_ENABLE_REPLAY_PROTECTION = (1 << 17); // Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts // Interpreter.SCRIPT_VERIFY_CONST_SCRIPTCODE = (1 << 16); // Verify taproot script // Interpreter.SCRIPT_VERIFY_TAPROOT = (1 << 17); // Making unknown Taproot leaf versions non-standard // Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1 << 18); // Making unknown OP_SUCCESS non-standard Interpreter.SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1 << 19); // Making unknown public key versions (in BIP 342 scripts) non-standard Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1 << 20); /* Below flags apply in the context of BIP 68*/ /** * If this flag set, CTxIn::nSequence is NOT interpreted as a relative * lock-time. */ Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31); /** * If CTxIn::nSequence encodes a relative lock-time and this flag is set, * the relative lock-time has units of 512 seconds, otherwise it specifies * blocks with a granularity of 1. */ Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22); /** * If CTxIn::nSequence encodes a relative lock-time, this mask is applied to * extract that lock-time from the sequence field. */ Interpreter.SEQUENCE_LOCKTIME_MASK = 0x0000ffff; /** Signature hash sizes */ Interpreter.WITNESS_V0_SCRIPTHASH_SIZE = 32; Interpreter.WITNESS_V0_KEYHASH_SIZE = 20; Interpreter.WITNESS_V1_TAPROOT_SIZE = 32; Interpreter.TAPROOT_LEAF_MASK = 0xfe; Interpreter.TAPROOT_LEAF_TAPSCRIPT = 0xc0; Interpreter.TAPROOT_CONTROL_BASE_SIZE = 33; Interpreter.TAPROOT_CONTROL_NODE_SIZE = 32; Interpreter.TAPROOT_CONTROL_MAX_NODE_COUNT = 128; Interpreter.TAPROOT_CONTROL_MAX_SIZE = Interpreter.TAPROOT_CONTROL_BASE_SIZE + Interpreter.TAPROOT_CONTROL_NODE_SIZE * Interpreter.TAPROOT_CONTROL_MAX_NODE_COUNT; // Conceptually, this doesn't really belong with the Interpreter, but I haven't found a better place for it. Interpreter.PROTOCOL_VERSION = 70016; Interpreter.castToBool = function(buf) { for (var i = 0; i < buf.length; i++) { if (buf[i] !== 0) { // can be negative zero if (i === buf.length - 1 && buf[i] === 0x80) { return false; } return true; } } return false; }; /** * Translated from bitcoind's CheckSignatureEncoding */ Interpreter.prototype.checkSignatureEncoding = function(buf) { var sig; // Empty signature. Not strictly DER encoded, but allowed to provide a // compact way to provide an invalid signature for use with CHECK(MULTI)SIG if (buf.length == 0) { return true; } if ((this.flags & (Interpreter.SCRIPT_VERIFY_DERSIG | Interpreter.SCRIPT_VERIFY_LOW_S | Interpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) { this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT'; return false; } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) { sig = Signature.fromTxFormat(buf); if (!sig.hasLowS()) { this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S'; return false; } } else if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { sig = Signature.fromTxFormat(buf); if (!sig.hasDefinedHashtype()) { this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; return false; } } return true; }; /** * Translated from bitcoind's CheckPubKeyEncoding */ Interpreter.prototype.checkPubkeyEncoding = function(buf) { if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !PublicKey.isValid(buf)) { this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; return false; } // Only compressed keys are accepted in segwit if ((this.flags & Interpreter.SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) != 0 && this.sigversion == Signature.Version.WITNESS_V0 && !PublicKey.fromBuffer(buf).compressed) { this.errstr = 'SCRIPT_ERR_WITNESS_PUBKEYTYPE'; return false; } return true; }; /** * Verifies ECDSA signature * @param {Signature} sig * @param {PublicKey} pubkey * @param {Number} nin * @param {Script} subscript * @param {Number} satoshis * @returns {Boolean} */ Interpreter.prototype.checkEcdsaSignature = function(sig, pubkey, nin, subscript, satoshis) { var subscriptBuffer = subscript.toBuffer(); var scriptCodeWriter = new BufferWriter(); scriptCodeWriter.writeVarintNum(subscriptBuffer.length); scriptCodeWriter.write(subscriptBuffer); $.checkState(JSUtil.isNaturalNumber(satoshis)); var satoshisBuffer = new BufferWriter().writeUInt64LEBN(new BN(satoshis)).toBuffer(); var verified = SighashWitness.verify( this, sig, pubkey, nin, scriptCodeWriter.toBuffer(), satoshisBuffer ); return verified; }; /** * Verifies Schnorr signature * @param {Signature|Buffer} sig * @param {PublicKey|Buffer} pubkey * @param {Number} sigversion * @param {Object} execdata * @returns {Boolean} */ Interpreter.prototype.checkSchnorrSignature = function(sig, pubkey, sigversion, execdata) { $.checkArgument(sig && Buffer.isBuffer(sig), 'Missing sig'); $.checkArgument(pubkey && Buffer.isBuffer(pubkey), 'Missing pubkey'); $.checkArgument(sigversion, 'Missing sigversion'); $.checkArgument(execdata, 'Missing execdata'); $.checkArgument(pubkey.length === 32, 'Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this.'); // Note that in Tapscript evaluation, empty signatures are treated specially (invalid signature that does not // abort script execution). This is implemented in EvalChecksigTapscript, which won't invoke // CheckSchnorrSignature in that case. In other contexts, they are invalid like every other signature with // size different from 64 or 65. if (!(sig.length === 64 || sig.length === 65)) { this.errstr = 'SCRIPT_ERR_SCHNORR_SIG_SIZE'; return false; } if (sig.length === 65 && sig[sig.length - 1] === Signature.SIGHASH_DEFAULT) { this.errstr = 'SCRIPT_ERR_SCHNORR_SIG_HASHTYPE'; return false; } sig = Signature.fromSchnorr(sig); const verified = SighashSchnorr.verify( this.tx, sig, pubkey, sigversion, this.nin, execdata ); return verified; }; /** * Based on bitcoind's EvalChecksigPreTapscript function * bitcoind commit: a0988140b71485ad12c3c3a4a9573f7c21b1eff8 */ Interpreter.prototype._evalChecksigPreTapscript = function(bufSig, bufPubkey) { $.checkArgument( this.sigversion === Signature.Version.BASE || this.sigversion === Signature.Version.WITNESS_V0, 'sigversion must be base or witness_v0' ); // Success signifies if the signature is valid. // Result signifies the result of this funciton, which also takes flags into account. const retVal = { success: false, result: false }; const subscript = new Script().set({ chunks: this.script.chunks.slice(this.pbegincodehash) }); // Drop the signature in pre-segwit scripts but not segwit scripts if (this.sigversion === Signature.Version.BASE) { // Drop the signature, since there's no way for a signature to sign itself const tmpScript = new Script().add(bufSig); let found = subscript.chunks.length; subscript.findAndDelete(tmpScript); found = found == subscript.chunks.length + 1; // found if a chunk was removed if (found && (this.flags & Interpreter.SCRIPT_VERIFY_CONST_SCRIPTCODE)) { this.errstr = 'SCRIPT_ERR_SIG_FINDANDDELETE'; return retVal; } } if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { return retVal; } try { const sig = Signature.fromTxFormat(bufSig); const pubkey = PublicKey.fromBuffer(bufPubkey, false); retVal.success = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis); } catch (e) { //invalid sig or pubkey retVal.success = false; } if (!retVal.success && (this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL) && bufSig.length) { this.errstr = 'SCRIPT_ERR_SIG_NULLFAIL'; return retVal; } // If it reaches here, then true retVal.result = true; return retVal; }; /** * Based on bitcoind's EvalChecksigTapscript function * bitcoind commit: a0988140b71485ad12c3c3a4a9573f7c21b1eff8 */ Interpreter.prototype._evalChecksigTapscript = function(bufSig, bufPubkey) { $.checkArgument(this.sigversion == Signature.Version.TAPSCRIPT, 'this.sigversion must by TAPSCRIPT'); /* * The following validation sequence is consensus critical. Please note how -- * upgradable public key versions precede other rules; * the script execution fails when using empty signature with invalid public key; * the script execution fails when using non-empty invalid signature. */ // Success signifies if the signature is valid. // Result signifies the result of this funciton, which also takes flags into account. const retVal = { success: bufSig.length > 0, result: false } if (retVal.success) { // Implement the sigops/witnesssize ratio test. // Passing with an upgradable public key version is also counted. $.checkState(this.execdata.validationWeightLeftInit, 'validationWeightLeftInit is false'); this.execdata.validationWeightLeft -= Script.VALIDATION_WEIGHT_PER_SIGOP_PASSED; if (this.execdata.validationWeightLeft < 0) { this.errstr = 'SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT'; return retVal; } } if (bufPubkey.length === 0) { this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; return retVal; } else if (bufPubkey.length == 32) { if (retVal.success && !this.tx.checkSchnorrSignature(bufSig, bufPubkey, this.nin, this.sigversion, this.execdata)) { this.errstr = 'SCRIPT_ERR_SCHNORR_SIG'; return retVal; } } else { /* * New public key version softforks should be defined before this `else` block. * Generally, the new code should not do anything but failing the script execution. To avoid * consensus bugs, it should not modify any existing values (including `success`). */ if ((this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE'; return retVal; } } // If it reaches here, then true retVal.result = true; return retVal; } /** * Based on bitcoind's EvalChecksig function * bitcoind commit: a0988140b71485ad12c3c3a4a9573f7c21b1eff8 * @returns {{ success: Boolean, verified: Boolean }} */ Interpreter.prototype._evalCheckSig = function(bufSig, bufPubkey) { switch(this.sigversion) { case Signature.Version.BASE: case Signature.Version.WITNESS_V0: // const verified = this._evalChecksigPreTapscript(bufSig, bufPubkey); // return { success: verified, verified }; // This is to keep the same return format as _evalCheckSigTapscript return this._evalChecksigPreTapscript(bufSig, bufPubkey); case Signature.Version.TAPSCRIPT: return this._evalChecksigTapscript(bufSig, bufPubkey); case Signature.Version.TAPROOT: // Key path spending in Taproot has no script, so this is unreachable. throw new Error('Called evalCheckSig with a TAPROOT sigversion. Check your implementation'); } }; /** * Based on bitcoind's EvalScript function, with the inner loop moved to * Interpreter.prototype.step() * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 */ Interpreter.prototype.evaluate = function() { // sigversion cannot be TAPROOT here, as it admits no script execution. $.checkArgument(this.sigversion == Signature.Version.BASE || this.sigversion == Signature.Version.WITNESS_V0 || this.sigversion == Signature.Version.TAPSCRIPT, 'invalid sigversion'); if ( (this.sigversion == Signature.Version.BASE || this.sigversion == Signature.Version.WITNESS_V0) && this.script.toBuffer().length > Interpreter.MAX_SCRIPT_SIZE ) { this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE'; return false; } try { while (this.pc < this.script.chunks.length) { var fSuccess = this.step(); if (!fSuccess) { return false; } } } catch (e) { this.errstr = 'SCRIPT_ERR_UNKNOWN_ERROR: ' + e; return false; } if (this.vfExec.length > 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } return true; }; /** * Checks a locktime parameter with the transaction's locktime. * There are two times of nLockTime: lock-by-blockheight and lock-by-blocktime, * distinguished by whether nLockTime < LOCKTIME_THRESHOLD = 500000000 * * See the corresponding code on bitcoin core: * https://github.com/bitcoin/bitcoin/blob/ffd75adce01a78b3461b3ff05bcc2b530a9ce994/src/script/interpreter.cpp#L1129 * * @param {BN} nLockTime the locktime read from the script * @return {boolean} true if the transaction's locktime is less than or equal to * the transaction's locktime */ Interpreter.prototype.checkLockTime = function(nLockTime) { // We want to compare apples to apples, so fail the script // unless the type of nLockTime being tested is the same as // the nLockTime in the transaction. if (!( (this.tx.nLockTime < Interpreter.LOCKTIME_THRESHOLD && nLockTime.lt(Interpreter.LOCKTIME_THRESHOLD_BN)) || (this.tx.nLockTime >= Interpreter.LOCKTIME_THRESHOLD && nLockTime.gte(Interpreter.LOCKTIME_THRESHOLD_BN)) )) { return false; } // Now that we know we're comparing apples-to-apples, the // comparison is a simple numeric one. if (nLockTime.gt(new BN(this.tx.nLockTime))) { return false; } // Finally the nLockTime feature can be disabled and thus // CHECKLOCKTIMEVERIFY bypassed if every txin has been // finalized by setting nSequence to maxint. The // transaction would be allowed into the blockchain, making // the opcode ineffective. // // Testing if this vin is not final is sufficient to // prevent this condition. Alternatively we could test all // inputs, but testing just this input minimizes the data // required to prove correct CHECKLOCKTIMEVERIFY execution. if (!this.tx.inputs[this.nin].isFinal()) { return false; } return true; } /** * Checks a sequence parameter with the transaction's sequence. * @param {BN} nSequence the sequence read from the script * @return {boolean} true if the transaction's sequence is less than or equal to * the transaction's sequence */ Interpreter.prototype.checkSequence = function(nSequence) { // Relative lock times are supported by comparing the passed in operand to // the sequence number of the input. var txToSequence = this.tx.inputs[this.nin].sequenceNumber; // Fail if the transaction's version number is not set high enough to // trigger BIP 68 rules. if (this.tx.version < 2) { return false; } // Sequence numbers with their most significant bit set are not consensus // constrained. Testing that the transaction's sequence number do not have // this bit set prevents using this property to get around a // CHECKSEQUENCEVERIFY check. if (txToSequence & Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG) { return false; } // Mask off any bits that do not have consensus-enforced meaning before // doing the integer comparisons var nLockTimeMask = Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG | Interpreter.SEQUENCE_LOCKTIME_MASK; var txToSequenceMasked = new BN(txToSequence & nLockTimeMask); var nSequenceMasked = nSequence.and(new BN(nLockTimeMask)); // There are two kinds of nSequence: lock-by-blockheight and // lock-by-blocktime, distinguished by whether nSequenceMasked < // CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG. // // We want to compare apples to apples, so fail the script unless the type // of nSequenceMasked being tested is the same as the nSequenceMasked in the // transaction. var SEQUENCE_LOCKTIME_TYPE_FLAG_BN = new BN(Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG); if (!((txToSequenceMasked.lt(SEQUENCE_LOCKTIME_TYPE_FLAG_BN) && nSequenceMasked.lt(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)) || (txToSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN) && nSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)))) { return false; } // Now that we know we're comparing apples-to-apples, the comparison is a // simple numeric one. return nSequenceMasked.lte(txToSequenceMasked) } Interpreter.computeTapleafHash = function(leafVersion, scriptBuf) { const tagWriter = TaggedHash.TAPLEAF; tagWriter.writeUInt8(leafVersion); tagWriter.writeVarintNum(scriptBuf.length); tagWriter.write(scriptBuf); return tagWriter.finalize(); }; Interpreter.computeTaprootMerkleRoot = function(control, tapleafHash) { const pathLen = (control.length - Interpreter.TAPROOT_CONTROL_BASE_SIZE) / Interpreter.TAPROOT_CONTROL_NODE_SIZE; let k = tapleafHash; for (let i = 0; i < pathLen; ++i) { const tagWriter = TaggedHash.TAPBRANCH; const start = Interpreter.TAPROOT_CONTROL_BASE_SIZE + Interpreter.TAPROOT_CONTROL_NODE_SIZE * i; const node = control.slice(start, start + Interpreter.TAPROOT_CONTROL_NODE_SIZE); if (Buffer.compare(k, node) === -1) { tagWriter.write(k); tagWriter.write(node); } else { tagWriter.write(node); tagWriter.write(k); } k = tagWriter.finalize(); } return k; }; Interpreter.verifyTaprootCommitment = function(control, program, tapleafHash) { $.checkArgument(control.length >= Interpreter.TAPROOT_CONTROL_BASE_SIZE, 'control too short'); $.checkArgument(program.length >= 32, 'program is too short'); try { //! The internal pubkey (x-only, so no Y coordinate parity). const p = PublicKey.fromX(false, control.slice(1, Interpreter.TAPROOT_CONTROL_BASE_SIZE)); //! The output pubkey (taken from the scriptPubKey). const q = PublicKey.fromX(false, program); // Compute the Merkle root from the leaf and the provided path. const merkleRoot = Interpreter.computeTaprootMerkleRoot(control, tapleafHash); // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. return q.checkTapTweak(p, merkleRoot, control); } catch (err) { return false; } }; /** * Based on the inner loop of bitcoind's EvalScript function * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 */ Interpreter.prototype.step = function() { var fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; //bool fExec = !count(vfExec.begin(), vfExec.end(), false); var fExec = (this.vfExec.indexOf(false) === -1); var buf, buf1, buf2, spliced, n, x1, x2, bn, bn1, bn2, bufSig, bufPubkey, subscript; var sig, pubkey; var fValue, fSuccess; this.execdata = this.execdata || {}; if (!this.execdata.codeseparatorPosInit) { this.execdata.codeseparatorPos = new BN(0xFFFFFFFF); this.execdata.codeseparatorPosInit = true; } // Read instruction var chunk = this.script.chunks[this.pc]; this.pc++; var opcodenum = chunk.opcodenum; if (_.isUndefined(opcodenum)) { this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE'; return false; } if (chunk.buf && chunk.buf.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) { this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; return false; } if (this.sigversion === Signature.Version.BASE || this.sigversion === Signature.Version.WITNESS_V0) { // Note how Opcode.OP_RESERVED does not count towards the opcode limit. if (opcodenum > Opcode.OP_16 && ++(this.nOpCount) > 201) { this.errstr = 'SCRIPT_ERR_OP_COUNT'; return false; } } if (opcodenum === Opcode.OP_CAT || opcodenum === Opcode.OP_SUBSTR || opcodenum === Opcode.OP_LEFT || opcodenum === Opcode.OP_RIGHT || opcodenum === Opcode.OP_INVERT || opcodenum === Opcode.OP_AND || opcodenum === Opcode.OP_OR || opcodenum === Opcode.OP_XOR || opcodenum === Opcode.OP_2MUL || opcodenum === Opcode.OP_2DIV || opcodenum === Opcode.OP_MUL || opcodenum === Opcode.OP_DIV || opcodenum === Opcode.OP_MOD || opcodenum === Opcode.OP_LSHIFT || opcodenum === Opcode.OP_RSHIFT) { this.errstr = 'SCRIPT_ERR_DISABLED_OPCODE'; return false; } // With SCRIPT_VERIFY_CONST_SCRIPTCODE, OP_CODESEPARATOR in non-segwit script is rejected even in an unexecuted branch if (opcodenum == Opcode.OP_CODESEPARATOR && this.sigversion === Signature.Version.BASE && (this.flags & Interpreter.SCRIPT_VERIFY_CONST_SCRIPTCODE)) { this.errstr = 'SCRIPT_ERR_OP_CODESEPARATOR'; return false; } if (fExec && 0 <= opcodenum && opcodenum <= Opcode.OP_PUSHDATA4) { if (fRequireMinimal && !this.script.checkMinimalPush(this.pc - 1)) { this.errstr = 'SCRIPT_ERR_MINIMALDATA'; return false; } if (!chunk.buf) { this.stack.push(Interpreter.false); } else if (chunk.len !== chunk.buf.length) { throw new Error('Length of push value not equal to length of data'); } else { this.stack.push(chunk.buf); } } else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) { switch (opcodenum) { // Push value case Opcode.OP_1NEGATE: case Opcode.OP_1: case Opcode.OP_2: case Opcode.OP_3: case Opcode.OP_4: case Opcode.OP_5: case Opcode.OP_6: case Opcode.OP_7: case Opcode.OP_8: case Opcode.OP_9: case Opcode.OP_10: case Opcode.OP_11: case Opcode.OP_12: case Opcode.OP_13: case Opcode.OP_14: case Opcode.OP_15: case Opcode.OP_16: { // ( -- value) // ScriptNum bn((int)opcode - (int)(Opcode.OP_1 - 1)); n = opcodenum - (Opcode.OP_1 - 1); buf = new BN(n).toScriptNumBuffer(); this.stack.push(buf); // The result of these opcodes should always be the minimal way to push the data // they push, so no need for a CheckMinimalPush here. } break; // // Control // case Opcode.OP_NOP: break; case Opcode.OP_NOP2: case Opcode.OP_CHECKLOCKTIMEVERIFY: if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { // not enabled; treat as a NOP2 if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } break; } if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } // Note that elsewhere numeric opcodes are limited to // operands in the range -2**31+1 to 2**31-1, however it is // legal for opcodes to produce results exceeding that // range. This limitation is implemented by CScriptNum's // default 4-byte limit. // // If we kept to that limit we'd have a year 2038 problem, // even though the nLockTime field in transactions // themselves is uint32 which only becomes meaningless // after the year 2106. // // Thus as a special case we tell CScriptNum to accept up // to 5-byte bignums, which are good until 2**39-1, well // beyond the 2**32-1 limit of the nLockTime field itself. var nLockTime = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5); // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use // 0 MAX CHECKLOCKTIMEVERIFY. if (nLockTime.lt(new BN(0))) { this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME'; return false; } // Actually compare the specified lock time with the transaction. if (!this.checkLockTime(nLockTime)) { this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME'; return false; } break; case Opcode.OP_NOP3: case Opcode.OP_CHECKSEQUENCEVERIFY: if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)) { // not enabled; treat as a NOP3 if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } break; } if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } // nSequence, like nLockTime, is a 32-bit unsigned // integer field. See the comment in CHECKLOCKTIMEVERIFY // regarding 5-byte numeric operands. var nSequence = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5); // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use // 0 MAX CHECKSEQUENCEVERIFY. if (nSequence.lt(new BN(0))) { this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME'; return false; } // To provide for future soft-fork extensibility, if the // operand has the disabled lock-time flag set, // CHECKSEQUENCEVERIFY behaves as a NOP. if ((nSequence & Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) { break; } // Actually compare the specified lock time with the transaction. if (!this.checkSequence(nSequence)) { this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME'; return false; } break; case Opcode.OP_NOP1: case Opcode.OP_NOP4: case Opcode.OP_NOP5: case Opcode.OP_NOP6: case Opcode.OP_NOP7: case Opcode.OP_NOP8: case Opcode.OP_NOP9: case Opcode.OP_NOP10: { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } } break; case Opcode.OP_IF: case Opcode.OP_NOTIF: { // <expression> if [statements] [else [statements]] endif // bool fValue = false; fValue = false; if (fExec) { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } buf = this.stack[this.stack.length - 1]; // Tapscript requires minimal IF/NOTIF inputs as a consensus rule. if (this.sigversion === Signature.Version.TAPSCRIPT) { // The input argument to the OP_IF and OP_NOTIF opcodes must be either // exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1). if (buf.length > 1 || (buf.length === 1 && buf[0] !== 1)) { this.errstr = 'SCRIPT_ERR_TAPSCRIPT_MINIMALIF'; return false; } } // Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF. if (this.sigversion === Signature.Version.WITNESS_V0 && (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALIF)) { buf = this.stack[this.stack.length - 1]; if (buf.length > 1) { this.errstr = 'SCRIPT_ERR_MINIMALIF'; return false; } if (buf.length == 1 && buf[0]!=1) { this.errstr = 'SCRIPT_ERR_MINIMALIF'; return false; } } fValue = Interpreter.castToBool(buf); if (opcodenum === Opcode.OP_NOTIF) { fValue = !fValue; } this.stack.pop(); } this.vfExec.push(fValue); } break; case Opcode.OP_ELSE: { if (this.vfExec.length === 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } this.vfExec[this.vfExec.length - 1] = !this.vfExec[this.vfExec.length - 1]; } break; case Opcode.OP_ENDIF: { if (this.vfExec.length === 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } this.vfExec.pop(); } break; case Opcode.OP_VERIFY: { // (true -- ) or // (false -- false) and return if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } buf = this.stack[this.stack.length - 1]; fValue = Interpreter.castToBool(buf); if (fValue) { this.stack.pop(); } else { this.errstr = 'SCRIPT_ERR_VERIFY'; return false; } } break; case Opcode.OP_RETURN: { this.errstr = 'SCRIPT_ERR_OP_RETURN'; return false; } break; // // Stack ops // case Opcode.OP_TOALTSTACK: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.altstack.push(this.stack.pop()); } break; case Opcode.OP_FROMALTSTACK: { if (this.altstack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_ALTSTACK_OPERATION'; return false; } this.stack.push(this.altstack.pop()); } break; case Opcode.OP_2DROP: { // (x1 x2 -- ) if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.pop(); this.stack.pop(); } break; case Opcode.OP_2DUP: { // (x1 x2 -- x1 x2 x1 x2) if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } buf1 = this.stack[this.stack.length - 2]; buf2 = this.stack[this.stack.length - 1]; this.stack.push(buf1); this.stack.push(buf2); } break; case Opcode.OP_3DUP: { // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) if (this.stack.length < 3) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } buf1 = this.stack[this.stack.length - 3]; buf2 = this.stack[this.stack.length - 2]; var buf3 = this.stack[this.stack.length - 1]; this.stack.push(buf1); this.stack.push(buf2); this.stack.push(buf3); } break; case Opcode.OP_2OVER: { // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) if (this.stack.length < 4) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } buf1 = this.stack[this.stack.length - 4]; buf2 = t