bitcore-lib
Version:
A pure and powerful JavaScript Bitcoin library.
1,455 lines (1,263 loc) • 70.5 kB
JavaScript
'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