|
@ -1,5 +1,3 @@ |
|
|
// FIXME: To all ye that enter here, be weary of Buffers, Arrays and Hex interchanging between the outpoints
|
|
|
|
|
|
|
|
|
|
|
|
var assert = require('assert') |
|
|
var assert = require('assert') |
|
|
var bufferutils = require('./bufferutils') |
|
|
var bufferutils = require('./bufferutils') |
|
|
var crypto = require('./crypto') |
|
|
var crypto = require('./crypto') |
|
@ -12,32 +10,16 @@ var ECKey = require('./eckey') |
|
|
var Script = require('./script') |
|
|
var Script = require('./script') |
|
|
|
|
|
|
|
|
var DEFAULT_SEQUENCE = 0xffffffff |
|
|
var DEFAULT_SEQUENCE = 0xffffffff |
|
|
|
|
|
var SIGHASH_ALL = 0x01 |
|
|
|
|
|
var SIGHASH_NONE = 0x02 |
|
|
|
|
|
var SIGHASH_SINGLE = 0x03 |
|
|
|
|
|
var SIGHASH_ANYONECANPAY = 0x80 |
|
|
|
|
|
|
|
|
function Transaction(doc) { |
|
|
function Transaction() { |
|
|
if (!(this instanceof Transaction)) { return new Transaction(doc) } |
|
|
|
|
|
this.version = 1 |
|
|
this.version = 1 |
|
|
this.locktime = 0 |
|
|
this.locktime = 0 |
|
|
this.ins = [] |
|
|
this.ins = [] |
|
|
this.outs = [] |
|
|
this.outs = [] |
|
|
|
|
|
|
|
|
if (doc) { |
|
|
|
|
|
if (doc.hash) this.hash = doc.hash; |
|
|
|
|
|
if (doc.version) this.version = doc.version; |
|
|
|
|
|
if (doc.locktime) this.locktime = doc.locktime; |
|
|
|
|
|
if (doc.ins && doc.ins.length) { |
|
|
|
|
|
doc.ins.forEach(function(input) { |
|
|
|
|
|
this.addInput(new TransactionIn(input)) |
|
|
|
|
|
}, this) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (doc.outs && doc.outs.length) { |
|
|
|
|
|
doc.outs.forEach(function(output) { |
|
|
|
|
|
this.addOutput(new TransactionOut(output)) |
|
|
|
|
|
}, this) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.hash = this.hash || this.getHash() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -45,36 +27,35 @@ function Transaction(doc) { |
|
|
* |
|
|
* |
|
|
* Can be called with any of: |
|
|
* Can be called with any of: |
|
|
* |
|
|
* |
|
|
* - An existing TransactionIn object |
|
|
|
|
|
* - A transaction and an index |
|
|
* - A transaction and an index |
|
|
* - A transaction hash and an index |
|
|
* - A transaction hash and an index |
|
|
* - A single string argument of the form txhash:index |
|
|
|
|
|
* |
|
|
* |
|
|
* Note that this method does not sign the created input. |
|
|
* Note that this method does not sign the created input. |
|
|
*/ |
|
|
*/ |
|
|
Transaction.prototype.addInput = function (tx, outIndex) { |
|
|
Transaction.prototype.addInput = function(tx, index) { |
|
|
if (arguments[0] instanceof TransactionIn) { |
|
|
|
|
|
this.ins.push(arguments[0]) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var hash |
|
|
var hash |
|
|
if (arguments[0].length > 65) { |
|
|
|
|
|
var args = arguments[0].split(':') |
|
|
if (typeof tx === 'string') { |
|
|
hash = args[0] |
|
|
hash = new Buffer(tx, 'hex') |
|
|
outIndex = parseInt(args[1]) |
|
|
assert.equal(hash.length, 32, 'Expected Transaction or string, got ' + tx) |
|
|
|
|
|
|
|
|
|
|
|
// TxHash hex is big-endian, we need little-endian
|
|
|
|
|
|
Array.prototype.reverse.call(hash) |
|
|
|
|
|
|
|
|
} else { |
|
|
} else { |
|
|
hash = typeof tx === "string" ? tx : tx.hash |
|
|
assert(tx instanceof Transaction, 'Expected Transaction or string, got ' + tx) |
|
|
|
|
|
hash = crypto.hash256(tx.toBuffer()) |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.ins.push(new TransactionIn({ |
|
|
assert.equal(typeof index, 'number', 'Expected number index, got ' + index) |
|
|
outpoint: { |
|
|
|
|
|
|
|
|
return (this.ins.push({ |
|
|
hash: hash, |
|
|
hash: hash, |
|
|
index: outIndex |
|
|
index: index, |
|
|
}, |
|
|
script: Script.EMPTY, |
|
|
script: Script.EMPTY |
|
|
sequence: DEFAULT_SEQUENCE |
|
|
})) |
|
|
}) - 1) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -82,33 +63,27 @@ Transaction.prototype.addInput = function (tx, outIndex) { |
|
|
* |
|
|
* |
|
|
* Can be called with: |
|
|
* Can be called with: |
|
|
* |
|
|
* |
|
|
* i) An existing TransactionOut object |
|
|
* - A base58 address string and a value |
|
|
* ii) An address object or a string address, and a value |
|
|
* - An Address object and a value |
|
|
* iii) An address:value string |
|
|
* - A scriptPubKey Script and a value |
|
|
* |
|
|
|
|
|
* FIXME: This is a bit convoluted |
|
|
|
|
|
*/ |
|
|
*/ |
|
|
Transaction.prototype.addOutput = function (address, value) { |
|
|
Transaction.prototype.addOutput = function(scriptPubKey, value) { |
|
|
if (arguments[0] instanceof TransactionOut) { |
|
|
// Attempt to get a valid address if it's a base58 address string
|
|
|
this.outs.push(arguments[0]) |
|
|
if (typeof scriptPubKey === 'string') { |
|
|
return |
|
|
scriptPubKey = Address.fromBase58Check(scriptPubKey) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (typeof address === 'string') { |
|
|
// Attempt to get a valid script if it's an Address object
|
|
|
if (arguments[0].indexOf(':') >= 0) { |
|
|
if (scriptPubKey instanceof Address) { |
|
|
var args = arguments[0].split(':') |
|
|
var address = scriptPubKey |
|
|
address = args[0] |
|
|
|
|
|
value = parseInt(args[1]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
address = Address.fromBase58Check(address) |
|
|
scriptPubKey = address.toOutputScript() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.outs.push(new TransactionOut({ |
|
|
return (this.outs.push({ |
|
|
|
|
|
script: scriptPubKey, |
|
|
value: value, |
|
|
value: value, |
|
|
script: address.toOutputScript(), |
|
|
}) - 1) |
|
|
address: address // TODO: Remove me
|
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Transaction.prototype.toBuffer = function () { |
|
|
Transaction.prototype.toBuffer = function () { |
|
@ -150,13 +125,8 @@ Transaction.prototype.toBuffer = function () { |
|
|
writeVarInt(this.ins.length) |
|
|
writeVarInt(this.ins.length) |
|
|
|
|
|
|
|
|
this.ins.forEach(function(txin) { |
|
|
this.ins.forEach(function(txin) { |
|
|
var hash = new Buffer(txin.outpoint.hash, 'hex') // FIXME: Performance: convert on tx.addInput instead
|
|
|
writeSlice(txin.hash) |
|
|
|
|
|
writeUInt32(txin.index) |
|
|
// TxHash hex is big-endian, we need little-endian
|
|
|
|
|
|
Array.prototype.reverse.call(hash) |
|
|
|
|
|
|
|
|
|
|
|
writeSlice(hash) |
|
|
|
|
|
writeUInt32(txin.outpoint.index) |
|
|
|
|
|
writeVarInt(txin.script.buffer.length) |
|
|
writeVarInt(txin.script.buffer.length) |
|
|
writeSlice(txin.script.buffer) |
|
|
writeSlice(txin.script.buffer) |
|
|
writeUInt32(txin.sequence) |
|
|
writeUInt32(txin.sequence) |
|
@ -179,11 +149,6 @@ Transaction.prototype.toHex = function() { |
|
|
return this.toBuffer().toString('hex') |
|
|
return this.toBuffer().toString('hex') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var SIGHASH_ALL = 0x01 |
|
|
|
|
|
var SIGHASH_NONE = 0x02 |
|
|
|
|
|
var SIGHASH_SINGLE = 0x03 |
|
|
|
|
|
var SIGHASH_ANYONECANPAY = 0x80 |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Hash transaction for signing a specific input. |
|
|
* Hash transaction for signing a specific input. |
|
|
* |
|
|
* |
|
@ -226,7 +191,7 @@ Transaction.prototype.hashForSignature = function(prevOutScript, inIndex, hashTy |
|
|
return crypto.hash256(buffer) |
|
|
return crypto.hash256(buffer) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Transaction.prototype.getHash = function () { |
|
|
Transaction.prototype.getId = function () { |
|
|
var buffer = crypto.hash256(this.toBuffer()) |
|
|
var buffer = crypto.hash256(this.toBuffer()) |
|
|
|
|
|
|
|
|
// Big-endian is used for TxHash
|
|
|
// Big-endian is used for TxHash
|
|
@ -240,21 +205,26 @@ Transaction.prototype.clone = function () { |
|
|
newTx.version = this.version |
|
|
newTx.version = this.version |
|
|
newTx.locktime = this.locktime |
|
|
newTx.locktime = this.locktime |
|
|
|
|
|
|
|
|
this.ins.forEach(function(txin) { |
|
|
newTx.ins = this.ins.map(function(txin) { |
|
|
newTx.addInput(txin.clone()) |
|
|
return { |
|
|
|
|
|
hash: txin.hash, |
|
|
|
|
|
index: txin.index, |
|
|
|
|
|
script: txin.script, |
|
|
|
|
|
sequence: txin.sequence |
|
|
|
|
|
} |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
this.outs.forEach(function(txout) { |
|
|
newTx.outs = this.outs.map(function(txout) { |
|
|
newTx.addOutput(txout.clone()) |
|
|
return { |
|
|
|
|
|
script: txout.script, |
|
|
|
|
|
value: txout.value |
|
|
|
|
|
} |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
return newTx |
|
|
return newTx |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Transaction.fromBuffer = function(buffer) { |
|
|
Transaction.fromBuffer = function(buffer) { |
|
|
// Copy because we mutate (reverse TxOutHashs)
|
|
|
|
|
|
buffer = new Buffer(buffer) |
|
|
|
|
|
|
|
|
|
|
|
var offset = 0 |
|
|
var offset = 0 |
|
|
function readSlice(n) { |
|
|
function readSlice(n) { |
|
|
offset += n |
|
|
offset += n |
|
@ -276,55 +246,41 @@ Transaction.fromBuffer = function(buffer) { |
|
|
return vi.number |
|
|
return vi.number |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var ins = [] |
|
|
var tx = new Transaction() |
|
|
var outs = [] |
|
|
tx.version = readUInt32() |
|
|
|
|
|
|
|
|
var version = readUInt32() |
|
|
|
|
|
var vinLen = readVarInt() |
|
|
var vinLen = readVarInt() |
|
|
|
|
|
|
|
|
for (var i = 0; i < vinLen; ++i) { |
|
|
for (var i = 0; i < vinLen; ++i) { |
|
|
var hash = readSlice(32) |
|
|
var hash = readSlice(32) |
|
|
|
|
|
|
|
|
// TxHash is little-endian, we want big-endian hex
|
|
|
|
|
|
Array.prototype.reverse.call(hash) |
|
|
|
|
|
|
|
|
|
|
|
var vout = readUInt32() |
|
|
var vout = readUInt32() |
|
|
var scriptLen = readVarInt() |
|
|
var scriptLen = readVarInt() |
|
|
var script = readSlice(scriptLen) |
|
|
var script = readSlice(scriptLen) |
|
|
var sequence = readUInt32() |
|
|
var sequence = readUInt32() |
|
|
|
|
|
|
|
|
ins.push({ |
|
|
tx.ins.push({ |
|
|
outpoint: { |
|
|
hash: hash, |
|
|
hash: hash.toString('hex'), |
|
|
|
|
|
index: vout, |
|
|
index: vout, |
|
|
}, |
|
|
|
|
|
script: Script.fromBuffer(script), |
|
|
script: Script.fromBuffer(script), |
|
|
sequence: sequence |
|
|
sequence: sequence |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var voutLen = readVarInt() |
|
|
var voutLen = readVarInt() |
|
|
|
|
|
|
|
|
for (i = 0; i < voutLen; ++i) { |
|
|
for (i = 0; i < voutLen; ++i) { |
|
|
var value = readUInt64() |
|
|
var value = readUInt64() |
|
|
var scriptLen = readVarInt() |
|
|
var scriptLen = readVarInt() |
|
|
var script = readSlice(scriptLen) |
|
|
var script = readSlice(scriptLen) |
|
|
|
|
|
|
|
|
outs.push({ |
|
|
tx.outs.push({ |
|
|
value: value, |
|
|
value: value, |
|
|
script: Script.fromBuffer(script) |
|
|
script: Script.fromBuffer(script) |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var locktime = readUInt32() |
|
|
tx.locktime = readUInt32() |
|
|
assert.equal(offset, buffer.length, 'Invalid transaction') |
|
|
assert.equal(offset, buffer.length, 'Invalid transaction') |
|
|
|
|
|
|
|
|
return new Transaction({ |
|
|
return tx |
|
|
version: version, |
|
|
|
|
|
ins: ins, |
|
|
|
|
|
outs: outs, |
|
|
|
|
|
locktime: locktime |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Transaction.fromHex = function(hex) { |
|
|
Transaction.fromHex = function(hex) { |
|
@ -334,26 +290,26 @@ Transaction.fromHex = function(hex) { |
|
|
/** |
|
|
/** |
|
|
* Signs a pubKeyHash output at some index with the given key |
|
|
* Signs a pubKeyHash output at some index with the given key |
|
|
*/ |
|
|
*/ |
|
|
Transaction.prototype.sign = function(index, key, type) { |
|
|
Transaction.prototype.sign = function(index, privKey, hashType) { |
|
|
var prevOutScript = key.pub.getAddress().toOutputScript() |
|
|
var prevOutScript = privKey.pub.getAddress().toOutputScript() |
|
|
var signature = this.signInput(index, prevOutScript, key, type) |
|
|
var signature = this.signInput(index, prevOutScript, privKey, hashType) |
|
|
|
|
|
|
|
|
// FIXME: Assumed prior TX was pay-to-pubkey-hash
|
|
|
// FIXME: Assumed prior TX was pay-to-pubkey-hash
|
|
|
var scriptSig = scripts.pubKeyHashInput(signature, key.pub) |
|
|
var scriptSig = scripts.pubKeyHashInput(signature, privKey.pub) |
|
|
this.setInputScript(index, scriptSig) |
|
|
this.setInputScript(index, scriptSig) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Transaction.prototype.signInput = function(index, prevOutScript, key, type) { |
|
|
Transaction.prototype.signInput = function(index, prevOutScript, privKey, hashType) { |
|
|
type = type || SIGHASH_ALL |
|
|
hashType = hashType || SIGHASH_ALL |
|
|
assert(key instanceof ECKey, 'Invalid private key') |
|
|
assert(privKey instanceof ECKey, 'Expected ECKey, got ' + privKey) |
|
|
|
|
|
|
|
|
var hash = this.hashForSignature(prevOutScript, index, type) |
|
|
var hash = this.hashForSignature(prevOutScript, index, hashType) |
|
|
var signature = key.sign(hash) |
|
|
var signature = privKey.sign(hash) |
|
|
var DERencoded = ecdsa.serializeSig(signature) |
|
|
var DERencoded = ecdsa.serializeSig(signature) |
|
|
|
|
|
|
|
|
return Buffer.concat([ |
|
|
return Buffer.concat([ |
|
|
new Buffer(DERencoded), |
|
|
new Buffer(DERencoded), |
|
|
new Buffer([type]) |
|
|
new Buffer([hashType]) |
|
|
]) |
|
|
]) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -361,63 +317,15 @@ Transaction.prototype.setInputScript = function(index, script) { |
|
|
this.ins[index].script = script |
|
|
this.ins[index].script = script |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// FIXME: should probably be validateInput(index, pub)
|
|
|
// FIXME: could be validateInput(index, prevTxOut, pub)
|
|
|
Transaction.prototype.validateInput = function(index, script, pub, DERsig) { |
|
|
Transaction.prototype.validateInput = function(index, prevOutScript, pubKey, DERsig) { |
|
|
var type = DERsig.readUInt8(DERsig.length - 1) |
|
|
var type = DERsig.readUInt8(DERsig.length - 1) |
|
|
DERsig = DERsig.slice(0, -1) |
|
|
DERsig = DERsig.slice(0, -1) |
|
|
|
|
|
|
|
|
var hash = this.hashForSignature(script, index, type) |
|
|
var hash = this.hashForSignature(prevOutScript, index, type) |
|
|
var sig = ecdsa.parseSig(DERsig) |
|
|
var signature = ecdsa.parseSig(DERsig) |
|
|
|
|
|
|
|
|
return pub.verify(hash, sig) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Transaction.feePerKb = 20000 |
|
|
|
|
|
Transaction.prototype.estimateFee = function(feePerKb){ |
|
|
|
|
|
var uncompressedInSize = 180 |
|
|
|
|
|
var outSize = 34 |
|
|
|
|
|
var fixedPadding = 34 |
|
|
|
|
|
|
|
|
|
|
|
if(feePerKb == undefined) feePerKb = Transaction.feePerKb; |
|
|
|
|
|
var size = this.ins.length * uncompressedInSize + this.outs.length * outSize + fixedPadding |
|
|
|
|
|
|
|
|
|
|
|
return feePerKb * Math.ceil(size / 1000) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function TransactionIn(data) { |
|
|
|
|
|
assert(data.outpoint && data.script, 'Invalid TxIn parameters') |
|
|
|
|
|
this.outpoint = data.outpoint |
|
|
|
|
|
this.script = data.script |
|
|
|
|
|
this.sequence = data.sequence == undefined ? DEFAULT_SEQUENCE : data.sequence |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TransactionIn.prototype.clone = function () { |
|
|
|
|
|
return new TransactionIn({ |
|
|
|
|
|
outpoint: { |
|
|
|
|
|
hash: this.outpoint.hash, |
|
|
|
|
|
index: this.outpoint.index |
|
|
|
|
|
}, |
|
|
|
|
|
script: this.script, |
|
|
|
|
|
sequence: this.sequence |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function TransactionOut(data) { |
|
|
|
|
|
this.script = data.script |
|
|
|
|
|
this.value = data.value |
|
|
|
|
|
this.address = data.address |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
TransactionOut.prototype.clone = function() { |
|
|
return pubKey.verify(hash, signature) |
|
|
return new TransactionOut({ |
|
|
|
|
|
script: this.script, |
|
|
|
|
|
value: this.value, |
|
|
|
|
|
address: this.address |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
module.exports = { |
|
|
module.exports = Transaction |
|
|
Transaction: Transaction, |
|
|
|
|
|
TransactionIn: TransactionIn, |
|
|
|
|
|
TransactionOut: TransactionOut |
|
|
|
|
|
} |
|
|
|
|
|