more accurate script interpretation.
This commit is contained in:
parent
e96e09493d
commit
ad11d33038
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user