more errors for scripting system.

This commit is contained in:
Christopher Jeffrey 2016-04-19 22:44:54 -07:00
parent ce0c6f4fc7
commit 50ab39aafb
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 142 additions and 167 deletions

View File

@ -599,7 +599,7 @@ Stack.prototype.toalt = function toalt() {
Stack.prototype.fromalt = function fromalt() {
if (this.alt.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', opcodes.OP_FROMALTSTACK);
throw new ScriptError('INVALID_ALTSTACK_OPERATION', opcodes.OP_FROMALTSTACK);
this.push(this.alt.pop());
};
@ -1009,30 +1009,6 @@ Script.prototype.getSubscript = function getSubscript(lastSep) {
return new Script(code);
};
/**
* Execute the script and return false on script execution errors.
* @param {Stack} stack - Script execution stack.
* @param {Number?} flags - Script standard flags.
* @param {TX?} tx - Transaction being verified.
* @param {Number?} index - Index of input being verified.
* @param {Number?} version - Signature hash version (0=legacy, 1=segwit).
* @returns {Boolean} Whether the execution was successful.
*/
Script.prototype.execute = function execute(stack, flags, tx, index, version) {
try {
return this.interpret(stack, flags, tx, index, version);
} catch (e) {
if (e.type === 'ScriptError') {
bcoin.debug('Script error: %s.', e.message);
} else {
bcoin.debug('Script interpreter threw:');
bcoin.debug(e.stack + '');
}
return false;
}
};
/**
* Execute and interpret the script.
* @param {Stack} stack - Script execution stack.
@ -1044,7 +1020,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, version) {
* @returns {Boolean} Whether the execution was successful.
*/
Script.prototype.interpret = function interpret(stack, flags, tx, index, version) {
Script.prototype.execute = function execute(stack, flags, tx, index, version) {
var ip = 0;
var lastSep = -1;
var opCount = 0;
@ -1064,7 +1040,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
stack.negate = 0;
if (this.getSize() > constants.script.MAX_SIZE)
throw new ScriptError('SCRIPT_SIZE', null, -1);
throw new ScriptError('SCRIPT_SIZE');
for (ip = 0; ip < this.code.length; ip++) {
op = this.code[ip];
@ -1200,7 +1176,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
}
if (!tx)
throw new ScriptError('NO_TX', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.');
if (stack.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
@ -1226,7 +1202,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
}
if (!tx)
throw new ScriptError('NO_TX', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.');
if (stack.length === 0)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
@ -1355,7 +1331,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
res = utils.equals(stack.pop(), stack.pop());
if (op === opcodes.OP_EQUALVERIFY) {
if (!res)
throw new ScriptError('VERIFY', op, ip);
throw new ScriptError('EQUALVERIFY', op, ip);
} else {
stack.push(res ? STACK_TRUE : STACK_FALSE);
}
@ -1466,12 +1442,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('BAD_SHIFT', op, ip);
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('BAD_SHIFT', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'Bad shift.');
n = n1.ushrn(n2.toNumber());
break;
case opcodes.OP_BOOLAND:
@ -1570,7 +1546,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
case opcodes.OP_CHECKSIGVERIFY:
case opcodes.OP_CHECKSIG: {
if (!tx)
throw new ScriptError('NO_TX', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.');
if (stack.length < 2)
throw new ScriptError('INVALID_STACK_OPERATION', op, ip);
@ -1582,11 +1558,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
if (version === 0)
subscript.removeData(sig);
if (!Script.isValidSignature(sig, flags))
throw new ScriptError('SIG_DER_OR_HASHTYPE', op, ip);
if (!Script.isValidKey(key, flags))
throw new ScriptError('PUBKEYTYPE', op, ip);
Script.validateSignature(sig, flags);
Script.validateKey(key, flags);
type = sig[sig.length - 1];
@ -1605,7 +1578,7 @@ 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', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'No TX passed in.');
i = 1;
if (stack.length < i)
@ -1653,11 +1626,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
sig = stack.top(-isig);
key = stack.top(-ikey);
if (!Script.isValidSignature(sig, flags))
throw new ScriptError('SIG_DER_OR_HASHTYPE', op, ip);
if (!Script.isValidKey(key, flags))
throw new ScriptError('PUBKEYTYPE', op, ip);
Script.validateSignature(sig, flags);
Script.validateKey(key, flags);
type = sig[sig.length - 1];
hash = tx.signatureHash(index, subscript, type, version);
@ -1682,7 +1652,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
if (flags & constants.flags.VERIFY_NULLDUMMY) {
if (!Script.isDummy(stack.top(-1)))
throw new ScriptError('VERIFY_NULLDUMMY', op, ip);
throw new ScriptError('SIG_NULLDUMMY', op, ip);
}
stack.pop();
@ -1713,7 +1683,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
v2 = Script.num(stack.pop(), flags).toNumber(); // begin
v1 = stack.pop(); // string
if (v2 < 0 || v3 < v2)
throw new ScriptError('STRING_OUT_OF_RANGE', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'String out of range.');
if (v2 > v1.length)
v2 = v1.length;
if (v3 > v1.length)
@ -1728,7 +1698,7 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
v2 = Script.num(stack.pop(), flags).toNumber(); // size
v1 = stack.pop(); // string
if (v2 < 0)
throw new ScriptError('STRING_OUT_OF_RANGE', op, ip);
throw new ScriptError('UNKNOWN_ERROR', 'String size out of range.');
if (v2 > v1.length)
v2 = v1.length;
if (op === opcodes.OP_LEFT)
@ -1905,7 +1875,7 @@ Script.num = function num(value, flags, size) {
size = 4;
if (value.length > size)
throw new ScriptError('Script number overflow.');
throw new ScriptError('UNKNOWN_ERROR', 'Script number overflow.');
if ((flags & constants.flags.VERIFY_MINIMALDATA) && value.length > 0) {
// If the low bits on the last byte are unset,
@ -1917,8 +1887,11 @@ Script.num = function num(value, flags, size) {
// to avoid negative zero (also avoids positive
// zero).
if (!(value[value.length - 1] & 0x7f)) {
if (value.length === 1 || !(value[value.length - 2] & 0x80))
throw new ScriptError('Non-minimally encoded Script number.');
if (value.length === 1 || !(value[value.length - 2] & 0x80)) {
throw new ScriptError(
'UNKNOWN_ERROR',
'Non-minimally encoded Script number.');
}
}
}
@ -3232,20 +3205,19 @@ Script.isZero = function isZero(op) {
* @param {Buffer} key
* @param {VerifyFlags?} flags
* @returns {Boolean}
* @throws {ScriptError}
*/
Script.isValidKey = function isValidKey(key, flags) {
Script.validateKey = function validateKey(key, flags) {
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (!Script.isPush(key))
return false;
throw new ScriptError('BAD_OPCODE');
if (flags & constants.flags.VERIFY_STRICTENC) {
if (!Script.isKeyEncoding(key)) {
bcoin.debug('Script failed key encoding test.');
return false;
}
if (!Script.isKeyEncoding(key))
throw new ScriptError('PUBKEYTYPE');
}
return true;
@ -3286,14 +3258,15 @@ Script.isKeyEncoding = function isKeyEncoding(key) {
* @param {Buffer} sig
* @param {VerifyFlags?} flags
* @returns {Boolean}
* @throws {ScriptError}
*/
Script.isValidSignature = function isValidSignature(sig, flags) {
Script.validateSignature = function validateSignature(sig, flags) {
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (!Script.isPush(sig))
return false;
throw new ScriptError('BAD_OPCODE');
// Allow empty sigs
if (sig.length === 0)
@ -3302,24 +3275,18 @@ Script.isValidSignature = function isValidSignature(sig, flags) {
if ((flags & constants.flags.VERIFY_DERSIG)
|| (flags & constants.flags.VERIFY_LOW_S)
|| (flags & constants.flags.VERIFY_STRICTENC)) {
if (!Script.isSignatureEncoding(sig)) {
bcoin.debug('Script does not have a proper signature encoding.');
return false;
}
if (!Script.isSignatureEncoding(sig))
throw new ScriptError('SIG_DER');
}
if (flags & constants.flags.VERIFY_LOW_S) {
if (!Script.isLowDER(sig)) {
bcoin.debug('Script does not have a low DER.');
return false;
}
if (!Script.isLowDER(sig))
throw new ScriptError('SIG_HIGH_S');
}
if (flags & constants.flags.VERIFY_STRICTENC) {
if (!Script.isHashType(sig)) {
bcoin.debug('Script does not have a valid hash type.');
return false;
}
if (!Script.isHashType(sig))
throw new ScriptError('SIG_HASHTYPE');
}
return true;
@ -3809,10 +3776,11 @@ Script.fromSymbolic = function fromSymbolic(items) {
* @param {Number} i
* @param {VerifyFlags} flags
* @returns {Boolean}
* @throws {ScriptError}
*/
Script.verify = function verify(input, witness, output, tx, i, flags) {
var copy, res, raw, redeem, hadWitness;
var copy, raw, redeem, hadWitness;
var stack = new Stack();
if (flags == null)
@ -3820,36 +3788,32 @@ Script.verify = function verify(input, witness, output, tx, i, flags) {
if (flags & constants.flags.VERIFY_SIGPUSHONLY) {
if (!input.isPushOnly())
return false;
throw new ScriptError('SIG_PUSHONLY');
}
// Execute the input script
res = input.execute(stack, flags, tx, i, 0);
if (!res)
return false;
input.execute(stack, flags, tx, i, 0);
// Copy the stack for P2SH
if (flags & constants.flags.VERIFY_P2SH)
copy = stack.clone();
// Execute the previous output script
res = output.execute(stack, flags, tx, i, 0);
output.execute(stack, flags, tx, i, 0);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !Script.bool(stack.pop()))
return false;
if (stack.length === 0 || !Script.bool(stack.pop()))
throw new ScriptError('EVAL_FALSE');
if ((flags & constants.flags.VERIFY_WITNESS) && output.isWitnessProgram()) {
hadWitness = true;
// Input script must be empty.
if (input.code.length !== 0)
return false;
throw new ScriptError('WITNESS_MALLEATED');
// Verify the program in the output script
if (!Script.verifyProgram(witness, output, flags, tx, i))
return false;
Script.verifyProgram(witness, output, flags, tx, i);
// Force a cleanstack
stack.length = 0;
@ -3859,40 +3823,35 @@ Script.verify = function verify(input, witness, output, tx, i, flags) {
if ((flags & constants.flags.VERIFY_P2SH) && output.isScripthash()) {
// P2SH can only have push ops in the scriptSig
if (!input.isPushOnly())
return false;
throw new ScriptError('SIG_PUSHONLY');
// Reset the stack
stack = copy;
// Stack should not be empty at this point
if (stack.length === 0)
return false;
throw new ScriptError('EVAL_FALSE');
// Grab the real redeem script
raw = stack.pop();
if (!Script.isPush(raw))
return false;
redeem = new Script(raw);
// Execute the redeem script
res = redeem.execute(stack, flags, tx, i, 0);
redeem.execute(stack, flags, tx, i, 0);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !Script.bool(stack.pop()))
return false;
if (stack.length === 0 || !Script.bool(stack.pop()))
throw new ScriptError('EVAL_FALSE');
if ((flags & constants.flags.VERIFY_WITNESS) && redeem.isWitnessProgram()) {
hadWitness = true;
// Input script must be exactly one push of the redeem script.
if (!(input.code.length === 1 && utils.equals(input.code[0], raw)))
return false;
throw new ScriptError('WITNESS_MALLEATED');
// Verify the program in the redeem script
if (!Script.verifyProgram(witness, redeem, flags, tx, i))
return false;
Script.verifyProgram(witness, redeem, flags, tx, i);
// Force a cleanstack
stack.length = 0;
@ -3904,14 +3863,14 @@ Script.verify = function verify(input, witness, output, tx, i, flags) {
assert((flags & constants.flags.VERIFY_P2SH) !== 0);
// assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
if (stack.length !== 0)
return false;
throw new ScriptError('CLEANSTACK');
}
// If we had a witness but no witness program, fail.
if (flags & constants.flags.VERIFY_WITNESS) {
assert((flags & constants.flags.VERIFY_P2SH) !== 0);
if (!hadWitness && witness.length > 0)
return false;
throw new ScriptError('WITNESS_UNEXPECTED');
}
return true;
@ -3926,23 +3885,38 @@ Script.verify = function verify(input, witness, output, tx, i, flags) {
* @param {VerifyFlags} flags
* @param {TX} tx
* @param {Number} i
* @throws {ScriptError}
*/
Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) {
var program, witnessScript, redeem, stack, j, res;
var program = output.getWitnessProgram();
var stack = witness.toStack();
var witnessScript, redeem, j;
assert(program, 'verifyProgram called on non-witness-program.');
assert((flags & constants.flags.VERIFY_WITNESS) !== 0);
assert(output.isWitnessProgram());
program = output.getWitnessProgram();
if (program.version === 0) {
if (program.data.length === 32) {
if (stack.length === 0)
throw new ScriptError('WITNESS_PROGRAM_WITNESS_EMPTY');
// Failure on version=0 (bad program data length)
if (!program.type) {
bcoin.debug('Malformed witness program.');
return false;
}
witnessScript = stack.pop();
if (program.version > 0) {
if (!utils.equals(utils.sha256(witnessScript), program.data))
throw new ScriptError('WITNESS_PROGRAM_MISMATCH');
redeem = new Script(witnessScript);
} else if (program.data.length === 20) {
if (stack.length !== 2)
throw new ScriptError('WITNESS_PROGRAM_MISMATCH');
redeem = Script.createPubkeyhash(program.data);
} else {
// Failure on version=0 (bad program data length)
throw new ScriptError('WITNESS_PROGRAM_WRONG_LENGTH');
}
} else {
bcoin.debug('Unknown witness program version: %s', program.version);
// Anyone can spend (we can return true here
// if we want to always relay these transactions).
@ -3952,45 +3926,24 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, i) {
// succeed in a block, but fail in the mempool
// due to VERIFY_CLEANSTACK.
if (flags & constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
return false;
throw new ScriptError('DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM');
return true;
}
stack = witness.toStack();
if (program.type === 'witnesspubkeyhash') {
if (stack.length !== 2)
return false;
redeem = Script.createPubkeyhash(program.data);
} else if (program.type === 'witnessscripthash') {
if (stack.length === 0)
return false;
witnessScript = stack.pop();
if (!utils.equals(utils.sha256(witnessScript), program.data))
return false;
redeem = new Script(witnessScript);
} else {
assert(false);
}
for (j = 0; j < stack.length; j++) {
if (stack.get(j).length > constants.script.MAX_PUSH)
return false;
throw new ScriptError('PUSH_SIZE');
}
res = redeem.execute(stack, flags, tx, i, 1);
redeem.execute(stack, flags, tx, i, 1);
// Verify the script did not fail as well as the stack values
if (!res || stack.length === 0 || !Script.bool(stack.pop()))
return false;
if (stack.length === 0 || !Script.bool(stack.pop()))
throw new ScriptError('EVAL_FALSE');
// Witnesses always require cleanstack
if (stack.length !== 0)
return false;
throw new ScriptError('EVAL_FALSE');
return true;
};
@ -4310,25 +4263,31 @@ function ScriptError(code, op, ip) {
this.type = 'ScriptError';
this.code = code;
if (Buffer.isBuffer(op))
op = 'PUSHDATA[' + op.length + ']';
if (typeof op !== 'string') {
if (Buffer.isBuffer(op))
op = 'PUSHDATA[' + op.length + ']';
if (op || ip != null) {
code += '(';
if (op) {
op = constants.opcodesByVal[op] || op;
code += 'op=' + op;
if (op || ip != null) {
code += ' (';
if (op) {
op = constants.opcodesByVal[op] || op;
code += 'op=' + op;
if (ip != null)
code += ', ';
}
if (ip != null)
code += ', ';
code += 'ip=' + ip;
code += ')';
}
if (ip != null)
code += 'ip=' + ip;
code + ')';
}
this.message = code;
this.op = op;
this.ip = ip;
this.message = code;
this.op = op || '';
this.ip = ip != null ? ip : -1;
} else {
this.message = op;
this.op = '';
this.ip = -1;
}
}
utils.inherits(ScriptError, Error);

View File

@ -515,7 +515,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, type) {
*/
TX.prototype.verify = function verify(index, force, flags) {
var i, input, res;
var i, input;
// Valid if included in block
if (!force && this.ts !== 0)
@ -544,17 +544,23 @@ TX.prototype.verify = function verify(index, force, flags) {
return false;
}
res = Script.verify(
input.script,
input.witness,
input.coin.script,
this,
i,
flags
);
if (!res)
try {
Script.verify(
input.script,
input.witness,
input.coin.script,
this,
i,
flags
);
} catch (e) {
if (e.type === 'ScriptError') {
bcoin.debug('Script verification error: %s', e.message);
} else {
bcoin.debug('Script interpreter threw: %s', e.stack + '');
}
return false;
}
}
return true;
@ -1133,7 +1139,7 @@ TX.prototype.isStandard = function isStandard(flags, ret) {
TX.prototype.hasStandardInputs = function hasStandardInputs(flags) {
var maxSigops = constants.script.MAX_SCRIPTHASH_SIGOPS;
var VERIFY_NONE = constants.flags.VERIFY_NONE;
var i, input, stack, res, redeem;
var i, input, stack, redeem;
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
@ -1160,10 +1166,11 @@ TX.prototype.hasStandardInputs = function hasStandardInputs(flags) {
stack = new Stack();
res = input.script.execute(stack, VERIFY_NONE, this, i, 0);
if (!res)
try {
input.script.execute(stack, VERIFY_NONE, this, i, 0);
} catch (e) {
return false;
}
if (stack.length === 0)
return false;

View File

@ -254,11 +254,20 @@ describe('Script', function() {
delete input.raw;
delete output.raw;
}
var res = Script.verify(input, witness, output, tx, 0, flags);
if (expected === 'OK')
assert.ok(res);
else
assert.ok(!res);
var err, res;
try {
res = Script.verify(input, witness, output, tx, 0, flags);
} catch (e) {
err = e;
}
if (expected !== 'OK') {
assert(!res);
assert(err);
assert.equal(err.code, expected);
return;
}
utils.assert.noError(err);
assert(res);
});
});
});