diff --git a/lib/script/script.js b/lib/script/script.js index b27fe203..a80b2d64 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -301,11 +301,13 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { var alt = []; var state = []; var negate = 0; - var op, code, data, val, v1, v2, v3; - var n, n1, n2, n3; - var res, key, sig, type, subscript, hash; - var i, j, m, ikey, ikey2, isig; - var locktime; + var op, code, data + var val, v1, v2, v3; + var num, n1, n2, n3; + var m, n, key, sig; + var type, subscript, hash; + var ikey, isig, ikey2; + var i, j, res, locktime; if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; @@ -324,6 +326,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { if (data) { if (data.length > constants.script.MAX_PUSH) throw new ScriptError('PUSH_SIZE', op, ip); + // Note that minimaldata is not checked // on unexecuted branches of code. if (negate === 0) { @@ -331,6 +334,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { throw new ScriptError('MINIMALDATA', op, ip); stack.push(data); } + continue; } @@ -371,7 +375,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { if (stack.length < 1) throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - val = stack.pop(); + val = stack.top(-1); if (version === 1 && (flags & constants.flags.VERIFY_MINIMALIF)) { if (val.length > 1) @@ -385,6 +389,8 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { if (op === opcodes.OP_NOTIF) val = !val; + + stack.pop(); } state.push(val); @@ -531,104 +537,209 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_VERIFY: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (!Script.bool(stack.pop())) + + if (!Script.bool(stack.top(-1))) throw new ScriptError('VERIFY', op, ip); + + stack.pop(); + break; } case opcodes.OP_RETURN: { throw new ScriptError('OP_RETURN', op, ip); } case opcodes.OP_TOALTSTACK: { - stack.toalt(alt); + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + alt.push(stack.pop()); break; } case opcodes.OP_FROMALTSTACK: { - stack.fromalt(alt); + if (alt.length === 0) + throw new ScriptError('INVALID_ALTSTACK_OPERATION', op, ip); + + stack.push(alt.pop()); break; } case opcodes.OP_2DROP: { - stack.drop2(); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.pop(); + stack.pop(); break; } case opcodes.OP_2DUP: { - stack.dup2(); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + v1 = stack.top(-2); + v2 = stack.top(-1); + + stack.push(v1); + stack.push(v2); break; } case opcodes.OP_3DUP: { - stack.dup3(); + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + v1 = stack.top(-3); + v2 = stack.top(-2); + v3 = stack.top(-1); + + stack.push(v1); + stack.push(v2); + stack.push(v3); break; } case opcodes.OP_2OVER: { - stack.over2(); + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + v1 = stack.top(-4); + v2 = stack.top(-3); + + stack.push(v1); + stack.push(v2); break; } case opcodes.OP_2ROT: { - stack.rot2(); + if (stack.length < 6) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + v1 = stack.top(-6); + v2 = stack.top(-5); + + stack.erase(-6, -4); + stack.push(v1); + stack.push(v2); break; } case opcodes.OP_2SWAP: { - stack.swap2(); + if (stack.length < 4) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-4, -2); + stack.swap(-3, -1); break; } case opcodes.OP_IFDUP: { - stack.ifdup(); + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + val = stack.top(-1); + + if (Script.bool(val)) + stack.push(val); break; } case opcodes.OP_DEPTH: { - stack.depth(); + stack.push(Script.array(stack.length)); break; } case opcodes.OP_DROP: { - stack.drop(); + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.pop(); break; } case opcodes.OP_DUP: { - stack.dup(); + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(stack.top(-1)); break; } case opcodes.OP_NIP: { - stack.nip(); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.remove(-2); break; } case opcodes.OP_OVER: { - stack.over(); - break; - } - case opcodes.OP_PICK: { - stack.pick(flags); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(stack.top(-2)); break; } + case opcodes.OP_PICK: case opcodes.OP_ROLL: { - stack.roll(flags); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op); + + num = Script.num(stack.top(-1), flags).toNumber(); + stack.pop(); + + if (num < 0 || num >= stack.length) + throw new ScriptError('INVALID_STACK_OPERATION', op); + + val = stack.top(-num - 1); + + if (op === opcodes.OP_ROLL) + stack.remove(-num - 1); + + stack.push(val); break; } case opcodes.OP_ROT: { - stack.rot(); + if (stack.length < 3) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-3, -2); + stack.swap(-2, -1); break; } case opcodes.OP_SWAP: { - stack.swap(); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.swap(-2, -1); break; } case opcodes.OP_TUCK: { - stack.tuck(); + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.insert(-2, stack.top(-1)); break; } case opcodes.OP_SIZE: { - stack.size(); + if (stack.length < 1) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + stack.push(Script.array(stack.top(-1).length)); break; } case opcodes.OP_EQUAL: case opcodes.OP_EQUALVERIFY: { + // case opcodes.OP_NOTEQUAL: // use OP_NUMNOTEQUAL if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - res = utils.equal(stack.pop(), stack.pop()); + + v1 = stack.top(-2); + v2 = stack.top(-1); + + res = utils.equal(v1, v2); + + // if (op == opcodes.OP_NOTEQUAL) + // res = !res; + + stack.pop(); + stack.pop(); + + stack.push(res ? STACK_TRUE : STACK_FALSE); + if (op === opcodes.OP_EQUALVERIFY) { if (!res) throw new ScriptError('EQUALVERIFY', op, ip); - } else { - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pop(); } + break; } case opcodes.OP_1ADD: @@ -641,39 +752,45 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_0NOTEQUAL: { if (stack.length < 1) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - n = Script.num(stack.pop(), flags); + + num = Script.num(stack.top(-1), flags); + switch (op) { case opcodes.OP_1ADD: - n.iaddn(1); + num.iaddn(1); break; case opcodes.OP_1SUB: - n.isubn(1); + num.isubn(1); break; case opcodes.OP_2MUL: - n.iushln(1); + num.iushln(1); break; case opcodes.OP_2DIV: - n.iushrn(1); + num.iushrn(1); break; case opcodes.OP_NEGATE: - n.ineg(); + num.ineg(); break; case opcodes.OP_ABS: - if (n.cmpn(0) < 0) - n.ineg(); + if (num.cmpn(0) < 0) + num.ineg(); break; case opcodes.OP_NOT: - n = n.cmpn(0) === 0; + num = num.cmpn(0) === 0; + num = new bn(num ? 1 : 0); break; case opcodes.OP_0NOTEQUAL: - n = n.cmpn(0) !== 0; + num = num.cmpn(0) !== 0; + num = new bn(num ? 1 : 0); break; default: assert(false, 'Fatal script error.'); + break; } - if (typeof n === 'boolean') - n = new bn(n ? 1 : 0); - stack.push(Script.array(n)); + + stack.pop(); + stack.push(Script.array(num)); + break; } case opcodes.OP_ADD: @@ -694,101 +811,95 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_GREATERTHANOREQUAL: case opcodes.OP_MIN: case opcodes.OP_MAX: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + n1 = Script.num(stack.top(-2), flags); + n2 = Script.num(stack.top(-1), flags); + 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: - case opcodes.OP_NUMEQUALVERIFY: - case opcodes.OP_NUMNOTEQUAL: - case opcodes.OP_LESSTHAN: - case opcodes.OP_GREATERTHAN: - case opcodes.OP_LESSTHANOREQUAL: - case opcodes.OP_GREATERTHANOREQUAL: - case opcodes.OP_MIN: - case opcodes.OP_MAX: - if (stack.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - n2 = Script.num(stack.pop(), flags); - n1 = Script.num(stack.pop(), flags); - n = new bn(0); - switch (op) { - case opcodes.OP_ADD: - n = n1.add(n2); - break; - 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('UNKNOWN_ERROR', 'Bad shift.'); - n = n1.ushln(n2.toNumber()); - break; - case opcodes.OP_RSHIFT: - if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) - throw new ScriptError('UNKNOWN_ERROR', 'Bad shift.'); - n = n1.ushrn(n2.toNumber()); - break; - case opcodes.OP_BOOLAND: - n = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0; - break; - case opcodes.OP_BOOLOR: - n = n1.cmpn(0) !== 0 || n2.cmpn(0) !== 0; - break; - case opcodes.OP_NUMEQUAL: - n = n1.cmp(n2) === 0; - break; - case opcodes.OP_NUMEQUALVERIFY: - n = n1.cmp(n2) === 0; - break; - case opcodes.OP_NUMNOTEQUAL: - n = n1.cmp(n2) !== 0; - break; - case opcodes.OP_LESSTHAN: - n = n1.cmp(n2) < 0; - break; - case opcodes.OP_GREATERTHAN: - n = n1.cmp(n2) > 0; - break; - case opcodes.OP_LESSTHANOREQUAL: - n = n1.cmp(n2) <= 0; - break; - case opcodes.OP_GREATERTHANOREQUAL: - n = n1.cmp(n2) >= 0; - break; - case opcodes.OP_MIN: - n = n1.cmp(n2) < 0 ? n1 : n2; - break; - case opcodes.OP_MAX: - n = n1.cmp(n2) > 0 ? n1 : n2; - break; - default: - assert(false, 'Fatal script error.'); - } - if (typeof n === 'boolean') - n = new bn(n ? 1 : 0); - if (op === opcodes.OP_NUMEQUALVERIFY) { - if (!Script.bool(n)) - throw new ScriptError('NUMEQUALVERIFY', op, ip); - } else { - stack.push(Script.array(n)); - } + num = n1.add(n2); break; + case opcodes.OP_SUB: + num = n1.sub(n2); + break; + case opcodes.OP_MUL: + num = n1.mul(n2); + break; + case opcodes.OP_DIV: + num = n1.div(n2); + break; + case opcodes.OP_MOD: + num = n1.mod(n2); + break; + case opcodes.OP_LSHIFT: + if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) + throw new ScriptError('UNKNOWN_ERROR', 'Bad shift.'); + + num = n1.ushln(n2.toNumber()); + break; + case opcodes.OP_RSHIFT: + if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) + throw new ScriptError('UNKNOWN_ERROR', 'Bad shift.'); + + num = n1.ushrn(n2.toNumber()); + break; + case opcodes.OP_BOOLAND: + num = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_BOOLOR: + num = n1.cmpn(0) !== 0 || n2.cmpn(0) !== 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_NUMEQUAL: + num = n1.cmp(n2) === 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_NUMEQUALVERIFY: + num = n1.cmp(n2) === 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_NUMNOTEQUAL: + num = n1.cmp(n2) !== 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_LESSTHAN: + num = n1.cmp(n2) < 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_GREATERTHAN: + num = n1.cmp(n2) > 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_LESSTHANOREQUAL: + num = n1.cmp(n2) <= 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_GREATERTHANOREQUAL: + num = n1.cmp(n2) >= 0; + num = new bn(num ? 1 : 0); + break; + case opcodes.OP_MIN: + num = n1.cmp(n2) < 0 ? n1 : n2; + break; + case opcodes.OP_MAX: + num = n1.cmp(n2) > 0 ? n1 : n2; + break; + default: + assert(false, 'Fatal script error.'); + break; + } + + stack.pop(); + stack.pop(); + stack.push(Script.array(num)); + + if (op === opcodes.OP_NUMEQUALVERIFY) { + if (!Script.bool(stack.top(-1))) + throw new ScriptError('NUMEQUALVERIFY', op, ip); + stack.pop(); } break; @@ -796,40 +907,52 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_WITHIN: { if (stack.length < 3) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - n3 = Script.num(stack.pop(), flags); - n2 = Script.num(stack.pop(), flags); - n1 = Script.num(stack.pop(), flags); + + n1 = Script.num(stack.top(-3), flags); + n2 = Script.num(stack.top(-2), flags); + n3 = Script.num(stack.top(-1), flags); + val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + + stack.pop(); + stack.pop(); + stack.pop(); + stack.push(val ? STACK_TRUE : STACK_FALSE); break; } case opcodes.OP_RIPEMD160: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(crypto.ripemd160(stack.pop())); break; } case opcodes.OP_SHA1: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(crypto.sha1(stack.pop())); break; } case opcodes.OP_SHA256: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(crypto.sha256(stack.pop())); break; } case opcodes.OP_HASH160: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(crypto.hash160(stack.pop())); break; } case opcodes.OP_HASH256: { if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(crypto.hash256(stack.pop())); break; } @@ -845,32 +968,38 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - key = stack.pop(); - sig = stack.pop(); + sig = stack.top(-2); + key = stack.top(-1); + res = false; subscript = this.getSubscript(lastSep); + if (version === 0) subscript.removeData(sig); Script.validateSignature(sig, flags); Script.validateKey(key, flags); - type = sig[sig.length - 1]; + if (sig.length > 0) { + type = sig[sig.length - 1]; + hash = tx.signatureHash(index, subscript, type, version); + res = Script.checksig(hash, sig, key, flags); + } - hash = tx.signatureHash(index, subscript, type, version); - - res = Script.checksig(hash, sig, key, flags); + stack.pop(); + stack.pop(); if (!res && (flags & constants.flags.VERIFY_NULLFAIL)) { if (sig.length !== 0) throw new ScriptError('NULLFAIL', op, ip); } + stack.push(res ? STACK_TRUE : STACK_FALSE); + if (op === opcodes.OP_CHECKSIGVERIFY) { if (!res) throw new ScriptError('CHECKSIGVERIFY', op, ip); - } else { - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pop(); } break; @@ -930,12 +1059,14 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { Script.validateSignature(sig, flags); Script.validateKey(key, flags); - type = sig[sig.length - 1]; - hash = tx.signatureHash(index, subscript, type, version); + if (sig.length > 0) { + type = sig[sig.length - 1]; + hash = tx.signatureHash(index, subscript, type, version); - if (Script.checksig(hash, sig, key, flags)) { - isig++; - m--; + if (Script.checksig(hash, sig, key, flags)) { + isig++; + m--; + } } ikey++; @@ -965,11 +1096,12 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { stack.pop(); + stack.push(res ? STACK_TRUE : STACK_FALSE); + if (op === opcodes.OP_CHECKMULTISIGVERIFY) { if (!res) throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); - } else { - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pop(); } break; @@ -977,52 +1109,73 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_CAT: { if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - v2 = stack.pop(); - v1 = stack.pop(); - stack.push(utils.concat(v1, v2)); - if (stack.top(-1).length > constants.script.MAX_PUSH) - throw new ScriptError('PUSH_SIZE', op, ip); + + v1 = stack.top(-2); + v2 = stack.top(-1); + + stack.set(-2, utils.concat(v1, v2)); + + stack.pop(); + break; } case opcodes.OP_SUBSTR: { if (stack.length < 3) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - v3 = Script.num(stack.pop(), flags).toNumber(); // end - v2 = Script.num(stack.pop(), flags).toNumber(); // begin - v1 = stack.pop(); // string + + v1 = stack.top(-3); + v2 = Script.num(stack.top(-2), flags).toNumber(); + v3 = Script.num(stack.top(-1), flags).toNumber(); + if (v2 < 0 || v3 < v2) throw new ScriptError('UNKNOWN_ERROR', 'String out of range.'); + if (v2 > v1.length) v2 = v1.length; + if (v3 > v1.length) v3 = v1.length; - stack.push(v1.slice(v2, v3)); + + stack.set(-3, v1.slice(v2, v3)); + stack.pop(); + stack.pop(); + break; } case opcodes.OP_LEFT: case opcodes.OP_RIGHT: { if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - v2 = Script.num(stack.pop(), flags).toNumber(); // size - v1 = stack.pop(); // string + + v1 = stack.top(-2); + v2 = Script.num(stack.top(-1), flags).toNumber(); + if (v2 < 0) throw new ScriptError('UNKNOWN_ERROR', 'String size out of range.'); + if (v2 > v1.length) v2 = v1.length; + if (op === opcodes.OP_LEFT) v1 = v1.slice(0, v2); else v1 = v1.slice(v1.length - v2); - stack.push(v1); + + stack.set(-2, v1); + stack.pop(); + break; } case opcodes.OP_INVERT: { if (stack.length < 1) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - val = utils.copy(stack.pop()); + + val = utils.copy(stack.top(-1)); + stack.set(-1, val); + for (i = 0; i < val.length; i++) val[i] = ~val[i] & 0xff; - stack.push(val); + break; } case opcodes.OP_AND: @@ -1030,18 +1183,26 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { case opcodes.OP_XOR: { if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - v2 = stack.pop(); - v1 = utils.copy(stack.pop()); + + v1 = utils.copy(stack.top(-2)); + v2 = stack.top(-1); + if (v1.length < v2.length) { - v3 = new Buffer(v2.length - v1.length); + v3 = new Buffer(v2.length); v3.fill(0); - v1 = utils.concat(v1, v3); + v1.copy(v3, 0); + v1 = v3; } + if (v2.length < v1.length) { - v3 = new Buffer(v1.length - v2.length); + v3 = new Buffer(v1.length); v3.fill(0); - v2 = utils.concat(v2, v3); + v2.copy(v3, 0); + v2 = v3; } + + stack.set(-2, v1); + if (op === opcodes.OP_AND) { for (i = 0; i < v1.length; i++) v1[i] &= v2[i]; @@ -1052,7 +1213,9 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { for (i = 0; i < v1.length; i++) v1[i] ^= v2[i]; } - stack.push(v1); + + stack.pop(); + break; } default: { @@ -1061,7 +1224,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) { } } - if (stack.getSize(alt) > constants.script.MAX_STACK) + if (stack.length + alt.length > constants.script.MAX_STACK) throw new ScriptError('STACK_SIZE', op, ip); if (state.length !== 0) @@ -1145,9 +1308,6 @@ Script.checkSequence = function checkSequence(sequence, tx, i) { Script.bool = function bool(value) { var i; - if (bn.isBN(value)) - return value.cmpn(0) !== 0; - assert(Buffer.isBuffer(value)); for (i = 0; i < value.length; i++) { @@ -1235,9 +1395,6 @@ Script.num = function num(value, flags, size) { Script.array = function(value) { var neg, result; - if (Buffer.isBuffer(value)) - return value; - if (utils.isNumber(value)) value = new bn(value); @@ -1299,6 +1456,8 @@ Script.prototype.removeData = function removeData(data) { op = this.code[i]; if (op.value === -1) { + // Can't reserialize + // a parse error. if (index.length > 0) index.push(i); break; @@ -1400,11 +1559,7 @@ Script.isMinimal = function isMinimal(data, opcode, flags) { Script.isCode = function isCode(raw) { var i, op, code; - if (!raw) - return false; - - if (!Buffer.isBuffer(raw)) - return false; + assert(Buffer.isBuffer(raw)); code = Script.decode(raw); @@ -1606,8 +1761,7 @@ Script.prototype.fromAddress = function fromAddress(address) { if (typeof address === 'string') address = Address.fromBase58(address); - if (!address) - throw new Error('Unknown address type.'); + assert(address instanceof Address, 'Not an address.'); if (address.type === scriptTypes.PUBKEYHASH) return this.fromPubkeyhash(address.hash); @@ -1618,7 +1772,7 @@ Script.prototype.fromAddress = function fromAddress(address) { if (address.version !== -1) return this.fromProgram(address.version, address.hash); - assert(false, 'Bad address type.'); + throw new Error('Unknown address type.'); }; /** @@ -2564,8 +2718,7 @@ Script.validateKey = function validateKey(key, flags) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (!Buffer.isBuffer(key)) - throw new ScriptError('BAD_OPCODE'); + assert(Buffer.isBuffer(key)); if (flags & constants.flags.VERIFY_STRICTENC) { if (!Script.isKeyEncoding(key)) @@ -2582,8 +2735,7 @@ Script.validateKey = function validateKey(key, flags) { */ Script.isKeyEncoding = function isKeyEncoding(key) { - if (!Buffer.isBuffer(key)) - return false; + assert(Buffer.isBuffer(key)); if (key.length < 33) return false; @@ -2617,8 +2769,7 @@ Script.validateSignature = function validateSignature(sig, flags) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (!Buffer.isBuffer(sig)) - throw new ScriptError('BAD_OPCODE'); + assert(Buffer.isBuffer(sig)); // Allow empty sigs if (sig.length === 0) @@ -2654,8 +2805,7 @@ Script.validateSignature = function validateSignature(sig, flags) { Script.isSignatureEncoding = function isSignatureEncoding(sig) { var lenR, lenS; - if (!Buffer.isBuffer(sig)) - return false; + assert(Buffer.isBuffer(sig)); // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash] // * total-length: 1-byte length descriptor of everything that follows, @@ -2745,8 +2895,7 @@ Script.isSignatureEncoding = function isSignatureEncoding(sig) { Script.isHashType = function isHashType(sig) { var type; - if (!Buffer.isBuffer(sig)) - return false; + assert(Buffer.isBuffer(sig)); if (sig.length === 0) return false; @@ -3149,8 +3298,7 @@ Script.getSmall = function getSmall(op) { */ Script.verify = function verify(input, witness, output, tx, i, flags) { - var copy, raw, redeem, hadWitness; - var stack = new Stack(); + var stack, copy, raw, redeem, hadWitness; if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; @@ -3160,6 +3308,9 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { throw new ScriptError('SIG_PUSHONLY'); } + // Setup a stack. + stack = new Stack(); + // Execute the input script input.execute(stack, flags, tx, i, 0); @@ -3171,7 +3322,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { output.execute(stack, flags, tx, i, 0); // Verify the stack values. - if (stack.length === 0 || !Script.bool(stack.pop())) + if (stack.length === 0 || !Script.bool(stack.top(-1))) throw new ScriptError('EVAL_FALSE'); if ((flags & constants.flags.VERIFY_WITNESS) && output.isProgram()) { @@ -3185,7 +3336,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { Script.verifyProgram(witness, output, flags, tx, i); // Force a cleanstack - stack.length = 0; + stack.length = 1; } // If the script is P2SH, execute the real output script @@ -3209,7 +3360,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { redeem.execute(stack, flags, tx, i, 0); // Verify the the stack values. - if (stack.length === 0 || !Script.bool(stack.pop())) + if (stack.length === 0 || !Script.bool(stack.top(-1))) throw new ScriptError('EVAL_FALSE'); if ((flags & constants.flags.VERIFY_WITNESS) && redeem.isProgram()) { @@ -3223,15 +3374,14 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { Script.verifyProgram(witness, redeem, flags, tx, i); // Force a cleanstack. - stack.length = 0; + stack.length = 1; } } // Ensure there is nothing left on the stack. if (flags & constants.flags.VERIFY_CLEANSTACK) { assert((flags & constants.flags.VERIFY_P2SH) !== 0); - // assert((flags & constants.flags.VERIFY_WITNESS) !== 0); - if (stack.length !== 0) + if (stack.length !== 1) throw new ScriptError('CLEANSTACK'); } @@ -3311,7 +3461,7 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) { redeem.execute(stack, flags, tx, i, 1); // Verify the stack values. - if (stack.length !== 1 || !Script.bool(stack.pop())) + if (stack.length !== 1 || !Script.bool(stack.top(-1))) throw new ScriptError('EVAL_FALSE'); return true; @@ -3344,12 +3494,12 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { if (stack.length < 4) throw new ScriptError('INVALID_MAST_STACK'); - metadata = stack.pop(); + metadata = stack.top(-1); if (metadata.length < 1 || metadata.length > 5) throw new ScriptError('INVALID_MAST_STACK'); subscripts = metadata[0]; - if (subscripts === 0 || stack.length < subscripts + 2) + if (subscripts === 0 || stack.length < subscripts + 3) throw new ScriptError('INVALID_MAST_STACK'); ops = subscripts; @@ -3371,7 +3521,7 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { mastRoot.writeU32(version); - pathdata = stack.pop(); + pathdata = stack.top(-2); if (pathdata.length & 0x1f) throw new ScriptError('INVALID_MAST_STACK'); @@ -3392,7 +3542,7 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { for (j = 0; j < depth; j++) path.push(pathdata.slice(j * 32, j * 32 + 32)); - posdata = stack.pop(); + posdata = stack.top(-3); if (posdata.length > 4) throw new ScriptError('INVALID_MAST_STACK'); @@ -3417,7 +3567,7 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { scripts.writeBytes(output.raw); for (j = 0; j < subscripts; j++) { - script = stack.pop(); + script = stack.top(-(4 + j)); if (version === 0) { if ((scripts.written + script.length) > constants.script.MAX_SIZE) throw new ScriptError('SCRIPT_SIZE'); @@ -3436,18 +3586,20 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { throw new ScriptError('WITNESS_PROGRAM_MISMATCH'); if (version === 0) { + stack.length -= 3 + subscripts; + for (j = 0; j < stack.length; j++) { if (stack.get(j).length > constants.script.MAX_PUSH) throw new ScriptError('PUSH_SIZE'); } + + output = new Script(scripts.render()); + output.execute(stack, flags, tx, i, 1); + + if (stack.length !== 0) + throw new ScriptError('EVAL_FALSE'); } - output = new Script(scripts.render()); - output.execute(stack, flags, tx, i, 1); - - if (stack.length !== 0) - throw new ScriptError('EVAL_FALSE'); - return true; }; @@ -3471,9 +3623,6 @@ Script.checksig = function checksig(msg, sig, key, flags) { if (flags == null) flags = constants.flags.STANDARD_VERIFY_FLAGS; - if (!Buffer.isBuffer(sig)) - return false; - // Attempt to normalize the signature // length before passing to elliptic. // Note: We only do this for historical data! diff --git a/lib/script/stack.js b/lib/script/stack.js index c9b081fb..17a2c12e 100644 --- a/lib/script/stack.js +++ b/lib/script/stack.js @@ -9,9 +9,6 @@ module.exports = Stack; -var constants = require('../protocol/constants'); -var opcodes = constants.opcodes; -var ScriptError = require('../utils/errors').ScriptError; var Script = require('./script'); var Witness = require('./witness'); @@ -88,16 +85,6 @@ Stack.prototype.clone = function clone() { return new Stack(this.items.slice()); }; -/** - * Get total size of the stack, including the alt stack. - * @param {Array} alt - Alt stack. - * @returns {Number} - */ - -Stack.prototype.getSize = function getSize(alt) { - return this.items.length + alt.length; -}; - /** * Push item onto stack. * @see Array#push @@ -125,11 +112,12 @@ Stack.prototype.unshift = function unshift(item) { * @param {Number} start * @param {Number} end * @see Array#slice - * @returns {Buffer[]} + * @returns {Stack} */ Stack.prototype.slice = function slice(start, end) { - return this.items.slice(start, end); + this.items = this.items.slice(start, end); + return this; }; /** @@ -142,11 +130,62 @@ Stack.prototype.slice = function slice(start, end) { */ Stack.prototype.splice = function splice(i, remove, insert) { + if (i < 0) + i = this.items.length + i; + if (insert === undefined) return this.items.splice(i, remove); + return this.items.splice(i, remove, insert); }; +/** + * Erase stack items. + * @param {Number} start + * @param {Number} end + * @returns {Buffer[]} + */ + +Stack.prototype.erase = function erase(start, end) { + if (start < 0) + start = this.items.length + start; + + if (end < 0) + end = this.items.length + end; + + this.items.splice(start, end - start); +}; + +/** + * Insert an item. + * @param {Number} index + * @param {Buffer} item + * @returns {Buffer} + */ + +Stack.prototype.insert = function insert(i, item) { + if (i < 0) + i = this.items.length + i; + + this.items.splice(i, 0, item); +}; + +/** + * Remove an item. + * @param {Number} index + * @returns {Buffer} + */ + +Stack.prototype.remove = function remove(i) { + if (i < 0) + i = this.items.length + i; + + if (i >= this.items.length) + return; + + return this.items.splice(i, 1)[0]; +}; + /** * Pop a stack item. * @see Array#pop @@ -206,21 +245,26 @@ Stack.prototype.clear = function clear() { */ Stack.prototype.set = function set(i, value) { + if (i < 0) + i = this.items.length + i; + return this.items[i] = value; }; /** * Swap stack values. - * @private * @param {Number} i1 - Index 1. * @param {Number} i2 - Index 2. */ -Stack.prototype._swap = function _swap(i1, i2) { +Stack.prototype.swap = function swap(i1, i2) { var v1, v2; - i1 = this.items.length + i1; - i2 = this.items.length + i2; + if (i1 < 0) + i1 = this.items.length + i1; + + if (i2 < 0) + i2 = this.items.length + i2; v1 = this.items[i1]; v2 = this.items[i2]; @@ -229,300 +273,6 @@ Stack.prototype._swap = function _swap(i1, i2) { this.items[i2] = v1; }; -/** - * Perform the OP_TOALTSTACK operation. - * @param {Array} alt - Alt stack. - * @throws {ScriptError} - */ - -Stack.prototype.toalt = function toalt(alt) { - if (this.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TOALTSTACK); - - alt.push(this.pop()); -}; - -/** - * Perform the OP_FROMALTSTACK operation. - * @param {Array} alt - Alt stack. - * @throws {ScriptError} - */ - -Stack.prototype.fromalt = function fromalt(alt) { - if (alt.length === 0) - throw new ScriptError('INVALID_ALTSTACK_OPERATION', opcodes.OP_FROMALTSTACK); - - this.push(alt.pop()); -}; - -/** - * Perform the OP_IFDUP operation. - * @throws {ScriptError} - */ - -Stack.prototype.ifdup = function ifdup() { - if (this.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_IFDUP); - - if (Script.bool(this.top(-1))) - this.push(this.top(-1)); -}; - -/** - * Perform the OP_DEPTH operation. - * @throws {ScriptError} - */ - -Stack.prototype.depth = function depth() { - this.push(Script.array(this.length)); -}; - -/** - * Perform the OP_DROP operation. - * @throws {ScriptError} - */ - -Stack.prototype.drop = function drop() { - if (this.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DROP); - - this.pop(); -}; - -/** - * Perform the OP_DUP operation. - * @throws {ScriptError} - */ - -Stack.prototype.dup = function dup() { - if (this.length === 0) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DUP); - - this.push(this.top(-1)); -}; - -/** - * Perform the OP_NIP operation. - * @throws {ScriptError} - */ - -Stack.prototype.nip = function nip() { - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_NIP); - - this.splice(this.length - 2, 1); -}; - -/** - * Perform the OP_OVER operation. - * @throws {ScriptError} - */ - -Stack.prototype.over = function over() { - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_OVER); - - this.push(this.top(-2)); -}; - -/** - * Perform the OP_PICK operation. - * @param {VerifyFlags} flags - * @throws {ScriptError} - */ - -Stack.prototype.pick = function pick(flags) { - return this._pickroll(opcodes.OP_PICK, flags); -}; - -/** - * Perform the OP_ROLL operation. - * @param {VerifyFlags} flags - * @throws {ScriptError} - */ - -Stack.prototype.roll = function roll(flags) { - return this._pickroll(opcodes.OP_ROLL, flags); -}; - -/** - * Perform a pick or roll. - * @private - * @param {Number} op - * @param {VerifyFlags} flags - * @throws {ScriptError} - */ - -Stack.prototype._pickroll = function pickroll(op, flags) { - var val, n; - - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', op); - - val = this.pop(); - n = Script.num(val, flags).toNumber(); - - if (n < 0 || n >= this.length) - throw new ScriptError('INVALID_STACK_OPERATION', op); - - val = this.top(-n - 1); - - if (op === opcodes.OP_ROLL) - this.splice(this.length - n - 1, 1); - - this.push(val); -}; - -/** - * Perform the OP_ROT operation. - * @throws {ScriptError} - */ - -Stack.prototype.rot = function rot() { - if (this.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_ROT); - - this._swap(-3, -2); - this._swap(-2, -1); -}; - -/** - * Perform the OP_SWAP operation. - * @throws {ScriptError} - */ - -Stack.prototype.swap = function swap() { - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SWAP); - - this._swap(-2, -1); -}; - -/** - * Perform the OP_TUCK operation. - * @throws {ScriptError} - */ - -Stack.prototype.tuck = function tuck() { - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TUCK); - - this.splice(this.length - 2, 0, this.top(-1)); -}; - -/** - * Perform the OP_2DROP operation. - * @throws {ScriptError} - */ - -Stack.prototype.drop2 = function drop2() { - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DROP); - - this.pop(); - this.pop(); -}; - -/** - * Perform the OP_2DUP operation. - * @throws {ScriptError} - */ - -Stack.prototype.dup2 = function dup2() { - var v1, v2; - - if (this.length < 2) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DUP); - - v1 = this.top(-2); - v2 = this.top(-1); - - this.push(v1); - this.push(v2); -}; - -/** - * Perform the OP_3DUP operation. - * @throws {ScriptError} - */ - -Stack.prototype.dup3 = function dup3() { - var v1, v2, v3; - - if (this.length < 3) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_3DUP); - - v1 = this.top(-3); - v2 = this.top(-2); - v3 = this.top(-1); - - this.push(v1); - this.push(v2); - this.push(v3); -}; - -/** - * Perform the OP_2OVER operation. - * @throws {ScriptError} - */ - -Stack.prototype.over2 = function over2() { - var v1, v2; - - if (this.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2OVER); - - v1 = this.top(-4); - v2 = this.top(-3); - - this.push(v1); - this.push(v2); -}; - -/** - * Perform the OP_2ROT operation. - * @throws {ScriptError} - */ - -Stack.prototype.rot2 = function rot2() { - var v1, v2; - - if (this.length < 6) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2ROT); - - v1 = this.top(-6); - v2 = this.top(-5); - - this.splice(this.length - 6, 2); - this.push(v1); - this.push(v2); -}; - -/** - * Perform the OP_2SWAP operation. - * @throws {ScriptError} - */ - -Stack.prototype.swap2 = function swap2() { - if (this.length < 4) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2SWAP); - - this._swap(-4, -2); - this._swap(-3, -1); -}; - -/** - * Perform the OP_SIZE operation. - * @throws {ScriptError} - */ - -Stack.prototype.size = function size() { - if (this.length < 1) - throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SIZE); - - this.push(Script.array(this.top(-1).length)); -}; - /** * Test an object to see if it is a Stack. * @param {Object} obj diff --git a/test/script-test.js b/test/script-test.js index 03e82daf..06b1ae22 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -67,7 +67,7 @@ describe('Script', function() { inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res); - assert.deepEqual(stack.slice(), [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]]); var inputScript = new Script([opcodes.OP_1, opcodes.OP_2]); var prevOutScript = new Script([ @@ -84,7 +84,7 @@ describe('Script', function() { inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res); - assert.deepEqual(stack.slice(), [[1], [4], [5]]); + assert.deepEqual(stack.items, [[1], [4], [5]]); var inputScript = new Script([opcodes.OP_1, opcodes.OP_2]); var prevOutScript = new Script([ @@ -99,7 +99,7 @@ describe('Script', function() { inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res); - assert.deepEqual(stack.slice(), [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]]); var inputScript = new Script([opcodes.OP_1, opcodes.OP_2]); var prevOutScript = new Script([ @@ -114,7 +114,7 @@ describe('Script', function() { inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res); - assert.deepEqual(stack.slice(), [[1], [5]]); + assert.deepEqual(stack.items, [[1], [5]]); var inputScript = new Script([opcodes.OP_1, opcodes.OP_2]); var prevOutScript = new Script([ @@ -129,7 +129,7 @@ describe('Script', function() { inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res); - assert.deepEqual(stack.slice(), [[1], [3], [5]]); + assert.deepEqual(stack.items, [[1], [3], [5]]); }); function success(res, stack) {