Merge pull request #777 from maraoz/add/tx_invalid/tests
add tx_invalid.json tests
This commit is contained in:
commit
9f442e8ec4
|
@ -29,6 +29,9 @@ function Block(arg) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/b5fa132329f0377d787a4a21c1686609c2bfaece/src/primitives/block.h#L14
|
||||||
|
Block.MAX_BLOCK_SIZE = 1000000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {*} - A Buffer, JSON string or Object
|
* @param {*} - A Buffer, JSON string or Object
|
||||||
* @returns {Object} - An object representing block data
|
* @returns {Object} - An object representing block data
|
||||||
|
|
|
@ -889,7 +889,7 @@ ScriptInterpreter.prototype.step = function() {
|
||||||
try {
|
try {
|
||||||
var sig = Signature.fromTxFormat(bufSig);
|
var sig = Signature.fromTxFormat(bufSig);
|
||||||
var pubkey = PublicKey.fromBuffer(bufPubkey, false);
|
var pubkey = PublicKey.fromBuffer(bufPubkey, false);
|
||||||
fSuccess = this.tx.verify(sig, pubkey, this.nin, subscript);
|
fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//invalid sig or pubkey
|
//invalid sig or pubkey
|
||||||
fSuccess = false;
|
fSuccess = false;
|
||||||
|
@ -978,7 +978,7 @@ ScriptInterpreter.prototype.step = function() {
|
||||||
try {
|
try {
|
||||||
var sig = Signature.fromTxFormat(bufSig);
|
var sig = Signature.fromTxFormat(bufSig);
|
||||||
var pubkey = PublicKey.fromBuffer(bufPubkey, false);
|
var pubkey = PublicKey.fromBuffer(bufPubkey, false);
|
||||||
fOk = this.tx.verify(sig, pubkey, this.nin, subscript);
|
fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//invalid sig or pubkey
|
//invalid sig or pubkey
|
||||||
fOk = false;
|
fOk = false;
|
||||||
|
|
|
@ -148,4 +148,12 @@ Input.prototype.isValidSignature = function(transaction, signature) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns true if this is a coinbase input (represents no input)
|
||||||
|
*/
|
||||||
|
Input.prototype.isNull = function() {
|
||||||
|
return this.prevTxId.toString('hex') === '0000000000000000000000000000000000000000000000000000000000000000' &&
|
||||||
|
this.outputIndex === 0xffffffff;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Input;
|
module.exports = Input;
|
||||||
|
|
|
@ -22,6 +22,8 @@ var MultiSigScriptHashInput = Input.MultiSigScriptHash;
|
||||||
var Output = require('./output');
|
var Output = require('./output');
|
||||||
var Script = require('../script');
|
var Script = require('../script');
|
||||||
var PrivateKey = require('../privatekey');
|
var PrivateKey = require('../privatekey');
|
||||||
|
var Block = require('../block');
|
||||||
|
var BN = require('../crypto/bn');
|
||||||
|
|
||||||
var CURRENT_VERSION = 1;
|
var CURRENT_VERSION = 1;
|
||||||
var DEFAULT_NLOCKTIME = 0;
|
var DEFAULT_NLOCKTIME = 0;
|
||||||
|
@ -57,10 +59,13 @@ function Transaction(serialized) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// max amount of satoshis in circulation
|
||||||
|
Transaction.MAX_MONEY = 21000000 * 1e8;
|
||||||
|
|
||||||
/* Constructors and Serialization */
|
/* Constructors and Serialization */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a "shallow" copy of the transaction, by serializing and deserializing
|
* Create a 'shallow' copy of the transaction, by serializing and deserializing
|
||||||
* it dropping any additional information that inputs and outputs may have hold
|
* it dropping any additional information that inputs and outputs may have hold
|
||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
|
@ -562,8 +567,82 @@ Transaction.prototype.isValidSignature = function(signature) {
|
||||||
/**
|
/**
|
||||||
* @returns {bool} whether the signature is valid for this transaction input
|
* @returns {bool} whether the signature is valid for this transaction input
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.verify = function(sig, pubkey, nin, subscript) {
|
Transaction.prototype.verifySignature = function(sig, pubkey, nin, subscript) {
|
||||||
return Sighash.verify(this, sig, pubkey, nin, subscript);
|
return Sighash.verify(this, sig, pubkey, nin, subscript);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a transaction passes basic sanity tests. If not, return a string
|
||||||
|
* describing the error. This function contains the same logic as
|
||||||
|
* CheckTransaction in bitcoin core.
|
||||||
|
*/
|
||||||
|
Transaction.prototype.verify = function() {
|
||||||
|
// Basic checks that don't depend on any context
|
||||||
|
if (this.inputs.length === 0) {
|
||||||
|
return 'transaction txins empty';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.outputs.length === 0) {
|
||||||
|
return 'transaction txouts empty';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size limits
|
||||||
|
if (this.toBuffer().length > Block.MAX_BLOCK_SIZE) {
|
||||||
|
return 'transaction over the maximum block size';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for negative or overflow output values
|
||||||
|
var valueoutbn = BN(0);
|
||||||
|
for (var i = 0; i < this.outputs.length; i++) {
|
||||||
|
var txout = this.outputs[i];
|
||||||
|
var valuebn = BN(txout.satoshis.toString(16));
|
||||||
|
if (valuebn.lt(0)) {
|
||||||
|
return 'transaction txout ' + i + ' negative';
|
||||||
|
}
|
||||||
|
if (valuebn.gt(Transaction.MAX_MONEY)) {
|
||||||
|
return 'transaction txout ' + i + ' greater than MAX_MONEY';
|
||||||
|
}
|
||||||
|
valueoutbn = valueoutbn.add(valuebn);
|
||||||
|
if (valueoutbn.gt(Transaction.MAX_MONEY)) {
|
||||||
|
return 'transaction txout ' + i + ' total output greater than MAX_MONEY';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate inputs
|
||||||
|
var txinmap = {};
|
||||||
|
for (i = 0; i < this.inputs.length; i++) {
|
||||||
|
var txin = this.inputs[i];
|
||||||
|
|
||||||
|
var inputid = txin.prevTxId + ':' + txin.outputIndex;
|
||||||
|
if (!_.isUndefined(txinmap[inputid])) {
|
||||||
|
return 'transaction input ' + i + ' duplicate input';
|
||||||
|
}
|
||||||
|
txinmap[inputid] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isCoinbase = this.isCoinbase();
|
||||||
|
if (isCoinbase) {
|
||||||
|
var buf = this.inputs[0]._script.toBuffer();
|
||||||
|
if (buf.length < 2 || buf.length > 100) {
|
||||||
|
return 'coinbase trasaction script size invalid';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < this.inputs.length; i++) {
|
||||||
|
if (this.inputs[i].isNull()) {
|
||||||
|
return 'tranasction input ' + i + ' has null input';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analagous to bitcoind's IsCoinBase function in transaction.h
|
||||||
|
*/
|
||||||
|
Transaction.prototype.isCoinbase = function() {
|
||||||
|
return (this.inputs.length === 1 && this.inputs[0].isNull());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = Transaction;
|
module.exports = Transaction;
|
||||||
|
|
|
@ -9,6 +9,7 @@ var BN = bitcore.crypto.BN;
|
||||||
var BufferReader = bitcore.encoding.BufferReader;
|
var BufferReader = bitcore.encoding.BufferReader;
|
||||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||||
var Opcode = bitcore.Opcode;
|
var Opcode = bitcore.Opcode;
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
var script_valid = require('./data/bitcoind/script_valid');
|
var script_valid = require('./data/bitcoind/script_valid');
|
||||||
var script_invalid = require('./data/bitcoind/script_invalid');
|
var script_invalid = require('./data/bitcoind/script_invalid');
|
||||||
|
@ -223,13 +224,14 @@ describe('ScriptInterpreter', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('bitcoind transaction evaluation fixtures', function() {
|
describe('bitcoind transaction evaluation fixtures', function() {
|
||||||
|
var test_txs = function(set, expected) {
|
||||||
var c = 0;
|
var c = 0;
|
||||||
tx_valid.forEach(function(vector) {
|
set.forEach(function(vector) {
|
||||||
if (vector.length === 1) {
|
if (vector.length === 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c++;
|
c++;
|
||||||
it('should pass tx_valid vector ' + c, function() {
|
it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + c, function() {
|
||||||
var inputs = vector[0];
|
var inputs = vector[0];
|
||||||
var txhex = vector[1];
|
var txhex = vector[1];
|
||||||
var flags = getFlags(vector[2]);
|
var flags = getFlags(vector[2]);
|
||||||
|
@ -246,6 +248,7 @@ describe('ScriptInterpreter', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
var tx = Transaction(txhex);
|
var tx = Transaction(txhex);
|
||||||
|
var allInputsVerified = true;
|
||||||
tx.inputs.forEach(function(txin, j) {
|
tx.inputs.forEach(function(txin, j) {
|
||||||
var scriptSig = txin.script;
|
var scriptSig = txin.script;
|
||||||
var txidhex = txin.prevTxId.toString('hex');
|
var txidhex = txin.prevTxId.toString('hex');
|
||||||
|
@ -255,61 +258,19 @@ describe('ScriptInterpreter', function() {
|
||||||
should.exist(scriptSig);
|
should.exist(scriptSig);
|
||||||
var interp = ScriptInterpreter();
|
var interp = ScriptInterpreter();
|
||||||
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
|
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
|
||||||
verified.should.equal(true);
|
if (!verified) {
|
||||||
});
|
allInputsVerified = false;
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
c = 0;
|
|
||||||
tx_invalid.forEach(function(vector) {
|
|
||||||
if (vector.length === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
c++;
|
|
||||||
|
|
||||||
// tests intentionally not performed by the script interpreter:
|
|
||||||
// TODO: check this?
|
|
||||||
/*
|
|
||||||
if (c === 7 || // tests if valuebn is negative
|
|
||||||
c === 8 || // tests if valuebn is greater than MAX_MONEY
|
|
||||||
c === 10 || // tests if two inputs are equal
|
|
||||||
c === 11 || // coinbase
|
|
||||||
c === 12 || // coinbase
|
|
||||||
c === 13 // null input
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
it.skip('should pass tx_invalid vector ' + c, function() {
|
|
||||||
var inputs = vector[0];
|
|
||||||
var txhex = vector[1];
|
|
||||||
var flags = getFlags(vector[2]);
|
|
||||||
|
|
||||||
var map = {};
|
|
||||||
inputs.forEach(function(input) {
|
|
||||||
var txoutnum = input[1];
|
|
||||||
if (txoutnum === -1) {
|
|
||||||
txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int
|
|
||||||
}
|
|
||||||
map[input[0] + ':' + txoutnum] = Script.fromBitcoindString(input[2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
var tx = Transaction().fromBuffer(new Buffer(txhex, 'hex'));
|
|
||||||
if (tx.txins.length > 0) {
|
|
||||||
tx.txins.some(function(txin, j) {
|
|
||||||
var scriptSig = txin.script;
|
|
||||||
var txidhex = BufferReader(txin.txidbuf).readReverse().toString('hex');
|
|
||||||
var txoutnum = txin.txoutnum;
|
|
||||||
var scriptPubkey = map[txidhex + ':' + txoutnum];
|
|
||||||
should.exist(scriptPubkey);
|
|
||||||
var interp = ScriptInterpreter();
|
|
||||||
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
|
|
||||||
return verified === false;
|
|
||||||
}).should.equal(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
var txVerified = tx.verify();
|
||||||
|
txVerified = _.isBoolean(txVerified);
|
||||||
|
allInputsVerified = allInputsVerified && txVerified;
|
||||||
|
allInputsVerified.should.equal(expected);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
test_txs(tx_valid, true);
|
||||||
|
test_txs(tx_invalid, false);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue