From e1fa7af1da79e86ccaae5f3cd2a6c4644f546f8a Mon Sep 17 00:00:00 2001 From: Jochen Hoenicke Date: Wed, 8 Nov 2017 01:13:21 +0100 Subject: [PATCH] Byte-precise size estimate for fees Fixes issue #232. It assumes largest possible signature size for all inputs. For segwit multisig it can be .25 bytes off due to difference between segwit encoding (varint) vs. non-segwit encoding (op_push) of the multisig script. --- firmware/crypto.h | 2 + firmware/signing.c | 21 ++++++++-- firmware/transaction.c | 93 +++++++++++++++++++++++++++++++++++++++--- firmware/transaction.h | 3 +- 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/firmware/crypto.h b/firmware/crypto.h index e974df26ac..c77e4a9a00 100644 --- a/firmware/crypto.h +++ b/firmware/crypto.h @@ -30,6 +30,8 @@ #include "coins.h" #include "types.pb.h" +#define ser_length_size(len) ((len) < 253 ? 1 : (len) < 0x10000 ? 3 : 5) + uint32_t ser_length(uint32_t len, uint8_t *out); uint32_t ser_length_hash(SHA256_CTX *ctx, uint32_t len); diff --git a/firmware/signing.c b/firmware/signing.c index ff1ddf6f87..6cef721e63 100644 --- a/firmware/signing.c +++ b/firmware/signing.c @@ -67,6 +67,7 @@ static bool multisig_fp_set, multisig_fp_mismatch; static uint8_t multisig_fp[32]; static uint32_t in_address_n[8]; static size_t in_address_n_count; +static uint32_t tx_weight; /* A marker for in_address_n_count to indicate a mismatch in bip32 paths in input */ @@ -79,6 +80,13 @@ static size_t in_address_n_count; use and still allow to quickly brute-force the correct bip32 path. */ #define BIP32_MAX_LAST_ELEMENT 1000000 +/* transaction header size: 4 byte version */ +#define TXSIZE_HEADER 4 +/* transaction footer size: 4 byte lock time */ +#define TXSIZE_FOOTER 4 +/* transaction segwit overhead 2 marker */ +#define TXSIZE_SEGWIT_OVERHEAD 2 + enum { SIGHASH_ALL = 1, SIGHASH_FORKID = 0x40, @@ -433,6 +441,10 @@ void signing_init(uint32_t _inputs_count, uint32_t _outputs_count, const CoinInf version = _version; lock_time = _lock_time; + tx_weight = 4 * (TXSIZE_HEADER + TXSIZE_FOOTER + + ser_length_size(inputs_count) + + ser_length_size(outputs_count)); + signatures = 0; idx1 = 0; to_spend = 0; @@ -590,15 +602,13 @@ static bool signing_check_fee(void) { return false; } uint64_t fee = to_spend - spending; - uint64_t tx_est_size_kb = (transactionEstimateSize(inputs_count, outputs_count) + 999) / 1000; - if (fee > tx_est_size_kb * coin->maxfee_kb) { + if (fee > ((uint64_t) tx_weight * coin->maxfee_kb)/4000) { layoutFeeOverThreshold(coin, fee); if (!protectButton(ButtonRequestType_ButtonRequest_FeeOverThreshold, false)) { fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); signing_abort(); return false; } - layoutProgress(_("Signing transaction"), progress); } // last confirmation layoutConfirmTx(coin, to_spend - change_spend, fee); @@ -799,6 +809,7 @@ void signing_txack(TransactionType *tx) switch (signing_stage) { case STAGE_REQUEST_1_INPUT: signing_check_input(&tx->inputs[0]); + tx_weight += tx_input_weight(&tx->inputs[0]); if (tx->inputs[0].script_type == InputScriptType_SPENDMULTISIG || tx->inputs[0].script_type == InputScriptType_SPENDADDRESS) { memcpy(&input, tx->inputs, sizeof(TxInputType)); @@ -849,6 +860,9 @@ void signing_txack(TransactionType *tx) signing_abort(); return; } + if (!to.is_segwit) { + tx_weight += TXSIZE_SEGWIT_OVERHEAD + to.inputs_len; + } #if !ENABLE_SEGWIT_NONSEGWIT_MIXING // don't mix segwit and non-segwit inputs if (idx1 == 0) { @@ -939,6 +953,7 @@ void signing_txack(TransactionType *tx) if (!signing_check_output(&tx->outputs[0])) { return; } + tx_weight += tx_output_weight(coin, &tx->outputs[0]); phase1_request_next_output(); return; case STAGE_REQUEST_4_INPUT: diff --git a/firmware/transaction.c b/firmware/transaction.c index 965e9cffbf..40a872d0a3 100644 --- a/firmware/transaction.c +++ b/firmware/transaction.c @@ -35,19 +35,41 @@ #define SEGWIT_VERSION_0 0 +/* transaction input size (without script): 32 prevhash, 4 idx, 4 sequence */ +#define TXSIZE_INPUT 40 +/* transaction output size (without script): 8 amount */ +#define TXSIZE_OUTPUT 8 +/* size of a pubkey */ +#define TXSIZE_PUBKEY 33 +/* size of a DER signature (3 type bytes, 3 len bytes, 33 R, 32 S, 1 sighash */ +#define TXSIZE_SIGNATURE 72 +/* size of a multiscript without pubkey (1 M, 1 N, 1 checksig) */ +#define TXSIZE_MULTISIGSCRIPT 3 +/* size of a p2wpkh script (1 version, 1 push, 20 hash) */ +#define TXSIZE_WITNESSPKHASH 22 +/* size of a p2wsh script (1 version, 1 push, 32 hash) */ +#define TXSIZE_WITNESSSCRIPT 34 +/* size of a p2pkh script (dup, hash, push, 20 pubkeyhash, equal, checksig) */ +#define TXSIZE_P2PKHASH 25 +/* size of a p2sh script (hash, push, 20 scripthash, equal) */ +#define TXSIZE_P2SCRIPT 23 + static const uint8_t segwit_header[2] = {0,1}; +#define op_push_size(len) ((len) < 0x4c ? 1 : (len) < 0x100 ? 2 : \ + (len) < 0x10000 ? 3 : 5) + uint32_t op_push(uint32_t i, uint8_t *out) { if (i < 0x4C) { out[0] = i & 0xFF; return 1; } - if (i < 0xFF) { + if (i < 0x100) { out[0] = 0x4C; out[1] = i & 0xFF; return 2; } - if (i < 0xFFFF) { + if (i < 0x10000) { out[0] = 0x4D; out[1] = i & 0xFF; out[2] = (i >> 8) & 0xFF; @@ -560,7 +582,68 @@ void tx_hash_final(TxStruct *t, uint8_t *hash, bool reverse) } } -uint32_t transactionEstimateSize(uint32_t inputs, uint32_t outputs) -{ - return 10 + inputs * 149 + outputs * 35; +uint32_t tx_input_weight(const TxInputType *txinput) { + uint32_t input_script_size; + if (txinput->has_multisig) { + uint32_t multisig_script_size = TXSIZE_MULTISIGSCRIPT + + txinput->multisig.pubkeys_count * (1 + TXSIZE_PUBKEY); + input_script_size = 1 // the OP_FALSE bug in multisig + + txinput->multisig.m * (1 + TXSIZE_SIGNATURE) + + op_push_size(multisig_script_size) + multisig_script_size; + } else { + input_script_size = (1 + TXSIZE_SIGNATURE + 1 + TXSIZE_PUBKEY); + } + uint32_t weight = 4 * TXSIZE_INPUT; + if (txinput->script_type == InputScriptType_SPENDADDRESS + || txinput->script_type == InputScriptType_SPENDMULTISIG) { + input_script_size += ser_length_size(input_script_size); + weight += 4 * input_script_size; + } else if (txinput->script_type == InputScriptType_SPENDWITNESS + || txinput->script_type == InputScriptType_SPENDP2SHWITNESS) { + if (txinput->script_type == InputScriptType_SPENDP2SHWITNESS) { + weight += 4 * (2 + (txinput->has_multisig + ? TXSIZE_WITNESSSCRIPT : TXSIZE_WITNESSPKHASH)); + } else { + weight += 4; // empty input script + } + weight += input_script_size; // discounted witness + } + return weight; +} + +uint32_t tx_output_weight(const CoinInfo *coin, const TxOutputType *txoutput) { + uint32_t output_script_size = 0; + if (txoutput->script_type == OutputScriptType_PAYTOOPRETURN) { + output_script_size = 1 + op_push_size(txoutput->op_return_data.size) + + txoutput->op_return_data.size; + } else if (txoutput->address_n_count > 0) { + if (txoutput->script_type == OutputScriptType_PAYTOWITNESS) { + output_script_size = txoutput->has_multisig + ? TXSIZE_WITNESSSCRIPT : TXSIZE_WITNESSPKHASH; + } else if (txoutput->script_type == OutputScriptType_PAYTOP2SHWITNESS) { + output_script_size = TXSIZE_P2SCRIPT; + } else { + output_script_size = txoutput->has_multisig + ? TXSIZE_P2SCRIPT : TXSIZE_P2PKHASH; + } + } else { + uint8_t addr_raw[MAX_ADDR_RAW_SIZE]; + int witver; + size_t addr_raw_len; + if (coin->bech32_prefix + && segwit_addr_decode(&witver, addr_raw, &addr_raw_len, coin->bech32_prefix, txoutput->address)) { + output_script_size = 2 + addr_raw_len; + } else { + addr_raw_len = base58_decode_check(txoutput->address, addr_raw, MAX_ADDR_RAW_SIZE); + if (coin->has_address_type + && address_check_prefix(addr_raw, coin->address_type)) { + output_script_size = TXSIZE_P2PKHASH; + } else if (coin->has_address_type_p2sh + && address_check_prefix(addr_raw, coin->address_type_p2sh)) { + output_script_size = TXSIZE_P2SCRIPT; + } + } + } + output_script_size += ser_length_size(output_script_size); + return 4 * (TXSIZE_OUTPUT + output_script_size); } diff --git a/firmware/transaction.h b/firmware/transaction.h index f5e4ee30d4..d802ece30c 100644 --- a/firmware/transaction.h +++ b/firmware/transaction.h @@ -71,6 +71,7 @@ uint32_t tx_serialize_output_hash(TxStruct *tx, const TxOutputBinType *output); uint32_t tx_serialize_extra_data_hash(TxStruct *tx, const uint8_t *data, uint32_t datalen); void tx_hash_final(TxStruct *t, uint8_t *hash, bool reverse); -uint32_t transactionEstimateSize(uint32_t inputs, uint32_t outputs); +uint32_t tx_input_weight(const TxInputType *txinput); +uint32_t tx_output_weight(const CoinInfo *coin, const TxOutputType *txoutput); #endif