From ce0c6f4fc7b99dc79f41edc704028dd4628656d6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 19 Apr 2016 21:43:40 -0700 Subject: [PATCH] accurate scripting error messages. --- lib/bcoin/script.js | 469 +++++++++++++++++++++++--------------------- 1 file changed, 245 insertions(+), 224 deletions(-) diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 71e7204b..0bf40344 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -587,7 +587,7 @@ Stack.prototype._swap = function _swap(i1, i2) { Stack.prototype.toalt = function toalt() { if (this.length === 0) - throw new ScriptError('Stack too small.', opcodes.OP_TOALTSTACK); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TOALTSTACK); this.alt.push(this.pop()); }; @@ -599,7 +599,7 @@ Stack.prototype.toalt = function toalt() { Stack.prototype.fromalt = function fromalt() { if (this.alt.length === 0) - throw new ScriptError('Stack too small.', opcodes.OP_FROMALTSTACK); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_FROMALTSTACK); this.push(this.alt.pop()); }; @@ -611,7 +611,7 @@ Stack.prototype.fromalt = function fromalt() { Stack.prototype.ifdup = function ifdup() { if (this.length === 0) - throw new ScriptError('Stack too small.', opcodes.OP_IFDUP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_IFDUP); if (Script.bool(this.top(-1))) this.push(Script.array(this.top(-1))); @@ -633,7 +633,7 @@ Stack.prototype.depth = function depth() { Stack.prototype.drop = function drop() { if (this.length === 0) - throw new ScriptError('Stack too small.', opcodes.OP_DROP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DROP); this.pop(); }; @@ -645,7 +645,7 @@ Stack.prototype.drop = function drop() { Stack.prototype.dup = function dup() { if (this.length === 0) - throw new ScriptError('Stack too small.', opcodes.OP_DUP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_DUP); this.push(this.top(-1)); }; @@ -657,7 +657,7 @@ Stack.prototype.dup = function dup() { Stack.prototype.nip = function nip() { if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_NIP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_NIP); this.splice(this.length - 2, 1); }; @@ -669,7 +669,7 @@ Stack.prototype.nip = function nip() { Stack.prototype.over = function over() { if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_OVER); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_OVER); this.push(this.top(-2)); }; @@ -698,13 +698,13 @@ Stack.prototype._pickroll = function pickroll(op, flags) { var val, n; if (this.length < 2) - throw new ScriptError('Stack too small.', op); + 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('Bad value.', op); + throw new ScriptError('INVALID_STACK_OPERATION', op); val = this.top(-n - 1); @@ -721,7 +721,7 @@ Stack.prototype._pickroll = function pickroll(op, flags) { Stack.prototype.rot = function rot() { if (this.length < 3) - throw new ScriptError('Stack too small.', opcodes.OP_ROT); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_ROT); this._swap(-3, -2); this._swap(-2, -1); @@ -734,7 +734,7 @@ Stack.prototype.rot = function rot() { Stack.prototype.swap = function swap() { if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_SWAP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SWAP); this._swap(-2, -1); }; @@ -746,7 +746,7 @@ Stack.prototype.swap = function swap() { Stack.prototype.tuck = function tuck() { if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_TUCK); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_TUCK); this.splice(this.length - 2, 0, this.top(-1)); }; @@ -758,7 +758,7 @@ Stack.prototype.tuck = function tuck() { Stack.prototype.drop2 = function drop2() { if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_2DROP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DROP); this.pop(); this.pop(); @@ -773,7 +773,7 @@ Stack.prototype.dup2 = function dup2() { var v1, v2; if (this.length < 2) - throw new ScriptError('Stack too small.', opcodes.OP_2DUP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2DUP); v1 = this.top(-2); v2 = this.top(-1); @@ -791,7 +791,7 @@ Stack.prototype.dup3 = function dup3() { var v1, v2, v3; if (this.length < 3) - throw new ScriptError('Stack too small.', opcodes.OP_3DUP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_3DUP); v1 = this.top(-3); v2 = this.top(-2); @@ -811,7 +811,7 @@ Stack.prototype.over2 = function over2() { var v1, v2; if (this.length < 4) - throw new ScriptError('Stack too small.', opcodes.OP_2OVER); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2OVER); v1 = this.top(-4); v2 = this.top(-3); @@ -829,7 +829,7 @@ Stack.prototype.rot2 = function rot2() { var v1, v2; if (this.length < 6) - throw new ScriptError('Stack too small.', opcodes.OP_2ROT); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2ROT); v1 = this.top(-6); v2 = this.top(-5); @@ -846,7 +846,7 @@ Stack.prototype.rot2 = function rot2() { Stack.prototype.swap2 = function swap2() { if (this.length < 4) - throw new ScriptError('Stack too small.', opcodes.OP_2SWAP); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_2SWAP); this._swap(-4, -2); this._swap(-3, -1); @@ -859,7 +859,7 @@ Stack.prototype.swap2 = function swap2() { Stack.prototype.size = function size() { if (this.length < 1) - throw new ScriptError('Stack too small.', opcodes.OP_SIZE); + throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_SIZE); this.push(Script.array(this.top(-1).length)); }; @@ -1064,28 +1064,28 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version stack.negate = 0; if (this.getSize() > constants.script.MAX_SIZE) - throw new ScriptError('Script too large.'); + throw new ScriptError('SCRIPT_SIZE', null, -1); for (ip = 0; ip < this.code.length; ip++) { op = this.code[ip]; if (Buffer.isBuffer(op)) { if (!Script.checkPush(op)) - throw new ScriptError('Pushdata out of range.', op, ip); + throw new ScriptError('BAD_OPCODE', op, ip); if (op.length > constants.script.MAX_PUSH) - throw new ScriptError('Pushdata too large.', op, ip); + throw new ScriptError('PUSH_SIZE', op, ip); // Note that minimaldata is not checked // on unexecuted branches of code. if (stack.negate === 0) { if (!Script.checkMinimal(op, flags)) - throw new ScriptError('Push verification failed.', op, ip); + throw new ScriptError('MINIMALDATA', op, ip); stack.push(op); } continue; } if (op > opcodes.OP_16 && ++opCount > constants.script.MAX_OPS) - throw new ScriptError('Too many opcodes.', op, ip); + throw new ScriptError('OP_COUNT', op, ip); // It's very important to make a distiction // here: these opcodes will fail _even if they @@ -1107,7 +1107,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version || op == opcodes.OP_MOD || op == opcodes.OP_LSHIFT || op == opcodes.OP_RSHIFT) { - throw new ScriptError('Disabled opcode.', op, ip); + throw new ScriptError('DISABLED_OPCODE', op, ip); } if (op >= opcodes.OP_IF && op <= opcodes.OP_ENDIF) { @@ -1116,7 +1116,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_NOTIF: { if (stack.negate === 0) { if (stack.length < 1) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); val = Script.bool(stack.pop()); if (op === opcodes.OP_NOTIF) val = !val; @@ -1131,7 +1131,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version } case opcodes.OP_ELSE: { if (stack.state.length === 0) - throw new ScriptError('Unexpected else.', op, ip); + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); stack.state[stack.state.length - 1] ^= 1; if (stack.state[stack.state.length - 1] === 0) stack.negate++; @@ -1141,14 +1141,14 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version } case opcodes.OP_ENDIF: { if (stack.state.length === 0) - throw new ScriptError('Unexpected endif.', op, ip); + throw new ScriptError('UNBALANCED_CONDITIONAL', 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); + throw new ScriptError('BAD_OPCODE', op, ip); } default: { assert.fatal(false, 'Fatal script error.'); @@ -1160,24 +1160,92 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (stack.negate !== 0) continue; - if (op === opcodes.OP_0) { - stack.push(STACK_FALSE); - continue; - } - - if (op >= opcodes.OP_1 && op <= opcodes.OP_16) { - stack.push(new Buffer([op - 0x50])); - continue; - } - - if (op === opcodes.OP_1NEGATE) { - stack.push(STACK_NEGATE); - continue; - } - switch (op) { - case opcodes.OP_NOP: + case opcodes.OP_0: { + stack.push(STACK_FALSE); break; + } + case opcodes.OP_1NEGATE: { + stack.push(STACK_NEGATE); + break; + } + case opcodes.OP_1: + case opcodes.OP_2: + case opcodes.OP_3: + case opcodes.OP_4: + case opcodes.OP_5: + case opcodes.OP_6: + case opcodes.OP_7: + case opcodes.OP_8: + case opcodes.OP_9: + case opcodes.OP_10: + case opcodes.OP_11: + case opcodes.OP_12: + case opcodes.OP_13: + case opcodes.OP_14: + case opcodes.OP_15: + case opcodes.OP_16: { + stack.push(new Buffer([op - 0x50])); + break; + } + case opcodes.OP_NOP: { + break; + } + case opcodes.OP_CHECKLOCKTIMEVERIFY: { + // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 + if (!(flags & constants.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); + break; + } + + if (!tx) + throw new ScriptError('NO_TX', op, ip); + + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + locktime = Script.num(stack.top(-1), flags, 5); + + if (locktime.cmpn(0) < 0) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + + locktime = locktime.uand(utils.U32).toNumber(); + + if (!Script.checkLocktime(locktime, tx, index)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + + break; + } + case opcodes.OP_CHECKSEQUENCEVERIFY: { + // OP_CHECKSEQUENCEVERIFY = OP_NOP3 + if (!(flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY)) { + if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); + break; + } + + if (!tx) + throw new ScriptError('NO_TX', op, ip); + + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + + locktime = Script.num(stack.top(-1), flags, 5); + + if (locktime.cmpn(0) < 0) + throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); + + locktime = locktime.uand(utils.U32).toNumber(); + + if ((locktime & constants.sequence.DISABLE_FLAG) !== 0) + break; + + if (!Script.checkSequence(locktime, tx, index)) + throw new ScriptError('UNSATISFIED_LOCKTIME', op, ip); + + break; + } case opcodes.OP_NOP1: case opcodes.OP_NOP4: case opcodes.OP_NOP5: @@ -1187,18 +1255,18 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_NOP9: case opcodes.OP_NOP10: { if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('Upgradable NOP used.', op, ip); + throw new ScriptError('DISCOURAGE_UPGRADABLE_NOPS', op, ip); break; } case opcodes.OP_VERIFY: { if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); if (!Script.bool(stack.pop())) - throw new ScriptError('Verification failed.', op, ip); + throw new ScriptError('VERIFY', op, ip); break; } case opcodes.OP_RETURN: { - throw new ScriptError('Script returned.', op, ip); + throw new ScriptError('OP_RETURN', op, ip); } case opcodes.OP_TOALTSTACK: { stack.toalt(); @@ -1208,6 +1276,30 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version stack.fromalt(); break; } + case opcodes.OP_2DROP: { + stack.drop2(); + break; + } + case opcodes.OP_2DUP: { + stack.dup2(); + break; + } + case opcodes.OP_3DUP: { + stack.dup3(); + break; + } + case opcodes.OP_2OVER: { + stack.over2(); + break; + } + case opcodes.OP_2ROT: { + stack.rot2(); + break; + } + case opcodes.OP_2SWAP: { + stack.swap2(); + break; + } case opcodes.OP_IFDUP: { stack.ifdup(); break; @@ -1252,34 +1344,23 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version stack.tuck(); break; } - case opcodes.OP_2DROP: { - stack.drop2(); - break; - } - case opcodes.OP_2DUP: { - stack.dup2(); - break; - } - case opcodes.OP_3DUP: { - stack.dup3(); - break; - } - case opcodes.OP_2OVER: { - stack.over2(); - break; - } - case opcodes.OP_2ROT: { - stack.rot2(); - break; - } - case opcodes.OP_2SWAP: { - stack.swap2(); - break; - } case opcodes.OP_SIZE: { stack.size(); break; } + case opcodes.OP_EQUAL: + case opcodes.OP_EQUALVERIFY: { + if (stack.length < 2) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + res = utils.equals(stack.pop(), stack.pop()); + if (op === opcodes.OP_EQUALVERIFY) { + if (!res) + throw new ScriptError('VERIFY', op, ip); + } else { + stack.push(res ? STACK_TRUE : STACK_FALSE); + } + break; + } case opcodes.OP_1ADD: case opcodes.OP_1SUB: case opcodes.OP_2MUL: @@ -1289,7 +1370,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_NOT: case opcodes.OP_0NOTEQUAL: { if (stack.length < 1) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); n = Script.num(stack.pop(), flags); switch (op) { case opcodes.OP_1ADD: @@ -1342,8 +1423,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_LESSTHANOREQUAL: case opcodes.OP_GREATERTHANOREQUAL: case opcodes.OP_MIN: - case opcodes.OP_MAX: - case opcodes.OP_WITHIN: { + case opcodes.OP_MAX: { switch (op) { case opcodes.OP_ADD: case opcodes.OP_SUB: @@ -1364,7 +1444,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_MIN: case opcodes.OP_MAX: if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); n2 = Script.num(stack.pop(), flags); n1 = Script.num(stack.pop(), flags); n = new bn(0); @@ -1386,12 +1466,12 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version break; case opcodes.OP_LSHIFT: if (n2.cmpn(0) < 0 || n2.cmpn(2048) > 0) - throw new ScriptError('Shift out of range.', op, ip); + throw new ScriptError('BAD_SHIFT', 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); + throw new ScriptError('BAD_SHIFT', op, ip); n = n1.ushrn(n2.toNumber()); break; case opcodes.OP_BOOLAND: @@ -1434,78 +1514,66 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version n = new bn(n ? 1 : 0); if (op === opcodes.OP_NUMEQUALVERIFY) { if (!Script.bool(n)) - throw new ScriptError('Verify failed.', op, ip); + throw new ScriptError('NUMEQUALVERIFY', op, ip); } else { stack.push(Script.array(n)); } break; - case opcodes.OP_WITHIN: - if (stack.length < 3) - 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); - val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; - stack.push(val ? STACK_TRUE : STACK_FALSE); - break; } break; } + 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); + val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + 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(utils.ripemd160(stack.pop())); + break; + } + case opcodes.OP_SHA1: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(utils.sha1(stack.pop())); + break; + } + case opcodes.OP_SHA256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(utils.sha256(stack.pop())); + break; + } + case opcodes.OP_HASH256: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(utils.dsha256(stack.pop())); + break; + } + case opcodes.OP_HASH160: { + if (stack.length === 0) + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); + stack.push(utils.ripesha(stack.pop())); + break; + } case opcodes.OP_CODESEPARATOR: { lastSep = ip; break; } - case opcodes.OP_RIPEMD160: { - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - stack.push(utils.ripemd160(stack.pop())); - break; - } - case opcodes.OP_SHA1: { - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - stack.push(utils.sha1(stack.pop())); - break; - } - case opcodes.OP_SHA256: { - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - stack.push(utils.sha256(stack.pop())); - break; - } - case opcodes.OP_HASH256: { - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - stack.push(utils.dsha256(stack.pop())); - break; - } - case opcodes.OP_HASH160: { - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - stack.push(utils.ripesha(stack.pop())); - break; - } - case opcodes.OP_EQUALVERIFY: - case opcodes.OP_EQUAL: { - if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); - res = utils.equals(stack.pop(), stack.pop()); - if (op === opcodes.OP_EQUALVERIFY) { - if (!res) - throw new ScriptError('Equal verification failed.', op, ip); - } else { - stack.push(res ? STACK_TRUE : STACK_FALSE); - } - break; - } case opcodes.OP_CHECKSIGVERIFY: case opcodes.OP_CHECKSIG: { if (!tx) - throw new ScriptError('No TX passed in.', op, ip); + throw new ScriptError('NO_TX', op, ip); if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); key = stack.pop(); sig = stack.pop(); @@ -1514,11 +1582,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version if (version === 0) subscript.removeData(sig); - if (!Script.isValidKey(key, flags)) - throw new ScriptError('Key is not valid.', op, ip); - if (!Script.isValidSignature(sig, flags)) - throw new ScriptError('Signature is not valid.', op, ip); + throw new ScriptError('SIG_DER_OR_HASHTYPE', op, ip); + + if (!Script.isValidKey(key, flags)) + throw new ScriptError('PUBKEYTYPE', op, ip); type = sig[sig.length - 1]; @@ -1527,7 +1595,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version res = Script.checksig(hash, sig, key, flags); if (op === opcodes.OP_CHECKSIGVERIFY) { if (!res) - throw new ScriptError('Signature verification failed.', op, ip); + throw new ScriptError('CHECKSIGVERIFY', op, ip); } else { stack.push(res ? STACK_TRUE : STACK_FALSE); } @@ -1537,40 +1605,40 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_CHECKMULTISIGVERIFY: case opcodes.OP_CHECKMULTISIG: { if (!tx) - throw new ScriptError('No TX passed in.', op, ip); + throw new ScriptError('NO_TX', op, ip); i = 1; if (stack.length < i) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); n = Script.num(stack.top(-i), flags).toNumber(); if (!(n >= 0 && n <= constants.script.MAX_MULTISIG_PUBKEYS)) - throw new ScriptError('`n` is out of bounds.', op, ip); + throw new ScriptError('PUBKEY_COUNT', op, ip); opCount += n; if (opCount > constants.script.MAX_OPS) - throw new ScriptError('Too many ops.', op, ip); + throw new ScriptError('OP_COUNT', op, ip); i++; ikey = i; i += n; if (stack.length < i) - throw new ScriptError('`n` exceeds stack size.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); m = Script.num(stack.top(-i), flags).toNumber(); if (!(m >= 0 && m <= n)) - throw new ScriptError('`m` is out of bounds.', op, ip); + throw new ScriptError('SIG_COUNT', op, ip); i++; isig = i; i += m; if (stack.length < i) - throw new ScriptError('`m` exceeds stack size.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); subscript = this.getSubscript(lastSep); @@ -1586,10 +1654,10 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version key = stack.top(-ikey); if (!Script.isValidSignature(sig, flags)) - throw new ScriptError('Signature is not valid.', op, ip); + throw new ScriptError('SIG_DER_OR_HASHTYPE', op, ip); if (!Script.isValidKey(key, flags)) - throw new ScriptError('Key is not valid.', op, ip); + throw new ScriptError('PUBKEYTYPE', op, ip); type = sig[sig.length - 1]; hash = tx.signatureHash(index, subscript, type, version); @@ -1610,97 +1678,42 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version stack.pop(); if (stack.length < 1) - throw new ScriptError('No dummy present.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); if (flags & constants.flags.VERIFY_NULLDUMMY) { if (!Script.isDummy(stack.top(-1))) - throw new ScriptError('Dummy did not verify.', op, ip); + throw new ScriptError('VERIFY_NULLDUMMY', op, ip); } stack.pop(); if (op === opcodes.OP_CHECKMULTISIGVERIFY) { if (!res) - throw new ScriptError('Signature verification failed.', op, ip); + throw new ScriptError('CHECKMULTISIGVERIFY', op, ip); } else { stack.push(res ? STACK_TRUE : STACK_FALSE); } break; } - case opcodes.OP_CHECKLOCKTIMEVERIFY: { - // OP_CHECKLOCKTIMEVERIFY = OP_NOP2 - if (!(flags & constants.flags.VERIFY_CHECKLOCKTIMEVERIFY)) { - if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('Upgradable NOP used.', op, ip); - break; - } - - if (!tx) - throw new ScriptError('No TX passed in.', op, ip); - - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - - locktime = Script.num(stack.top(-1), flags, 5); - - if (locktime.cmpn(0) < 0) - throw new ScriptError('Negative locktime.', op, ip); - - locktime = locktime.uand(utils.U32).toNumber(); - - if (!Script.checkLocktime(locktime, tx, index)) - throw new ScriptError('Locktime verification failed.', op, ip); - - break; - } - case opcodes.OP_CHECKSEQUENCEVERIFY: { - // OP_CHECKSEQUENCEVERIFY = OP_NOP3 - if (!(flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY)) { - if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS) - throw new ScriptError('Upgradable NOP used.', op, ip); - break; - } - - if (!tx) - throw new ScriptError('No TX passed in.', op, ip); - - if (stack.length === 0) - throw new ScriptError('Stack too small.', op, ip); - - locktime = Script.num(stack.top(-1), flags, 5); - - if (locktime.cmpn(0) < 0) - throw new ScriptError('Negative sequence.', op, ip); - - locktime = locktime.uand(utils.U32).toNumber(); - - if ((locktime & constants.sequence.DISABLE_FLAG) !== 0) - break; - - if (!Script.checkSequence(locktime, tx, index)) - throw new ScriptError('Sequence verification failed.', op, ip); - - break; - } case opcodes.OP_CAT: { if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); v2 = stack.pop(); v1 = stack.pop(); stack.push(Buffer.concat([v1, v2])); if (stack.top(-1).length > constants.script.MAX_PUSH) - throw new ScriptError('Push data too large.', op, ip); + throw new ScriptError('PUSH_SIZE', op, ip); break; } case opcodes.OP_SUBSTR: { if (stack.length < 3) - throw new ScriptError('Stack too small.', op, ip); + 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 if (v2 < 0 || v3 < v2) - throw new ScriptError('String begin or end out of range.', op, ip); + throw new ScriptError('STRING_OUT_OF_RANGE', op, ip); if (v2 > v1.length) v2 = v1.length; if (v3 > v1.length) @@ -1711,11 +1724,11 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_LEFT: case opcodes.OP_RIGHT: { if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); v2 = Script.num(stack.pop(), flags).toNumber(); // size v1 = stack.pop(); // string if (v2 < 0) - throw new ScriptError('String size is negative.', op, ip); + throw new ScriptError('STRING_OUT_OF_RANGE', op, ip); if (v2 > v1.length) v2 = v1.length; if (op === opcodes.OP_LEFT) @@ -1727,7 +1740,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version } case opcodes.OP_INVERT: { if (stack.length < 1) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); val = utils.slice(stack.pop()); for (i = 0; i < val.length; i++) val[i] = ~val[i] & 0xff; @@ -1738,7 +1751,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version case opcodes.OP_OR: case opcodes.OP_XOR: { if (stack.length < 2) - throw new ScriptError('Stack too small.', op, ip); + throw new ScriptError('INVALID_STACK_OPERATION', op, ip); v2 = stack.pop(); v1 = utils.slice(stack.pop()); if (v1.length < v2.length) { @@ -1765,16 +1778,16 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version break; } default: { - throw new ScriptError('Unknown opcode.', op, ip); + throw new ScriptError('BAD_OPCODE', op, ip); } } } if (stack.getSize() > constants.script.MAX_STACK) - throw new ScriptError('Stack size too large.', op, ip); + throw new ScriptError('STACK_SIZE', op, ip); if (stack.state.length !== 0) - throw new ScriptError('Expected endif.', op, ip); + throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); return true; }; @@ -4279,33 +4292,41 @@ Script.isScript = function isScript(obj) { * @global * @constructor * @extends Error - * @param {String} msg - Error message. + * @param {String} code - Error code. * @param {(Number|String)?} op - Opcode. * @param {Number?} ip - Instruction pointer. * @property {String} message - Error message. + * @property {String} code - Original code passed in. * @property {String?} op - Symbolic opcode. * @property {Number?} ip - Instruction pointer. */ -function ScriptError(msg, op, ip) { +function ScriptError(code, op, ip) { Error.call(this); + if (Error.captureStackTrace) Error.captureStackTrace(this, ScriptError); + this.type = 'ScriptError'; + this.code = code; + if (Buffer.isBuffer(op)) op = 'PUSHDATA[' + op.length + ']'; + if (op || ip != null) { - msg += '('; + code += '('; if (op) { op = constants.opcodesByVal[op] || op; - msg += 'op=' + op; + code += 'op=' + op; if (ip != null) - msg += ', '; + code += ', '; } if (ip != null) - msg += 'ip=' + ip; + code += 'ip=' + ip; + code + ')'; } - this.message = msg; + + this.message = code; this.op = op; this.ip = ip; }