|
|
@ -1,6 +1,7 @@ |
|
|
|
'use strict'; |
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
var $ = require('../util/preconditions'); |
|
|
|
var buffer = require('buffer'); |
|
|
|
var assert = require('assert'); |
|
|
|
|
|
|
@ -27,8 +28,7 @@ var DEFAULT_NLOCKTIME = 0; |
|
|
|
var DEFAULT_SEQNUMBER = 0xFFFFFFFF; |
|
|
|
|
|
|
|
/** |
|
|
|
* Represents a transaction, a set of inputs and outputs to change |
|
|
|
* ownership of tokens |
|
|
|
* Represents a transaction, a set of inputs and outputs to change ownership of tokens |
|
|
|
* |
|
|
|
* @param {*} serialized |
|
|
|
* @constructor |
|
|
@ -196,6 +196,45 @@ Transaction.prototype._newTransaction = function() { |
|
|
|
|
|
|
|
/* Transaction creation interface */ |
|
|
|
|
|
|
|
/** |
|
|
|
* Add an input to this transaction. This is a high level interface |
|
|
|
* to add an input, for more control, use @{link Transaction#addInput}. |
|
|
|
* |
|
|
|
* Can receive, as output information, the output of bitcoind's `listunspent` command, |
|
|
|
* and a slightly fancier format recognized by bitcore: |
|
|
|
* |
|
|
|
* ``` |
|
|
|
* { |
|
|
|
* address: 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1', |
|
|
|
* txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', |
|
|
|
* outputIndex: 0, |
|
|
|
* script: Script.empty(), |
|
|
|
* satoshis: 1020000 |
|
|
|
* } |
|
|
|
* ``` |
|
|
|
* Where `address` can be either a string or a bitcore Address object. The |
|
|
|
* same is true for `script`, which can be a string or a bitcore Script. |
|
|
|
* |
|
|
|
* Beware that this resets all the signatures for inputs (in further versions, |
|
|
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). |
|
|
|
* |
|
|
|
* @example |
|
|
|
* var transaction = new Transaction(); |
|
|
|
* |
|
|
|
* // From a pay to public key hash output from bitcoind's listunspent
|
|
|
|
* transaction.from({'txid': '0000...', vout: 0, amount: 0.1, scriptPubKey: 'OP_DUP ...'}); |
|
|
|
* |
|
|
|
* // From a pay to public key hash output
|
|
|
|
* transaction.from({'txId': '0000...', outputIndex: 0, satoshis: 1000, script: 'OP_DUP ...'}); |
|
|
|
* |
|
|
|
* // From a multisig P2SH output
|
|
|
|
* transaction.from({'txId': '0000...', inputIndex: 0, satoshis: 1000, script: '... OP_HASH'}, |
|
|
|
* ['03000...', '02000...'], 2); |
|
|
|
* |
|
|
|
* @param {Object} utxo |
|
|
|
* @param {Array=} pubkeys |
|
|
|
* @param {number=} threshold |
|
|
|
*/ |
|
|
|
Transaction.prototype.from = function(utxo, pubkeys, threshold) { |
|
|
|
if (pubkeys && threshold) { |
|
|
|
this._fromMultiSigP2SH(utxo, pubkeys, threshold); |
|
|
@ -283,6 +322,7 @@ Transaction.prototype._fromMultisigOldUtxo = function(utxo, pubkeys, threshold) |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) { |
|
|
|
this._changeSetup = false; |
|
|
|
utxo.address = utxo.address && new Address(utxo.address); |
|
|
|
utxo.script = new Script(util.isHexa(utxo.script) ? new buffer.Buffer(utxo.script, 'hex') : utxo.script); |
|
|
|
this.inputs.push(new MultiSigScriptHashInput({ |
|
|
@ -298,24 +338,58 @@ Transaction.prototype._fromMultisigNewUtxo = function(utxo, pubkeys, threshold) |
|
|
|
this._inputAmount += utxo.satoshis; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Returns true if the transaction has enough info on all inputs to be correctly validated |
|
|
|
* |
|
|
|
* @return {boolean} |
|
|
|
*/ |
|
|
|
Transaction.prototype.hasAllUtxoInfo = function() { |
|
|
|
return _.all(this.inputs.map(function(input) { |
|
|
|
return !!input.output; |
|
|
|
})); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Manually set the fee for this transaction. Beware that this resets all the signatures |
|
|
|
* for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not |
|
|
|
* be reset). |
|
|
|
* |
|
|
|
* @param {number} amount satoshis to be sent |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.fee = function(amount) { |
|
|
|
this._fee = amount; |
|
|
|
this._changeSetup = false; |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
/* Output management */ |
|
|
|
|
|
|
|
/** |
|
|
|
* Set the change address for this transaction |
|
|
|
* |
|
|
|
* Beware that this resets all the signatures for inputs (in further versions, |
|
|
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). |
|
|
|
* |
|
|
|
* @param {number} amount satoshis to be sent |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.change = function(address) { |
|
|
|
this._change = address; |
|
|
|
this._change = new Address(address); |
|
|
|
this._changeSetup = false; |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Add an output to the transaction. |
|
|
|
* |
|
|
|
* Beware that this resets all the signatures for inputs (in further versions, |
|
|
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). |
|
|
|
* |
|
|
|
* @param {string|Address} address |
|
|
|
* @param {number} amount in satoshis |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.to = function(address, amount) { |
|
|
|
this._addOutput(new Output({ |
|
|
|
script: Script(new Address(address)), |
|
|
@ -324,11 +398,16 @@ Transaction.prototype.to = function(address, amount) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype._addOutput = function(output) { |
|
|
|
this.outputs.push(output); |
|
|
|
this._outputAmount += output.satoshis; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Add an OP_RETURN output to the transaction. |
|
|
|
* |
|
|
|
* Beware that this resets all the signatures for inputs (in further versions, |
|
|
|
* SIGHASH_SINGLE or SIGHASH_NONE signatures will not be reset). |
|
|
|
* |
|
|
|
* @param {Buffer|string} value the data to be stored in the OP_RETURN output. |
|
|
|
* In case of a string, the UTF-8 representation will be stored |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.addData = function(value) { |
|
|
|
this._addOutput(new Output({ |
|
|
|
script: Script.buildDataOut(value), |
|
|
@ -337,19 +416,100 @@ Transaction.prototype.addData = function(value) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype._addOutput = function(output) { |
|
|
|
this.outputs.push(output); |
|
|
|
this._changeSetup = false; |
|
|
|
this._outputAmount += output.satoshis; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype._updateChangeOutput = function() { |
|
|
|
if (!this._change) { |
|
|
|
return; |
|
|
|
} |
|
|
|
if (this._changeSetup) { |
|
|
|
return; |
|
|
|
} |
|
|
|
if (!_.isUndefined(this._changeSetup)) { |
|
|
|
this._clearSignatures(); |
|
|
|
} |
|
|
|
if (!_.isUndefined(this._changeOutput)) { |
|
|
|
this.removeOutput(this._changeOutput); |
|
|
|
} |
|
|
|
var estimatedSize = this._estimateSize(); |
|
|
|
var available = this._inputAmount - this._outputAmount; |
|
|
|
var fee = this._fee || Transaction._estimateFee(estimatedSize, available); |
|
|
|
if (available - fee > 0) { |
|
|
|
this._changeOutput = this.outputs.length; |
|
|
|
this._addOutput(new Output({ |
|
|
|
script: Script.fromAddress(this._change), |
|
|
|
satoshis: available - fee |
|
|
|
})); |
|
|
|
} else { |
|
|
|
this._changeOutput = undefined; |
|
|
|
} |
|
|
|
this._changeSetup = true; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype._clearSignatures = function() { |
|
|
|
_.each(this.inputs, function(input) { |
|
|
|
input.clearSignatures(); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.FEE_PER_KB = 10000; |
|
|
|
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4; |
|
|
|
|
|
|
|
Transaction._estimateFee = function(size, amountAvailable) { |
|
|
|
var fee = Math.ceil(size / Transaction.FEE_PER_KB); |
|
|
|
if (amountAvailable > fee) { |
|
|
|
// Safe upper bound for change address script
|
|
|
|
size += Transaction.CHANGE_OUTPUT_MAX_SIZE; |
|
|
|
} |
|
|
|
return Math.ceil(size / 1000 / Transaction.FEE_PER_KB) * 1000; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4; |
|
|
|
|
|
|
|
Transaction.prototype._estimateSize = function() { |
|
|
|
var result = Transaction.MAXIMUM_EXTRA_SIZE; |
|
|
|
_.each(this.inputs, function(input) { |
|
|
|
result += input._estimateSize(); |
|
|
|
}); |
|
|
|
_.each(this.outputs, function(output) { |
|
|
|
result += output.script.toBuffer().length + 9; |
|
|
|
}); |
|
|
|
return result; |
|
|
|
}; |
|
|
|
|
|
|
|
Transaction.prototype.removeOutput = function(index) { |
|
|
|
var output = this.outputs[index]; |
|
|
|
this._outputAmount -= output.satoshis; |
|
|
|
this.outputs = _.without(this.outputs, this.outputs[this._changeOutput]); |
|
|
|
}; |
|
|
|
|
|
|
|
/* Signature handling */ |
|
|
|
|
|
|
|
Transaction.prototype.sign = function(privKey, sigtype) { |
|
|
|
// TODO: Change for preconditions
|
|
|
|
assert(this.hasAllUtxoInfo()); |
|
|
|
/** |
|
|
|
* Sign the transaction using one or more private keys. |
|
|
|
* |
|
|
|
* It tries to sign each input, verifying that the signature will be valid |
|
|
|
* (matches a public key). |
|
|
|
* |
|
|
|
* @param {Array|String|PrivateKey} privateKey |
|
|
|
* @param {number} sigtype |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.sign = function(privateKey, sigtype) { |
|
|
|
$.checkState(this.hasAllUtxoInfo()); |
|
|
|
this._updateChangeOutput(); |
|
|
|
var self = this; |
|
|
|
if (_.isArray(privKey)) { |
|
|
|
_.each(privKey, function(privKey) { |
|
|
|
self.sign(privKey); |
|
|
|
if (_.isArray(privateKey)) { |
|
|
|
_.each(privateKey, function(privateKey) { |
|
|
|
self.sign(privateKey); |
|
|
|
}); |
|
|
|
return this; |
|
|
|
} |
|
|
|
_.each(this.getSignatures(privKey, sigtype), function(signature) { |
|
|
|
_.each(this.getSignatures(privateKey, sigtype), function(signature) { |
|
|
|
self.applySignature(signature); |
|
|
|
}); |
|
|
|
return this; |
|
|
@ -369,6 +529,16 @@ Transaction.prototype._getPrivateKeySignatures = function(privKey, sigtype) { |
|
|
|
return results; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Add a signature to the transaction |
|
|
|
* |
|
|
|
* @param {Object} signature |
|
|
|
* @param {number} signature.inputIndex |
|
|
|
* @param {number} signature.sighash |
|
|
|
* @param {PublicKey} signature.publicKey |
|
|
|
* @param {Signature} signature.signature |
|
|
|
* @return {Transaction} this, for chaining |
|
|
|
*/ |
|
|
|
Transaction.prototype.applySignature = function(signature) { |
|
|
|
this.inputs[signature.inputIndex].addSignature(this, signature); |
|
|
|
return this; |
|
|
|