TransactionBuilder: add fee safety

This commit is contained in:
Daniel Cousens 2016-11-09 13:01:29 +11:00 committed by Thomas Kerin
parent 11079bfafb
commit e835f1fe95
4 changed files with 67 additions and 22 deletions

View file

@ -272,7 +272,10 @@ TransactionBuilder.fromTransaction = function (transaction, network) {
// Copy inputs // Copy inputs
transaction.ins.forEach(function (txIn) { transaction.ins.forEach(function (txIn) {
txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script) txb.__addInputUnsafe(txIn.hash, txIn.index, {
sequence: txIn.sequence,
script: txIn.script
})
}) })
// fix some things not possible through the public API // fix some things not possible through the public API
@ -288,6 +291,8 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
throw new Error('No, this would invalidate signatures') throw new Error('No, this would invalidate signatures')
} }
var value
// is it a hex string? // is it a hex string?
if (typeof txHash === 'string') { if (typeof txHash === 'string') {
// transaction hashs's are displayed in reverse order, un-reverse it // transaction hashs's are displayed in reverse order, un-reverse it
@ -295,14 +300,21 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu
// is it a Transaction object? // is it a Transaction object?
} else if (txHash instanceof Transaction) { } else if (txHash instanceof Transaction) {
prevOutScript = txHash.outs[vout].script var txOut = txHash.outs[vout]
prevOutScript = txOut.script
value = txOut.value
txHash = txHash.getHash() txHash = txHash.getHash()
} }
return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript) return this.__addInputUnsafe(txHash, vout, {
sequence: sequence,
prevOutScript: prevOutScript,
value: value
})
} }
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) { TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options) {
if (Transaction.isCoinbaseHash(txHash)) { if (Transaction.isCoinbaseHash(txHash)) {
throw new Error('coinbase inputs not supported') throw new Error('coinbase inputs not supported')
} }
@ -313,16 +325,21 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence
var input = {} var input = {}
// derive what we can from the scriptSig // derive what we can from the scriptSig
if (scriptSig) { if (options.script !== undefined) {
input = expandInput(scriptSig) input = expandInput(options.script)
}
// if an input value was given, retain it
if (options.value !== undefined) {
input.value = options.value
} }
// derive what we can from the previous transactions output script // derive what we can from the previous transactions output script
if (!input.prevOutScript && prevOutScript) { if (!input.prevOutScript && options.prevOutScript) {
var prevOutType var prevOutType
if (!input.pubKeys && !input.signatures) { if (!input.pubKeys && !input.signatures) {
var expanded = expandOutput(prevOutScript) var expanded = expandOutput(options.prevOutScript)
if (expanded.pubKeys) { if (expanded.pubKeys) {
input.pubKeys = expanded.pubKeys input.pubKeys = expanded.pubKeys
@ -332,11 +349,11 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence
prevOutType = expanded.scriptType prevOutType = expanded.scriptType
} }
input.prevOutScript = prevOutScript input.prevOutScript = options.prevOutScript
input.prevOutType = prevOutType || bscript.classifyOutput(prevOutScript) input.prevOutType = prevOutType || bscript.classifyOutput(options.prevOutScript)
} }
var vin = this.tx.addInput(txHash, vout, sequence, scriptSig) var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig)
this.inputs[vin] = input this.inputs[vin] = input
this.prevTxMap[prevTxOut] = true this.prevTxMap[prevTxOut] = true
@ -367,6 +384,9 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
if (!allowIncomplete) { if (!allowIncomplete) {
if (!this.tx.ins.length) throw new Error('Transaction has no inputs') if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
if (!this.tx.outs.length) throw new Error('Transaction has no outputs') if (!this.tx.outs.length) throw new Error('Transaction has no outputs')
// do not rely on this, its merely a last resort
if (this.__hasAbsurdFee()) throw new Error('Transaction has absurd fees')
} }
var tx = this.tx.clone() var tx = this.tx.clone()
@ -479,4 +499,18 @@ TransactionBuilder.prototype.__canModifyOutputs = function () {
}) })
} }
TransactionBuilder.prototype.__hasAbsurdFee = function () {
// not all inputs will have .value defined
var incoming = this.inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0)
// but all outputs do, and if we have any input value
// we can immediately determine if the outputs are too small
var outgoing = this.tx.outs.reduce(function (a, x) { return a + x.value }, 0)
var fee = incoming - outgoing
// its not fool-proof, but, it might help somebody
// fee > 0.2BTC
return fee > (0.2 * 1e8)
}
module.exports = TransactionBuilder module.exports = TransactionBuilder

View file

@ -10,7 +10,7 @@ function BIP32Path (value) {
} }
BIP32Path.toJSON = function () { return 'BIP32 derivation path' } BIP32Path.toJSON = function () { return 'BIP32 derivation path' }
var SATOSHI_MAX = 2.1 * 1e15 var SATOSHI_MAX = 21 * 1e14
function Satoshi (value) { function Satoshi (value) {
return typeforce.UInt53(value) && value <= SATOSHI_MAX return typeforce.UInt53(value) && value <= SATOSHI_MAX
} }

View file

@ -657,20 +657,33 @@
"inputs": [ "inputs": [
{ {
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"vout": 0, "vout": 0
"signs": []
} }
], ],
"outputs": [] "outputs": []
}, },
{
"exception": "Transaction has absurd fees",
"inputs": [
{
"txHex": "01000000000100e1f505000000000000000000",
"vout": 0
}
],
"outputs": [
{
"script": "OP_DUP OP_HASH160 ff99e06c1a4ac394b4e1cb3d3a4b2b47749e339a OP_EQUALVERIFY OP_CHECKSIG",
"value": 100000
}
]
},
{ {
"description": "Incomplete transaction, nothing assumed", "description": "Incomplete transaction, nothing assumed",
"exception": "Transaction is not complete", "exception": "Transaction is not complete",
"inputs": [ "inputs": [
{ {
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"vout": 0, "vout": 0
"signs": []
} }
], ],
"outputs": [ "outputs": [
@ -697,8 +710,7 @@
{ {
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"vout": 1, "vout": 1,
"prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", "prevTxScript": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG"
"signs": []
} }
], ],
"outputs": [ "outputs": [
@ -715,13 +727,11 @@
"inputs": [ "inputs": [
{ {
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"vout": 0, "vout": 0
"signs": []
}, },
{ {
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"vout": 0, "vout": 0
"signs": []
} }
], ],
"outputs": [ "outputs": [

View file

@ -37,6 +37,7 @@ function construct (f, sign) {
if (sign === false) return txb if (sign === false) return txb
f.inputs.forEach(function (input, index) { f.inputs.forEach(function (input, index) {
if (!input.signs) return
input.signs.forEach(function (sign) { input.signs.forEach(function (sign) {
var keyPair = ECPair.fromWIF(sign.keyPair, network) var keyPair = ECPair.fromWIF(sign.keyPair, network)
var redeemScript var redeemScript