From e89ed67843b0f5a5f0664ead54d5bb188aa2776c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 27 Mar 2016 18:54:13 -0700 Subject: [PATCH] script errors. --- lib/bcoin/script.js | 226 +++++++++++++++++++++++++------------------- lib/bcoin/tx.js | 2 +- 2 files changed, 129 insertions(+), 99 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 88e143db..645ea951 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -221,21 +221,21 @@ Stack.prototype._swap = function _swap(i1, i2) { Stack.prototype.toalt = function toalt() { if (this.length === 0) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'toaltstack'); this.alt.push(this.pop()); }; Stack.prototype.fromalt = function fromalt() { if (this.alt.length === 0) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'fromaltstack'); this.push(this.alt.pop()); }; Stack.prototype.ifdup = function ifdup() { if (this.length === 0) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'ifdup'); if (Script.bool(this.top(-1))) this.push(Script.array(this.top(-1))); @@ -247,28 +247,28 @@ Stack.prototype.depth = function depth() { Stack.prototype.drop = function drop() { if (this.length === 0) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'drop'); this.pop(); }; Stack.prototype.dup = function dup() { if (this.length === 0) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'dup'); this.push(this.top(-1)); }; Stack.prototype.nip = function nip() { if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'nip'); this.splice(this.length - 2, 1); }; Stack.prototype.over = function over() { if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'over'); this.push(this.top(-2)); }; @@ -285,13 +285,13 @@ Stack.prototype._pickroll = function pickroll(op, flags) { var val, n; if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', op); val = this.pop(); n = Script.num(val, flags).toNumber(); if (n <= 0 || n > this.length) - throw new Error('Bad value.'); + throw new ScriptError('Bad value.', op); val = this.get(-n - 1); @@ -303,7 +303,7 @@ Stack.prototype._pickroll = function pickroll(op, flags) { Stack.prototype.rot = function rot() { if (this.length < 3) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'rot'); this._swap(-3, -2); this._swap(-2, -1); @@ -311,21 +311,21 @@ Stack.prototype.rot = function rot() { Stack.prototype.swap = function swap() { if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'swap'); this._swap(-2, -1); }; Stack.prototype.tuck = function tuck() { if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'tuck'); this.splice(this.length - 2, 0, this.top(-1)); }; Stack.prototype.drop2 = function drop2() { if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', '2drop'); this.pop(); this.pop(); @@ -335,7 +335,7 @@ Stack.prototype.dup2 = function dup2() { var v1, v2; if (this.length < 2) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', '2dup'); v1 = this.top(-2); v2 = this.top(-1); @@ -348,7 +348,7 @@ Stack.prototype.dup3 = function dup3() { var v1, v2, v3; if (this.length < 3) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', '3dup'); v1 = this.top(-3); v2 = this.top(-2); @@ -363,7 +363,7 @@ Stack.prototype.over2 = function over2() { var v1, v2; if (this.length < 4) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', '2over'); v1 = this.top(-4); v2 = this.top(-3); @@ -376,7 +376,7 @@ Stack.prototype.rot2 = function rot2() { var v1, v2; if (this.length < 6) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', '2rot'); v1 = this.top(-6); v2 = this.top(-5); @@ -393,7 +393,7 @@ Stack.prototype.swap2 = function swap2() { Stack.prototype.size = function size() { if (this.length < 1) - throw new Error('Bad stack length.'); + throw new ScriptError('Stack too small.', 'size'); this.push(Script.array(this.top(-1).length)); }; @@ -492,16 +492,21 @@ Script.prototype._next = function _next(to, code, ip) { return -1; }; -Script.prototype.execute = function execute(stack, tx, index, flags, version) { +Script.prototype.execute = function execute(stack, flags, tx, index, version) { try { - return this._execute(stack, tx, index, flags, version); + return this.interpret(stack, flags, tx, index, version); } catch (e) { - utils.debug('Script error: %s.', e.message); + if (e.type === 'ScriptError') { + utils.debug('Script error: %s.', e.message); + } else { + utils.debug('Script interpreter threw:'); + utils.debug(e.stack + ''); + } return false; } }; -Script.prototype._execute = function _execute(stack, tx, index, flags, version) { +Script.prototype.interpret = function interpret(stack, flags, tx, index, version) { var code = this.code.slice(); var ip = 0; var lastSep = -1; @@ -518,16 +523,16 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) flags = constants.flags.STANDARD_VERIFY_FLAGS; if (code.length > constants.script.maxOps) - return false; + throw new ScriptError('Script too large.'); for (ip = 0; ip < code.length; ip++) { op = code[ip]; if (Buffer.isBuffer(op)) { if (op.length > constants.script.maxPush) - return false; + throw new ScriptError('Push data too large.', op, ip); if (!Script.checkPush(op, flags)) - return false; + throw new ScriptError('Push verification failed.', op, ip); stack.push(op); continue; } @@ -553,7 +558,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) case 'nop9': case 'nop10': { if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - return false; + throw new ScriptError('Upgradable NOP used.', op, ip); break; } case '1negate': { @@ -563,7 +568,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) case 'if': case 'notif': { if (stack.length < 1) - return false; + throw new ScriptError('Stack too small.', op, ip); val = Script.bool(stack.pop()); if (op === 'notif') val = !val; @@ -573,7 +578,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) // Splice out the statement blocks we don't need if (val) { if (endif === -1) - return false; + throw new ScriptError('Missing endif.', op, ip); if (else_ === -1) { code.splice(endif, 1); code.splice(if_, 1); @@ -583,7 +588,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) } } else { if (endif === -1) - return false; + throw new ScriptError('Missing endif.', op, ip); if (else_ === -1) { code.splice(if_, (endif - if_) + 1); } else { @@ -596,20 +601,20 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) break; } case 'else': { - return false; + throw new ScriptError('Unexpected else.', op, ip); } case 'endif': { - return false; + throw new ScriptError('Unexpected endif.', op, ip); } case 'verify': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); if (!Script.bool(stack.pop())) - return false; + throw new ScriptError('Verification failed.', op, ip); break; } case 'return': { - return false; + throw new ScriptError('Script returned.', op, ip); } case 'toaltstack': { stack.toalt(); @@ -698,7 +703,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) case 'not': case '0notequal': { if (stack.length < 1) - return false; + throw new ScriptError('Stack too small.', op, ip); n = Script.num(stack.pop(), flags); switch (op) { case '1add': @@ -756,7 +761,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) case 'min': case 'max': if (stack.length < 2) - return false; + throw new ScriptError('Stack too small.', op, ip); n2 = Script.num(stack.pop(), flags); n1 = Script.num(stack.pop(), flags); n = new bn(0, 'le'); @@ -815,7 +820,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) break; case 'within': if (stack.length < 3) - return false; + throw new ScriptError('Stack too small.', op, ip); n3 = Script.num(stack.pop(), flags); n2 = Script.num(stack.pop(), flags); n1 = Script.num(stack.pop(), flags); @@ -832,42 +837,42 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) } case 'ripemd160': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); stack.push(utils.ripemd160(stack.pop())); break; } case 'sha1': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); stack.push(utils.sha1(stack.pop())); break; } case 'sha256': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); stack.push(utils.sha256(stack.pop())); break; } case 'hash256': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); stack.push(utils.dsha256(stack.pop())); break; } case 'hash160': { if (stack.length === 0) - return false; + throw new ScriptError('Stack too small.', op, ip); stack.push(utils.ripesha(stack.pop())); break; } case 'equalverify': case 'equal': { if (stack.length < 2) - return false; + throw new ScriptError('Stack too small.', op, ip); res = utils.isEqual(stack.pop(), stack.pop()); if (op === 'equalverify') { if (!res) - return false; + throw new ScriptError('Equal verification failed.', op, ip); } else { stack.push(res ? new Buffer([1]) : new Buffer([])); } @@ -875,17 +880,20 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) } case 'checksigverify': case 'checksig': { - if (!tx || stack.length < 2) - return false; + if (!tx) + throw new ScriptError('No TX passed in.', op, ip); + + if (stack.length < 2) + throw new ScriptError('Stack too small.', op, ip); key = stack.pop(); sig = stack.pop(); if (!Script.isValidKey(key, flags)) - return false; + throw new ScriptError('Key is not valid.', op, ip); if (!Script.isValidSignature(sig, flags)) - return false; + throw new ScriptError('Signature is not valid.', op, ip); type = sig[sig.length - 1]; @@ -897,7 +905,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) res = Script.checksig(hash, sig, key, flags); if (op === 'checksigverify') { if (!res) - return false; + throw new ScriptError('Signature verification failed.', op, ip); } else { stack.push(res ? new Buffer([1]) : new Buffer([])); } @@ -906,23 +914,26 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) } case 'checkmultisigverify': case 'checkmultisig': { - if (!tx || stack.length < 4) - return false; + if (!tx) + throw new ScriptError('No TX passed in.', op, ip); + + if (stack.length < 4) + throw new ScriptError('Stack too small.', op, ip); n = Script.num(stack.pop(), flags).toNumber(); if (!(n >= 1 && n <= 15)) - return false; + throw new ScriptError('`n` is out of bounds.', op, ip); if (stack.length < n + 1) - return false; + throw new ScriptError('`n` exceeds stack size.', op, ip); keys = []; for (i = 0; i < n; i++) { key = stack.pop(); if (!Script.isValidKey(key, flags)) - return false; + throw new ScriptError('Key is not valid.', op, ip); keys.push(key); } @@ -930,10 +941,10 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) m = Script.num(stack.pop(), flags).toNumber(); if (!(m >= 1 && m <= n)) - return false; + throw new ScriptError('`m` is out of bounds.', op, ip); if (stack.length < m) - return false; + throw new ScriptError('`m` exceeds stack size.', op, ip); subscript = this.getSubscript(lastSep); @@ -947,7 +958,7 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) sig = stack.pop(); if (!Script.isValidSignature(sig, flags)) - return false; + throw new ScriptError('Signature is not valid.', op, ip); type = sig[sig.length - 1]; @@ -962,23 +973,20 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) } if (stack.length < 1) - return false; + throw new ScriptError('No dummy present.', op, ip); val = stack.pop(); if (flags & constants.flags.VERIFY_NULLDUMMY) { if (!Script.isDummy(val)) - return false; + throw new ScriptError('Dummy did not verify.', op, ip); } res = succ >= m; - if (!res) - utils.debug('checkmultisig failed: succ: %d, m: %d', succ, m); - if (op === 'checkmultisigverify') { if (!res) - return false; + throw new ScriptError('Signature verification failed.', op, ip); } else { stack.push(res ? new Buffer([1]) : new Buffer([])); } @@ -989,28 +997,25 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 if (!(flags & constants.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - return false; + throw new ScriptError('Upgradable NOP used.', op, ip); break; } - if (!tx || stack.length === 0) - return false; + if (!tx) + throw new ScriptError('No TX passed in.', op, ip); - locktime = stack.top(-1); - - if (!Buffer.isBuffer(locktime)) - return false; + if (stack.length === 0) + throw new ScriptError('Stack too small.', op, ip); // NOTE: Bitcoind accepts 5 byte locktimes. - // 4 byte locktimes become useless in 2106 - // (will people still be using bcoin then?). - locktime = Script.num(locktime, flags, 4).toNumber(); + // 4 byte locktimes become useless in 2106. + locktime = Script.num(stack.top(-1), flags, 5).toNumber(); if (locktime < 0) - return false; + throw new ScriptError('Negative locktime.', op, ip); if (!Script.checkLocktime(locktime, tx, index)) - return false; + throw new ScriptError('Locktime verification failed.', op, ip); break; } @@ -1018,44 +1023,40 @@ Script.prototype._execute = function _execute(stack, tx, index, flags, version) // OP_CHECKSEQUENCEVERIFY = OP_NOP3 if (!(flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY)) { if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - return false; + throw new ScriptError('Upgradable NOP used.', op, ip); break; } - if (!tx || stack.length === 0) - return false; + if (!tx) + throw new ScriptError('No TX passed in.', op, ip); - locktime = stack.top(-1); - - if (!Buffer.isBuffer(locktime)) - return false; + if (stack.length === 0) + throw new ScriptError('Stack too small.', op, ip); // NOTE: Bitcoind accepts 5 byte locktimes. // 4 byte locktimes become useless in 2106 // (will people still be using bcoin then?). - locktime = Script.num(locktime, flags, 4).toNumber(); + locktime = Script.num(stack.top(-1), flags, 4).toNumber(); if (locktime < 0) - return false; + throw new ScriptError('Negative sequence.', op, ip); if ((locktime & constants.sequenceLocktimeDisableFlag) !== 0) break; if (!Script.checkSequence(locktime, tx, index)) - return false; + throw new ScriptError('Sequence verification failed.', op, ip); break; } default: { - // Unknown operation - utils.debug('Unknown opcode "%s" at offset %d', op, ip); - return false; + throw new ScriptError('Unknown opcode.', op, ip); } } } if (stack.getSize() > constants.script.maxStack) - return false; + throw new ScriptError('Stack size too large.', op, ip); return true; }; @@ -1147,7 +1148,7 @@ Script.num = function num(value, flags, size) { size = 4; if (value.length > size) - throw new Error('Script number overflow.'); + throw new ScriptError('Script number overflow.'); if ((flags & constants.flags.VERIFY_MINIMALDATA) && value.length > 0) { // If the low bits on the last byte are unset, @@ -1160,7 +1161,7 @@ Script.num = function num(value, flags, size) { // zero). if (!(value[value.length - 1] & 0x7f)) { if (value.length === 1 || !(value[value.length - 2] & 0x80)) - throw new Error('Non-minimally encoded Script number.'); + throw new ScriptError('Non-minimally encoded Script number.'); } } @@ -2387,14 +2388,14 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { } // Execute the input script - input.execute(stack, tx, i, flags, 0); + input.execute(stack, flags, tx, i, 0); // Copy the stack for P2SH if (flags & constants.flags.VERIFY_P2SH) copy = stack.clone(); // Execute the previous output script - res = output.execute(stack, tx, i, flags, 0); + res = output.execute(stack, flags, tx, i, 0); // Verify the script did not fail as well as the stack values if (!res || stack.length === 0 || !Script.bool(stack.pop())) @@ -2408,7 +2409,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { return false; // Verify the program in the output script - if (!Script.verifyProgram(witness, output, tx, i, flags)) + if (!Script.verifyProgram(witness, output, flags, tx, i)) return false; // Force a cleanstack @@ -2437,7 +2438,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { redeem = new Script(raw); // Execute the redeem script - res = redeem.execute(stack, tx, i, flags, 0); + res = redeem.execute(stack, flags, tx, i, 0); // Verify the script did not fail as well as the stack values if (!res || stack.length === 0 || !Script.bool(stack.pop())) @@ -2451,7 +2452,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { return false; // Verify the program in the redeem script - if (!Script.verifyProgram(witness, redeem, tx, i, flags)) + if (!Script.verifyProgram(witness, redeem, flags, tx, i)) return false; // Force a cleanstack @@ -2477,7 +2478,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { return true; }; -Script.verifyProgram = function verifyProgram(witness, output, tx, i, flags) { +Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { var program, witnessScript, redeem, stack, j, res; assert((flags & constants.flags.VERIFY_WITNESS) !== 0); @@ -2531,7 +2532,7 @@ Script.verifyProgram = function verifyProgram(witness, output, tx, i, flags) { return false; } - res = redeem.execute(stack, tx, i, flags, 1); + res = redeem.execute(stack, flags, tx, i, 1); // Verify the script did not fail as well as the stack values if (!res || stack.length === 0 || !Script.bool(stack.pop())) @@ -2753,6 +2754,35 @@ Script.isScript = function isScript(obj) { && typeof obj.getSubscript === 'function'; }; +/** + * ScriptError + */ + +function ScriptError(msg, op, ip) { + Error.call(this); + if (Error.captureStackTrace) + Error.captureStackTrace(this, ScriptError); + this.type = 'ScriptError'; + if (Buffer.isBuffer(op)) + op = 'pushdata[' + op.length + ']'; + if (op || ip != null) { + msg += '('; + if (op) { + msg += 'op=' + op; + if (ip != null) + msg += ', '; + } + if (ip != null) + msg += 'ip=' + ip; + } + this.message = msg; + this.op = op; + this.ip = ip; +} + +utils.inherits(ScriptError, Error); + Script.witness = Witness; Script.stack = Stack; +Script.error = ScriptError; module.exports = Script; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index 14b3f886..451c6458 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -807,7 +807,7 @@ TX.prototype.isStandardInputs = function isStandardInputs(flags) { if (!input.script.isPushOnly()) return false; - res = input.script.execute(stack, this, i, flags, 0); + res = input.script.execute(stack, flags, this, i, 0); if (!res) return false;