mcore-lib-matrixbit
Version:
A pure and powerful JavaScript Bitcoin Cash library.
1,613 lines (1,400 loc) • 59.3 kB
JavaScript
'use strict';
var _ = require('lodash');
var Script = require('./script');
var Opcode = require('../opcode');
var BN = require('../crypto/bn');
var Hash = require('../crypto/hash');
var Signature = require('../crypto/signature');
var PublicKey = require('../publickey');
var ECDSA = require('../crypto/ecdsa');
/**
* 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();
}
};
/**
* 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} satoshisBN - amount in satoshis of the input to be verified (when FORKID signhash is used)
*
* Translated from bitcoind's VerifyScript
*/
Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, satoshisBN) {
var Transaction = require('../transaction');
if (_.isUndefined(tx)) {
tx = new Transaction();
}
if (_.isUndefined(nin)) {
nin = 0;
}
if (_.isUndefined(flags)) {
flags = 0;
}
// If FORKID is enabled, we also ensure strict encoding.
if (flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) {
flags |= Interpreter.SCRIPT_VERIFY_STRICTENC;
// If FORKID is enabled, we need the input amount.
if (!satoshisBN) {
throw new Error('internal error - need satoshisBN to verify FORKID transactions');
}
}
this.set({
script: scriptSig,
tx: tx,
nin: nin,
flags: flags,
satoshisBN: satoshisBN,
});
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,
satoshisBN: satoshisBN,
});
// 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;
}
// 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,
satoshisBN: satoshisBN,
});
// 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;
}
}
// 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 ((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 ((flags & Interpreter.SCRIPT_VERIFY_P2SH) == 0) {
throw new Error('internal error - CLEANSTACK without P2SH');
}
if (stackCopy.length != 1) {
this.errstr = 'SCRIPT_ERR_CLEANSTACK';
return false;
}
}
return true;
};
module.exports = Interpreter;
Interpreter.prototype.initialize = function(obj) {
this.stack = [];
this.altstack = [];
this.pc = 0;
this.pbegincodehash = 0;
this.nOpCount = 0;
this.vfExec = [];
this.errstr = '';
this.flags = 0;
};
Interpreter.prototype.set = function(obj) {
this.script = obj.script || this.script;
this.tx = obj.tx || this.tx;
this.nin = typeof obj.nin !== 'undefined' ? obj.nin : this.nin;
this.satoshisBN = obj.satoshisBN || this.satoshisBN;
this.stack = obj.stack || this.stack;
this.altstack = obj.altack || this.altstack;
this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc;
this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash;
this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount;
this.vfExec = obj.vfExec || this.vfExec;
this.errstr = obj.errstr || this.errstr;
this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags;
};
Interpreter.true = Buffer.from([1]);
Interpreter.false = Buffer.from([]);
Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520;
Interpreter.MAXIMUM_ELEMENT_SIZE = 4;
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),
// CLTV See BIP65 for details.
Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9);
// support CHECKSEQUENCEVERIFY opcode
//
// See BIP112 for details
Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = (1 << 10);
// 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_COMPRESSED_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);
// Enable new opcodes.
//
Interpreter.SCRIPT_ENABLE_CHECKDATASIG = (1 << 18),
/* 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;
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.checkRawSignatureEncoding = function(buf) {
var sig;
if ((this.flags & (Interpreter.SCRIPT_VERIFY_DERSIG | Interpreter.SCRIPT_VERIFY_LOW_S | Interpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isDER(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;
}
}
return true;
};
// Back compat
Interpreter.prototype.checkSignatureEncoding =
Interpreter.prototype.checkTxSignatureEncoding = function(buf) {
// 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.checkRawSignatureEncoding(buf.slice(0,buf.length-1))) {
return false;
}
if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) {
var sig = Signature.fromTxFormat(buf);
if (!sig.hasDefinedHashtype()) {
this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE';
return false;
}
if (!(this.flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) &&
(sig.nhashtype & Signature.SIGHASH_FORKID)) {
this.errstr = 'SCRIPT_ERR_ILLEGAL_FORKID';
return false;
}
if ( (this.flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) &&
!(sig.nhashtype & Signature.SIGHASH_FORKID)) {
this.errstr = 'SCRIPT_ERR_MUST_USE_FORKID';
return false;
}
}
return true;
};
Interpreter.prototype.checkDataSignatureEncoding = function(buf) {
// 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;
}
return this.checkRawSignatureEncoding(buf);
};
/**
* 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;
}
return true;
};
/**
*
* Check the buffer is minimally encoded (see https://github.com/bitcoincashorg/spec/blob/master/may-2018-reenabled-opcodes.md#op_bin2num)
*
*
*/
Interpreter._isMinimallyEncoded = function(buf, nMaxNumSize) {
nMaxNumSize = nMaxNumSize || Interpreter.MAXIMUM_ELEMENT_SIZE;
if (buf.length > nMaxNumSize ) {
return false;
}
if (buf.length > 0) {
// Check that the number is encoded with the minimum possible number
// of bytes.
//
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((buf[buf.length-1] & 0x7f) == 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set it
// would conflict with the sign bit. An example of this case is
// +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) == 0) {
return false;
}
}
}
return true;
};
/**
*
* minimally encode the buffer content
*
* @param {number} nMaxNumSize (max allowed size)
*/
Interpreter._minimallyEncode = function(buf) {
if (buf.length == 0) {
return buf;
}
// If the last byte is not 0x00 or 0x80, we are minimally encoded.
var last = buf[buf.length - 1];
if (last & 0x7f) {
return buf;
}
// If the script is one byte long, then we have a zero, which encodes as an
// empty array.
if (buf.length == 1) {
return Buffer.from('');
}
// If the next byte has it sign bit set, then we are minimaly encoded.
if (buf[buf.length - 2] & 0x80) {
return buf;
}
// We are not minimally encoded, we need to figure out how much to trim.
for (var i = buf.length - 1; i > 0; i--) {
// We found a non zero byte, time to encode.
if (buf[i - 1] != 0) {
if (buf[i - 1] & 0x80) {
// We found a byte with it sign bit set so we need one more
// byte.
buf[i++] = last;
} else {
// the sign bit is clear, we can use it.
buf[i - 1] |= last;
}
return buf.slice(0,i);
}
}
// If we the whole thing is zeros, then we have a zero.
return Buffer.from('');
}
/**
* Based on bitcoind's EvalScript function, with the inner loop moved to
* Interpreter.prototype.step()
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
*/
Interpreter.prototype.evaluate = function() {
if (this.script.toBuffer().length > 10000) {
this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE';
return false;
}
try {
while (this.pc < this.script.chunks.length) {
var fSuccess = this.step();
if (!fSuccess) {
return false;
}
}
// Size limits
if (this.stack.length + this.altstack.length > 1000) {
this.errstr = 'SCRIPT_ERR_STACK_SIZE';
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 & 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(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.
if (nSequenceMasked.gt(txToSequenceMasked)) {
return false;
}
return true;
}
/**
* Based on the inner loop of bitcoind's EvalScript function
* bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
*/
Interpreter.prototype.step = function() {
var self = this;
function stacktop(i) {
return self.stack[self.stack.length +i];
}
function isOpcodeDisabled(opcode) {
switch (opcode) {
case Opcode.OP_INVERT:
case Opcode.OP_2MUL:
case Opcode.OP_2DIV:
case Opcode.OP_MUL:
case Opcode.OP_LSHIFT:
case Opcode.OP_RSHIFT:
// Disabled opcodes.
return true;
case Opcode.OP_DIV:
case Opcode.OP_MOD:
case Opcode.OP_SPLIT:
case Opcode.OP_CAT:
case Opcode.OP_AND:
case Opcode.OP_OR:
case Opcode.OP_XOR:
case Opcode.OP_BIN2NUM:
case Opcode.OP_NUM2BIN:
// Opcodes that have been reenabled and do not need any flag as for Nov 14,2018
return false;
default:
break;
}
return false;
}
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, bufMessage, subscript;
var sig, pubkey;
var fValue, fSuccess;
// 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;
}
// 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 (isOpcodeDisabled(opcodenum)) {
this.errstr = 'SCRIPT_ERR_DISABLED_OPCODE';
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(stacktop(-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 = stacktop(-1);
if (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALIF) {
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 = stacktop(-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 = stacktop(-2);
buf2 = stacktop(-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 = stacktop(-3);
buf2 = stacktop(-2);
var buf3 = stacktop(-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 = stacktop(-4);
buf2 = stacktop(-3);
this.stack.push(buf1);
this.stack.push(buf2);
}
break;
case Opcode.OP_2ROT:
{
// (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2)
if (this.stack.length < 6) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
spliced = this.stack.splice(this.stack.length - 6, 2);
this.stack.push(spliced[0]);
this.stack.push(spliced[1]);
}
break;
case Opcode.OP_2SWAP:
{
// (x1 x2 x3 x4 -- x3 x4 x1 x2)
if (this.stack.length < 4) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
spliced = this.stack.splice(this.stack.length - 4, 2);
this.stack.push(spliced[0]);
this.stack.push(spliced[1]);
}
break;
case Opcode.OP_IFDUP:
{
// (x - 0 | x x)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf = stacktop(-1);
fValue = Interpreter.castToBool(buf);
if (fValue) {
this.stack.push(buf);
}
}
break;
case Opcode.OP_DEPTH:
{
// -- stacksize
buf = new BN(this.stack.length).toScriptNumBuffer();
this.stack.push(buf);
}
break;
case Opcode.OP_DROP:
{
// (x -- )
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
this.stack.pop();
}
break;
case Opcode.OP_DUP:
{
// (x -- x x)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
this.stack.push(stacktop(-1));
}
break;
case Opcode.OP_NIP:
{
// (x1 x2 -- x2)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
this.stack.splice(this.stack.length - 2, 1);
}
break;
case Opcode.OP_OVER:
{
// (x1 x2 -- x1 x2 x1)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
this.stack.push(stacktop(-2));
}
break;
case Opcode.OP_PICK:
case Opcode.OP_ROLL:
{
// (xn ... x2 x1 x0 n - xn ... x2 x1 x0 xn)
// (xn ... x2 x1 x0 n - ... x2 x1 x0 xn)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf = stacktop(-1);
bn = BN.fromScriptNumBuffer(buf, fRequireMinimal);
n = bn.toNumber();
this.stack.pop();
if (n < 0 || n >= this.stack.length) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf = stacktop(-n-1);
if (opcodenum === Opcode.OP_ROLL) {
this.stack.splice(this.stack.length - n - 1, 1);
}
this.stack.push(buf);
}
break;
case Opcode.OP_ROT:
{
// (x1 x2 x3 -- x2 x3 x1)
// x2 x1 x3 after first swap
// x2 x3 x1 after second swap
if (this.stack.length < 3) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
x1 = stacktop(-3);
x2 = stacktop(-2);
var x3 = stacktop(-1);
this.stack[this.stack.length - 3] = x2;
this.stack[this.stack.length - 2] = x3;
this.stack[this.stack.length - 1] = x1;
}
break;
case Opcode.OP_SWAP:
{
// (x1 x2 -- x2 x1)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
x1 = stacktop(-2);
x2 = stacktop(-1);
this.stack[this.stack.length - 2] = x2;
this.stack[this.stack.length - 1] = x1;
}
break;
case Opcode.OP_TUCK:
{
// (x1 x2 -- x2 x1 x2)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
this.stack.splice(this.stack.length - 2, 0, stacktop(-1));
}
break;
case Opcode.OP_SIZE:
{
// (in -- in size)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
bn = new BN(stacktop(-1).length);
this.stack.push(bn.toScriptNumBuffer());
}
break;
//
// Bitwise logic
//
case Opcode.OP_AND:
case Opcode.OP_OR:
case Opcode.OP_XOR:
{
// (x1 x2 - out)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf1 = stacktop(-2);
buf2 = stacktop(-1);
// Inputs must be the same size
if (buf1.length !== buf2.length) {
this.errstr = 'SCRIPT_ERR_INVALID_OPERAND_SIZE';
return false;
}
// To avoid allocating, we modify vch1 in place.
switch (opcodenum) {
case Opcode.OP_AND:
for (var i = 0; i < buf1.length; i++) {
buf1[i] &= buf2[i];
}
break;
case Opcode.OP_OR:
for (var i = 0; i < buf1.length; i++) {
buf1[i] |= buf2[i];
}
break;
case Opcode.OP_XOR:
for (var i = 0; i < buf1.length; i++) {
buf1[i] ^= buf2[i];
}
break;
default:
break;
}
// And pop vch2.
this.stack.pop()
}
break;
case Opcode.OP_EQUAL:
case Opcode.OP_EQUALVERIFY:
//case Opcode.OP_NOTEQUAL: // use Opcode.OP_NUMNOTEQUAL
{
// (x1 x2 - bool)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf1 = stacktop(-2);
buf2 = stacktop(-1);
var fEqual = buf1.toString('hex') === buf2.toString('hex');
this.stack.pop();
this.stack.pop();
this.stack.push(fEqual ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_EQUALVERIFY) {
if (fEqual) {
this.stack.pop();
} else {
this.errstr = 'SCRIPT_ERR_EQUALVERIFY';
return false;
}
}
}
break;
//
// Numeric
//
case Opcode.OP_1ADD:
case Opcode.OP_1SUB:
case Opcode.OP_NEGATE:
case Opcode.OP_ABS:
case Opcode.OP_NOT:
case Opcode.OP_0NOTEQUAL:
{
// (in -- out)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf = stacktop(-1);
bn = BN.fromScriptNumBuffer(buf, fRequireMinimal);
switch (opcodenum) {
case Opcode.OP_1ADD:
bn = bn.add(BN.One);
break;
case Opcode.OP_1SUB:
bn = bn.sub(BN.One);
break;
case Opcode.OP_NEGATE:
bn = bn.neg();
break;
case Opcode.OP_ABS:
if (bn.cmp(BN.Zero) < 0) {
bn = bn.neg();
}
break;
case Opcode.OP_NOT:
bn = new BN((bn.cmp(BN.Zero) === 0) + 0);
break;
case Opcode.OP_0NOTEQUAL:
bn = new BN((bn.cmp(BN.Zero) !== 0) + 0);
break;
//default: assert(!'invalid opcode'); break; // TODO: does this ever occur?
}
this.stack.pop();
this.stack.push(bn.toScriptNumBuffer());
}
break;
case Opcode.OP_ADD:
case Opcode.OP_SUB:
case Opcode.OP_MOD:
case Opcode.OP_DIV:
case Opcode.OP_BOOLAND:
case Opcode.OP_BOOLOR:
case Opcode.OP_NUMEQUAL:
case Opcode.OP_NUMEQUALVERIFY:
case Opcode.OP_NUMNOTEQUAL:
case Opcode.OP_LESSTHAN:
case Opcode.OP_GREATERTHAN:
case Opcode.OP_LESSTHANOREQUAL:
case Opcode.OP_GREATERTHANOREQUAL:
case Opcode.OP_MIN:
case Opcode.OP_MAX:
{
// (x1 x2 -- out)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
bn1 = BN.fromScriptNumBuffer(stacktop(-2), fRequireMinimal);
bn2 = BN.fromScriptNumBuffer(stacktop(-1), fRequireMinimal);
bn = new BN(0);
switch (opcodenum) {
case Opcode.OP_ADD:
bn = bn1.add(bn2);
break;
case Opcode.OP_SUB:
bn = bn1.sub(bn2);
break;
case Opcode.OP_DIV:
// denominator must not be 0
if (bn2 == 0) {
this.errstr = 'SCRIPT_ERR_DIV_BY_ZERO';
return false;
}
bn = bn1.div(bn2);
break;
case Opcode.OP_MOD:
// divisor must not be 0
if (bn2 == 0) {
this.errstr = 'SCRIPT_ERR_DIV_BY_ZERO';
return false;
}
bn = bn1.mod(bn2);
break;
case Opcode.OP_BOOLAND:
bn = new BN(((bn1.cmp(BN.Zero) !== 0) && (bn2.cmp(BN.Zero) !== 0)) + 0);
break;
// case Opcode.OP_BOOLOR: bn = (bn1 != bnZero || bn2 != bnZero); break;
case Opcode.OP_BOOLOR:
bn = new BN(((bn1.cmp(BN.Zero) !== 0) || (bn2.cmp(BN.Zero) !== 0)) + 0);
break;
// case Opcode.OP_NUMEQUAL: bn = (bn1 == bn2); break;
case Opcode.OP_NUMEQUAL:
bn = new BN((bn1.cmp(bn2) === 0) + 0);
break;
// case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 == bn2); break;
case Opcode.OP_NUMEQUALVERIFY:
bn = new BN((bn1.cmp(bn2) === 0) + 0);
break;
// case Opcode.OP_NUMNOTEQUAL: bn = (bn1 != bn2); break;
case Opcode.OP_NUMNOTEQUAL:
bn = new BN((bn1.cmp(bn2) !== 0) + 0);
break;
// case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break;
case Opcode.OP_LESSTHAN:
bn = new BN((bn1.cmp(bn2) < 0) + 0);
break;
// case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break;
case Opcode.OP_GREATERTHAN:
bn = new BN((bn1.cmp(bn2) > 0) + 0);
break;
// case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break;
case Opcode.OP_LESSTHANOREQUAL:
bn = new BN((bn1.cmp(bn2) <= 0) + 0);
break;
// case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break;
case Opcode.OP_GREATERTHANOREQUAL:
bn = new BN((bn1.cmp(bn2) >= 0) + 0);
break;
case Opcode.OP_MIN:
bn = (bn1.cmp(bn2) < 0 ? bn1 : bn2);
break;
case Opcode.OP_MAX:
bn = (bn1.cmp(bn2) > 0 ? bn1 : bn2);
break;
// default: assert(!'invalid opcode'); break; //TODO: does this ever occur?
}
this.stack.pop();
this.stack.pop();
this.stack.push(bn.toScriptNumBuffer());
if (opcodenum === Opcode.OP_NUMEQUALVERIFY) {
// if (CastToBool(stacktop(-1)))
if (Interpreter.castToBool(stacktop(-1))) {
this.stack.pop();
} else {
this.errstr = 'SCRIPT_ERR_NUMEQUALVERIFY';
return false;
}
}
}
break;
case Opcode.OP_WITHIN:
{
// (x min max -- out)
if (this.stack.length < 3) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
bn1 = BN.fromScriptNumBuffer(stacktop(-3), fRequireMinimal);
bn2 = BN.fromScriptNumBuffer(stacktop(-2), fRequireMinimal);
var bn3 = BN.fromScriptNumBuffer(stacktop(-1), fRequireMinimal);
//bool fValue = (bn2 <= bn1 && bn1 < bn3);
fValue = (bn2.cmp(bn1) <= 0) && (bn1.cmp(bn3) < 0);
this.stack.pop();
this.stack.pop();
this.stack.pop();
this.stack.push(fValue ? Interpreter.true : Interpreter.false);
}
break;
//
// Crypto
//
case Opcode.OP_RIPEMD160:
case Opcode.OP_SHA1:
case Opcode.OP_SHA256:
case Opcode.OP_HASH160:
case Opcode.OP_HASH256:
{
// (in -- hash)
if (this.stack.length < 1) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
buf = stacktop(-1);
//valtype vchHash((opcode == Opcode.OP_RIPEMD160 ||
// opcode == Opcode.OP_SHA1 || opcode == Opcode.OP_HASH160) ? 20 : 32);
var bufHash;
if (opcodenum === Opcode.OP_RIPEMD160) {
bufHash = Hash.ripemd160(buf);
} else if (opcodenum === Opcode.OP_SHA1) {
bufHash = Hash.sha1(buf);
} else if (opcodenum === Opcode.OP_SHA256) {
bufHash = Hash.sha256(buf);
} else if (opcodenum === Opcode.OP_HASH160) {
bufHash = Hash.sha256ripemd160(buf);
} else if (opcodenum === Opcode.OP_HASH256) {
bufHash = Hash.sha256sha256(buf);
}
this.stack.pop();
this.stack.push(bufHash);
}
break;
case Opcode.OP_CODESEPARATOR:
{
// Hash starts after the code separator
this.pbegincodehash = this.pc;
}
break;
case Opcode.OP_CHECKSIG:
case Opcode.OP_CHECKSIGVERIFY:
{
// (sig pubkey -- bool)
if (this.stack.length < 2) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
bufSig = stacktop(-2);
bufPubkey = stacktop(-1);
if (!this.checkTxSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) {
return false;
}
// Subset of script starting at the most recent codeseparator
// CScript scriptCode(pbegincodehash, pend);
subscript = new Script().set({
chunks: this.script.chunks.slice(this.pbegincodehash)
});
// Drop the signature, since there's no way for a signature to sign itself
var tmpScript = new Script().add(bufSig);
subscript.findAndDelete(tmpScript);
try {
sig = Signature.fromTxFormat(bufSig);
pubkey = PublicKey.fromBuffer(bufPubkey, false);
fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.satoshisBN, this.flags);
} catch (e) {
//invalid sig or pubkey
fSuccess = false;
}
if (!fSuccess && (this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL) &&
bufSig.length) {
this.errstr = 'SCRIPT_ERR_NULLFAIL';
return false;
}
this.stack.pop();
this.stack.pop();
// stack.push_back(fSuccess ? vchTrue : vchFalse);
this.stack.push(fSuccess ? Interpreter.true : Interpreter.false);
if (opcodenum === Opcode.OP_CHECKSIGVERIFY) {
if (fSuccess) {
this.stack.pop();
} else {
this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY';
return false;
}
}
}
break;
case Opcode.OP_CHECKDATASIG:
case Opcode.OP_CHECKDATASIGVERIFY:
{
// Make sure this remains an error before activation.
if ((this.flags & Interpreter.SCRIPT_ENABLE_CHECKDATASIG) == 0) {
this.errstr = 'SCRIPT_ERR_CHECKDATASIG';
return false;
}
// (sig message pubkey -- bool)
if (this.stack.length < 3) {
this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
return false;
}
bufSig = stacktop(-3);
bufMessage = stacktop(-2);
bufPubkey = stacktop(-1);
if (!this.checkDataSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) {
return false;
}
fSuccess = false;
try {
sig = Signature.fromDataFormat(bufSig);
pubkey = PublicKey.fromBuffer(bufPubkey, false);