From bb29c1e5f84759a61e7a168bf69f905c03f6971a Mon Sep 17 00:00:00 2001 From: Martin Milata Date: Tue, 30 Jan 2024 01:54:02 +0100 Subject: [PATCH] fix(core/bitcoin): display descriptors for taproot XPUBs --- core/.changelog.d/3475.changed | 1 + core/src/apps/bitcoin/common.py | 46 +++++++++++++++++++++++++ core/src/apps/bitcoin/get_public_key.py | 28 ++++++++++++++- tests/input_flows.py | 1 + tests/ui_tests/fixtures.json | 24 ++++++------- 5 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 core/.changelog.d/3475.changed diff --git a/core/.changelog.d/3475.changed b/core/.changelog.d/3475.changed new file mode 100644 index 0000000000..0d77c669c0 --- /dev/null +++ b/core/.changelog.d/3475.changed @@ -0,0 +1 @@ +Display descriptors for BTC Taproot public keys. diff --git a/core/src/apps/bitcoin/common.py b/core/src/apps/bitcoin/common.py index 5f9642100e..d1d011db38 100644 --- a/core/src/apps/bitcoin/common.py +++ b/core/src/apps/bitcoin/common.py @@ -212,3 +212,49 @@ def format_fee_rate( shortcut = "" return f"{fee_rate_formatted} sat{shortcut}/{'v' if coin.segwit else ''}B" + + +# function copied from python/src/trezorlib/tools.py +def descriptor_checksum(desc: str) -> str: + def _polymod(c: int, val: int) -> int: + c0 = c >> 35 + c = ((c & 0x7FFFFFFFF) << 5) ^ val + if c0 & 1: + c ^= 0xF5DEE51989 + if c0 & 2: + c ^= 0xA9FDCA3312 + if c0 & 4: + c ^= 0x1BAB10E32D + if c0 & 8: + c ^= 0x3706B1677A + if c0 & 16: + c ^= 0x644D626FFD + return c + + INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ " + CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + c = 1 + cls = 0 + clscount = 0 + for ch in desc: + pos = INPUT_CHARSET.find(ch) + if pos == -1: + return "" + c = _polymod(c, pos & 31) + cls = cls * 3 + (pos >> 5) + clscount += 1 + if clscount == 3: + c = _polymod(c, cls) + cls = 0 + clscount = 0 + if clscount > 0: + c = _polymod(c, cls) + for j in range(0, 8): + c = _polymod(c, 0) + c ^= 1 + + ret = [""] * 8 + for j in range(0, 8): + ret[j] = CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31] + return "".join(ret) diff --git a/core/src/apps/bitcoin/get_public_key.py b/core/src/apps/bitcoin/get_public_key.py index 83c08c34ad..d1f60ec74a 100644 --- a/core/src/apps/bitcoin/get_public_key.py +++ b/core/src/apps/bitcoin/get_public_key.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from trezor.enums import InputScriptType from trezor.messages import GetPublicKey, PublicKey from trezor.protobuf import MessageType @@ -96,8 +97,13 @@ async def get_public_key( account = coin.coin_shortcut else: account = f"{coin.coin_shortcut} {account_name}" + show_xpub = node_xpub + if script_type == InputScriptType.SPENDTAPROOT: + show_xpub = _xpub_descriptor( + node_xpub, address_n, script_type, node.fingerprint() + ) await show_pubkey( - node_xpub, + show_xpub, "XPUB", account=account, path=path, @@ -110,3 +116,23 @@ async def get_public_key( xpub=node_xpub, root_fingerprint=keychain.root_fingerprint(), ) + + +def _xpub_descriptor( + node_xpub: str, + address_n: list[int], + script_type: InputScriptType, + fingerprint: int, +) -> str: + from trezor.enums import InputScriptType + + from apps.common import paths + + from .common import descriptor_checksum + + if script_type != InputScriptType.SPENDTAPROOT: + raise ValueError("Unsupported script type.") + path = paths.address_n_to_str(address_n) + descriptor = f"tr([{fingerprint:08x}{path[1:]}]{node_xpub}/<0;1>/*)" + checksum = descriptor_checksum(descriptor) + return f"{descriptor}#{checksum}" diff --git a/tests/input_flows.py b/tests/input_flows.py index f1a590fbcc..3373f0b1c8 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -479,6 +479,7 @@ class InputFlowShowXpubQRCode(InputFlowBase): # Go through details and back self.debug.press_right(wait=True) self.debug.press_right(wait=True) + self.debug.press_right(wait=True) self.debug.press_left(wait=True) self.debug.press_left(wait=True) assert br.pages is not None diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 03663df457..4debaf0a50 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -809,24 +809,24 @@ "TR_bitcoin-test_bcash.py::test_send_bch_multisig_wrongchange": "fa268b2481cce18a041c8f8bf773f16cbfed6ad372a89c7d7220a2de3aa9042e", "TR_bitcoin-test_bcash.py::test_send_bch_nochange": "395635f081e0c9bdf3f6de9a1540d9211c3a1d68665d8ac13ffb8c236add0a3b", "TR_bitcoin-test_bcash.py::test_send_bch_oldaddr": "3957efb1b584f974d8b32523eadb4c18596956b38dec2c85005e7e4f0bf85593", -"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--301d7568": "7fb7f797b6fb89460beab4df5fbd5f61cbdcd2931349b8c2bae696bf45195b7c", +"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--301d7568": "70524243a1d093df71ce94251a1bdfe8ff82b7ee84c2b13b71c09908be89dbc4", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-44-InputScriptType.SPENDADDRESS-pkh-a1b0211f": "ad9329ebacf27f00e736d1d3d8a33a539512a23fd134806ddfe6da4e4b517d1a", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-49-InputScriptType.SPENDP2SHWITNESS-75f8d49f": "cb4cea77c61cafeece033128c8c050060cba7c64e4ef255dc59b235324bcf213", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-84-InputScriptType.SPENDWITNESS-wpk-cee65569": "0ffab62bd4c51d042e77c23f5569b9b9a46d9aae102f55edf20d7894dc75b17d", -"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-86-InputScriptType.SPENDTAPROOT-tr(-6b548a1a": "7d3ad845c22d4b74965e62437e8090a2359ddedd528604e3295cab2527b1f877", +"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-86-InputScriptType.SPENDTAPROOT-tr(-6b548a1a": "17a198a8b24035578345b5e85ec307ec8d800a2f343d051a1820ca3121dba696", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-44-InputScriptType.SPENDADDRESS-pkh-37215d1b": "bad6123462cf0f8c9d938ca92ac9841b53221e5152f27bc92959e870d4108f1e", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-49-InputScriptType.SPENDP2SHWITNESS-3ccd985b": "c66f3f30c1a6c809fb8c99a9773c9a9ebea29207c4383f8507db751ba59eb11f", "TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-84-InputScriptType.SPENDWITNESS-wpk-21c3fa4c": "ab012153490f26746ff9a1b5388f2418bf7c9d98b127217babbeab339245d8f7", -"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-3a85f3dd": "d37242f2ae1be0a8a9bb3e2eea693e3cf989062d953b44d90f3563c1b21f3bc9", -"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--591134d6": "fd8f0b4079d5ae46955439d95df5c75877670b93634dc71661beaa54bc1b2ae3", +"TR_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-3a85f3dd": "695853ac56e78df434995eeb457f7d4b466f66c9af560fb103f6a4fba717e0fb", +"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--591134d6": "c3519859e757af09f9fed46739e484845fdee3d3aa4fb84ee89e7df63d3ccf95", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-44-InputScriptType.SPENDADDRESS-pkh-b7612f41": "482a7d3ce0d25e9b940cb3571201b35a856187ed2215f12ce05b06b32e5ebcc1", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-49-InputScriptType.SPENDP2SHWITNESS-4408e6b6": "442a787c067f35af606eb27a2a585a15272bb920af01070f408a402603572b39", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-84-InputScriptType.SPENDWITNESS-wpk-49d5549d": "d53ebc2f1c866b3d5920b13d9ac909fc81d4ca943a0625801b5dc533f2fe6ef5", -"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-86-InputScriptType.SPENDTAPROOT-tr(-51c6f7dc": "ad765a86ca402de45b89ec4ac839d8ea720df2751243c4bef60a7a2877146562", +"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-86-InputScriptType.SPENDTAPROOT-tr(-51c6f7dc": "7798b65e874be333b51ce443ccb26713bb0a17565217f09bd89eff635ddff8a6", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-44-InputScriptType.SPENDADDRESS-pkh-671fabde": "b42ab862d2d45bb9e0509fc4ba4d68405acde83fda935c5dafd44d9e48cb29d3", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-49-InputScriptType.SPENDP2SHWITNESS-6a0c7b09": "6b7c12363fc566de9d35013be5156c970e9fba33c2ed73b4a7e9b06e0974e867", "TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-84-InputScriptType.SPENDWITNESS-wpk-7c651f2d": "7714578913a2a1e573193ac78cddc20c2df6c2856ad8c50d447906bfee41e4f6", -"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-86-InputScriptType.SPENDTAPROOT-tr(-b37d77de": "094264a5b04a8042806562f315c71c9edecb2018af6db957a001fcdc31b0296d", +"TR_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-86-InputScriptType.SPENDTAPROOT-tr(-b37d77de": "67b86cfe9f4ab3b54a829e70565061d80304a3f75ea70a4fbd92c22a643737ef", "TR_bitcoin-test_firo.py::test_spend_lelantus": "aff133d149549783adbdaa9563cb990af6cc5f8fafba3f50d82bbbd2765bb4ff", "TR_bitcoin-test_fujicoin.py::test_send_p2tr": "6ab906b521bb73e0725c3d0f3eb5a6ecf9f582853f4cd71672f73190f15fb917", "TR_bitcoin-test_getaddress.py::test_address_mac": "8c801bd0142e5c1ad4aad50b34c7debb1b8f17a2e0a87eb7f95531b9fd15e095", @@ -2181,24 +2181,24 @@ "TT_bitcoin-test_decred.py::test_send_decred": "7e8b1532772cf7695fbcf3460d179d08f22c410687c057ff128882e32d923c92", "TT_bitcoin-test_decred.py::test_send_decred_change": "d28de2959972fb3ef29bb267fa8bf6b96ac9c7d9ef4550538db5235a5b45307d", "TT_bitcoin-test_decred.py::test_spend_from_stake_generation_and_revocation_decred": "d4657fa66b4d9e90d99de7bf5ad3123d74fec9c528a819f691bc565b521257f3", -"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--301d7568": "b38ee9ea74d82cf94bcd1db9a70e673d3d515aa9995787bff2f0188371201fab", +"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-10025-InputScriptType.SPENDTAPROOT--301d7568": "75cf40b6d916ab5107d5241143ba1edd408d359076edcfdfd6ebef4f6202598f", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-44-InputScriptType.SPENDADDRESS-pkh-a1b0211f": "7dd16059ade8769aca12649b7b2523ac52718fb7bb9eb416fa13a5cb747be1b2", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-49-InputScriptType.SPENDP2SHWITNESS-75f8d49f": "8b434c5de0caf71301bd6b21481bb1944d4b43dd596a7ecb9acb921c8083e955", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-84-InputScriptType.SPENDWITNESS-wpk-cee65569": "e8628d184603bceaad9c5ae5b9f51460054e7b5622ea9c1a3dc84f84034808bc", -"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-86-InputScriptType.SPENDTAPROOT-tr(-6b548a1a": "0e5a8b8f71fe27225eb8fa35dd338eebee9dcd265aed2f0b925fb3c23a34ddea", +"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-0-86-InputScriptType.SPENDTAPROOT-tr(-6b548a1a": "d9e035a4407e3fbbeaad3726c1c19c32d45a788a943187e909b0f2787aad7486", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-44-InputScriptType.SPENDADDRESS-pkh-37215d1b": "a8f46662c3d14e2defdd5aa2ff4486aeac3f566facf086916da7ddc7e0185130", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-49-InputScriptType.SPENDP2SHWITNESS-3ccd985b": "013171ff086e4565db1bc4f8a0d466a0fa7c6449ee58a3d27b27f073bfb74d63", "TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-84-InputScriptType.SPENDWITNESS-wpk-21c3fa4c": "3941cd14f74e1d7ea919cb32cbd7c5954c412d845305e4f0e495378c7701a53b", -"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-3a85f3dd": "9f228b072051500d298b3386082208b858521667424b0de352e1501e0912e2d2", -"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--591134d6": "1651987f970c1bfcaf3906b3185a37709dc940289493272cef36357d810edb9d", +"TT_bitcoin-test_descriptors.py::test_descriptors[Bitcoin-1-86-InputScriptType.SPENDTAPROOT-tr(-3a85f3dd": "2b38959aba5f6a57365ebee67831eaeb9fd3276f9ca0d6c6d99e6b46e97998bc", +"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-10025-InputScriptType.SPENDTAPROOT--591134d6": "9db02ff67c8b507a62ab3bca79b4a6d5cd3f3da7136f7bc0b237fde382b50739", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-44-InputScriptType.SPENDADDRESS-pkh-b7612f41": "d8e95f6a14ddabd6db4944802f847ccddd8161eed28559f35dc095b568461df7", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-49-InputScriptType.SPENDP2SHWITNESS-4408e6b6": "6ac0059ce935707618eefe9c23d403ff30807df3c3ec23b8615a50b648bbba75", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-84-InputScriptType.SPENDWITNESS-wpk-49d5549d": "b8e44bea2eb0d3ac672a41ebf83683adbb0e02a622cbafe2fb02283117bd4c4a", -"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-86-InputScriptType.SPENDTAPROOT-tr(-51c6f7dc": "fc9c8773c1cc4d8c4a455e29e3b916992bb4b3114a9e322cd809c19c21cde4f2", +"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-0-86-InputScriptType.SPENDTAPROOT-tr(-51c6f7dc": "eae032ac90ebf7d44b84eb6f06c43f67140de7adef88906d98c1759ffd4995cd", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-44-InputScriptType.SPENDADDRESS-pkh-671fabde": "4795ab245421deef1f7ebdb6568165a64c7066054747a84c4e30a78085c71e22", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-49-InputScriptType.SPENDP2SHWITNESS-6a0c7b09": "284b98fb9d745aae6f552999e1652300d84d4863c3b29452615ae5b691d7b266", "TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-84-InputScriptType.SPENDWITNESS-wpk-7c651f2d": "660130572e80e9161d733f3ebc465a5f2a9859a79eecd7201c7ebf955173df23", -"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-86-InputScriptType.SPENDTAPROOT-tr(-b37d77de": "f83fb40390de5ee5fca1cece2d24ec9c57c376ac4d294b1bede62b04ef0bb0fa", +"TT_bitcoin-test_descriptors.py::test_descriptors[Testnet-1-86-InputScriptType.SPENDTAPROOT-tr(-b37d77de": "35e60de9968a7c5bb296dc554970a16878165e062bb8480a5c8428f73a3cf921", "TT_bitcoin-test_firo.py::test_spend_lelantus": "213ced04e2b337576abe1861c11ef69352eb5542f2bef720e50d54cea031f9db", "TT_bitcoin-test_fujicoin.py::test_send_p2tr": "6104cd021625791fc9141ca802c3c18fb50815459acae06b2eeb05da432352f7", "TT_bitcoin-test_getaddress.py::test_address_mac": "80a6e289138a604cf351a29511cf6f85e2243591317894703152787e1351a1a3",