diff --git a/core/.changelog.d/2422.fixed b/core/.changelog.d/2422.fixed new file mode 100644 index 000000000..1fc900a63 --- /dev/null +++ b/core/.changelog.d/2422.fixed @@ -0,0 +1 @@ +Fix Decred transaction weight calculation. diff --git a/core/src/apps/bitcoin/sign_tx/decred.py b/core/src/apps/bitcoin/sign_tx/decred.py index e9db36b2b..25e87b029 100644 --- a/core/src/apps/bitcoin/sign_tx/decred.py +++ b/core/src/apps/bitcoin/sign_tx/decred.py @@ -7,6 +7,7 @@ from trezor.enums import DecredStakingSpendType, InputScriptType from trezor.messages import PrevOutput from trezor.utils import HashWriter, ensure +from apps.bitcoin.sign_tx.tx_weight import TxWeightCalculator from apps.common.writers import write_compact_size from .. import multisig, scripts_decred, writers @@ -40,7 +41,53 @@ if TYPE_CHECKING: from .sig_hasher import SigHasher +# Decred input size (without script): 32 prevhash, 4 idx, 1 Decred tree, 4 sequence +_TXSIZE_DECRED_INPUT = const(41) + +# Decred script version: 2 bytes +_TXSIZE_DECRED_SCRIPT_VERSION = const(2) + +# Decred expiry size: 4 bytes in footer +_TXSIZE_DECRED_EXPIRY = const(4) + +# Decred witness size (without script): 8 byte amount, 4 byte block height, 4 byte block index +_TXSIZE_DECRED_WITNESS = 16 + + +class DecredTxWeightCalculator(TxWeightCalculator): + def get_base_weight(self) -> int: + base_weight = super().get_base_weight() + base_weight += 4 * _TXSIZE_DECRED_EXPIRY + # Add witness input count. + base_weight += 4 * self.compact_size_len(self.inputs_count) + return base_weight + + def add_input(self, i: TxInput) -> None: + self.inputs_count += 1 + + # Input. + self.counter += 4 * _TXSIZE_DECRED_INPUT + + # Input witness. + input_script_size = self.input_script_size(i) + if i.script_type == InputScriptType.SPENDMULTISIG: + # Decred fixed the the OP_FALSE bug in multisig. + input_script_size -= 1 # Subtract one OP_FALSE byte. + + self.counter += 4 * _TXSIZE_DECRED_WITNESS + self.counter += 4 * self.compact_size_len(input_script_size) + self.counter += 4 * input_script_size + + def add_output(self, script: bytes) -> None: + super().add_output(script) + self.counter += 4 * _TXSIZE_DECRED_SCRIPT_VERSION + + class DecredApprover(BasicApprover): + def __init__(self, tx: SignTx, coin: CoinInfo) -> None: + super().__init__(tx, coin) + self.weight = DecredTxWeightCalculator() + async def add_decred_sstx_submission( self, txo: TxOutput, script_pubkey: bytes ) -> None: diff --git a/core/src/apps/bitcoin/sign_tx/tx_weight.py b/core/src/apps/bitcoin/sign_tx/tx_weight.py index 99b87619e..81289fe81 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_weight.py +++ b/core/src/apps/bitcoin/sign_tx/tx_weight.py @@ -48,9 +48,8 @@ class TxWeightCalculator: self.counter = 0 self.segwit_inputs_count = 0 - def add_input(self, i: TxInput) -> None: - self.inputs_count += 1 - + @classmethod + def input_script_size(cls, i: TxInput) -> int: script_type = i.script_type if common.input_is_external_unverified(i): assert i.script_pubkey is not None # checked in sanitize_tx_input @@ -75,28 +74,31 @@ class TxWeightCalculator: n = len(i.multisig.nodes) if i.multisig.nodes else len(i.multisig.pubkeys) multisig_script_size = _TXSIZE_MULTISIGSCRIPT + n * (1 + _TXSIZE_PUBKEY) if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: - multisig_script_size += self.compact_size_len(multisig_script_size) + multisig_script_size += cls.compact_size_len(multisig_script_size) else: - multisig_script_size += self.op_push_len(multisig_script_size) + multisig_script_size += cls.op_push_len(multisig_script_size) - input_script_size = ( + return ( 1 # the OP_FALSE bug in multisig + i.multisig.m * (1 + _TXSIZE_DER_SIGNATURE) + multisig_script_size ) elif script_type == InputScriptType.SPENDTAPROOT: - input_script_size = 1 + _TXSIZE_SCHNORR_SIGNATURE + return 1 + _TXSIZE_SCHNORR_SIGNATURE else: - input_script_size = 1 + _TXSIZE_DER_SIGNATURE + 1 + _TXSIZE_PUBKEY + return 1 + _TXSIZE_DER_SIGNATURE + 1 + _TXSIZE_PUBKEY + def add_input(self, i: TxInput) -> None: + self.inputs_count += 1 self.counter += 4 * _TXSIZE_INPUT + input_script_size = self.input_script_size(i) - if script_type in common.NONSEGWIT_INPUT_SCRIPT_TYPES: + if i.script_type in common.NONSEGWIT_INPUT_SCRIPT_TYPES: input_script_size += self.compact_size_len(input_script_size) self.counter += 4 * input_script_size - elif script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: + elif i.script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: self.segwit_inputs_count += 1 - if script_type == InputScriptType.SPENDP2SHWITNESS: + if i.script_type == InputScriptType.SPENDP2SHWITNESS: # add script_sig size if i.multisig: self.counter += 4 * (2 + _TXSIZE_WITNESSSCRIPT) @@ -105,7 +107,7 @@ class TxWeightCalculator: else: self.counter += 4 # empty script_sig (1 byte) self.counter += 1 + input_script_size # discounted witness - elif script_type == InputScriptType.EXTERNAL: + elif i.script_type == InputScriptType.EXTERNAL: if i.ownership_proof: script_sig, witness = ownership.read_scriptsig_witness( i.ownership_proof diff --git a/core/tests/test_apps.bitcoin.signtx_decred.py b/core/tests/test_apps.bitcoin.signtx_decred.py index 4c5f7a3f7..80e613d3d 100644 --- a/core/tests/test_apps.bitcoin.signtx_decred.py +++ b/core/tests/test_apps.bitcoin.signtx_decred.py @@ -86,8 +86,8 @@ class TestSignTxDecred(unittest.TestCase): outputs_count=1, ) - # precomputed tx weight is 768 - fee_rate = 100_000 / (768 / 4) + # precomputed tx weight is 864 + fee_rate = 100_000 / (864 / 4) messages = [ None, @@ -246,8 +246,8 @@ class TestSignTxDecred(unittest.TestCase): decred_staking_ticket=True, ) - # precomputed tx weight is 1076 - fee_rate = 100_000 / (1076 / 4) + # precomputed tx weight is 1188 + fee_rate = 100_000 / (1188 / 4) messages = [ None, diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index ad47f5d4d..f4de57348 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -631,11 +631,11 @@ "TT_bitcoin-test_bgold.py::test_send_p2sh_witness_change": "56a5f215c03ea1dde5e4766ce61b056ca908ef2aad4870f886c7c47d002d1c04", "TT_bitcoin-test_dash.py::test_send_dash": "84e84912d470e4bba6aa5c2190b36fcbb766bc2720e6ca4c7c890923e774545c", "TT_bitcoin-test_dash.py::test_send_dash_dip2_input": "d27291f1e3e712fdc60df170dc1a78b42f70551885b613e822430b1b9a185835", -"TT_bitcoin-test_decred.py::test_decred_multisig_change": "8fe54ce9dc6d0dc03045f4b1d16e33dde878605c7f6303f30ca21693712da5f7", -"TT_bitcoin-test_decred.py::test_purchase_ticket_decred": "194e88db477e4c9fca40d17d2e4071388237d4d0ac22ccb153f1a1c88234dc02", -"TT_bitcoin-test_decred.py::test_send_decred": "c6020c1167302ca39ed7876e3e5241dc8465ad1c9fb04ce1e684fef638511526", -"TT_bitcoin-test_decred.py::test_send_decred_change": "70f940e83c6ffb4f26fbaf940ab327ee83eb23b6a752a122af38324ed4c6e571", -"TT_bitcoin-test_decred.py::test_spend_from_stake_generation_and_revocation_decred": "2e280d383055c44cbcf8298d0fd31f1fe77af0c3265bb15805ffda23d627cbd4", +"TT_bitcoin-test_decred.py::test_decred_multisig_change": "4de82f12102539c4deebef75899ae6e1599ad5e96586180af317d68a98b6b56e", +"TT_bitcoin-test_decred.py::test_purchase_ticket_decred": "16b7fd6d4b88711b0932768432288df5357d16030de45c8520238322ab1d6a66", +"TT_bitcoin-test_decred.py::test_send_decred": "7aac07b8fb2dd0d18ae22de8f35623819df32c1d82b1d603e0109d61abbe0a51", +"TT_bitcoin-test_decred.py::test_send_decred_change": "1c450b76db5e0f4aa09bc078a9a6cc4a2f96851b7dfa81a5961df9e5a67833ec", +"TT_bitcoin-test_decred.py::test_spend_from_stake_generation_and_revocation_decred": "884f24d36e2ddf4637c8c6011d5a2f339614b8b3047c32a3fd6342d2b1e7577a", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-InputScriptType.SPENDADDRESS-pkh([5-7a80e3db": "24290396b20f26b49204a5551676d6f3c831009e30582d92d5b44fcc3c12fdb7", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-InputScriptType.SPENDP2SHWITNESS-sh-03d56ac2": "8977c539f5680a5196ad0a4c6e16c44ca1bbdb79235dbf97e33aad8d47fe5d0c", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-InputScriptType.SPENDTAPROOT-tr([5c-22751b2f": "3c31e8f9e396a1313c22480aac32f901086df23d8edf3f808d2f46a0d063034b", @@ -898,9 +898,9 @@ "TT_bitcoin-test_signtx_external.py::test_p2pkh_presigned": "8dd8089941ceb0d82c9425c69d54240f99e3ae7932ef24acd49313d28389b683", "TT_bitcoin-test_signtx_external.py::test_p2pkh_with_proof": "c09de07fbbf1e047442180e2facb5482d06a1a428891b875b7dd93c9e4704ae1", "TT_bitcoin-test_signtx_external.py::test_p2tr_external_presigned": "c714c4a4ea8b98dfbdd8185925adafbafc62570f415688972d6003a19d7b4d23", -"TT_bitcoin-test_signtx_external.py::test_p2tr_external_unverified": "c488c412a6c58788530c5acc1e0c7d072aad19b5a62cad41220dfd93e4b0ae6b", +"TT_bitcoin-test_signtx_external.py::test_p2tr_external_unverified": "d00df4d1001677c61d95b2cae7c32c4fd01c9c51995a7153d7dfe7ca103a44ba", "TT_bitcoin-test_signtx_external.py::test_p2tr_with_proof": "d6723e2243bc38231ec4eb9ed63afd39610460c0d859b4c576b12db1f7915d02", -"TT_bitcoin-test_signtx_external.py::test_p2wpkh_external_unverified": "78b720588e1450972b38083857599d843f1b5ffae68de84dedf90a868d597bca", +"TT_bitcoin-test_signtx_external.py::test_p2wpkh_external_unverified": "64d9b691b6442d44e008e1dca7ee4010820d569ff88f28ff1ce5291e79070537", "TT_bitcoin-test_signtx_external.py::test_p2wpkh_in_p2sh_presigned": "8313bff77e41aef142c3b25818ab58dcc7e9d658d38e2e8fc50629ebbe05869b", "TT_bitcoin-test_signtx_external.py::test_p2wpkh_in_p2sh_with_proof": "c09de07fbbf1e047442180e2facb5482d06a1a428891b875b7dd93c9e4704ae1", "TT_bitcoin-test_signtx_external.py::test_p2wpkh_presigned": "4608478b1d61415cf0ec93a0ea4397c35d17a91d4b6d25e9c024b77330e398eb",