more accurate script interpretation.

This commit is contained in:
Christopher Jeffrey 2016-04-17 06:39:31 -07:00
parent e96e09493d
commit ad11d33038
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 235 additions and 81 deletions

View File

@ -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;
};

View File

@ -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));