Merge pull request #812 from eordano/feature/safeSerialize
Modify Transaction Interface
This commit is contained in:
commit
85169a3874
|
@ -34,6 +34,28 @@ module.exports = [{
|
||||||
}, {
|
}, {
|
||||||
name: 'InvalidArgumentType',
|
name: 'InvalidArgumentType',
|
||||||
message: format('Invalid Argument for {2}, expected {1} but got ') + '+ typeof arguments[0]',
|
message: format('Invalid Argument for {2}, expected {1} but got ') + '+ typeof arguments[0]',
|
||||||
|
}, {
|
||||||
|
name: 'Transaction',
|
||||||
|
message: format('Internal Error on Transaction {0}'),
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
name: 'Input',
|
||||||
|
message: format('Internal Error on Input {0}'),
|
||||||
|
errors: [{
|
||||||
|
name: 'MissingScript',
|
||||||
|
message: format('Need a script to create an input')
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
name: 'NeedMoreInfo',
|
||||||
|
message: format('{0}')
|
||||||
|
}, {
|
||||||
|
name: 'FeeError',
|
||||||
|
message: format('Fees are not correctly set {0}'),
|
||||||
|
}, {
|
||||||
|
name: 'ChangeAddressMissing',
|
||||||
|
message: format('Change address is missing')
|
||||||
|
}
|
||||||
|
]
|
||||||
}, {
|
}, {
|
||||||
name: 'Script',
|
name: 'Script',
|
||||||
message: format('Internal Error on Script {0}'),
|
message: format('Internal Error on Script {0}'),
|
||||||
|
|
|
@ -8,6 +8,7 @@ var BufferUtil = require('../../util/buffer');
|
||||||
var JSUtil = require('../../util/js');
|
var JSUtil = require('../../util/js');
|
||||||
var Script = require('../../script');
|
var Script = require('../../script');
|
||||||
var Sighash = require('../sighash');
|
var Sighash = require('../sighash');
|
||||||
|
var Output = require('../output');
|
||||||
|
|
||||||
function Input(params) {
|
function Input(params) {
|
||||||
if (!(this instanceof Input)) {
|
if (!(this instanceof Input)) {
|
||||||
|
@ -33,12 +34,15 @@ Input.prototype._fromObject = function(params) {
|
||||||
if (_.isString(params.prevTxId) && JSUtil.isHexa(params.prevTxId)) {
|
if (_.isString(params.prevTxId) && JSUtil.isHexa(params.prevTxId)) {
|
||||||
params.prevTxId = new buffer.Buffer(params.prevTxId, 'hex');
|
params.prevTxId = new buffer.Buffer(params.prevTxId, 'hex');
|
||||||
}
|
}
|
||||||
this.output = params.output;
|
this.output = params.output ?
|
||||||
|
(params.output instanceof Output ? params.output : new Output(params.output)) : undefined;
|
||||||
this.prevTxId = params.prevTxId;
|
this.prevTxId = params.prevTxId;
|
||||||
this.outputIndex = params.outputIndex;
|
this.outputIndex = params.outputIndex;
|
||||||
this.sequenceNumber = params.sequenceNumber;
|
this.sequenceNumber = params.sequenceNumber;
|
||||||
if (params.script || params.scriptBuffer) {
|
if (!_.isUndefined(params.script) || !_.isUndefined(params.scriptBuffer)) {
|
||||||
this.setScript(params.script || params.scriptBuffer);
|
this.setScript(_.isUndefined(params.script) ? params.scriptBuffer : params.script);
|
||||||
|
} else {
|
||||||
|
throw new errors.Transaction.Input.MissingScript();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -48,7 +52,8 @@ Input.prototype.toObject = function toObject() {
|
||||||
prevTxId: this.prevTxId.toString('hex'),
|
prevTxId: this.prevTxId.toString('hex'),
|
||||||
outputIndex: this.outputIndex,
|
outputIndex: this.outputIndex,
|
||||||
sequenceNumber: this.sequenceNumber,
|
sequenceNumber: this.sequenceNumber,
|
||||||
script: this._script.toString()
|
script: this.script.toString(),
|
||||||
|
output: this.output ? this.output.toObject() : undefined
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,6 +66,7 @@ Input.fromJSON = function(json) {
|
||||||
json = JSON.parse(json);
|
json = JSON.parse(json);
|
||||||
}
|
}
|
||||||
return new Input({
|
return new Input({
|
||||||
|
output: json.output ? new Output(json.output) : undefined,
|
||||||
prevTxId: json.prevTxId || json.txidbuf,
|
prevTxId: json.prevTxId || json.txidbuf,
|
||||||
outputIndex: _.isUndefined(json.outputIndex) ? json.txoutnum : json.outputIndex,
|
outputIndex: _.isUndefined(json.outputIndex) ? json.txoutnum : json.outputIndex,
|
||||||
sequenceNumber: json.sequenceNumber || json.seqnum,
|
sequenceNumber: json.sequenceNumber || json.seqnum,
|
||||||
|
@ -99,11 +105,13 @@ Input.prototype.setScript = function(script) {
|
||||||
if (script instanceof Script) {
|
if (script instanceof Script) {
|
||||||
this._script = script;
|
this._script = script;
|
||||||
this._scriptBuffer = script.toBuffer();
|
this._scriptBuffer = script.toBuffer();
|
||||||
|
} else if (_.isString(script)) {
|
||||||
|
this._script = new Script(script);
|
||||||
|
this._scriptBuffer = this._script.toBuffer();
|
||||||
} else if (BufferUtil.isBuffer(script)) {
|
} else if (BufferUtil.isBuffer(script)) {
|
||||||
this._script = null;
|
this._script = null;
|
||||||
this._scriptBuffer = new buffer.Buffer(script);
|
this._scriptBuffer = new buffer.Buffer(script);
|
||||||
} else {
|
} else {
|
||||||
console.log(script);
|
|
||||||
throw new TypeError('Invalid Argument');
|
throw new TypeError('Invalid Argument');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -156,4 +164,10 @@ Input.prototype.isNull = function() {
|
||||||
this.outputIndex === 0xffffffff;
|
this.outputIndex === 0xffffffff;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Input.prototype._estimateSize = function() {
|
||||||
|
var bufferWriter = new BufferWriter();
|
||||||
|
this.toBufferWriter(bufferWriter);
|
||||||
|
return bufferWriter.toBuffer().length;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Input;
|
module.exports = Input;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var JSUtil = require('../../util/js');
|
|
||||||
var inherits = require('inherits');
|
var inherits = require('inherits');
|
||||||
var Input = require('./input');
|
var Input = require('./input');
|
||||||
var Output = require('../output');
|
var Output = require('../output');
|
||||||
|
@ -123,11 +122,14 @@ MultiSigScriptHashInput.prototype.isValidSignature = function(transaction, signa
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
MultiSigScriptHashInput.OPCODES_SIZE = 10;
|
MultiSigScriptHashInput.OPCODES_SIZE = 7; // serialized size (<=3) + 0 .. N .. M OP_CHECKMULTISIG
|
||||||
MultiSigScriptHashInput.SIGNATURE_SIZE = 36;
|
MultiSigScriptHashInput.SIGNATURE_SIZE = 74; // size (1) + DER (<=72) + sighash (1)
|
||||||
|
MultiSigScriptHashInput.PUBKEY_SIZE = 34; // size (1) + DER (<=33)
|
||||||
|
|
||||||
MultiSigScriptHashInput.prototype._estimateSize = function() {
|
MultiSigScriptHashInput.prototype._estimateSize = function() {
|
||||||
return MultiSigScriptHashInput.OPCODES_SIZE + this.threshold * MultiSigScriptHashInput.SIGNATURE_SIZE;
|
return MultiSigScriptHashInput.OPCODES_SIZE +
|
||||||
|
this.threshold * MultiSigScriptHashInput.SIGNATURE_SIZE +
|
||||||
|
this.publicKeys.length * MultiSigScriptHashInput.PUBKEY_SIZE;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = MultiSigScriptHashInput;
|
module.exports = MultiSigScriptHashInput;
|
||||||
|
|
|
@ -85,10 +85,10 @@ PublicKeyHashInput.prototype.isFullySigned = function() {
|
||||||
return this.script.isPublicKeyHashIn();
|
return this.script.isPublicKeyHashIn();
|
||||||
};
|
};
|
||||||
|
|
||||||
PublicKeyHashInput.FIXED_SIZE = 32 + 4 + 2;
|
PublicKeyHashInput.SCRIPT_MAX_SIZE = 73 + 34; // sigsize (1 + 72) + pubkey (1 + 33)
|
||||||
PublicKeyHashInput.SCRIPT_MAX_SIZE = 34 + 20;
|
|
||||||
PublicKeyHashInput.prototype._estimateSize = function() {
|
PublicKeyHashInput.prototype._estimateSize = function() {
|
||||||
return PublicKeyHashInput.FIXED_SIZE + PublicKeyHashInput.SCRIPT_MAX_SIZE;
|
return PublicKeyHashInput.SCRIPT_MAX_SIZE;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = PublicKeyHashInput;
|
module.exports = PublicKeyHashInput;
|
||||||
|
|
|
@ -54,7 +54,7 @@ Output.prototype._fromObject = function(param) {
|
||||||
Output.prototype.toObject = function toObject() {
|
Output.prototype.toObject = function toObject() {
|
||||||
return {
|
return {
|
||||||
satoshis: this.satoshis,
|
satoshis: this.satoshis,
|
||||||
script: this._script.toString()
|
script: this.script.toString()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,10 +76,14 @@ Output.prototype.setScript = function(script) {
|
||||||
if (script instanceof Script) {
|
if (script instanceof Script) {
|
||||||
this._scriptBuffer = script.toBuffer();
|
this._scriptBuffer = script.toBuffer();
|
||||||
this._script = script;
|
this._script = script;
|
||||||
|
} else if (_.isString(script)) {
|
||||||
|
this._script = new Script(script);
|
||||||
|
this._scriptBuffer = this._script.toBuffer();
|
||||||
} else if (bufferUtil.isBuffer(script)) {
|
} else if (bufferUtil.isBuffer(script)) {
|
||||||
this._scriptBuffer = script;
|
this._scriptBuffer = script;
|
||||||
this._script = null;
|
this._script = null;
|
||||||
} else {
|
} else {
|
||||||
|
console.log(script);
|
||||||
throw new TypeError('Unrecognized Argument');
|
throw new TypeError('Unrecognized Argument');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var $ = require('../util/preconditions');
|
var $ = require('../util/preconditions');
|
||||||
var buffer = require('buffer');
|
var buffer = require('buffer');
|
||||||
var assert = require('assert');
|
|
||||||
|
|
||||||
|
var errors = require('../errors');
|
||||||
var util = require('../util/js');
|
var util = require('../util/js');
|
||||||
var bufferUtil = require('../util/buffer');
|
var bufferUtil = require('../util/buffer');
|
||||||
var JSUtil = require('../util/js');
|
var JSUtil = require('../util/js');
|
||||||
|
@ -53,6 +53,8 @@ function Transaction(serialized) {
|
||||||
this.fromBuffer(serialized);
|
this.fromBuffer(serialized);
|
||||||
} else if (_.isObject(serialized)) {
|
} else if (_.isObject(serialized)) {
|
||||||
this.fromObject(serialized);
|
this.fromObject(serialized);
|
||||||
|
} else {
|
||||||
|
throw new errors.InvalidArgument('Must provide an object or string to deserialize a transaction');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._newTransaction();
|
this._newTransaction();
|
||||||
|
@ -98,12 +100,48 @@ Transaction.prototype._getHash = function() {
|
||||||
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
||||||
* (decoderawtransaction, sendrawtransaction)
|
* (decoderawtransaction, sendrawtransaction)
|
||||||
*
|
*
|
||||||
|
* @param {boolean=} unsafe if true, skip testing for fees that are too high
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.serialize = Transaction.prototype.toString = function() {
|
Transaction.prototype.serialize = function(unsafe) {
|
||||||
|
if (unsafe) {
|
||||||
|
return this.uncheckedSerialize();
|
||||||
|
} else {
|
||||||
|
return this.checkedSerialize();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.uncheckedSerialize = Transaction.prototype.toString = function() {
|
||||||
return this.toBuffer().toString('hex');
|
return this.toBuffer().toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.checkedSerialize = Transaction.prototype.toString = function() {
|
||||||
|
var feeError = this._validateFees();
|
||||||
|
if (feeError) {
|
||||||
|
var changeError = this._validateChange();
|
||||||
|
if (changeError) {
|
||||||
|
throw new errors.Transaction.ChangeAddressMissing();
|
||||||
|
} else {
|
||||||
|
throw new errors.Transaction.FeeError(feeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.uncheckedSerialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.FEE_SECURITY_MARGIN = 15;
|
||||||
|
|
||||||
|
Transaction.prototype._validateFees = function() {
|
||||||
|
if (this._getUnspentValue() > Transaction.FEE_SECURITY_MARGIN * this._estimateFee()) {
|
||||||
|
return 'Fee is more than ' + Transaction.FEE_SECURITY_MARGIN + ' times the suggested amount';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._validateChange = function() {
|
||||||
|
if (!this._change) {
|
||||||
|
return 'Missing change address';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Transaction.prototype.inspect = function() {
|
Transaction.prototype.inspect = function() {
|
||||||
return '<Transaction: ' + this.toString() + '>';
|
return '<Transaction: ' + this.toString() + '>';
|
||||||
};
|
};
|
||||||
|
@ -186,6 +224,18 @@ Transaction.prototype.toObject = function toObject() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.fromObject = function(transaction) {
|
||||||
|
var self = this;
|
||||||
|
_.each(transaction.inputs, function(input) {
|
||||||
|
self.addInput(new Input(input));
|
||||||
|
});
|
||||||
|
_.each(transaction.outputs, function(output) {
|
||||||
|
self.addOutput(new Output(output));
|
||||||
|
});
|
||||||
|
this.nLockTime = transaction.nLockTime;
|
||||||
|
this.version = transaction.version;
|
||||||
|
};
|
||||||
|
|
||||||
Transaction.prototype.toJSON = function toJSON() {
|
Transaction.prototype.toJSON = function toJSON() {
|
||||||
return JSON.stringify(this.toObject());
|
return JSON.stringify(this.toObject());
|
||||||
};
|
};
|
||||||
|
@ -329,10 +379,9 @@ Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold)
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
|
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) {
|
||||||
this._changeSetup = false;
|
|
||||||
utxo.address = utxo.address && new Address(utxo.address);
|
utxo.address = utxo.address && new Address(utxo.address);
|
||||||
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
|
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script);
|
||||||
this.inputs.push(new MultiSigScriptHashInput({
|
this.addInput(new MultiSigScriptHashInput({
|
||||||
output: new Output({
|
output: new Output({
|
||||||
script: utxo.script,
|
script: utxo.script,
|
||||||
satoshis: utxo.satoshis
|
satoshis: utxo.satoshis
|
||||||
|
@ -342,7 +391,49 @@ Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold)
|
||||||
sequenceNumber: DEFAULT_SEQNUMBER,
|
sequenceNumber: DEFAULT_SEQNUMBER,
|
||||||
script: Script.empty()
|
script: Script.empty()
|
||||||
}, pubkeys, threshold));
|
}, pubkeys, threshold));
|
||||||
this._inputAmount += utxo.satoshis;
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an input to this transaction. The input must be an instance of the `Input` class.
|
||||||
|
* It should have information about the Output that it's spending, but if it's not already
|
||||||
|
* set, two additional parameters, `outputScript` and `satoshis` can be provided.
|
||||||
|
*
|
||||||
|
* @param {Input} input
|
||||||
|
* @param {String|Script} outputScript
|
||||||
|
* @param {number} satoshis
|
||||||
|
* @return Transaction this, for chaining
|
||||||
|
*/
|
||||||
|
Transaction.prototype.addInput = function(input, outputScript, satoshis) {
|
||||||
|
$.checkArgumentType(input, Input, 'input');
|
||||||
|
if (!input.output || !(input.output instanceof Output) && !outputScript && !satoshis) {
|
||||||
|
throw new errors.Transaction.NeedMoreInfo('Need information about the UTXO script and satoshis');
|
||||||
|
}
|
||||||
|
if (!input.output && outputScript && satoshis) {
|
||||||
|
outputScript = outputScript instanceof Script ? outputScript : new Script(outputScript);
|
||||||
|
$.checkArgumentType(satoshis, 'number', 'satoshis');
|
||||||
|
input.output = new Output({
|
||||||
|
script: outputScript,
|
||||||
|
satoshis: satoshis
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.uncheckedAddInput(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an input to this transaction, without checking that the input has information about
|
||||||
|
* the output that it's spending.
|
||||||
|
*
|
||||||
|
* @param {Input} input
|
||||||
|
* @return Transaction this, for chaining
|
||||||
|
*/
|
||||||
|
Transaction.prototype.uncheckedAddInput = function(input) {
|
||||||
|
$.checkArgumentType(input, Input, 'input');
|
||||||
|
this._changeSetup = false;
|
||||||
|
this.inputs.push(input);
|
||||||
|
if (input.output) {
|
||||||
|
this._inputAmount += input.output.satoshis;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -398,7 +489,7 @@ Transaction.prototype.change = function(address) {
|
||||||
* @return {Transaction} this, for chaining
|
* @return {Transaction} this, for chaining
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.to = function(address, amount) {
|
Transaction.prototype.to = function(address, amount) {
|
||||||
this._addOutput(new Output({
|
this.addOutput(new Output({
|
||||||
script: Script(new Address(address)),
|
script: Script(new Address(address)),
|
||||||
satoshis: amount
|
satoshis: amount
|
||||||
}));
|
}));
|
||||||
|
@ -416,14 +507,15 @@ Transaction.prototype.to = function(address, amount) {
|
||||||
* @return {Transaction} this, for chaining
|
* @return {Transaction} this, for chaining
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.addData = function(value) {
|
Transaction.prototype.addData = function(value) {
|
||||||
this._addOutput(new Output({
|
this.addOutput(new Output({
|
||||||
script: Script.buildDataOut(value),
|
script: Script.buildDataOut(value),
|
||||||
satoshis: 0
|
satoshis: 0
|
||||||
}));
|
}));
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._addOutput = function(output) {
|
Transaction.prototype.addOutput = function(output) {
|
||||||
|
$.checkArgumentType(output, Output, 'output');
|
||||||
this.outputs.push(output);
|
this.outputs.push(output);
|
||||||
this._changeSetup = false;
|
this._changeSetup = false;
|
||||||
this._outputAmount += output.satoshis;
|
this._outputAmount += output.satoshis;
|
||||||
|
@ -442,12 +534,11 @@ Transaction.prototype._updateChangeOutput = function() {
|
||||||
if (!_.isUndefined(this._changeOutput)) {
|
if (!_.isUndefined(this._changeOutput)) {
|
||||||
this.removeOutput(this._changeOutput);
|
this.removeOutput(this._changeOutput);
|
||||||
}
|
}
|
||||||
var estimatedSize = this._estimateSize();
|
var available = this._getUnspentValue();
|
||||||
var available = this._inputAmount - this._outputAmount;
|
var fee = this.getFee();
|
||||||
var fee = this._fee || Transaction._estimateFee(estimatedSize, available);
|
|
||||||
if (available - fee > 0) {
|
if (available - fee > 0) {
|
||||||
this._changeOutput = this.outputs.length;
|
this._changeOutput = this.outputs.length;
|
||||||
this._addOutput(new Output({
|
this.addOutput(new Output({
|
||||||
script: Script.fromAddress(this._change),
|
script: Script.fromAddress(this._change),
|
||||||
satoshis: available - fee
|
satoshis: available - fee
|
||||||
}));
|
}));
|
||||||
|
@ -457,6 +548,20 @@ Transaction.prototype._updateChangeOutput = function() {
|
||||||
this._changeSetup = true;
|
this._changeSetup = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.getFee = function() {
|
||||||
|
return this._fee || this._estimateFee();
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._estimateFee = function() {
|
||||||
|
var estimatedSize = this._estimateSize();
|
||||||
|
var available = this._getUnspentValue();
|
||||||
|
return Transaction._estimateFee(estimatedSize, available);
|
||||||
|
};
|
||||||
|
|
||||||
|
Transaction.prototype._getUnspentValue = function() {
|
||||||
|
return this._inputAmount - this._outputAmount;
|
||||||
|
};
|
||||||
|
|
||||||
Transaction.prototype._clearSignatures = function() {
|
Transaction.prototype._clearSignatures = function() {
|
||||||
_.each(this.inputs, function(input) {
|
_.each(this.inputs, function(input) {
|
||||||
input.clearSignatures();
|
input.clearSignatures();
|
||||||
|
@ -472,7 +577,7 @@ Transaction._estimateFee = function(size, amountAvailable) {
|
||||||
// Safe upper bound for change address script
|
// Safe upper bound for change address script
|
||||||
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
|
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
|
||||||
}
|
}
|
||||||
return Math.ceil(size / 1000 / Transaction.FEE_PER_KB) * 1000;
|
return Math.ceil(size / 1000) * Transaction.FEE_PER_KB;
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
|
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
|
||||||
|
|
|
@ -80,6 +80,6 @@
|
||||||
"change", ["3BazTqvkvEBcWk7J4sbgRnxUw6rjYrogf9"],
|
"change", ["3BazTqvkvEBcWk7J4sbgRnxUw6rjYrogf9"],
|
||||||
"sign", ["L2U9m5My3cdyN5qX1PH4B7XstGDZFWwyukdX8gj8vsJ3fkrqArQo"],
|
"sign", ["L2U9m5My3cdyN5qX1PH4B7XstGDZFWwyukdX8gj8vsJ3fkrqArQo"],
|
||||||
"sign", ["L4jFVcDaqZCkknP5KQWjCBgiLFxKxRxywNGTucm3jC3ozByZcbZv"],
|
"sign", ["L4jFVcDaqZCkknP5KQWjCBgiLFxKxRxywNGTucm3jC3ozByZcbZv"],
|
||||||
"serialize", "010000000220c24f763536edb05ce8df2a4816d971be4f20b58451d71589db434aca98bfaf00000000fdfe0000483045022100f71c1c4d174c41c9ecd57306f46c9a04ab79f8ee69af54ab47ab17992622e675022047d850988556cf73ac7d0642be67a707ae3ba71018f9ebbf98cf8ca67da1c52f01483045022100b8360e5ad52099cb5a0d624fc73a905871a36ba43b77a93234ea64c47fc2d558022042991aecde368acefcfa8af60b49824a71d76e85331eecff1d82b3e3d7bcdbce014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffffa0644cd1606e081c59eb65fe69d4a83a3a822da423bc392c91712fb77a192edc00000000fdfd000047304402201065d3d9e7009f8c53ac802fe3757009e21a0849138acf96eab860912293b5ee022014552a7c8e2d84bf46468a7927a414eb0cf8cd42601cd7b6d07f44558afbfce901483045022100c7b3d9df174e6b97d3832a9d2cf75b064ae09b258e56448601c6f9e2ea91f0e3022005a723ed84e5b6c8c4ab6637176dd2adb8cb1cd618550cb9fd0e9a0d4db51184014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffff03f04902000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af387007102000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af38763b204000000000017a9146c8d8b04c6a1e664b1ec20ec932760760c97688e8700000000"
|
"serialize", "010000000220c24f763536edb05ce8df2a4816d971be4f20b58451d71589db434aca98bfaf00000000fdfd000047304402202c34fd898bbea3521c5f88fdeef4bb65f36ce01142633c1121e9b7bef307938902205e1aad62a66bd294898dc71678a4a5448281126baad90bfd9b4139663e852d26014830450221009d9f2b46595dc2578f4c4ac65c779e45e493bcc4649ef6786477e40df40d21fc02206da8a0f80fa2aa2d2b89d56951761c8f0506fdafd53ef6fe7d5c878251bb216b014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffffa0644cd1606e081c59eb65fe69d4a83a3a822da423bc392c91712fb77a192edc00000000fdfd000047304402202e64f052cd5be367f2f9efb31d3a34d0f8339700beff426d31b53c9a776a0368022057c714fda22a5ec301765dd187be3c522bcfe040127e857067163a5fb9ed46c001483045022100bf7a1e5a9e7204361e70312e15746efc95c7be8957a66c76574d2a07db359c550220318dc70b703c84a76fe3a460e96d466462f4c1c54b9fbfb75157b1afe2c51f8e014c695221020483ebb834d91d494a3b649cf0e8f5c9c4fcec5f194ab94341cc99bb440007f2210271ebaeef1c2bf0c1a4772d1391eab03e4d96a6e9b48551ab4e4b0d2983eb452b2103a659828aabe443e2dedabb1db5a22335c5ace5b5b7126998a288d63c99516dd853aeffffffff03f04902000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af387007102000000000017a9144de752833233fe69a20064f29b2ca0f6399c8af3873b8f04000000000017a9146c8d8b04c6a1e664b1ec20ec932760760c97688e8700000000"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -202,26 +202,26 @@ describe('Interpreter', function() {
|
||||||
var hashbuf = new Buffer(32);
|
var hashbuf = new Buffer(32);
|
||||||
hashbuf.fill(0);
|
hashbuf.fill(0);
|
||||||
var credtx = Transaction();
|
var credtx = Transaction();
|
||||||
credtx.inputs.push(new Transaction.Input({
|
credtx.uncheckedAddInput(new Transaction.Input({
|
||||||
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
outputIndex: 0xffffffff,
|
outputIndex: 0xffffffff,
|
||||||
sequenceNumber: 0xffffffff,
|
sequenceNumber: 0xffffffff,
|
||||||
script: Script('OP_0 OP_0')
|
script: Script('OP_0 OP_0')
|
||||||
}));
|
}));
|
||||||
credtx._addOutput(new Transaction.Output({
|
credtx.addOutput(new Transaction.Output({
|
||||||
script: scriptPubkey,
|
script: scriptPubkey,
|
||||||
satoshis: 0
|
satoshis: 0
|
||||||
}));
|
}));
|
||||||
var idbuf = credtx.id;
|
var idbuf = credtx.id;
|
||||||
|
|
||||||
var spendtx = Transaction();
|
var spendtx = Transaction();
|
||||||
spendtx.inputs.push(new Transaction.Input({
|
spendtx.uncheckedAddInput(new Transaction.Input({
|
||||||
prevTxId: idbuf.toString('hex'),
|
prevTxId: idbuf.toString('hex'),
|
||||||
outputIndex: 0,
|
outputIndex: 0,
|
||||||
sequenceNumber: 0xffffffff,
|
sequenceNumber: 0xffffffff,
|
||||||
script: scriptSig
|
script: scriptSig
|
||||||
}));
|
}));
|
||||||
spendtx._addOutput(new Transaction.Output({
|
spendtx.addOutput(new Transaction.Output({
|
||||||
script: Script(),
|
script: Script(),
|
||||||
satoshis: 0
|
satoshis: 0
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var bitcore = require('../../..');
|
||||||
|
var errors = bitcore.errors;
|
||||||
|
var PrivateKey = bitcore.PrivateKey;
|
||||||
|
var Address = bitcore.Address;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var Networks = bitcore.Networks;
|
||||||
|
var Input = bitcore.Transaction.Input;
|
||||||
|
|
||||||
|
describe('Transaction.Input', function() {
|
||||||
|
|
||||||
|
var privateKey = new PrivateKey('KwF9LjRraetZuEjR8VqEq539z137LW5anYDUnVK11vM3mNMHTWb4');
|
||||||
|
var publicKey = privateKey.publicKey;
|
||||||
|
var address = new Address(publicKey, Networks.livenet);
|
||||||
|
var output = {
|
||||||
|
address: '33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb',
|
||||||
|
prevTxId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: new Script(address),
|
||||||
|
satoshis: 1000000
|
||||||
|
};
|
||||||
|
var coinbase = {
|
||||||
|
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
outputIndex: 0xFFFFFFFF,
|
||||||
|
script: new Script(),
|
||||||
|
satoshis: 1000000
|
||||||
|
};
|
||||||
|
|
||||||
|
it('has abstract methods: "getSignatures", "isFullySigned", "addSignature", "clearSignatures"', function() {
|
||||||
|
var input = new Input(output);
|
||||||
|
_.each(['getSignatures', 'isFullySigned', 'addSignature', 'clearSignatures'], function(method) {
|
||||||
|
expect(function() {
|
||||||
|
return input[method]();
|
||||||
|
}).to.throw(errors.AbstractMethodInvoked);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('detects coinbase transactions', function() {
|
||||||
|
new Input(output).isNull().should.equal(false);
|
||||||
|
new Input(coinbase).isNull().should.equal(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,96 @@
|
||||||
|
'use strict';
|
||||||
|
/* jshint unused: false */
|
||||||
|
|
||||||
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var bitcore = require('../../..');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var PrivateKey = bitcore.PrivateKey;
|
||||||
|
var Address = bitcore.Address;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var Signature = bitcore.crypto.Signature;
|
||||||
|
|
||||||
|
describe('MultiSigScriptHashInput', function() {
|
||||||
|
|
||||||
|
var privateKey1 = new PrivateKey('KwF9LjRraetZuEjR8VqEq539z137LW5anYDUnVK11vM3mNMHTWb4');
|
||||||
|
var privateKey2 = new PrivateKey('L4PqnaPTCkYhAqH3YQmefjxQP6zRcF4EJbdGqR8v6adtG9XSsadY');
|
||||||
|
var privateKey3 = new PrivateKey('L4CTX79zFeksZTyyoFuPQAySfmP7fL3R41gWKTuepuN7hxuNuJwV');
|
||||||
|
var public1 = privateKey1.publicKey;
|
||||||
|
var public2 = privateKey2.publicKey;
|
||||||
|
var public3 = privateKey3.publicKey;
|
||||||
|
var address = new Address('33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb');
|
||||||
|
|
||||||
|
var output = {
|
||||||
|
address: '33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb',
|
||||||
|
txId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: new Script(address),
|
||||||
|
satoshis: 1000000
|
||||||
|
};
|
||||||
|
it('can count missing signatures', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output, [public1, public2, public3], 2)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
|
||||||
|
input.countSignatures().should.equal(0);
|
||||||
|
|
||||||
|
transaction.sign(privateKey1);
|
||||||
|
input.countSignatures().should.equal(1);
|
||||||
|
input.countMissingSignatures().should.equal(1);
|
||||||
|
input.isFullySigned().should.equal(false);
|
||||||
|
|
||||||
|
transaction.sign(privateKey2);
|
||||||
|
input.countSignatures().should.equal(2);
|
||||||
|
input.countMissingSignatures().should.equal(0);
|
||||||
|
input.isFullySigned().should.equal(true);
|
||||||
|
});
|
||||||
|
it('returns a list of public keys with missing signatures', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output, [public1, public2, public3], 2)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
|
||||||
|
_.all(input.publicKeysWithoutSignature(), function(publicKeyMissing) {
|
||||||
|
var serialized = publicKeyMissing.toString();
|
||||||
|
return serialized === public1.toString() ||
|
||||||
|
serialized === public2.toString() ||
|
||||||
|
serialized === public3.toString();
|
||||||
|
}).should.equal(true);
|
||||||
|
transaction.sign(privateKey1);
|
||||||
|
_.all(input.publicKeysWithoutSignature(), function(publicKeyMissing) {
|
||||||
|
var serialized = publicKeyMissing.toString();
|
||||||
|
return serialized === public2.toString() ||
|
||||||
|
serialized === public3.toString();
|
||||||
|
}).should.equal(true);
|
||||||
|
});
|
||||||
|
it('can clear all signatures', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output, [public1, public2, public3], 2)
|
||||||
|
.to(address, 1000000)
|
||||||
|
.sign(privateKey1)
|
||||||
|
.sign(privateKey2);
|
||||||
|
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
input.isFullySigned().should.equal(true);
|
||||||
|
input.clearSignatures();
|
||||||
|
input.isFullySigned().should.equal(false);
|
||||||
|
});
|
||||||
|
it('can estimate how heavy is the output going to be', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output, [public1, public2, public3], 2)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
input._estimateSize().should.equal(257);
|
||||||
|
});
|
||||||
|
it('uses SIGHASH_ALL by default', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output, [public1, public2, public3], 2)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
var sigs = input.getSignatures(transaction, privateKey1, 0);
|
||||||
|
sigs[0].sigtype.should.equal(Signature.SIGHASH_ALL);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
'use strict';
|
||||||
|
/* jshint unused: false */
|
||||||
|
|
||||||
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
var bitcore = require('../../..');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var PrivateKey = bitcore.PrivateKey;
|
||||||
|
var Address = bitcore.Address;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var Networks = bitcore.Networks;
|
||||||
|
var Signature = bitcore.crypto.Signature;
|
||||||
|
|
||||||
|
describe('PublicKeyHashInput', function() {
|
||||||
|
|
||||||
|
var privateKey = new PrivateKey('KwF9LjRraetZuEjR8VqEq539z137LW5anYDUnVK11vM3mNMHTWb4');
|
||||||
|
var publicKey = privateKey.publicKey;
|
||||||
|
var address = new Address(publicKey, Networks.livenet);
|
||||||
|
|
||||||
|
var output = {
|
||||||
|
address: '33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb',
|
||||||
|
txId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: new Script(address),
|
||||||
|
satoshis: 1000000
|
||||||
|
};
|
||||||
|
it('can count missing signatures', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
|
||||||
|
input.isFullySigned().should.equal(false);
|
||||||
|
transaction.sign(privateKey);
|
||||||
|
input.isFullySigned().should.equal(true);
|
||||||
|
});
|
||||||
|
it('it\'s size can be estimated', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
input._estimateSize().should.equal(107);
|
||||||
|
});
|
||||||
|
it('it\'s signature can be removed', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
|
||||||
|
transaction.sign(privateKey);
|
||||||
|
input.clearSignatures();
|
||||||
|
input.isFullySigned().should.equal(false);
|
||||||
|
});
|
||||||
|
it('returns an empty array if private key mismatches', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(output)
|
||||||
|
.to(address, 1000000);
|
||||||
|
var input = transaction.inputs[0];
|
||||||
|
|
||||||
|
input.getSignatures(transaction, new PrivateKey(), 0);
|
||||||
|
input.isFullySigned().should.equal(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,6 +3,7 @@
|
||||||
/* jshint unused: false */
|
/* jshint unused: false */
|
||||||
/* jshint latedef: false */
|
/* jshint latedef: false */
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
|
var expect = require('chai').expect;
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var bitcore = require('../..');
|
var bitcore = require('../..');
|
||||||
|
@ -11,6 +12,7 @@ var PrivateKey = bitcore.PrivateKey;
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
|
var errors = bitcore.errors;
|
||||||
|
|
||||||
var transactionVector = require('../data/tx_creation');
|
var transactionVector = require('../data/tx_creation');
|
||||||
|
|
||||||
|
@ -21,6 +23,46 @@ describe('Transaction', function() {
|
||||||
transaction.serialize().should.equal(tx_1_hex);
|
transaction.serialize().should.equal(tx_1_hex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails if an invalid parameter is passed to constructor', function() {
|
||||||
|
expect(function() {
|
||||||
|
return new Transaction(1);
|
||||||
|
}).to.throw(errors.InvalidArgument);
|
||||||
|
});
|
||||||
|
|
||||||
|
var testScript = 'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a OP_EQUALVERIFY OP_CHECKSIG';
|
||||||
|
var testPrevTx = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
|
||||||
|
var testAmount = 1020000;
|
||||||
|
var testTransaction = new Transaction()
|
||||||
|
.from({
|
||||||
|
'txId': testPrevTx,
|
||||||
|
'outputIndex': 0,
|
||||||
|
'script': testScript,
|
||||||
|
'satoshis': testAmount
|
||||||
|
}).to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
||||||
|
|
||||||
|
it('can serialize to a plain javascript object', function() {
|
||||||
|
var object = testTransaction.toObject();
|
||||||
|
object.inputs[0].output.satoshis.should.equal(testAmount);
|
||||||
|
object.inputs[0].output.script.toString().should.equal(testScript);
|
||||||
|
object.inputs[0].prevTxId.should.equal(testPrevTx);
|
||||||
|
object.inputs[0].outputIndex.should.equal(0);
|
||||||
|
object.outputs[0].satoshis.should.equal(testAmount - 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the fee correctly', function() {
|
||||||
|
testTransaction.getFee().should.equal(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('serialize to Object roundtrip', function() {
|
||||||
|
new Transaction(testTransaction.toObject()).serialize().should.equal(testTransaction.serialize());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('constructor returns a shallow copy of another transaction', function() {
|
||||||
|
var transaction = new Transaction(tx_1_hex);
|
||||||
|
var copy = new Transaction(transaction);
|
||||||
|
copy.serialize().should.equal(transaction.serialize());
|
||||||
|
});
|
||||||
|
|
||||||
it('should display correctly in console', function() {
|
it('should display correctly in console', function() {
|
||||||
var transaction = new Transaction(tx_1_hex);
|
var transaction = new Transaction(tx_1_hex);
|
||||||
transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>');
|
transaction.inspect().should.equal('<Transaction: ' + tx_1_hex + '>');
|
||||||
|
@ -63,76 +105,21 @@ describe('Transaction', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Migrate this into a test for inputs
|
// TODO: Migrate this into a test for inputs
|
||||||
describe('MultiSigScriptHashInput', function() {
|
|
||||||
var MultiSigScriptHashInput = Transaction.Input.MultiSigScriptHash;
|
|
||||||
|
|
||||||
var privateKey1 = new PrivateKey('KwF9LjRraetZuEjR8VqEq539z137LW5anYDUnVK11vM3mNMHTWb4');
|
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||||
var privateKey2 = new PrivateKey('L4PqnaPTCkYhAqH3YQmefjxQP6zRcF4EJbdGqR8v6adtG9XSsadY');
|
var simpleUtxoWith100000Satoshis = {
|
||||||
var privateKey3 = new PrivateKey('L4CTX79zFeksZTyyoFuPQAySfmP7fL3R41gWKTuepuN7hxuNuJwV');
|
address: fromAddress,
|
||||||
var public1 = privateKey1.publicKey;
|
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
var public2 = privateKey2.publicKey;
|
outputIndex: 0,
|
||||||
var public3 = privateKey3.publicKey;
|
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||||
var address = new Address('33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb');
|
satoshis: 100000
|
||||||
|
};
|
||||||
|
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||||
|
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
||||||
|
var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf';
|
||||||
|
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||||
|
|
||||||
var output = {
|
|
||||||
address: '33zbk2aSZYdNbRsMPPt6jgy6Kq1kQreqeb',
|
|
||||||
txId: '66e64ef8a3b384164b78453fa8c8194de9a473ba14f89485a0e433699daec140',
|
|
||||||
outputIndex: 0,
|
|
||||||
script: new Script(address),
|
|
||||||
satoshis: 1000000
|
|
||||||
};
|
|
||||||
it('can count missing signatures', function() {
|
|
||||||
var transaction = new Transaction()
|
|
||||||
.from(output, [public1, public2, public3], 2)
|
|
||||||
.to(address, 1000000);
|
|
||||||
var input = transaction.inputs[0];
|
|
||||||
|
|
||||||
input.countSignatures().should.equal(0);
|
|
||||||
|
|
||||||
transaction.sign(privateKey1);
|
|
||||||
input.countSignatures().should.equal(1);
|
|
||||||
input.countMissingSignatures().should.equal(1);
|
|
||||||
input.isFullySigned().should.equal(false);
|
|
||||||
|
|
||||||
transaction.sign(privateKey2);
|
|
||||||
input.countSignatures().should.equal(2);
|
|
||||||
input.countMissingSignatures().should.equal(0);
|
|
||||||
input.isFullySigned().should.equal(true);
|
|
||||||
});
|
|
||||||
it('returns a list of public keys with missing signatures', function() {
|
|
||||||
var transaction = new Transaction()
|
|
||||||
.from(output, [public1, public2, public3], 2)
|
|
||||||
.to(address, 1000000);
|
|
||||||
var input = transaction.inputs[0];
|
|
||||||
|
|
||||||
_.all(input.publicKeysWithoutSignature(), function(publicKeyMissing) {
|
|
||||||
var serialized = publicKeyMissing.toString();
|
|
||||||
return serialized === public1.toString() ||
|
|
||||||
serialized === public2.toString() ||
|
|
||||||
serialized === public3.toString();
|
|
||||||
}).should.equal(true);
|
|
||||||
transaction.sign(privateKey1);
|
|
||||||
_.all(input.publicKeysWithoutSignature(), function(publicKeyMissing) {
|
|
||||||
var serialized = publicKeyMissing.toString();
|
|
||||||
return serialized === public2.toString() ||
|
|
||||||
serialized === public3.toString();
|
|
||||||
}).should.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('change address', function() {
|
describe('change address', function() {
|
||||||
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
|
||||||
var simpleUtxoWith100000Satoshis = {
|
|
||||||
address: fromAddress,
|
|
||||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
|
||||||
outputIndex: 0,
|
|
||||||
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
||||||
satoshis: 100000
|
|
||||||
};
|
|
||||||
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
|
||||||
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
|
||||||
var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf';
|
|
||||||
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
|
||||||
|
|
||||||
it('can calculate simply the output amount', function() {
|
it('can calculate simply the output amount', function() {
|
||||||
var transaction = new Transaction()
|
var transaction = new Transaction()
|
||||||
.from(simpleUtxoWith100000Satoshis)
|
.from(simpleUtxoWith100000Satoshis)
|
||||||
|
@ -140,7 +127,7 @@ describe('Transaction', function() {
|
||||||
.change(changeAddress)
|
.change(changeAddress)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
transaction.outputs.length.should.equal(2);
|
transaction.outputs.length.should.equal(2);
|
||||||
transaction.outputs[1].satoshis.should.equal(49000);
|
transaction.outputs[1].satoshis.should.equal(40000);
|
||||||
transaction.outputs[1].script.toString()
|
transaction.outputs[1].script.toString()
|
||||||
.should.equal(Script.fromAddress(changeAddress).toString());
|
.should.equal(Script.fromAddress(changeAddress).toString());
|
||||||
});
|
});
|
||||||
|
@ -162,7 +149,7 @@ describe('Transaction', function() {
|
||||||
.to(toAddress, 20000)
|
.to(toAddress, 20000)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
transaction.outputs.length.should.equal(3);
|
transaction.outputs.length.should.equal(3);
|
||||||
transaction.outputs[2].satoshis.should.equal(29000);
|
transaction.outputs[2].satoshis.should.equal(20000);
|
||||||
transaction.outputs[2].script.toString()
|
transaction.outputs[2].script.toString()
|
||||||
.should.equal(Script.fromAddress(changeAddress).toString());
|
.should.equal(Script.fromAddress(changeAddress).toString());
|
||||||
});
|
});
|
||||||
|
@ -201,6 +188,34 @@ describe('Transaction', function() {
|
||||||
transaction.outputs.length.should.equal(1);
|
transaction.outputs.length.should.equal(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var simpleUtxoWith1BTC = {
|
||||||
|
address: fromAddress,
|
||||||
|
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||||
|
outputIndex: 0,
|
||||||
|
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||||
|
satoshis: 1e8
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('checked serialize', function() {
|
||||||
|
it('fails if no change address was set', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.to(toAddress, 1);
|
||||||
|
expect(function() {
|
||||||
|
return transaction.serialize();
|
||||||
|
}).to.throw(errors.Transaction.ChangeAddressMissing);
|
||||||
|
});
|
||||||
|
it('fails if a high fee was set', function() {
|
||||||
|
var transaction = new Transaction()
|
||||||
|
.from(simpleUtxoWith1BTC)
|
||||||
|
.change(changeAddress)
|
||||||
|
.to(toAddress, 1);
|
||||||
|
expect(function() {
|
||||||
|
return transaction.serialize();
|
||||||
|
}).to.throw(errors.Transaction.FeeError);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var tx_empty_hex = '01000000000000000000';
|
var tx_empty_hex = '01000000000000000000';
|
||||||
|
|
Loading…
Reference in New Issue