From ad11d33038679cc52ff47bcc0df4bc7b8b569fa4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 17 Apr 2016 06:39:31 -0700 Subject: [PATCH] more accurate script interpretation. --- lib/bcoin/script.js | 303 ++++++++++++++++++++++++++++++++------------ lib/bcoin/utils.js | 13 ++ 2 files changed, 235 insertions(+), 81 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index c2804353..130266e3 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -285,12 +285,16 @@ Witness.isWitness = function isWitness(obj) { * @param {Buffer[]?} items - Stack items. * @property {Buffer[]} items - Stack items. * @property {Buffer[]} alt - Alt stack items. + * @property {Number[]} state - State of if statements. + * @property {Boolean} negate - State of if negations. * @property {Number} length - Size of stack. */ function Stack(items) { this.items = items || []; this.alt = []; + this.state = []; + this.negate = 0; } /** @@ -855,35 +859,6 @@ Script.prototype.getSubscript = function getSubscript(lastSep) { return new Script(code); }; -Script.prototype._next = function _next(to, code, ip) { - var depth = 0; - var op; - - while (code[ip]) { - op = code[ip]; - - if (op === opcodes.OP_IF || op === opcodes.OP_NOTIF) - depth++; - else if (op === opcodes.OP_ELSE) - depth--; - else if (op === opcodes.OP_ENDIF) - depth--; - - if (depth < 0) - break; - - if (depth === 0 && op === to) - return ip; - - if (op === opcodes.OP_ELSE) - depth++; - - ip++; - } - - return -1; -}; - /** * Execute the script and return false on script execution errors. * @param {Stack} stack - Script execution stack. @@ -920,10 +895,10 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { */ Script.prototype.interpret = function interpret(stack, flags, tx, index, version) { - var code = this.code.slice(); var ip = 0; var lastSep = -1; - var op, val, if_, else_, endif; + var opCount = 0; + var op, val, v1, v2, v3; var n, n1, n2, n3; var res, key, sig, type, subscript, hash; var keys, i, j, m; @@ -932,21 +907,107 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (code.length > constants.script.maxOps) + // The alt stack and execution + // stack are local to the script. + stack.alt.length = 0; + stack.state.length = 0; + stack.negate = 0; + + if (this.getSize() > constants.script.maxSize) throw new ScriptError('Script too large.'); - for (ip = 0; ip < code.length; ip++) { - op = code[ip]; + for (ip = 0; ip < this.code.length; ip++) { + op = this.code[ip]; if (Buffer.isBuffer(op)) { if (op.length > constants.script.maxPush) throw new ScriptError('Push data too large.', op, ip); - if (!Script.checkPush(op, flags)) - throw new ScriptError('Push verification failed.', op, ip); - stack.push(op); + // Note that minimaldata is not checked + // on unexecuted branches of code. + if (stack.negate === 0) { + if (!Script.checkPush(op, flags)) + throw new ScriptError('Push verification failed.', op, ip); + stack.push(op); + } continue; } + if (op > opcodes.OP_16 && ++opCount > constants.script.maxOps) + throw new ScriptError('Too many opcodes.', op, ip); + + // It's very important to make a distiction + // here: these opcodes will fail _even if they + // are in unexecuted branches of code_. Whereas + // a totally unknown opcode is fine as long as it + // is unexecuted. + if (op == opcodes.OP_CAT + || op == opcodes.OP_SUBSTR + || op == opcodes.OP_LEFT + || op == opcodes.OP_RIGHT + || op == opcodes.OP_INVERT + || op == opcodes.OP_AND + || op == opcodes.OP_OR + || op == opcodes.OP_XOR + || op == opcodes.OP_2MUL + || op == opcodes.OP_2DIV + || op == opcodes.OP_MUL + || op == opcodes.OP_DIV + || op == opcodes.OP_MOD + || op == opcodes.OP_LSHIFT + || op == opcodes.OP_RSHIFT) { + throw new ScriptError('Disabled opcode.', op, ip); + } + + if (op >= opcodes.OP_IF && op <= opcodes.OP_ENDIF) { + switch (op) { + case opcodes.OP_IF: + case opcodes.OP_NOTIF: { + if (stack.negate === 0) { + if (stack.length < 1) + throw new ScriptError('Stack too small.', op, ip); + val = Script.bool(stack.pop()); + if (op === opcodes.OP_NOTIF) + val = !val; + stack.state.push(val === true ? 1 : 0); + if (!val) + stack.negate++; + } else { + stack.state.push(0); + stack.negate++; + } + break; + } + case opcodes.OP_ELSE: { + if (stack.state.length === 0) + throw new ScriptError('Unexpected else.', op, ip); + stack.state[stack.state.length - 1] ^= 1; + if (stack.state[stack.state.length - 1] === 0) + stack.negate++; + else + stack.negate--; + break; + } + case opcodes.OP_ENDIF: { + if (stack.state.length === 0) + throw new ScriptError('Unexpected endif.', op, ip); + if (stack.state.pop() === 0) + stack.negate--; + break; + } + case opcodes.OP_VERIF: + case opcodes.OP_VERNOTIF: { + throw new ScriptError('Unknown opcode.', op, ip); + } + default: { + assert.fatal(false, 'Fatal script error.'); + } + } + continue; + } + + if (stack.negate !== 0) + continue; + if (op === opcodes.OP_0) { stack.push(STACK_FALSE); continue; @@ -976,47 +1037,6 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version throw new ScriptError('Upgradable NOP used.', op, ip); break; } - case opcodes.OP_IF: - case opcodes.OP_NOTIF: { - if (stack.length < 1) - throw new ScriptError('Stack too small.', op, ip); - val = Script.bool(stack.pop()); - if (op === opcodes.OP_NOTIF) - val = !val; - if_ = ip; - else_ = this._next(opcodes.OP_ELSE, code, ip); - endif = this._next(opcodes.OP_ENDIF, code, ip); - // Splice out the statement blocks we don't need - if (val) { - if (endif === -1) - throw new ScriptError('Missing endif.', op, ip); - if (else_ === -1) { - code.splice(endif, 1); - code.splice(if_, 1); - } else { - code.splice(else_, (endif - else_) + 1); - code.splice(if_, 1); - } - } else { - if (endif === -1) - throw new ScriptError('Missing endif.', op, ip); - if (else_ === -1) { - code.splice(if_, (endif - if_) + 1); - } else { - code.splice(endif, 1); - code.splice(if_, (else_ - if_) + 1); - } - } - // Subtract one since we removed the if/notif opcode - ip--; - break; - } - case opcodes.OP_ELSE: { - throw new ScriptError('Unexpected else.', op, ip); - } - case opcodes.OP_ENDIF: { - throw new ScriptError('Unexpected endif.', op, ip); - } case opcodes.OP_VERIFY: { if (stack.length === 0) throw new ScriptError('Stack too small.', op, ip); @@ -1109,6 +1129,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version } case opcodes.OP_1ADD: case opcodes.OP_1SUB: + case opcodes.OP_2MUL: + case opcodes.OP_2DIV: case opcodes.OP_NEGATE: case opcodes.OP_ABS: case opcodes.OP_NOT: @@ -1123,8 +1145,14 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_1SUB: n.isub(1); break; + case opcodes.OP_2MUL: + n.iushln(1); + break; + case opcodes.OP_2DIV: + n.iushrn(1); + break; case opcodes.OP_NEGATE: - n = n.neg(); + n.ineg(); break; case opcodes.OP_ABS: if (n.cmpn(0) < 0) @@ -1137,7 +1165,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version n = n.cmpn(0) !== 0; break; default: - return false; + assert.fatal(false, 'Fatal script error.'); } if (typeof n === 'boolean') n = new bn(n ? 1 : 0); @@ -1146,6 +1174,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version } case opcodes.OP_ADD: case opcodes.OP_SUB: + case opcodes.OP_MUL: + case opcodes.OP_DIV: + case opcodes.OP_MOD: + case opcodes.OP_LSHIFT: + case opcodes.OP_RSHIFT: case opcodes.OP_BOOLAND: case opcodes.OP_BOOLOR: case opcodes.OP_NUMEQUAL: @@ -1160,6 +1193,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version switch (op) { case opcodes.OP_ADD: case opcodes.OP_SUB: + case opcodes.OP_MUL: + case opcodes.OP_DIV: + case opcodes.OP_MOD: + case opcodes.OP_LSHIFT: + case opcodes.OP_RSHIFT: case opcodes.OP_BOOLAND: case opcodes.OP_BOOLOR: case opcodes.OP_NUMEQUAL: @@ -1183,6 +1221,25 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_SUB: n = n1.sub(n2); break; + case opcodes.OP_MUL: + n = n1.mul(n2); + break; + case opcodes.OP_DIV: + n = n1.div(n2); + break; + case opcodes.OP_MOD: + n = n1.mod(n2); + break; + case opcodes.OP_LSHIFT: + if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) + throw new ScriptError('Shift out of range.', op, ip); + n = n1.ushln(n2.toNumber()); + break; + case opcodes.OP_RSHIFT: + if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) + throw new ScriptError('Shift out of range.', op, ip); + n = n1.ushrn(n2.toNumber()); + break; case opcodes.OP_BOOLAND: n = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0; break; @@ -1217,7 +1274,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version n = n1.cmp(n2) > 0 ? n1 : n2; break; default: - return false; + assert.fatal(false, 'Fatal script error.'); } if (typeof n === 'boolean') n = new bn(n ? 1 : 0); @@ -1459,6 +1516,87 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version break; } + case opcodes.OP_CAT: { + if (stack.length < 2) + throw new ScriptError('Stack too small.', op, ip); + v2 = stack.pop(); + v1 = stack.pop(); + stack.push(Buffer.concat([v1, v2])); + if (stack.top(-1).length > constants.script.maxPush) + throw new ScriptError('Push data too large.', op, ip); + break; + } + case opcodes.OP_SUBSTR: { + if (stack.length < 3) + throw new ScriptError('Stack too small.', op, ip); + v1 = Script.num(stack.pop(), flags).toNumber(); // end + v2 = Script.num(stack.pop(), flags).toNumber(); // begin + v3 = stack.pop(); // string + if (v2 < 0 || v1 < v2) + throw new ScriptError('String begin and end out of range.', op, ip); + if (v2 > v3.length) + v2 = v3.length; + if (v1 > v3.length) + v1 = v3.length; + stack.push(v3.slice(v2, v1)); + break; + } + case opcodes.OP_LEFT: + case opcodes.OP_RIGHT: { + if (stack.length < 2) + throw new ScriptError('Stack too small.', op, ip); + v1 = Script.num(stack.pop(), flags).toNumber(); // size + v2 = stack.pop(); // string + if (v1 < 0) + throw new ScriptError('String size is negative.', op, ip); + if (v1 > v2.length) + v1 = v2.length; + if (op === opcodes.OP_LEFT) + v2 = v2.slice(0, v1); + else + v2 = v2.slice(v2.length - v1); + stack.push(v2); + break; + } + case opcodes.OP_INVERT: { + if (stack.length < 1) + throw new ScriptError('Stack too small.', op, ip); + val = utils.slice(stack.pop()); + for (i = 0; i < val.length; i++) + val[i] = ~val[i]; + stack.push(val); + break; + } + case opcodes.OP_AND: + case opcodes.OP_OR: + case opcodes.OP_XOR: { + if (stack.length < 2) + throw new ScriptError('Stack too small.', op, ip); + v2 = stack.pop(); + v1 = utils.slice(stack.pop()); + if (v1.length < v2.length) { + v3 = new Buffer(v2.length - v1.length); + v3.fill(0); + v1 = Buffer.concat([v1, v3]); + } + if (v2.length < v1.length) { + v3 = new Buffer(v1.length - v2.length); + v3.fill(0); + v2 = Buffer.concat([v2, v3]); + } + if (op === opcodes.OP_AND) { + for (i = 0; i < v1.length; i++) + v1[i] &= v2[i]; + } else if (op === opcodes.OP_OR) { + for (i = 0; i < v1.length; i++) + v1[i] |= v2[i]; + } else if (op === opcodes.OP_XOR) { + for (i = 0; i < v1.length; i++) + v1[i] ^= v2[i]; + } + stack.push(v1); + break; + } default: { throw new ScriptError('Unknown opcode.', op, ip); } @@ -1468,6 +1606,9 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (stack.getSize() > constants.script.maxStack) throw new ScriptError('Stack size too large.', op, ip); + if (stack.state.length !== 0) + throw new ScriptError('Expected endif.', op, ip); + return true; }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index df528d36..bfef3a94 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -605,6 +605,19 @@ function assert(val, msg) { throw new Error(msg || 'Assertion failed'); } +assert.fatal = function fatal(l, r, msg) { + if (!val) { + if (!msg) + msg = 'Assertion failed (fatal exception)'; + msg = new Error(msg); + console.error(msg.stack + ''); + if (process.exit) + process.exit(1); + else + throw msg; + } +}; + assert.equal = function assertEqual(l, r, msg) { if (l != r) throw new Error(msg || ('Assertion failed: ' + l + ' != ' + r));