Browse Source

Merge pull request #777 from maraoz/add/tx_invalid/tests

add tx_invalid.json tests
patch-2
Esteban Ordano 11 years ago
parent
commit
9f442e8ec4
  1. 3
      lib/block.js
  2. 4
      lib/script/interpreter.js
  3. 18
      lib/transaction/input/input.js
  4. 83
      lib/transaction/transaction.js
  5. 123
      test/script_interpreter.js

3
lib/block.js

@ -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

4
lib/script/interpreter.js

@ -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;

18
lib/transaction/input/input.js

@ -140,12 +140,20 @@ Input.prototype.isValidSignature = function(transaction, signature) {
// FIXME: Refactor signature so this is not necessary // FIXME: Refactor signature so this is not necessary
signature.signature.nhashtype = signature.sigtype; signature.signature.nhashtype = signature.sigtype;
return Sighash.verify( return Sighash.verify(
transaction, transaction,
signature.signature, signature.signature,
signature.publicKey, signature.publicKey,
signature.inputIndex, signature.inputIndex,
this.output.script this.output.script
); );
}; };
/**
* @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;

83
lib/transaction/transaction.js

@ -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;

123
test/script_interpreter.js

@ -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,93 +224,53 @@ describe('ScriptInterpreter', function() {
}); });
describe('bitcoind transaction evaluation fixtures', function() { describe('bitcoind transaction evaluation fixtures', function() {
var c = 0; var test_txs = function(set, expected) {
tx_valid.forEach(function(vector) { var c = 0;
if (vector.length === 1) { set.forEach(function(vector) {
return; if (vector.length === 1) {
} return;
c++; }
it('should pass tx_valid vector ' + c, function() { c++;
var inputs = vector[0]; it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + c, function() {
var txhex = vector[1]; var inputs = vector[0];
var flags = getFlags(vector[2]); var txhex = vector[1];
var flags = getFlags(vector[2]);
var map = {};
inputs.forEach(function(input) { var map = {};
var txid = input[0]; inputs.forEach(function(input) {
var txoutnum = input[1]; var txid = input[0];
var scriptPubKeyStr = input[2]; var txoutnum = input[1];
if (txoutnum === -1) { var scriptPubKeyStr = input[2];
txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int if (txoutnum === -1) {
} txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int
map[txid + ':' + txoutnum] = Script.fromBitcoindString(scriptPubKeyStr); }
}); map[txid + ':' + txoutnum] = Script.fromBitcoindString(scriptPubKeyStr);
});
var tx = Transaction(txhex);
tx.inputs.forEach(function(txin, j) { var tx = Transaction(txhex);
var scriptSig = txin.script; var allInputsVerified = true;
var txidhex = txin.prevTxId.toString('hex'); tx.inputs.forEach(function(txin, j) {
var txoutnum = txin.outputIndex;
var scriptPubkey = map[txidhex + ':' + txoutnum];
should.exist(scriptPubkey);
should.exist(scriptSig);
var interp = ScriptInterpreter();
var verified = interp.verify(scriptSig, scriptPubkey, tx, j, flags);
verified.should.equal(true);
});
});
});
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 scriptSig = txin.script;
var txidhex = BufferReader(txin.txidbuf).readReverse().toString('hex'); var txidhex = txin.prevTxId.toString('hex');
var txoutnum = txin.txoutnum; var txoutnum = txin.outputIndex;
var scriptPubkey = map[txidhex + ':' + txoutnum]; var scriptPubkey = map[txidhex + ':' + txoutnum];
should.exist(scriptPubkey); should.exist(scriptPubkey);
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);
return verified === false; if (!verified) {
}).should.equal(true); allInputsVerified = false;
} }
});
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…
Cancel
Save