Browse Source

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

add tx_invalid.json tests
patch-2
Esteban Ordano 10 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;
}
// https://github.com/bitcoin/bitcoin/blob/b5fa132329f0377d787a4a21c1686609c2bfaece/src/primitives/block.h#L14
Block.MAX_BLOCK_SIZE = 1000000;
/**
* @param {*} - A Buffer, JSON string or Object
* @returns {Object} - An object representing block data

4
lib/script/interpreter.js

@ -889,7 +889,7 @@ ScriptInterpreter.prototype.step = function() {
try {
var sig = Signature.fromTxFormat(bufSig);
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) {
//invalid sig or pubkey
fSuccess = false;
@ -978,7 +978,7 @@ ScriptInterpreter.prototype.step = function() {
try {
var sig = Signature.fromTxFormat(bufSig);
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) {
//invalid sig or pubkey
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
signature.signature.nhashtype = signature.sigtype;
return Sighash.verify(
transaction,
signature.signature,
signature.publicKey,
signature.inputIndex,
this.output.script
transaction,
signature.signature,
signature.publicKey,
signature.inputIndex,
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;

83
lib/transaction/transaction.js

@ -22,6 +22,8 @@ var MultiSigScriptHashInput = Input.MultiSigScriptHash;
var Output = require('./output');
var Script = require('../script');
var PrivateKey = require('../privatekey');
var Block = require('../block');
var BN = require('../crypto/bn');
var CURRENT_VERSION = 1;
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 */
/**
* 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
*
* @param {Transaction} transaction
@ -562,8 +567,82 @@ Transaction.prototype.isValidSignature = function(signature) {
/**
* @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);
};
/**
* 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;

123
test/script_interpreter.js

@ -9,6 +9,7 @@ var BN = bitcore.crypto.BN;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var Opcode = bitcore.Opcode;
var _ = require('lodash');
var script_valid = require('./data/bitcoind/script_valid');
var script_invalid = require('./data/bitcoind/script_invalid');
@ -223,93 +224,53 @@ describe('ScriptInterpreter', function() {
});
describe('bitcoind transaction evaluation fixtures', function() {
var c = 0;
tx_valid.forEach(function(vector) {
if (vector.length === 1) {
return;
}
c++;
it('should pass tx_valid vector ' + c, function() {
var inputs = vector[0];
var txhex = vector[1];
var flags = getFlags(vector[2]);
var map = {};
inputs.forEach(function(input) {
var txid = input[0];
var txoutnum = input[1];
var scriptPubKeyStr = input[2];
if (txoutnum === -1) {
txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int
}
map[txid + ':' + txoutnum] = Script.fromBitcoindString(scriptPubKeyStr);
});
var tx = Transaction(txhex);
tx.inputs.forEach(function(txin, j) {
var scriptSig = txin.script;
var txidhex = txin.prevTxId.toString('hex');
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 test_txs = function(set, expected) {
var c = 0;
set.forEach(function(vector) {
if (vector.length === 1) {
return;
}
c++;
it('should pass tx_' + (expected ? '' : 'in') + 'valid vector ' + c, function() {
var inputs = vector[0];
var txhex = vector[1];
var flags = getFlags(vector[2]);
var map = {};
inputs.forEach(function(input) {
var txid = input[0];
var txoutnum = input[1];
var scriptPubKeyStr = input[2];
if (txoutnum === -1) {
txoutnum = 0xffffffff; //bitcoind casts -1 to an unsigned int
}
map[txid + ':' + txoutnum] = Script.fromBitcoindString(scriptPubKeyStr);
});
var tx = Transaction(txhex);
var allInputsVerified = true;
tx.inputs.forEach(function(txin, j) {
var scriptSig = txin.script;
var txidhex = BufferReader(txin.txidbuf).readReverse().toString('hex');
var txoutnum = txin.txoutnum;
var txidhex = txin.prevTxId.toString('hex');
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);
return verified === false;
}).should.equal(true);
}
if (!verified) {
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