diff --git a/wallet/internal/txsizes/size.go b/wallet/internal/txsizes/size.go index 87a2435..4e513fb 100644 --- a/wallet/internal/txsizes/size.go +++ b/wallet/internal/txsizes/size.go @@ -5,6 +5,7 @@ package txsizes import ( + "github.com/roasbeef/btcd/blockchain" "github.com/roasbeef/btcd/wire" h "github.com/roasbeef/btcwallet/internal/helpers" @@ -52,7 +53,69 @@ const ( // - 25 bytes P2PKH output script P2PKHOutputSize = 8 + 1 + P2PKHPkScriptSize - // TODO(roasbeef): add estimates for nested p2wkh and p2pkh + // P2WPKHPkScriptSize is the size of a transaction output script that + // pays to a witness pubkey hash. It is calculated as: + // + // - OP_0 + // - OP_DATA_20 + // - 20 bytes pubkey hash + P2WPKHPkScriptSize = 1 + 1 + 20 + + // P2WPKHOutputSize is the serialize size of a transaction output with a + // P2WPKH output script. It is calculated as: + // + // - 8 bytes output value + // - 1 byte compact int encoding value 22 + // - 22 bytes P2PKH output script + P2WPKHOutputSize = 8 + 1 + P2WPKHPkScriptSize + + // RedeemP2WPKHScriptSize is the size of a transaction input script + // that spends a pay-to-witness-public-key hash (P2WPKH). The redeem + // script for P2WPKH spends MUST be empty. + RedeemP2WPKHScriptSize = 0 + + // RedeemP2WPKHInputSize is the worst case size of a transaction + // input redeeming a P2WPKH output. It is calculated as: + // + // - 32 bytes previous tx + // - 4 bytes output index + // - 1 byte encoding empty redeem script + // - 0 bytes redeem script + // - 4 bytes sequence + RedeemP2WPKHInputSize = 32 + 4 + 1 + RedeemP2WPKHScriptSize + 4 + + // RedeemNestedP2WPKHScriptSize is the worst case size of a transaction + // input script that redeems a pay-to-witness-key hash nested in P2SH + // (P2SH-P2WPKH). It is calculated as: + // + // - 1 byte compact int encoding value 22 + // - OP_0 + // - 1 byte compact int encoding value 20 + // - 20 byte key hash + RedeemNestedP2WPKHScriptSize = 1 + 1 + 1 + 20 + + // RedeemNestedP2WPKHInputSize is the worst case size of a + // transaction input redeeming a P2SH-P2WPKH output. It is + // calculated as: + // + // - 32 bytes previous tx + // - 4 bytes output index + // - 1 byte compact int encoding value 23 + // - 23 bytes redeem script (scriptSig) + // - 4 bytes sequence + RedeemNestedP2WPKHInputSize = 32 + 4 + 1 + + RedeemNestedP2WPKHScriptSize + 4 + + // RedeemP2WPKHInputWitnessWeight is the worst case weight of + // a witness for spending P2WPKH and nested P2WPKH outputs. It + // is calculated as: + // + // - 1 wu compact int encoding value 2 (number of items) + // - 1 wu compact int encoding value 73 + // - 72 wu DER signature + 1 wu sighash + // - 1 wu compact int encoding value 33 + // - 33 wu serialized compressed pubkey + RedeemP2WPKHInputWitnessWeight = 1 + 1 + 73 + 1 + 33 ) // EstimateSerializeSize returns a worst case serialize size estimate for a @@ -74,3 +137,48 @@ func EstimateSerializeSize(inputCount int, txOuts []*wire.TxOut, addChangeOutput h.SumOutputSerializeSizes(txOuts) + changeSize } + +// EstimateVirtualSize returns a worst case virtual size estimate for a +// signed transaction that spends the given number of P2PKH, P2WPKH and +// (nested) P2SH-P2WPKH outputs, and contains each transaction output +// from txOuts. The estimate is incremented for an additional P2PKH +// change output if addChangeOutput is true. +func EstimateVirtualSize(numP2PKHIns, numP2WPKHIns, numNestedP2WPKHIns int, + txOuts []*wire.TxOut, addChangeOutput bool) int { + changeSize := 0 + outputCount := len(txOuts) + if addChangeOutput { + // We are always using P2WPKH as change output. + changeSize = P2WPKHOutputSize + outputCount++ + } + + // Version 4 bytes + LockTime 4 bytes + Serialized var int size for the + // number of transaction inputs and outputs + size of redeem scripts + + // the size out the serialized outputs and change. + baseSize := 8 + + wire.VarIntSerializeSize( + uint64(numP2PKHIns+numP2WPKHIns+numNestedP2WPKHIns)) + + wire.VarIntSerializeSize(uint64(len(txOuts))) + + numP2PKHIns*RedeemP2PKHInputSize + + numP2WPKHIns*RedeemP2WPKHInputSize + + numNestedP2WPKHIns*RedeemNestedP2WPKHInputSize + + h.SumOutputSerializeSizes(txOuts) + + changeSize + + // If this transaction has any witness inputs, we must count the + // witness data. + witnessWeight := 0 + if numP2WPKHIns+numNestedP2WPKHIns > 0 { + // Additional 2 weight units for segwit marker + flag. + witnessWeight = 2 + + wire.VarIntSerializeSize( + uint64(numP2WPKHIns+numNestedP2WPKHIns)) + + numP2WPKHIns*RedeemP2WPKHInputWitnessWeight + + numNestedP2WPKHIns*RedeemP2WPKHInputWitnessWeight + } + + // We add 3 to the witness weight to make sure the result is + // always rounded up. + return baseSize + (witnessWeight+3)/blockchain.WitnessScaleFactor +}