commit
10cd5cf692
@ -81,6 +81,9 @@ module.exports = [{
|
||||
}, {
|
||||
name: 'DustOutputs',
|
||||
message: 'Dust amount detected in one output'
|
||||
}, {
|
||||
name: 'InvalidSatoshis',
|
||||
message: 'Output satoshis are invalid',
|
||||
}, {
|
||||
name: 'FeeError',
|
||||
message: 'Fees are not correctly set {0}',
|
||||
|
||||
@ -9,6 +9,8 @@ var BufferWriter = require('../encoding/bufferwriter');
|
||||
var Script = require('../script');
|
||||
var $ = require('../util/preconditions');
|
||||
|
||||
var MAX_SAFE_INTEGER = 0x1fffffffffffff;
|
||||
|
||||
function Output(params) {
|
||||
if (!(this instanceof Output)) {
|
||||
return new Output(params);
|
||||
@ -48,16 +50,33 @@ Object.defineProperty(Output.prototype, 'satoshis', {
|
||||
this._satoshis = parseInt(num);
|
||||
this._satoshisBN = BN.fromNumber(this._satoshis);
|
||||
} else {
|
||||
$.checkArgument(
|
||||
JSUtil.isNaturalNumber(num),
|
||||
'Output satoshis is not a natural number'
|
||||
);
|
||||
this._satoshisBN = BN.fromNumber(num);
|
||||
this._satoshis = num;
|
||||
}
|
||||
$.checkState(
|
||||
JSUtil.isPositiveInteger(this._satoshis),
|
||||
'Output satoshis is not a positive integer'
|
||||
JSUtil.isNaturalNumber(this._satoshis),
|
||||
'Output satoshis is not a natural number'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Output.prototype.invalidSatoshis = function() {
|
||||
if (this._satoshis > MAX_SAFE_INTEGER) {
|
||||
return 'transaction txout satoshis greater than max safe integer';
|
||||
}
|
||||
if (this._satoshis !== this._satoshisBN.toNumber()) {
|
||||
return 'transaction txout satoshis has corrupted value';
|
||||
}
|
||||
if (this._satoshis < 0) {
|
||||
return 'transaction txout negative';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Output.prototype._fromObject = function(param) {
|
||||
this.satoshis = param.satoshis;
|
||||
if (param.script || param.scriptBuffer) {
|
||||
|
||||
@ -21,7 +21,6 @@ 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');
|
||||
|
||||
/**
|
||||
@ -60,6 +59,7 @@ function Transaction(serialized) {
|
||||
|
||||
var CURRENT_VERSION = 1;
|
||||
var DEFAULT_NLOCKTIME = 0;
|
||||
var MAX_BLOCK_SIZE = 1000000;
|
||||
|
||||
// Minimum amount for an output for it not to be considered a dust output
|
||||
Transaction.DUST_AMOUNT = 546;
|
||||
@ -167,12 +167,22 @@ Transaction.prototype.checkedSerialize = function(opts) {
|
||||
var serializationError = this.getSerializationError(opts);
|
||||
if (serializationError) {
|
||||
serializationError.message += ' Use Transaction#uncheckedSerialize if you want to skip security checks. ' +
|
||||
'See http://bitcore.io/guide/transaction.html#Serialization for more info.'
|
||||
'See http://bitcore.io/guide/transaction.html#Serialization for more info.';
|
||||
throw serializationError;
|
||||
}
|
||||
return this.uncheckedSerialize();
|
||||
};
|
||||
|
||||
Transaction.prototype.invalidSatoshis = function() {
|
||||
var invalid = false;
|
||||
for (var i = 0; i < this.outputs.length; i++) {
|
||||
if (this.outputs[i].invalidSatoshis()) {
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
return invalid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve a possible error that could appear when trying to serialize and broadcast this transaction
|
||||
*
|
||||
@ -181,11 +191,15 @@ Transaction.prototype.checkedSerialize = function(opts) {
|
||||
*/
|
||||
Transaction.prototype.getSerializationError = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (this.invalidSatoshis()) {
|
||||
return new errors.Transaction.InvalidSatoshis();
|
||||
}
|
||||
|
||||
var missingChange = this._missingChange();
|
||||
var feeIsTooLarge = this._isFeeTooLarge();
|
||||
var feeIsTooSmall = this._isFeeTooSmall();
|
||||
var isFullySigned = this.isFullySigned();
|
||||
var hasDustOutputs = this._hasDustOutputs();
|
||||
|
||||
if (!opts.disableLargeFees && feeIsTooLarge) {
|
||||
if (missingChange) {
|
||||
@ -644,7 +658,7 @@ Transaction.prototype.getChangeOutput = function() {
|
||||
*/
|
||||
Transaction.prototype.to = function(address, amount) {
|
||||
$.checkArgument(
|
||||
JSUtil.isPositiveInteger(amount),
|
||||
JSUtil.isNaturalNumber(amount),
|
||||
'Amount is expected to be a positive integer'
|
||||
);
|
||||
this.addOutput(new Output({
|
||||
@ -981,28 +995,28 @@ Transaction.prototype.verify = function() {
|
||||
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 = new BN(0);
|
||||
for (var i = 0; i < this.outputs.length; i++) {
|
||||
var txout = this.outputs[i];
|
||||
var valuebn = txout._satoshisBN;
|
||||
if (valuebn.lt(BN.Zero)) {
|
||||
return 'transaction txout ' + i + ' negative';
|
||||
|
||||
if (txout.invalidSatoshis()) {
|
||||
return 'transaction txout ' + i + ' satoshis is invalid';
|
||||
}
|
||||
if (valuebn.gt(new BN(Transaction.MAX_MONEY, 10))) {
|
||||
if (txout._satoshisBN.gt(new BN(Transaction.MAX_MONEY, 10))) {
|
||||
return 'transaction txout ' + i + ' greater than MAX_MONEY';
|
||||
}
|
||||
valueoutbn = valueoutbn.add(valuebn);
|
||||
valueoutbn = valueoutbn.add(txout._satoshisBN);
|
||||
if (valueoutbn.gt(new BN(Transaction.MAX_MONEY))) {
|
||||
return 'transaction txout ' + i + ' total output greater than MAX_MONEY';
|
||||
}
|
||||
}
|
||||
|
||||
// Size limits
|
||||
if (this.toBuffer().length > MAX_BLOCK_SIZE) {
|
||||
return 'transaction over the maximum block size';
|
||||
}
|
||||
|
||||
// Check for duplicate inputs
|
||||
var txinmap = {};
|
||||
for (i = 0; i < this.inputs.length; i++) {
|
||||
|
||||
@ -70,12 +70,12 @@ module.exports = {
|
||||
return target;
|
||||
},
|
||||
/**
|
||||
* Checks that a value is a positive integer
|
||||
* Checks that a value is a natural number, a positive integer or zero.
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isPositiveInteger: function isPositiveInteger(value) {
|
||||
isNaturalNumber: function isNaturalNumber(value) {
|
||||
return typeof value === 'number' &&
|
||||
isFinite(value) &&
|
||||
Math.floor(value) === value &&
|
||||
|
||||
60
npm-shrinkwrap.json
generated
60
npm-shrinkwrap.json
generated
@ -1,63 +1,43 @@
|
||||
{
|
||||
"name": "bitcore",
|
||||
"version": "0.9.0",
|
||||
"version": "0.12.3",
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "0.16.1",
|
||||
"from": "bn.js@0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.1.tgz"
|
||||
"version": "2.0.4",
|
||||
"from": "bn.js@=2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-2.0.4.tgz"
|
||||
},
|
||||
"bs58": {
|
||||
"version": "2.0.0",
|
||||
"from": "bs58@2.0.0",
|
||||
"from": "bs58@=2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-2.0.0.tgz"
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "0.16.0",
|
||||
"from": "elliptic@0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-0.16.0.tgz",
|
||||
"version": "3.0.3",
|
||||
"from": "elliptic@=3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-3.0.3.tgz",
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "0.16.1",
|
||||
"from": "bn.js@0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-0.16.1.tgz"
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.0.1",
|
||||
"from": "brorand@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.1.tgz"
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "0.3.2",
|
||||
"from": "hash.js@0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.3.2.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@^2.0.1"
|
||||
"version": "1.0.5",
|
||||
"from": "brorand@^1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.0.2",
|
||||
"from": "hash.js@^1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.2.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@2.0.1",
|
||||
"from": "inherits@=2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "0.3.2",
|
||||
"from": "hash.js@0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-0.3.2.tgz",
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@^2.0.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "2.4.1",
|
||||
"from": "lodash@=2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz"
|
||||
"version": "2.4.1",
|
||||
"from": "lodash@=2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz"
|
||||
},
|
||||
"sha512": {
|
||||
"version": "0.0.1",
|
||||
|
||||
@ -79,10 +79,10 @@
|
||||
"request": "browser-request"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": "=0.16.1",
|
||||
"bn.js": "=2.0.4",
|
||||
"bs58": "=2.0.0",
|
||||
"elliptic": "=0.16.0",
|
||||
"hash.js": "=0.3.2",
|
||||
"elliptic": "=3.0.3",
|
||||
"hash.js": "=1.0.2",
|
||||
"inherits": "=2.0.1",
|
||||
"lodash": "=2.4.1",
|
||||
"sha512": "=0.0.1"
|
||||
|
||||
@ -10,21 +10,25 @@ describe('Transaction deserialization', function() {
|
||||
describe('valid transaction test case', function() {
|
||||
var index = 0;
|
||||
vectors_valid.forEach(function(vector) {
|
||||
if (vector.length > 1) {
|
||||
var hexa = vector[1];
|
||||
Transaction(hexa).serialize(true).should.equal(hexa);
|
||||
index++;
|
||||
}
|
||||
it('vector #' + index, function() {
|
||||
if (vector.length > 1) {
|
||||
var hexa = vector[1];
|
||||
Transaction(hexa).serialize(true).should.equal(hexa);
|
||||
index++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('invalid transaction test case', function() {
|
||||
var index = 0;
|
||||
vectors_invalid.forEach(function(vector) {
|
||||
if (vector.length > 1) {
|
||||
var hexa = vector[1];
|
||||
Transaction(hexa).serialize(true).should.equal(hexa);
|
||||
index++;
|
||||
}
|
||||
it('invalid vector #' + index, function() {
|
||||
if (vector.length > 1) {
|
||||
var hexa = vector[1];
|
||||
Transaction(hexa).serialize(true).should.equal(hexa);
|
||||
index++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -45,7 +45,7 @@ describe('Output', function() {
|
||||
satoshis: -100,
|
||||
script: Script.empty()
|
||||
});
|
||||
}).should.throw('Output satoshis is not a positive integer');
|
||||
}).should.throw('Output satoshis is not a natural number');
|
||||
});
|
||||
|
||||
it('1.1', function() {
|
||||
@ -54,7 +54,7 @@ describe('Output', function() {
|
||||
satoshis: 1.1,
|
||||
script: Script.empty()
|
||||
});
|
||||
}).should.throw('Output satoshis is not a positive integer');
|
||||
}).should.throw('Output satoshis is not a natural number');
|
||||
});
|
||||
|
||||
it('NaN', function() {
|
||||
@ -63,7 +63,7 @@ describe('Output', function() {
|
||||
satoshis: NaN,
|
||||
script: Script.empty()
|
||||
});
|
||||
}).should.throw('Output satoshis is not a positive integer');
|
||||
}).should.throw('Output satoshis is not a natural number');
|
||||
});
|
||||
|
||||
it('Infinity', function() {
|
||||
@ -72,7 +72,7 @@ describe('Output', function() {
|
||||
satoshis: Infinity,
|
||||
script: Script.empty()
|
||||
});
|
||||
}).should.throw('Output satoshis is not a positive integer');
|
||||
}).should.throw('Output satoshis is not a natural number');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ var _ = require('lodash');
|
||||
var sinon = require('sinon');
|
||||
|
||||
var bitcore = require('../..');
|
||||
var BN = bitcore.crypto.BN;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var PrivateKey = bitcore.PrivateKey;
|
||||
var Script = bitcore.Script;
|
||||
@ -245,6 +246,18 @@ describe('Transaction', function() {
|
||||
transaction.outputs.length.should.equal(2);
|
||||
transaction.outputs[1].satoshis.should.equal(10000);
|
||||
});
|
||||
it('if satoshis are invalid', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith100000Satoshis)
|
||||
.to(toAddress, 99999)
|
||||
.change(changeAddress)
|
||||
.sign(privateKey);
|
||||
transaction.outputs[0]._satoshis = 100;
|
||||
transaction.outputs[0]._satoshisBN = new BN(101, 10);
|
||||
expect(function() {
|
||||
return transaction.serialize();
|
||||
}).to.throw(errors.Transaction.InvalidSatoshis);
|
||||
});
|
||||
it('if fee is too small, fail serialization', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith100000Satoshis)
|
||||
@ -355,7 +368,7 @@ describe('Transaction', function() {
|
||||
var buildSkipTest = function(builder, check) {
|
||||
return function() {
|
||||
var transaction = new Transaction();
|
||||
transaction.from(simpleUtxoWith1BTC)
|
||||
transaction.from(simpleUtxoWith1BTC);
|
||||
builder(transaction);
|
||||
|
||||
var options = {};
|
||||
@ -410,6 +423,57 @@ describe('Transaction', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#verify', function() {
|
||||
|
||||
it('not if _satoshis and _satoshisBN have different values', function() {
|
||||
var tx = new Transaction()
|
||||
.from({
|
||||
'txId': testPrevTx,
|
||||
'outputIndex': 0,
|
||||
'script': testScript,
|
||||
'satoshis': testAmount
|
||||
}).to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
||||
|
||||
tx.outputs[0]._satoshis = 100;
|
||||
tx.outputs[0]._satoshisBN = new BN('fffffffffffffff', 16);
|
||||
var verify = tx.verify();
|
||||
verify.should.equal('transaction txout 0 satoshis is invalid');
|
||||
});
|
||||
|
||||
it('not if _satoshis is negative', function() {
|
||||
var tx = new Transaction()
|
||||
.from({
|
||||
'txId': testPrevTx,
|
||||
'outputIndex': 0,
|
||||
'script': testScript,
|
||||
'satoshis': testAmount
|
||||
}).to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
||||
|
||||
tx.outputs[0]._satoshis = -100;
|
||||
tx.outputs[0]._satoshisBN = new BN(-100, 10);
|
||||
var verify = tx.verify();
|
||||
verify.should.equal('transaction txout 0 satoshis is invalid');
|
||||
});
|
||||
|
||||
it('not if transaction is greater than max block size', function() {
|
||||
|
||||
var tx = new Transaction()
|
||||
.from({
|
||||
'txId': testPrevTx,
|
||||
'outputIndex': 0,
|
||||
'script': testScript,
|
||||
'satoshis': testAmount
|
||||
}).to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
||||
|
||||
tx.toBuffer = sinon.stub().returns({length: 10000000});
|
||||
|
||||
var verify = tx.verify();
|
||||
verify.should.equal('transaction over the maximum block size');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('to and from JSON', function() {
|
||||
it('takes a string that is a valid JSON and deserializes from it', function() {
|
||||
var simple = new Transaction();
|
||||
@ -616,7 +680,7 @@ describe('Transaction', function() {
|
||||
transaction.outputAmount.should.equal(99990000);
|
||||
});
|
||||
it('returns correct values for coinjoin transaction', function() {
|
||||
// see livenet tx c16467eea05f1f30d50ed6dbc06a38539d9bb15110e4b7dc6653046a3678a718
|
||||
// see livenet tx c16467eea05f1f30d50ed6dbc06a38539d9bb15110e4b7dc6653046a3678a718
|
||||
var transaction = new Transaction(txCoinJoinHex);
|
||||
transaction.outputAmount.should.equal(4191290961);
|
||||
expect(function() {
|
||||
|
||||
@ -32,44 +32,49 @@ describe('js utils', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('isPositiveInteger', function() {
|
||||
describe('isNaturalNumber', function() {
|
||||
it('false for float', function() {
|
||||
var a = JSUtil.isPositiveInteger(0.1);
|
||||
var a = JSUtil.isNaturalNumber(0.1);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for string float', function() {
|
||||
var a = JSUtil.isPositiveInteger('0.1');
|
||||
var a = JSUtil.isNaturalNumber('0.1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for string integer', function() {
|
||||
var a = JSUtil.isPositiveInteger('1');
|
||||
var a = JSUtil.isNaturalNumber('1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for negative integer', function() {
|
||||
var a = JSUtil.isPositiveInteger(-1);
|
||||
var a = JSUtil.isNaturalNumber(-1);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for negative integer string', function() {
|
||||
var a = JSUtil.isPositiveInteger('-1');
|
||||
var a = JSUtil.isNaturalNumber('-1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for infinity', function() {
|
||||
var a = JSUtil.isPositiveInteger(Infinity);
|
||||
var a = JSUtil.isNaturalNumber(Infinity);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for NaN', function() {
|
||||
var a = JSUtil.isPositiveInteger(NaN);
|
||||
var a = JSUtil.isNaturalNumber(NaN);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('true for zero', function() {
|
||||
var a = JSUtil.isNaturalNumber(0);
|
||||
a.should.equal(true);
|
||||
});
|
||||
|
||||
it('true for positive integer', function() {
|
||||
var a = JSUtil.isPositiveInteger(1000);
|
||||
var a = JSUtil.isNaturalNumber(1000);
|
||||
a.should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user