From c64a38a54ab4be3546fee4aa8203941265d6e60e Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Thu, 11 Dec 2014 15:59:54 -0300 Subject: [PATCH] trying to fix ScriptInterpreter --- lib/script_interpreter.js | 1142 ++++++++++++++++++++++++++++++++++++ test/script_interpreter.js | 306 ++++++++++ 2 files changed, 1448 insertions(+) create mode 100644 lib/script_interpreter.js create mode 100644 test/script_interpreter.js diff --git a/lib/script_interpreter.js b/lib/script_interpreter.js new file mode 100644 index 0000000..5576c7e --- /dev/null +++ b/lib/script_interpreter.js @@ -0,0 +1,1142 @@ +'use strict'; + +var _ = require('lodash'); + +var Script = require('./script'); +var Opcode = require('./opcode'); +var BN = require('./crypto/bn'); +var Hash = require('./crypto/hash'); +var BufferReader = require('./encoding/bufferreader'); +var BufferWriter = require('./encoding/bufferwriter'); +var Signature = require('./crypto/signature'); +var PublicKey = require('./publickey'); +var Transaction = require('./transaction'); + +/** + * 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., ScriptInterpreter().verify( ... ); + */ +var ScriptInterpreter = function ScriptInterpreter(obj) { + if (!(this instanceof ScriptInterpreter)) { + return new ScriptInterpreter(obj); + } + if (obj) { + this.initialize(); + this.set(obj); + } else { + this.initialize(); + } +}; + +module.exports = ScriptInterpreter; + +ScriptInterpreter.prototype.initialize = function(obj) { + this.stack = []; + this.altstack = []; + this.pc = 0; + this.pbegincodehash = 0; + this.nOpCount = 0; + this.vfExec = []; + this.errstr = ''; + this.flags = 0; +}; + +ScriptInterpreter.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.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; +}; + +ScriptInterpreter.true = new Buffer([1]); +ScriptInterpreter.false = new Buffer([]); + +ScriptInterpreter.MAX_SCRIPT_ELEMENT_SIZE = 520; + +// flags taken from bitcoind +// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 +ScriptInterpreter.SCRIPT_VERIFY_NONE = 0; + +// Evaluate P2SH subscripts (softfork safe, BIP16). +ScriptInterpreter.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). +ScriptInterpreter.SCRIPT_VERIFY_STRICTENC = (1 << 1); + +// Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) +ScriptInterpreter.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). +ScriptInterpreter.SCRIPT_VERIFY_LOW_S = (1 << 3); + +// verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). +ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY = (1 << 4); + +// Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). +ScriptInterpreter.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) +ScriptInterpreter.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. +ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7); + +ScriptInterpreter.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 + */ +ScriptInterpreter.prototype.checkSignatureEncoding = function(buf) { + var sig; + if ((this.flags & (ScriptInterpreter.SCRIPT_VERIFY_DERSIG | ScriptInterpreter.SCRIPT_VERIFY_LOW_S | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isTxDER(buf)) { + this.errstr = 'SCRIPT_ERR_SIG_DER'; + return false; + } else if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_LOW_S) !== 0) { + sig = Signature().fromTxFormat(buf); + if (!sig.hasLowS()) { + this.errstr = 'SCRIPT_ERR_SIG_DER'; + return false; + } + } else if ((this.flags & ScriptInterpreter.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 + */ +ScriptInterpreter.prototype.checkPubkeyEncoding = function(buf) { + if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_STRICTENC) !== 0 && !Pubkey.isCompressedOrUncompressed(buf)) { + this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; + return false; + } + return true; +}; + +/** + * Based on bitcoind's EvalScript function, with the inner loop moved to + * ScriptInterpreter.prototype.step() + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +ScriptInterpreter.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; +}; + +/** + * Based on the inner loop of bitcoind's EvalScript function + * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 + */ +ScriptInterpreter.prototype.step = function() { + + var fRequireMinimal = (this.flags & ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; + + //bool fExec = !count(vfExec.begin(), vfExec.end(), false); + var fExec = (this.vfExec.indexOf(false) !== -1); + + + // Read instruction + var chunk = this.script.chunks[this.pc]; + console.log('STEP!' + JSON.stringify(chunk)); + this.pc++; + var opcodenum = chunk.opcodenum; + if (_.isUndefined(opcodenum)) { + this.errstr = 'SCRIPT_ERR_BAD_OPCODE'; + return false; + } + if (chunk.buf && chunk.buf.length > ScriptInterpreter.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 (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; + } + + 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(ScriptInterpreter.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)); + var n = opcodenum - (Opcode.OP_1 - 1); + var buf = 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_NOP1: + case Opcode.OP_NOP2: + case Opcode.OP_NOP3: + 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 & ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { + this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; + return false; + } + } + break; + + case Opcode.OP_IF: + case Opcode.OP_NOTIF: + { + // if [statements] [else [statements]] endif + // bool fValue = false; + var fValue = false; + if (fExec) { + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; + return false; + } + var buf = this.stack.pop(); + fValue = ScriptInterpreter.castToBool(buf); + if (opcodenum === Opcode.OP_NOTIF) + fValue = !fValue; + } + 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; + } + var buf = this.stack[this.stack.length - 1]; + var fValue = ScriptInterpreter.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; + } + var buf1 = this.stack[this.stack.length - 2]; + var 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; + } + var buf1 = this.stack[this.stack.length - 3]; + var 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; + } + var buf1 = this.stack[this.stack.length - 4]; + var buf2 = this.stack[this.stack.length - 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; + } + var 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; + } + var 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; + } + var buf = this.stack[this.stack.length - 1]; + var fValue = ScriptInterpreter.castToBool(buf); + if (fValue) + this.stack.push(buf); + } + break; + + case Opcode.OP_DEPTH: + { + // -- stacksize + var buf = 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(this.stack[this.stack.length - 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(this.stack[this.stack.length - 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; + } + var buf = this.stack[this.stack.length - 1]; + var bn = BN().fromScriptNumBuffer(buf, fRequireMinimal); + var n = bn.toNumber(); + this.stack.pop(); + if (n < 0 || n >= this.stack.length) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var buf = this.stack[this.stack.length - 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; + } + var x1 = this.stack[this.stack.length - 3]; + var x2 = this.stack[this.stack.length - 2]; + var x3 = this.stack[this.stack.length - 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; + } + var x1 = this.stack[this.stack.length - 2]; + var x2 = this.stack[this.stack.length - 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, this.stack[this.stack.length - 1]); + } + break; + + + case Opcode.OP_SIZE: + { + // (in -- in size) + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + var bn = BN(this.stack[this.stack.length - 1].length); + this.stack.push(bn.toScriptNumBuffer()); + } + break; + + + // + // Bitwise logic + // + 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; + } + var buf1 = this.stack[this.stack.length - 2]; + var buf2 = this.stack[this.stack.length - 1]; + var fEqual = buf1.toString('hex') === buf2.toString('hex'); + // Opcode.OP_NOTEQUAL is disabled because it would be too easy to say + // something like n != 1 and have some wiseguy pass in 1 with extra + // zero bytes after it (numerically, 0x01 == 0x0001 == 0x000001) + //if (opcode == Opcode.OP_NOTEQUAL) + // fEqual = !fEqual; + this.stack.pop(); + this.stack.pop(); + this.stack.push(fEqual ? ScriptInterpreter.true : ScriptInterpreter.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; + } + var buf = this.stack[this.stack.length - 1]; + var bn = BN().fromScriptNumBuffer(buf, fRequireMinimal); + switch (opcodenum) { + case Opcode.OP_1ADD: + bn = bn.add(1); + break; + case Opcode.OP_1SUB: + bn = bn.sub(1); + break; + case Opcode.OP_NEGATE: + bn = bn.neg(); + break; + case Opcode.OP_ABS: + if (bn.cmp(0) < 0) bn = bn.neg(); + break; + case Opcode.OP_NOT: + bn = BN((bn.cmp(0) === 0) + 0); + break; + case Opcode.OP_0NOTEQUAL: + bn = BN((bn.cmp(0) !== 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_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; + } + var bn1 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + var bn2 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + var bn = 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_BOOLAND: bn = (bn1 != bnZero && bn2 != bnZero); break; + case Opcode.OP_BOOLAND: + bn = BN(((bn1.cmp(0) !== 0) && (bn2.cmp(0) !== 0)) + 0); + break; + // case Opcode.OP_BOOLOR: bn = (bn1 != bnZero || bn2 != bnZero); break; + case Opcode.OP_BOOLOR: + bn = BN(((bn1.cmp(0) !== 0) || (bn2.cmp(0) !== 0)) + 0); + break; + // case Opcode.OP_NUMEQUAL: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUAL: + bn = BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 == bn2); break; + case Opcode.OP_NUMEQUALVERIFY: + bn = BN((bn1.cmp(bn2) === 0) + 0); + break; + // case Opcode.OP_NUMNOTEQUAL: bn = (bn1 != bn2); break; + case Opcode.OP_NUMNOTEQUAL: + bn = BN((bn1.cmp(bn2) !== 0) + 0); + break; + // case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break; + case Opcode.OP_LESSTHAN: + bn = BN((bn1.cmp(bn2) < 0) + 0); + break; + // case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break; + case Opcode.OP_GREATERTHAN: + bn = BN((bn1.cmp(bn2) > 0) + 0); + break; + // case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break; + case Opcode.OP_LESSTHANOREQUAL: + bn = BN((bn1.cmp(bn2) <= 0) + 0); + break; + // case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break; + case Opcode.OP_GREATERTHANOREQUAL: + bn = 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 (ScriptInterpreter.castToBool(this.stack[this.stack.length - 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; + } + var bn1 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 3], fRequireMinimal); + var bn2 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 2], fRequireMinimal); + var bn3 = BN().fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal); + //bool fValue = (bn2 <= bn1 && bn1 < bn3); + var fValue = (bn2.cmp(bn1) <= 0) && (bn1.cmp(bn3) < 0); + this.stack.pop(); + this.stack.pop(); + this.stack.pop(); + this.stack.push(fValue ? ScriptInterpreter.true : ScriptInterpreter.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; + } + var buf = this.stack[this.stack.length - 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; + } + + var bufSig = this.stack[this.stack.length - 2]; + var bufPubkey = this.stack[this.stack.length - 1]; + + // Subset of script starting at the most recent codeseparator + // CScript scriptCode(pbegincodehash, pend); + var subscript = Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signature, since there's no way for a signature to sign itself + subscript.findAndDelete(Script().writeBuffer(bufSig)); + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + // serror is set + return false; + } + + var fSuccess; + try { + var sig = Signature().fromTxFormat(bufSig); + var pubkey = Pubkey().fromBuffer(bufPubkey, false); + fSuccess = this.tx.verify(sig, pubkey, this.nin, subscript); + } catch (e) { + //invalid sig or pubkey + fSuccess = false; + } + + this.stack.pop(); + this.stack.pop(); + // stack.push_back(fSuccess ? vchTrue : vchFalse); + this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); + if (opcodenum === Opcode.OP_CHECKSIGVERIFY) { + if (fSuccess) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_CHECKSIGVERIFY'; + return false; + } + } + } + break; + + case Opcode.OP_CHECKMULTISIG: + case Opcode.OP_CHECKMULTISIGVERIFY: + { + // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) + + var i = 1; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nKeysCount = BN().fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nKeysCount < 0 || nKeysCount > 20) { + this.errstr = 'SCRIPT_ERR_PUBKEY_COUNT'; + return false; + } + this.nOpCount += nKeysCount; + if (this.nOpCount > 201) { + this.errstr = 'SCRIPT_ERR_OP_COUNT'; + return false; + } + // int ikey = ++i; + var ikey = ++i; + i += nKeysCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + var nSigsCount = BN().fromScriptNumBuffer(this.stack[this.stack.length - i], fRequireMinimal).toNumber(); + if (nSigsCount < 0 || nSigsCount > nKeysCount) { + this.errstr = 'SCRIPT_ERR_SIG_COUNT'; + return false; + } + // int isig = ++i; + var isig = ++i; + i += nSigsCount; + if (this.stack.length < i) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + + // Subset of script starting at the most recent codeseparator + var subscript = Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }); + + // Drop the signatures, since there's no way for a signature to sign itself + for (var k = 0; k < nSigsCount; k++) { + var bufSig = this.stack[this.stack.length - isig - k]; + subscript.findAndDelete(Script().writeBuffer(bufSig)); + } + + var fSuccess = true; + while (fSuccess && nSigsCount > 0) { + // valtype& vchSig = stacktop(-isig); + var bufSig = this.stack[this.stack.length - isig]; + // valtype& vchPubKey = stacktop(-ikey); + var bufPubkey = this.stack[this.stack.length - ikey]; + + if (!this.checkSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { + // serror is set + return false; + } + + var fOk; + try { + var sig = Signature().fromTxFormat(bufSig); + var pubkey = Pubkey().fromBuffer(bufPubkey, false); + fOk = this.tx.verify(sig, pubkey, this.nin, subscript); + } catch (e) { + //invalid sig or pubkey + fOk = false; + } + + if (fOk) { + isig++; + nSigsCount--; + } + ikey++; + nKeysCount--; + + // If there are more signatures left than keys left, + // then too many signatures have failed + if (nSigsCount > nKeysCount) + fSuccess = false; + } + + // Clean up stack of actual arguments + while (i-- > 1) + this.stack.pop(); + + // A bug causes CHECKMULTISIG to consume one extra argument + // whose contents were not checked in any way. + // + // Unfortunately this is a potential source of mutability, + // so optionally verify it is exactly equal to zero prior + // to removing it from the stack. + if (this.stack.length < 1) { + this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; + return false; + } + if ((this.flags & ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY) && this.stack[this.stack.length - 1].length) { + this.errstr = 'SCRIPT_ERR_SIG_NULLDUMMY'; + return false; + } + this.stack.pop(); + + this.stack.push(fSuccess ? ScriptInterpreter.true : ScriptInterpreter.false); + + if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) { + if (fSuccess) + this.stack.pop(); + else { + this.errstr = 'SCRIPT_ERR_CHECKMULTISIGVERIFY'; + return false; + } + } + } + break; + + default: + this.errstr = 'SCRIPT_ERR_BAD_OPCODE'; + return false; + } + } + + return true; +} + +/** + * Translated from bitcoind's VerifyScript + */ +ScriptInterpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) { + if (_.isUndefined(tx)) { + tx = new Transaction(); + } + if (_.isUndefined(nin)) { + nin = 0; + } + this.set({ + script: scriptSig, + tx: tx, + nin: nin, + flags: flags + }); + + if ((flags & ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.isPushOnly()) { + this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; + return false; + } + + if (!this.evaluate()) + return false; + + if (flags & ScriptInterpreter.SCRIPT_VERIFY_P2SH) + var stackCopy = this.stack.slice(); + + var stack = this.stack; + this.initialize(); + this.set({ + script: scriptPubkey, + stack: stack, + tx: tx, + nin: nin, + flags: flags + }); + + if (!this.evaluate()) + return false; + + if (this.stack.length === 0) { + console.log('stack 0'); + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + + var buf = this.stack[this.stack.length - 1]; + if (!ScriptInterpreter.castToBool(buf)) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + + // Additional validation for spend-to-script-hash transactions: + if ((flags & ScriptInterpreter.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 pubkeySerialized = stackCopy[stackCopy.length - 1]; + var scriptPubkey2 = Script().fromBuffer(pubkeySerialized); + stackCopy.pop(); + + this.initialize(); + this.set({ + script: scriptPubkey2, + stack: stackCopy, + tx: tx, + nin: nin, + flags: flags + }); + + if (!this.evaluate()) + // serror is set + return false; + + if (stackCopy.length === 0) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } + + if (!ScriptInterpreter.castToBool(stackCopy[stackCopy.length - 1])) { + this.errstr = 'SCRIPT_ERR_EVAL_FALSE'; + return false; + } else { + return true; + } + } + + return true; +}; diff --git a/test/script_interpreter.js b/test/script_interpreter.js new file mode 100644 index 0000000..a6dd0c9 --- /dev/null +++ b/test/script_interpreter.js @@ -0,0 +1,306 @@ +'use strict'; + +var should = require('chai').should(); +var bitcore = require('..'); +var ScriptInterpreter = bitcore.ScriptInterpreter; +var Transaction = bitcore.Transaction; +var Script = bitcore.Script; +var BN = bitcore.crypto.BN; +var Sig = bitcore.crypto.Signature; +var BufferReader = bitcore.encoding.BufferReader; + +var script_valid = require('./data/bitcoind/script_valid'); +var script_invalid = require('./data/bitcoind/script_invalid'); +var tx_valid = require('./transaction/tx_valid'); +var tx_invalid = require('./transaction/tx_invalid'); + +describe('ScriptInterpreter', function() { + + it('should make a new interp', function() { + var interp = new ScriptInterpreter(); + (interp instanceof ScriptInterpreter).should.equal(true); + interp.stack.length.should.equal(0); + interp.altstack.length.should.equal(0); + interp.pc.should.equal(0); + interp.pbegincodehash.should.equal(0); + interp.nOpCount.should.equal(0); + interp.vfExec.length.should.equal(0); + interp.errstr.should.equal(''); + interp.flags.should.equal(0); + }); + + describe('@castToBool', function() { + + it('should cast these bufs to bool correctly', function() { + ScriptInterpreter.castToBool(BN(0).toSM({ + endian: 'little' + })).should.equal(false); + ScriptInterpreter.castToBool(new Buffer('0080', 'hex')).should.equal(false); //negative 0 + ScriptInterpreter.castToBool(BN(1).toSM({ + endian: 'little' + })).should.equal(true); + ScriptInterpreter.castToBool(BN(-1).toSM({ + endian: 'little' + })).should.equal(true); + + var buf = new Buffer('00', 'hex'); + var bool = BN().fromSM(buf, { + endian: 'little' + }).cmp(0) !== 0; + ScriptInterpreter.castToBool(buf).should.equal(bool); + }); + + }); + + describe('#verify', function() { + + it('should verify these trivial scripts', function() { + var verified; + var si = ScriptInterpreter(); + verified = si.verify(Script('OP_1'), Script('OP_1')); + console.log(si.errstr); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_0')); + verified.should.equal(false); + verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_1')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_CODESEPARATOR'), Script('OP_1')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script(''), Script('OP_DEPTH OP_0 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1 OP_2'), Script('OP_2 OP_EQUALVERIFY OP_1 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('9 0x000000000000000010'), Script('')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_1'), Script('OP_15 OP_ADD OP_16 OP_EQUAL')); + verified.should.equal(true); + verified = ScriptInterpreter().verify(Script('OP_0'), Script('OP_IF OP_VER OP_ELSE OP_1 OP_ENDIF')); + verified.should.equal(true); + }); + + it('should verify this new pay-to-pubkey script', function() { + var keypair = Keypair().fromRandom(); + var scriptPubkey = Script().writeBuffer(keypair.pubkey.toDER(true)).writeOp('OP_CHECKSIG'); + + var hashbuf = new Buffer(32); + hashbuf.fill(0); + var credtx = Transaction(); + credtx.addTxin(hashbuf, 0xffffffff, Script('OP_0 OP_0'), 0xffffffff); + credtx.addTxout(BN(0), scriptPubkey); + + var idbuf = credtx.hash(); + var spendtx = Transaction(); + spendtx.addTxin(idbuf, 0, Script(), 0xffffffff); + spendtx.addTxout(BN(0), Script()); + + var sig = spendtx.sign(keypair, Sig.SIGHASH_ALL, 0, scriptPubkey); + var scriptSig = Script().writeBuffer(sig.toTxFormat()); + spendtx.txins[0].setScript(scriptSig); + + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0); + verified.should.equal(true); + }); + + it('should verify this pay-to-pubkey script from script_valid.json', function() { + var scriptSig = Script().fromBitcoindString('0x47 0x3044022007415aa37ce7eaa6146001ac8bdefca0ddcba0e37c5dc08c4ac99392124ebac802207d382307fd53f65778b07b9c63b6e196edeadf0be719130c5db21ff1e700d67501'); + var scriptPubkey = Script().fromBitcoindString('0x41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 CHECKSIG'); + + var hashbuf = new Buffer(32); + hashbuf.fill(0); + var credtx = Transaction(); + credtx.addTxin(hashbuf, 0xffffffff, Script('OP_0 OP_0'), 0xffffffff); + credtx.addTxout(BN(0), scriptPubkey); + + var idbuf = credtx.hash(); + var spendtx = Transaction(); + spendtx.addTxin(idbuf, 0, scriptSig, 0xffffffff); + spendtx.addTxout(BN(0), Script()); + + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, 0); + verified.should.equal(true); + }); + + }); + + describe('vectors', function() { + + var getFlags = function getFlags(flagstr) { + var flags = 0; + if (flagstr.indexOf('NONE') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NONE; + } + if (flagstr.indexOf('P2SH') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_P2SH; + } + if (flagstr.indexOf('STRICTENC') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_STRICTENC; + } + if (flagstr.indexOf('DERSIG') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DERSIG; + } + if (flagstr.indexOf('LOW_S') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_LOW_S; + } + if (flagstr.indexOf('NULLDUMMY') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_NULLDUMMY; + } + if (flagstr.indexOf('SIGPUSHONLY') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_SIGPUSHONLY; + } + if (flagstr.indexOf('MINIMALDATA') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_MINIMALDATA; + } + if (flagstr.indexOf('DISCOURAGE_UPGRADABLE_NOPS') !== -1) { + flags = flags | ScriptInterpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS; + } + return flags; + }; + + var c = 0; + script_valid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + var descstr = vector[3]; + it('should pass script_valid vector ' + c + '(' + descstr + ')', function() { + var scriptSig = Script().fromBitcoindString(vector[0]); + var scriptPubkey = Script().fromBitcoindString(vector[1]); + var flags = getFlags(vector[2]); + + var hashbuf = new Buffer(32); + hashbuf.fill(0); + var credtx = Transaction(); + credtx.addTxin(hashbuf, 0xffffffff, Script('OP_0 OP_0'), 0xffffffff); + credtx.addTxout(BN(0), scriptPubkey); + + var idbuf = credtx.hash(); + var spendtx = Transaction(); + spendtx.addTxin(idbuf, 0, scriptSig, 0xffffffff); + spendtx.addTxout(BN(0), Script()); + + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags); + verified.should.equal(true); + }); + }); + + c = 0; + script_invalid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + var descstr = vector[3]; + it('should pass script_invalid vector ' + c + '(' + descstr + ')', function() { + var scriptSig = Script().fromBitcoindString(vector[0]); + var scriptPubkey = Script().fromBitcoindString(vector[1]); + var flags = getFlags(vector[2]); + + var hashbuf = new Buffer(32); + hashbuf.fill(0); + var credtx = Transaction(); + credtx.addTxin(hashbuf, 0xffffffff, Script('OP_0 OP_0'), 0xffffffff); + credtx.addTxout(BN(0), scriptPubkey); + + var idbuf = credtx.hash(); + var spendtx = Transaction(); + spendtx.addTxin(idbuf, 0, scriptSig, 0xffffffff); + spendtx.addTxout(BN(0), Script()); + + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, spendtx, 0, flags); + verified.should.equal(false); + }); + }); + + c = 0; + tx_valid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + it('should pass tx_valid vector ' + c, function() { + var inputs = vector[0]; + var txhex = vector[1]; + var flags = getFlags(vector[2]); + + var map = {}; + inputs.forEach(function(input) { + var txoutnum = input[1]; + if (txoutnum === -1) { + txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int + } + map[input[0] + ':' + txoutnum] = Script().fromBitcoindString(input[2]); + }); + + var tx = Transaction().fromBuffer(new Buffer(txhex, 'hex')); + tx.txins.forEach(function(txin, j) { + var scriptSig = txin.script; + var txidhex = BufR(txin.txidbuf).readReverse().toString('hex'); + var txoutnum = txin.txoutnum; + var scriptPubkey = map[txidhex + ':' + txoutnum]; + should.exist(scriptPubkey); + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags); + verified.should.equal(true); + }); + }); + }); + + c = 0; + tx_invalid.forEach(function(vector) { + if (vector.length === 1) { + return; + } + c++; + + // tests intentionally not performed by the script interpreter: + // TODO: check this? + /* + if (c === 7 || // tests if valuebn is negative + c === 8 || // tests if valuebn is greater than MAX_MONEY + c === 10 || // tests if two inputs are equal + c === 11 || // coinbase + c === 12 || // coinbase + c === 13 // null input + ) { + return; + } + */ + + it('should pass tx_invalid vector ' + c, function() { + var inputs = vector[0]; + var txhex = vector[1]; + var flags = getFlags(vector[2]); + + var map = {}; + inputs.forEach(function(input) { + var txoutnum = input[1]; + if (txoutnum === -1) { + txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int + } + map[input[0] + ':' + txoutnum] = Script().fromBitcoindString(input[2]); + }); + + var tx = Transaction().fromBuffer(new Buffer(txhex, 'hex')); + if (tx.txins.length > 0) { + tx.txins.some(function(txin, j) { + var scriptSig = txin.script; + var txidhex = BufR(txin.txidbuf).readReverse().toString('hex'); + var txoutnum = txin.txoutnum; + var scriptPubkey = map[txidhex + ':' + txoutnum]; + should.exist(scriptPubkey); + var interp = ScriptInterpreter(); + var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags); + return verified === false; + }).should.equal(true); + } + }); + }); + + }); + +});