From 50016e1e4851032ded9432dc06feb60e45cec8ca Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 17 Dec 2024 15:00:57 -0300 Subject: [PATCH 01/34] load taproot miniscript descriptors --- src/krux/key.py | 2 +- src/krux/pages/login.py | 33 ++++++++++------- src/krux/pages/wallet_settings.py | 59 +++++++++++++++++++++++++------ src/krux/wallet.py | 28 +++++++++++---- 4 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/krux/key.py b/src/krux/key.py index 61c5d5b8..8ebf8636 100644 --- a/src/krux/key.py +++ b/src/krux/key.py @@ -120,7 +120,7 @@ def __init__( self.network = network self.passphrase = passphrase self.account_index = account_index - self.script_type = script_type if policy_type == TYPE_SINGLESIG else P2WSH + self.script_type = script_type self.root = bip32.HDKey.from_seed( bip39.mnemonic_to_seed(mnemonic, passphrase), version=network["xprv"] ) diff --git a/src/krux/pages/login.py b/src/krux/pages/login.py index 4a70ea03..c0e4dcd9 100644 --- a/src/krux/pages/login.py +++ b/src/krux/pages/login.py @@ -23,12 +23,23 @@ import sys from embit.networks import NETWORKS from embit.wordlists.bip39 import WORDLIST +from . import ( + Page, + Menu, + DIGITS, + MENU_CONTINUE, + MENU_EXIT, + ESC_KEY, + LETTERS, + choose_len_mnemonic, +) from ..display import DEFAULT_PADDING, FONT_HEIGHT, BOTTOM_PROMPT_LINE from ..krux_settings import Settings from ..qr import FORMAT_UR from ..key import ( Key, P2WSH, + P2TR, SCRIPT_LONG_NAMES, TYPE_SINGLESIG, TYPE_MULTISIG, @@ -36,16 +47,8 @@ POLICY_TYPE_IDS, ) from ..krux_settings import t -from . import ( - Page, - Menu, - DIGITS, - MENU_CONTINUE, - MENU_EXIT, - ESC_KEY, - LETTERS, - choose_len_mnemonic, -) +from ..settings import NAME_SINGLE_SIG, NAME_MULTISIG, NAME_MINISCRIPT + DIGITS_HEX = "0123456789ABCDEF" DIGITS_OCT = "01234567" @@ -289,6 +292,8 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): account = 0 if policy_type == TYPE_SINGLESIG: script_type = SCRIPT_LONG_NAMES.get(Settings().wallet.script_type) + elif policy_type == TYPE_MINISCRIPT and Settings().wallet.script_type == P2TR: + script_type = P2TR else: script_type = P2WSH from ..wallet import Wallet @@ -299,11 +304,13 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): wallet_info = key.fingerprint_hex_str(True) + "\n" wallet_info += network["name"] + "\n" if policy_type == TYPE_SINGLESIG: - wallet_info += "Single-sig" + "\n" + wallet_info += NAME_SINGLE_SIG + "\n" elif policy_type == TYPE_MULTISIG: - wallet_info += "Multisig" + "\n" + wallet_info += NAME_MULTISIG + "\n" elif policy_type == TYPE_MINISCRIPT: - wallet_info += "Miniscript" + "\n" + if script_type == P2TR: + wallet_info += "TR " + wallet_info += NAME_MINISCRIPT + "\n" wallet_info += ( self.fit_to_line(key.derivation_str(True), crop_middle=False) + "\n" ) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index ac920a27..f61b7a99 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -47,6 +47,9 @@ from ..settings import ( MAIN_TXT, TEST_TXT, + NAME_SINGLE_SIG, + NAME_MULTISIG, + NAME_MINISCRIPT, ) from ..key import P2PKH, P2SH_P2WPKH, P2WPKH, P2WSH, P2TR @@ -120,6 +123,14 @@ def customize_wallet(self, key): script_type = key.script_type account = key.account_index while True: + wallet_info = network["name"] + "\n" + if policy_type == TYPE_SINGLESIG: + wallet_info += NAME_SINGLE_SIG + "\n" + elif policy_type == TYPE_MULTISIG: + wallet_info += NAME_MULTISIG + "\n" + elif policy_type == TYPE_MINISCRIPT: + wallet_info += NAME_MINISCRIPT + "\n" + wallet_info += str(script_type).upper() + "\n" derivation_path = "m/" if policy_type == TYPE_SINGLESIG: derivation_path += str(SINGLESIG_SCRIPT_PURPOSE[script_type]) @@ -133,12 +144,11 @@ def customize_wallet(self, key): derivation_path += "/" + str(account) + "'" if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): derivation_path += "/2'" + wallet_info += derivation_path self.ctx.display.clear() derivation_path = self.fit_to_line(derivation_path, crop_middle=False) - info_len = self.ctx.display.draw_hcentered_text( - derivation_path, info_box=True - ) + info_len = self.ctx.display.draw_hcentered_text(wallet_info, info_box=True) submenu = Menu( self.ctx, [ @@ -146,7 +156,7 @@ def customize_wallet(self, key): (t("Policy Type"), lambda: None), ( t("Script Type"), - (lambda: None) if policy_type == TYPE_SINGLESIG else None, + (lambda: None) if policy_type != TYPE_MULTISIG else None, ), (t("Account"), lambda: None), ], @@ -167,16 +177,30 @@ def customize_wallet(self, key): # If is single-sig, and script is p2wsh, force to pick a new type script_type = self._script_type() script_type = P2WPKH if script_type is None else script_type + + elif policy_type == TYPE_MULTISIG: + # If is multisig, force to p2wsh + script_type = P2WSH + + elif policy_type == TYPE_MINISCRIPT and script_type not in ( + P2WSH, + P2TR, + ): + # If is miniscript, pick P2WSH or P2TR + script_type = self._miniscript_type() + script_type = P2WSH if script_type is None else script_type + elif index == 2: - new_script_type = self._script_type() + if policy_type == TYPE_MINISCRIPT: + new_script_type = self._miniscript_type() + else: + new_script_type = self._script_type() if new_script_type is not None: script_type = new_script_type elif index == 3: account_temp = self._account(account) if account_temp is not None: account = account_temp - if policy_type != TYPE_SINGLESIG: - script_type = P2WSH return network, policy_type, script_type, account def _coin_type(self): @@ -199,9 +223,9 @@ def _policy_type(self): submenu = Menu( self.ctx, [ - ("Single-sig", lambda: MENU_EXIT), - ("Multisig", lambda: MENU_EXIT), - ("Miniscript (Experimental)", lambda: MENU_EXIT), + (NAME_SINGLE_SIG, lambda: MENU_EXIT), + (NAME_MULTISIG, lambda: MENU_EXIT), + (NAME_MINISCRIPT + " (Experimental)", lambda: MENU_EXIT), ], disable_statusbar=True, ) @@ -227,6 +251,21 @@ def _script_type(self): return None return script_type + def _miniscript_type(self): + """Script type selection menu for Taproot""" + submenu = Menu( + self.ctx, + [ + ("Native Segwit - P2WSH", lambda: P2WSH), + ("Taproot - TR", lambda: P2TR), + ], + disable_statusbar=True, + ) + index, script_type = submenu.run_loop() + if index == len(submenu.menu) - 1: + return None + return script_type + def _account(self, initial_account=None): """Account input""" account = self.capture_from_keypad( diff --git a/src/krux/wallet.py b/src/krux/wallet.py index f6ccd6ed..12a2d654 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -108,7 +108,10 @@ def is_miniscript(self): if self.descriptor: if self.descriptor.is_basic_multisig: return False - return self.descriptor.miniscript is not None + return ( + self.descriptor.miniscript is not None + or self.descriptor.taptree is not None + ) return False def is_loaded(self): @@ -134,8 +137,16 @@ def load(self, wallet_data, qr_format, allow_assumption=None): if self.key.xpub() not in descriptor_xpubs: raise ValueError("xpub not a multisig cosigner") elif self.is_miniscript(): - if descriptor.miniscript is None or descriptor.is_basic_multisig: - raise ValueError("not miniscript") + if self.key.script_type == P2WSH: + if descriptor.miniscript is None or descriptor.is_basic_multisig: + raise ValueError("not P2WSH miniscript") + elif self.key.script_type == P2TR: + if descriptor.taptree is None: + raise ValueError("not P2TR miniscript") + else: + raise ValueError( + "wrong miniscript script type" + ) # Temporary - debug if self.key.xpub() not in descriptor_xpubs: raise ValueError("xpub not a miniscript cosigner") else: @@ -149,8 +160,7 @@ def load(self, wallet_data, qr_format, allow_assumption=None): self.wallet_qr_format = qr_format self.descriptor = to_unambiguous_descriptor(descriptor) self.label = label - - if self.descriptor.key: + if self.descriptor.key and not self.descriptor.taptree: if not self.label: self.label = t("Single-sig") self.policy = {"type": self.descriptor.scriptpubkey_type()} @@ -168,9 +178,13 @@ def load(self, wallet_data, qr_format, allow_assumption=None): "n": n, "cosigners": cosigners, } - elif self.descriptor.miniscript is not None: + elif ( + self.descriptor.miniscript is not None + or self.descriptor.taptree is not None + ): + taproot_txt = "TR " if self.descriptor.taptree is not None else "" if not self.label: - self.label = t("Miniscript") + self.label = taproot_txt + t("Miniscript") cosigners = [key.key.to_base58() for key in self.descriptor.keys] cosigners = sorted(cosigners) self.policy = { From 8f7bd319e07a60c9a577a5640817be153adf477b Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 17 Dec 2024 16:48:10 -0300 Subject: [PATCH 02/34] refine taproot miniscript descriptor detection --- src/krux/key.py | 4 ++++ src/krux/pages/home_pages/pub_key_view.py | 2 +- src/krux/wallet.py | 2 +- tests/pages/home_pages/test_pub_key_view.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/krux/key.py b/src/krux/key.py index 8ebf8636..e0237992 100644 --- a/src/krux/key.py +++ b/src/krux/key.py @@ -120,6 +120,10 @@ def __init__( self.network = network self.passphrase = passphrase self.account_index = account_index + if policy_type == TYPE_MULTISIG and script_type != P2WSH: + script_type = P2WSH + if policy_type == TYPE_MINISCRIPT and script_type not in (P2WSH, P2TR): + script_type = P2WSH self.script_type = script_type self.root = bip32.HDKey.from_seed( bip39.mnemonic_to_seed(mnemonic, passphrase), version=network["xprv"] diff --git a/src/krux/pages/home_pages/pub_key_view.py b/src/krux/pages/home_pages/pub_key_view.py index ca668a99..cdf5194b 100644 --- a/src/krux/pages/home_pages/pub_key_view.py +++ b/src/krux/pages/home_pages/pub_key_view.py @@ -102,7 +102,7 @@ def _pub_key_qr(version): versions.append(self.ctx.wallet.key.network["ypub"]) elif self.ctx.wallet.key.script_type == P2SH_P2WSH: versions.append(self.ctx.wallet.key.network["Ypub"]) - elif self.ctx.wallet.key.script_type == P2WSH: + elif self.ctx.wallet.key.script_type == P2WSH and self.ctx.wallet.is_multisig(): versions.append(self.ctx.wallet.key.network["Zpub"]) pub_key_menu_items = [] for version in versions: diff --git a/src/krux/wallet.py b/src/krux/wallet.py index 12a2d654..a618085b 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -110,7 +110,7 @@ def is_miniscript(self): return False return ( self.descriptor.miniscript is not None - or self.descriptor.taptree is not None + or self.descriptor.taptree # taptree is "" when not present ) return False diff --git a/tests/pages/home_pages/test_pub_key_view.py b/tests/pages/home_pages/test_pub_key_view.py index 675e08f7..6a68a6c0 100644 --- a/tests/pages/home_pages/test_pub_key_view.py +++ b/tests/pages/home_pages/test_pub_key_view.py @@ -78,8 +78,8 @@ def test_public_key(mocker, m5stickv, tdata): pub_key_viewer.public_key() + print(ctx.wallet.key.policy_type, ctx.wallet.key.script_type) version = "Zpub" if ctx.wallet.key.policy_type == TYPE_MULTISIG else "zpub" - print(ctx.key.policy_type, version) qr_view_calls = [] print_qr_calls = [] From d10b9794e25a81679023c51696a4ef48ebac9860 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 19 Dec 2024 15:08:48 -0300 Subject: [PATCH 03/34] adapt PSBT signature checks to taproot miniscript --- src/krux/psbt.py | 61 ++++++++++++++++++++++++++++++++++++++++------ src/krux/wallet.py | 12 +++++---- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 7e7a01b6..eb028ebd 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -576,9 +576,9 @@ def xpubs(self): raise ValueError("missing xpubs") descriptor_keys = ( - [self.wallet.descriptor.key] - if self.wallet.descriptor.key - else self.wallet.descriptor.keys + self.wallet.descriptor.keys + if self.wallet.descriptor.keys + else [self.wallet.descriptor.key] ) xpubs = {} for descriptor_key in descriptor_keys: @@ -606,7 +606,8 @@ def is_miniscript(policy): # m and n will not be present in miniscript policies return ( "type" in policy - and P2WSH in policy["type"] + and policy["type"] in (P2WSH, P2TR) + and "miniscript" in policy and "m" not in policy and "n" not in policy ) @@ -637,7 +638,7 @@ def get_cosigners(pubkeys, derivations, xpubs): def get_cosigners_miniscript(derivations, xpubs): - """Returns xpubs used to derive pubkeys listed in derivations.""" + """Compares the derivations with the xpubs to check and get the cosigners""" cosigners = [] for pubkey, der in derivations.items(): for xpub in xpubs: @@ -653,7 +654,42 @@ def get_cosigners_miniscript(derivations, xpubs): break # Ensure all pubkeys have a matching xpub if len(cosigners) != len(derivations): - raise ValueError("Not all pubkeys in derivations have corresponding xpubs") + raise ValueError("cannot get all cosigners") + return sorted(cosigners) + + +def get_cosigners_taproot_miniscript(taproot_derivations, xpubs): + """ + Compares the taproot derivations with the xpubs to check get the cosigners + """ + + cosigners = [] + loop_count = 0 + for xonly_pubkey, der_info in taproot_derivations.items(): + loop_count += 1 + _, der = der_info # tap_leaf_hashes are not used + fp = der.fingerprint + full_path = der.derivation + + for xpub in xpubs: + origin_der = xpubs[xpub] + # Check that the fingerprint matches + if origin_der.fingerprint == fp: + # Check that the origin derivation is a prefix of the full derivation path + if full_path[: len(origin_der.derivation)] == origin_der.derivation: + # Derive the remainder of the path + remainder = full_path[len(origin_der.derivation) :] + # Verify that the xpub derives to the given xonly_pubkey + derived_key = xpub.derive(remainder).key + if derived_key.xonly() == xonly_pubkey.xonly(): + # Append the xpub as a base58 string + cosigners.append(xpub.to_base58()) + break + + # Ensure all pubkeys have a matching xpub + if len(cosigners) != len(taproot_derivations): + raise ValueError("cannot get all cosigners") + return sorted(cosigners) @@ -690,7 +726,18 @@ def get_policy(scope, scriptpubkey, xpubs): # Try to parse as miniscript # Will succeed to verify cosigners only if the descriptor is loaded cosigners = get_cosigners_miniscript(scope.bip32_derivations, xpubs) - policy.update({"cosigners": cosigners}) + policy.update({"cosigners": cosigners, "miniscript": P2WSH}) except: pass + elif script_type == P2TR: + try: + # Try to parse as taproot miniscript + # Will succeed to verify cosigners only if the descriptor is loaded + cosigners = get_cosigners_taproot_miniscript( + scope.taproot_bip32_derivations, xpubs + ) + policy.update({"cosigners": cosigners, "miniscript": P2TR}) + except Exception as e: + print("Error getting taproot PSBT cosigners: ", e) + return policy diff --git a/src/krux/wallet.py b/src/krux/wallet.py index a618085b..a02011b7 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -143,10 +143,6 @@ def load(self, wallet_data, qr_format, allow_assumption=None): elif self.key.script_type == P2TR: if descriptor.taptree is None: raise ValueError("not P2TR miniscript") - else: - raise ValueError( - "wrong miniscript script type" - ) # Temporary - debug if self.key.xpub() not in descriptor_xpubs: raise ValueError("xpub not a miniscript cosigner") else: @@ -182,7 +178,12 @@ def load(self, wallet_data, qr_format, allow_assumption=None): self.descriptor.miniscript is not None or self.descriptor.taptree is not None ): - taproot_txt = "TR " if self.descriptor.taptree is not None else "" + if self.descriptor.taptree is not None: + taproot_txt = "TR " + miniscript_type = P2TR + else: + taproot_txt = "" + miniscript_type = P2WSH if not self.label: self.label = taproot_txt + t("Miniscript") cosigners = [key.key.to_base58() for key in self.descriptor.keys] @@ -190,6 +191,7 @@ def load(self, wallet_data, qr_format, allow_assumption=None): self.policy = { "type": self.descriptor.scriptpubkey_type(), "cosigners": cosigners, + "miniscript": miniscript_type, } def wallet_qr(self): From db8981c8fd4e333eef3a4bb5e36c374bf57a5b10 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 19 Dec 2024 16:07:14 -0300 Subject: [PATCH 04/34] taproot miniscript refactor --- src/krux/pages/wallet_settings.py | 2 +- src/krux/psbt.py | 5 ++- src/krux/wallet.py | 48 ++++++++++++--------- tests/pages/home_pages/test_pub_key_view.py | 1 - 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index f61b7a99..4a061ca2 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -257,7 +257,7 @@ def _miniscript_type(self): self.ctx, [ ("Native Segwit - P2WSH", lambda: P2WSH), - ("Taproot - TR", lambda: P2TR), + ("Taproot - P2TR", lambda: P2TR), ], disable_statusbar=True, ) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index eb028ebd..58e9e3a7 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -736,7 +736,10 @@ def get_policy(scope, scriptpubkey, xpubs): cosigners = get_cosigners_taproot_miniscript( scope.taproot_bip32_derivations, xpubs ) - policy.update({"cosigners": cosigners, "miniscript": P2TR}) + policy.update({"cosigners": cosigners}) + if len(cosigners) > 1: + # Assume it is single-sig TR if there is only one cosigner + policy.update({"miniscript": P2TR}) except Exception as e: print("Error getting taproot PSBT cosigners: ", e) diff --git a/src/krux/wallet.py b/src/krux/wallet.py index a02011b7..6c283a9e 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -118,6 +118,30 @@ def is_loaded(self): """Returns a boolean indicating whether or not this wallet has been loaded""" return self.wallet_data is not None + def _validate_descriptor(self, descriptor, descriptor_xpubs): + """Validates the descriptor against the current key and policy type""" + + if self.is_multisig(): + if not descriptor.is_basic_multisig: + raise ValueError("not multisig") + if self.key.xpub() not in descriptor_xpubs: + raise ValueError("xpub not a multisig cosigner") + elif self.is_miniscript(): + if self.key.script_type == P2WSH: + if descriptor.miniscript is None or descriptor.is_basic_multisig: + raise ValueError("not P2WSH miniscript") + elif self.key.script_type == P2TR: + if descriptor.taptree is None: + raise ValueError("not P2TR miniscript") + if self.key.xpub() not in descriptor_xpubs: + raise ValueError("xpub not a miniscript cosigner") + else: + if not descriptor.key: + if len(descriptor.keys) > 1: + raise ValueError("not single-sig") + if self.key.xpub() != descriptor_xpubs[0]: + raise ValueError("xpub does not match") + def load(self, wallet_data, qr_format, allow_assumption=None): """Loads the wallet from the given data""" descriptor, label = parse_wallet(wallet_data, allow_assumption) @@ -131,26 +155,10 @@ def load(self, wallet_data, qr_format, allow_assumption=None): ) if self.key: - if self.is_multisig(): - if not descriptor.is_basic_multisig: - raise ValueError("not multisig") - if self.key.xpub() not in descriptor_xpubs: - raise ValueError("xpub not a multisig cosigner") - elif self.is_miniscript(): - if self.key.script_type == P2WSH: - if descriptor.miniscript is None or descriptor.is_basic_multisig: - raise ValueError("not P2WSH miniscript") - elif self.key.script_type == P2TR: - if descriptor.taptree is None: - raise ValueError("not P2TR miniscript") - if self.key.xpub() not in descriptor_xpubs: - raise ValueError("xpub not a miniscript cosigner") - else: - if not descriptor.key: - if len(descriptor.keys) > 1: - raise ValueError("not single-sig") - if self.key.xpub() != descriptor_xpubs[0]: - raise ValueError("xpub does not match") + try: + self._validate_descriptor(descriptor, descriptor_xpubs) + except ValueError as e: + raise ValueError("Invalid Descriptor: %s" % e) self.wallet_data = wallet_data self.wallet_qr_format = qr_format diff --git a/tests/pages/home_pages/test_pub_key_view.py b/tests/pages/home_pages/test_pub_key_view.py index 6a68a6c0..4fe5f88d 100644 --- a/tests/pages/home_pages/test_pub_key_view.py +++ b/tests/pages/home_pages/test_pub_key_view.py @@ -78,7 +78,6 @@ def test_public_key(mocker, m5stickv, tdata): pub_key_viewer.public_key() - print(ctx.wallet.key.policy_type, ctx.wallet.key.script_type) version = "Zpub" if ctx.wallet.key.policy_type == TYPE_MULTISIG else "zpub" qr_view_calls = [] print_qr_calls = [] From 62c44c87f781803e16762c97e133f93ff2b2ade3 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 19 Dec 2024 16:22:31 -0300 Subject: [PATCH 05/34] fix docstring of miniscript script type selection menu --- src/krux/pages/wallet_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index 4a061ca2..10e2bd22 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -252,7 +252,7 @@ def _script_type(self): return script_type def _miniscript_type(self): - """Script type selection menu for Taproot""" + """Script type selection menu for miniscript policy type""" submenu = Menu( self.ctx, [ From 48588684c3e93988e5050ad97b0d31098d479636 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 20 Dec 2024 14:23:51 -0300 Subject: [PATCH 06/34] don't add cosigners to taproot single sig policy --- mkdocs.yml | 2 +- pyproject.toml | 2 +- src/krux/metadata.py | 2 +- src/krux/psbt.py | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index d9894579..bc19fc38 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,7 +51,7 @@ edit_uri: edit/main/docs docs_dir: docs site_dir: public extra: - latest_krux: krux-v25.01.beta4 + latest_krux: krux-v25.01.beta5 latest_installer: v0.0.20-beta latest_installer_rpm: krux-installer-0.0.20_beta-1.x86_64.rpm latest_installer_deb: krux-installer_0.0.20-beta_amd64.deb diff --git a/pyproject.toml b/pyproject.toml index f2e229ec..03b7b1dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ [tool.poetry] name = "krux" -version = "25.01.beta4" +version = "25.01.beta5" description = "Open-source signing device firmware for Bitcoin" authors = ["Jeff S "] diff --git a/src/krux/metadata.py b/src/krux/metadata.py index 369513b2..6037afb8 100644 --- a/src/krux/metadata.py +++ b/src/krux/metadata.py @@ -19,5 +19,5 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -VERSION = "25.01.beta4" +VERSION = "25.01.beta5" SIGNER_PUBKEY = "03339e883157e45891e61ca9df4cd3bb895ef32d475b8e793559ea10a36766689b" diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 58e9e3a7..3a445c95 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -736,10 +736,9 @@ def get_policy(scope, scriptpubkey, xpubs): cosigners = get_cosigners_taproot_miniscript( scope.taproot_bip32_derivations, xpubs ) - policy.update({"cosigners": cosigners}) if len(cosigners) > 1: # Assume it is single-sig TR if there is only one cosigner - policy.update({"miniscript": P2TR}) + policy.update({"cosigners": cosigners, "miniscript": P2TR}) except Exception as e: print("Error getting taproot PSBT cosigners: ", e) From c2d53602ec910384efc21a9a1e3baad93c57acc3 Mon Sep 17 00:00:00 2001 From: odudex Date: Sat, 21 Dec 2024 21:19:49 -0300 Subject: [PATCH 07/34] add taproot_key_sig field to Embit don't trim SD card PSBTs taproot refactors --- src/krux/pages/home_pages/home.py | 4 +-- src/krux/psbt.py | 53 ++++++++++++++++++++++--------- vendor/embit | 2 +- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/krux/pages/home_pages/home.py b/src/krux/pages/home_pages/home.py index 5ee5dc14..864815e9 100644 --- a/src/krux/pages/home_pages/home.py +++ b/src/krux/pages/home_pages/home.py @@ -400,11 +400,10 @@ def sign_psbt(self): self.ctx.display.clear() self.ctx.display.draw_centered_text(t("Signing..")) - signer.sign() - title = t("Signed PSBT") if index == 0: # Sign to QR code + signer.sign() signed_psbt, qr_format = signer.psbt_qr() # memory management @@ -420,6 +419,7 @@ def sign_psbt(self): return MENU_CONTINUE # index == 1: Sign to SD card + signer.sign(trim=False) psbt_filename = self._format_psbt_file_extension(psbt_filename) gc.collect() diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 3a445c95..bf868a11 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -504,35 +504,52 @@ def _fill_zero_fingerprint_scope(self, scope): filled += 1 return filled - def sign(self): - """Signs the PSBT removing all irrelevant data""" + def sign(self, trim=True): + """Signs the PSBT and preserves necessary fields for the final transaction""" self.add_signatures() + if not trim: + return + trimmed_psbt = PSBT(self.psbt.tx) for i, inp in enumerate(self.psbt.inputs): - # Copy the final_scriptwitness if it's present (Taproot case) + # Copy the final_scriptwitness if present (for Taproot or other SegWit inputs) if inp.final_scriptwitness: trimmed_psbt.inputs[i].final_scriptwitness = inp.final_scriptwitness - # Copy partial signatures for multisig or other script types + # Copy any partial signatures (for multisig or other script types) if inp.partial_sigs: trimmed_psbt.inputs[i].partial_sigs = inp.partial_sigs - # Include the PSBT_IN_WITNESS_UTXO field if it exists - if hasattr(inp, "witness_utxo"): + # Preserve witness UTXO if present + if inp.witness_utxo: trimmed_psbt.inputs[i].witness_utxo = inp.witness_utxo - # Include the PSBT_IN_NON_WITNESS_UTXO field if it exists (Legacy) - if hasattr(inp, "non_witness_utxo"): + # Preserve non-witness UTXO if present (for legacy inputs) + if inp.non_witness_utxo: trimmed_psbt.inputs[i].non_witness_utxo = inp.non_witness_utxo - # Check for P2SH (Nested SegWit) and copy redeem_script if present - if hasattr(inp, "redeem_script"): + # Preserve redeem_script for P2SH or nested SegWit + if inp.redeem_script: trimmed_psbt.inputs[i].redeem_script = inp.redeem_script - # Check for P2WSH (SegWit multisig) and copy witness_script if present - if hasattr(inp, "witness_script"): + # Preserve witness_script for P2WSH multisig + if inp.witness_script: trimmed_psbt.inputs[i].witness_script = inp.witness_script + # # # --- Taproot-specific fields --- + + # # # Taproot BIP32 derivation paths (PSBT_IN_TAP_BIP32_DERIVATION) + # # if inp.taproot_bip32_derivations is not None: + # # trimmed_psbt.inputs[i].taproot_bip32_derivations = inp.taproot_bip32_derivations + + # # # Internal key (PSBT_IN_TAP_INTERNAL_KEY) + # # if inp.taproot_internal_key is not None: + # # trimmed_psbt.inputs[i].taproot_internal_key = inp.taproot_internal_key + + # # # Taproot leaf scripts (PSBT_IN_TAP_LEAF_SCRIPT) + # # if inp.taproot_scripts is not None: + # # trimmed_psbt.inputs[i].taproot_scripts = inp.taproot_scripts + self.psbt = trimmed_psbt def psbt_qr(self): @@ -724,21 +741,27 @@ def get_policy(scope, scriptpubkey, xpubs): except: try: # Try to parse as miniscript + policy.update({"miniscript": P2WSH}) # Will succeed to verify cosigners only if the descriptor is loaded cosigners = get_cosigners_miniscript(scope.bip32_derivations, xpubs) - policy.update({"cosigners": cosigners, "miniscript": P2WSH}) + policy.update({"cosigners": cosigners}) except: pass elif script_type == P2TR: try: # Try to parse as taproot miniscript + if len(scope.taproot_bip32_derivations) > 1: + # Assume is miniscript if there are multiple cosigners + policy.update({"miniscript": P2TR}) + # Will succeed to verify cosigners only if the descriptor is loaded cosigners = get_cosigners_taproot_miniscript( scope.taproot_bip32_derivations, xpubs ) + # Only add cosigners if is miniscript (multiple cosigners), + # otherwise it probably is single-sig taproot if len(cosigners) > 1: - # Assume it is single-sig TR if there is only one cosigner - policy.update({"cosigners": cosigners, "miniscript": P2TR}) + policy.update({"cosigners": cosigners}) except Exception as e: print("Error getting taproot PSBT cosigners: ", e) diff --git a/vendor/embit b/vendor/embit index 84cce66f..638a8246 160000 --- a/vendor/embit +++ b/vendor/embit @@ -1 +1 @@ -Subproject commit 84cce66fb831fa6d625fb73f28e03605f3c04e28 +Subproject commit 638a82466253fa08365c74a8fe9f579b84e224bf From 7e117a311e1edb9c17b4959d53f175e84415abfc Mon Sep 17 00:00:00 2001 From: odudex Date: Wed, 25 Dec 2024 16:53:01 -0300 Subject: [PATCH 08/34] TR miniscript - show PSBT fingerprints when signing and descriptor is not loaded --- src/krux/pages/home_pages/home.py | 32 +++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/krux/pages/home_pages/home.py b/src/krux/pages/home_pages/home.py index 864815e9..57b0c1b4 100644 --- a/src/krux/pages/home_pages/home.py +++ b/src/krux/pages/home_pages/home.py @@ -33,7 +33,7 @@ from ...qr import FORMAT_NONE, FORMAT_PMOFN from ...krux_settings import t, Settings from ...format import replace_decimal_separator -from ...key import TYPE_SINGLESIG +from ...key import TYPE_SINGLESIG, P2WSH, P2TR MAX_POLICY_COSIGNERS_DISPLAYED = 5 @@ -341,15 +341,27 @@ def sign_psbt(self): fingerprints = [] for inp in signer.psbt.inputs: # Do we need to loop through all the inputs or just one? - for pub in inp.bip32_derivations: - fingerprint_srt = Key.format_fingerprint( - inp.bip32_derivations[pub].fingerprint, True - ) - if fingerprint_srt not in fingerprints: - if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: - fingerprints[-1] = "..." - break - fingerprints.append(fingerprint_srt) + if signer.policy["type"] == P2WSH: + for pub in inp.bip32_derivations: + fingerprint_srt = Key.format_fingerprint( + inp.bip32_derivations[pub].fingerprint, True + ) + if fingerprint_srt not in fingerprints: + if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: + fingerprints[-1] = "..." + break + fingerprints.append(fingerprint_srt) + elif signer.policy["type"] == P2TR: + for pub in inp.taproot_bip32_derivations: + _, derivation_path = inp.taproot_bip32_derivations[pub] + fingerprint_srt = Key.format_fingerprint( + derivation_path.fingerprint, True + ) + if fingerprint_srt not in fingerprints: + if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: + fingerprints[-1] = "..." + break + fingerprints.append(fingerprint_srt) policy_str += "\n".join(fingerprints) self.ctx.display.clear() From d46733000fb866816d78557b1cf8f893a09430df Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 26 Dec 2024 14:41:42 -0300 Subject: [PATCH 09/34] preserve all PSBT fields when signing with SD fix lint and tests --- src/krux/pages/home_pages/home.py | 41 +----------------- src/krux/psbt.py | 67 ++++++++++++++++++++--------- tests/pages/home_pages/test_home.py | 20 +++++++-- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/krux/pages/home_pages/home.py b/src/krux/pages/home_pages/home.py index 57b0c1b4..d3f0c89d 100644 --- a/src/krux/pages/home_pages/home.py +++ b/src/krux/pages/home_pages/home.py @@ -33,10 +33,7 @@ from ...qr import FORMAT_NONE, FORMAT_PMOFN from ...krux_settings import t, Settings from ...format import replace_decimal_separator -from ...key import TYPE_SINGLESIG, P2WSH, P2TR - - -MAX_POLICY_COSIGNERS_DISPLAYED = 5 +from ...key import TYPE_SINGLESIG class Home(Page): @@ -329,41 +326,7 @@ def sign_psbt(self): not self.ctx.wallet.is_loaded() and not self.ctx.wallet.key.policy_type == TYPE_SINGLESIG ): - from ...key import Key - from ...psbt import is_multisig - - policy_str = "PSBT policy:\n" - policy_str += signer.policy["type"] + "\n" - if is_multisig(signer.policy): - policy_str += ( - str(signer.policy["m"]) + " of " + str(signer.policy["n"]) + "\n" - ) - fingerprints = [] - for inp in signer.psbt.inputs: - # Do we need to loop through all the inputs or just one? - if signer.policy["type"] == P2WSH: - for pub in inp.bip32_derivations: - fingerprint_srt = Key.format_fingerprint( - inp.bip32_derivations[pub].fingerprint, True - ) - if fingerprint_srt not in fingerprints: - if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: - fingerprints[-1] = "..." - break - fingerprints.append(fingerprint_srt) - elif signer.policy["type"] == P2TR: - for pub in inp.taproot_bip32_derivations: - _, derivation_path = inp.taproot_bip32_derivations[pub] - fingerprint_srt = Key.format_fingerprint( - derivation_path.fingerprint, True - ) - if fingerprint_srt not in fingerprints: - if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: - fingerprints[-1] = "..." - break - fingerprints.append(fingerprint_srt) - - policy_str += "\n".join(fingerprints) + policy_str = signer.psbt_policy_string() self.ctx.display.clear() self.ctx.display.draw_centered_text(policy_str) if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE): diff --git a/src/krux/psbt.py b/src/krux/psbt.py index bf868a11..2223530d 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import gc -from embit.psbt import PSBT +from embit.psbt import PSBT, CompressMode from embit.bip32 import HARDENED_INDEX from ur.ur import UR import urtypes @@ -40,6 +40,8 @@ # We always uses thin spaces after the ₿ in this file BTC_SYMBOL = "₿" +MAX_POLICY_COSIGNERS_DISPLAYED = 5 + class Counter(dict): """Helper class for dict""" @@ -68,7 +70,7 @@ def __init__(self, wallet, psbt_data, qr_format, psbt_filename=None): file_path = "/%s/%s" % (SD_PATH, psbt_filename) try: with open(file_path, "rb") as file: - self.psbt = PSBT.read_from(file, compress=1) + self.psbt = PSBT.read_from(file) self.validate() except: try: @@ -81,11 +83,12 @@ def __init__(self, wallet, psbt_data, qr_format, psbt_filename=None): psbt_data = file.read() self.psbt = PSBT.parse(base_decode(psbt_data, 64)) else: - # Legacy will fail to get policy from compressed PSBT - # so we load it uncompressed + # Try to load the PSBT in compressed mode with open(file_path, "rb") as file: file.seek(0) # Reset the file pointer to the beginning - self.psbt = PSBT.read_from(file) + self.psbt = PSBT.read_from( + file, compress=CompressMode.CLEAR_ALL + ) except Exception as e: raise ValueError("Error loading PSBT file: %s" % e) self.base_encoding = 64 # In case it is exported as QR code @@ -513,10 +516,11 @@ def sign(self, trim=True): trimmed_psbt = PSBT(self.psbt.tx) for i, inp in enumerate(self.psbt.inputs): - # Copy the final_scriptwitness if present (for Taproot or other SegWit inputs) + # Copy the final_scriptwitness if present if inp.final_scriptwitness: trimmed_psbt.inputs[i].final_scriptwitness = inp.final_scriptwitness - # Copy any partial signatures (for multisig or other script types) + + # Copy any partial signatures if inp.partial_sigs: trimmed_psbt.inputs[i].partial_sigs = inp.partial_sigs @@ -536,20 +540,6 @@ def sign(self, trim=True): if inp.witness_script: trimmed_psbt.inputs[i].witness_script = inp.witness_script - # # # --- Taproot-specific fields --- - - # # # Taproot BIP32 derivation paths (PSBT_IN_TAP_BIP32_DERIVATION) - # # if inp.taproot_bip32_derivations is not None: - # # trimmed_psbt.inputs[i].taproot_bip32_derivations = inp.taproot_bip32_derivations - - # # # Internal key (PSBT_IN_TAP_INTERNAL_KEY) - # # if inp.taproot_internal_key is not None: - # # trimmed_psbt.inputs[i].taproot_internal_key = inp.taproot_internal_key - - # # # Taproot leaf scripts (PSBT_IN_TAP_LEAF_SCRIPT) - # # if inp.taproot_scripts is not None: - # # trimmed_psbt.inputs[i].taproot_scripts = inp.taproot_scripts - self.psbt = trimmed_psbt def psbt_qr(self): @@ -606,6 +596,41 @@ def xpubs(self): ) return xpubs + def psbt_policy_string(self): + """Returns the policy string containing script type and cosigners' fingerprints""" + + policy_str = "PSBT policy:\n" + policy_str += self.policy["type"] + "\n" + if is_multisig(self.policy): + policy_str += str(self.policy["m"]) + " of " + str(self.policy["n"]) + "\n" + fingerprints = [] + for inp in self.psbt.inputs: + # Do we need to loop through all the inputs or just one? + if self.policy["type"] == P2WSH: + for pub in inp.bip32_derivations: + fingerprint_srt = Key.format_fingerprint( + inp.bip32_derivations[pub].fingerprint, True + ) + if fingerprint_srt not in fingerprints: + if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: + fingerprints[-1] = "..." + break + fingerprints.append(fingerprint_srt) + elif self.policy["type"] == P2TR: + for pub in inp.taproot_bip32_derivations: + _, derivation_path = inp.taproot_bip32_derivations[pub] + fingerprint_srt = Key.format_fingerprint( + derivation_path.fingerprint, True + ) + if fingerprint_srt not in fingerprints: + if len(fingerprints) > MAX_POLICY_COSIGNERS_DISPLAYED: + fingerprints[-1] = "..." + break + fingerprints.append(fingerprint_srt) + + policy_str += "\n".join(fingerprints) + return policy_str + def is_multisig(policy): """Returns a boolean indicating if the policy is a multisig""" diff --git a/tests/pages/home_pages/test_home.py b/tests/pages/home_pages/test_home.py index dabe3fe8..a44f5057 100644 --- a/tests/pages/home_pages/test_home.py +++ b/tests/pages/home_pages/test_home.py @@ -55,13 +55,17 @@ def tdata(mocker): P2WPKH_PSBT = b'psbt\xff\x01\x00q\x02\x00\x00\x00\x01\xcfe\xff;L\xd4\x7f\x12\x1f\xa7\xc9\x82(F\x18\xdb\x801G\xb0V\xd3\x93\x94\xd4\xecB\x0e\xfd\xfck\xa1\x02 l\xbd\xd8\x8a\xc5\x18l?.\xfd$%1\xedy\x17uvQ\xac&#t\xf3\xd3\x1d\x85\xd6\x16\xcdj\x81\x01\x00\x00\x00' + SIGNED_P2WPKH_PSBT_SD = b'psbt\xff\x01\x00q\x02\x00\x00\x00\x01\xcfe\xff;L\xd4\x7f\x12\x1f\xa7\xc9\x82(F\x18\xdb\x801G\xb0V\xd3\x93\x94\xd4\xecB\x0e\xfd\xfck\xa1\x02 l\xbd\xd8\x8a\xc5\x18l?.\xfd$%1\xedy\x17uvQ\xac&#t\xf3\xd3\x1d\x85\xd6\x16\xcdj\x81\x01"\x06\x02\xe7\xab%7\xb5\xd4\x9e\x97\x03\t\xaa\xe0n\x9eI\xf3l\xe1\xc9\xfe\xbb\xd4N\xc8\xe0\xd1\xcc\xa0\xb4\xf9\xc3\x19\x18s\xc5\xda\nT\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x02\x03]I\xec\xcdT\xd0\t\x9eCgbw\xc7\xa6\xd4b]a\x1d\xa8\x8a]\xf4\x9b\xf9Qzw\x91\xa7w\xa5\x18s\xc5\xda\nT\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' P2WPKH_PSBT_B64 = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgYC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxkYc8XaClQAAIABAACAAAAAgAAAAAAAAAAAACICA11J7M1U0AmeQ2did8em1GJdYR2oil30m/lReneRp3elGHPF2gpUAACAAQAAgAAAAIABAAAAAAAAAAAA" P2WPKH_PSBT_B64_ZEROES_FINGERPRINT = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgYC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxkYAAAAAFQAAIABAACAAAAAgAAAAAAAAAAAACICA11J7M1U0AmeQ2did8em1GJdYR2oil30m/lReneRp3elGHPF2gpUAACAAQAAgAAAAIABAAAAAAAAAAAA" P2TR_PSBT_BIN_ZEROES_FINGERPRINT = b"psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00O\x01\x045\x87\xcf\x03\xca\xd0\xf4J\x80\x00\x00\x00\x07\x1aOa\x83\xb1T7u\x18\xe8\xbd|\x9e\x0c\x9c\xa0\t\x8cV\x8a:J\x96\xa3\x9eK\xd9\xb4\xff\x9f4\x02\x8c\xc0\x83\xa0\x96^\x8c@A!\xf6\xd7\xa46#?3E\x89p\xb1E\xd3rk2lDL\x84\xfd\xdf\x10\x00\x00\x00\x00V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x01\x00\x87\x02\x00\x00\x00\x02\xc6\xfcO\x0f\t|9X\x93\xfc\x051\x12\x03(\xe7\xc38\x87\xee\xaf\xf9\x84\x06\xea\xf5\xa6)#\xf7\xa8;\x00\x00\x00\x00\x00\xfd\xff\xff\xff\xe1\xa1U,\x01\n?\x8e:Y1e\xf8\xc7`$\xb0\xa2\xb6V\xa5\x9e\x01\n\xcd0P\xe9%\x18n\xb5\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x01\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\xd4\xc1+\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x03\x04\x00\x00\x00\x00!\x16\x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x19\x00\x00\x00\x00\x00V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17 \x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x00\x00" SIGNED_P2WPKH_PSBT_B64 = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgIC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxlHMEQCID5l/ztM1H8SH6fJgihGGNuAMUewVtOTlNTsQg79/GuhAiBsvdiKxRhsPy79JCUx7XkXdXZRrCYjdPPTHYXWFs1qgQEAAAA=" + SIGNED_P2WPKH_PSBT_B64_SD = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgIC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxlHMEQCID5l/ztM1H8SH6fJgihGGNuAMUewVtOTlNTsQg79/GuhAiBsvdiKxRhsPy79JCUx7XkXdXZRrCYjdPPTHYXWFs1qgQEiBgLnqyU3tdSelwMJquBunknzbOHJ/rvUTsjg0cygtPnDGRhzxdoKVAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDXUnszVTQCZ5DZ2J3x6bUYl1hHaiKXfSb+VF6d5Gnd6UYc8XaClQAAIABAACAAAAAgAEAAAAAAAAAAAA=" SIGNED_P2TR_PSBT_BIN = b"psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x08B\x01@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca'9\xa6\x00\x00" + SIGNED_P2TR_PSBT_BIN_SD = b'psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00O\x01\x045\x87\xcf\x03\xca\xd0\xf4J\x80\x00\x00\x00\x07\x1aOa\x83\xb1T7u\x18\xe8\xbd|\x9e\x0c\x9c\xa0\t\x8cV\x8a:J\x96\xa3\x9eK\xd9\xb4\xff\x9f4\x02\x8c\xc0\x83\xa0\x96^\x8c@A!\xf6\xd7\xa46#?3E\x89p\xb1E\xd3rk2lDL\x84\xfd\xdf\x10\x00\x00\x00\x00V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x01\x00\x87\x02\x00\x00\x00\x02\xc6\xfcO\x0f\t|9X\x93\xfc\x051\x12\x03(\xe7\xc38\x87\xee\xaf\xf9\x84\x06\xea\xf5\xa6)#\xf7\xa8;\x00\x00\x00\x00\x00\xfd\xff\xff\xff\xe1\xa1U,\x01\n?\x8e:Y1e\xf8\xc7`$\xb0\xa2\xb6V\xa5\x9e\x01\n\xcd0P\xe9%\x18n\xb5\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x01\xe8\xe7\x01\x00\x00\x00\x00\x00"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe\'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\xd4\xc1+\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe\'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x03\x04\x00\x00\x00\x00\x01\x08B\x01@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca\'9\xa6\x01\x13@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca\'9\xa6!\x16\x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x19\x00\xe0\xc5\x95\xc5V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17 \x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x00\x00' P2WSH_PSBT = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00O\x01\x045\x87\xcf\x04>b\xdf~\x80\x00\x00\x02A+I\x84\xd5I\xba^\xef\x1c\xa6\xe8\xf3u]\x9a\xe0\x16\xdam\x16ir\xca\x0eQ@6~\xddP\xda\x025\xb8K1\xdc8*|\xfbC\xba:{\x17K\xe9AaA\xe8\x16\xf6r[\xd1%\x12\xb5\xb2\xc4\xa5\xac\x14\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\x9d\xb1\xd0\x00\x80\x00\x00\x02?\xd8\xd7;\xc7\xb8\x8c\xa4\x93Z\xa57\xbf8\x94\xd5\xe2\x88\x9f\xab4\x1ca\x8fJWo\x8f\x19\x18\xc2u\x02h\xc3\rV\x9d#j}\xccW\x1b+\xb1\xd2\xadO\xa9\xf9\xb3R\xa8\t6\xa2\x89\n\x99\xaa#\xdbx\xec\x14&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\xba\xc1H9\x80\x00\x00\x02\x1dO\xbe\xbd\xd9g\xe1\xafqL\t\x97\xd3\x8f\xcfg\x0b\\\xe9\xd3\x01\xc0D\x0b\xbc\xc3\xb6\xa2\x0e\xb7r\x1c\x03V\x8e\xa1\xf3`Q\x91n\xd1\xb6\x90\xc3\x9e\x12\xa8\xe7\x06\x03\xb2\x80\xbd0\xce_(\x1f)\x18\xa5Sc\xaa\x14s\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae"\x06\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae"\x06\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01iR!\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe!\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2kS\xae"\x02\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00"\x02\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2k\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' SIGNED_P2WSH_PSBT = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$"\x02\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87G0D\x02 h?m\x19\x04C\x89\x95\x8b\xba\xed\xbb\xba8)\t\xae^\xe3`\x16G\xc8\x8bq\x9c\x0e\xbc\xc5\xb1j\xa2\x02 \x05\rP(\xe0\x9cc])q\xe5\xe2S\x9f\xaf+\xe4_\xa9\xc6\xf9\r"%\xf4\xa2\x00;\xa2\xaf2W\x01\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5"\x02\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01G0D\x02 ~O\x1b\x8c\xbb\x87x\xa3\xbb\xff\x04\xd8\x10Cq\xc8Y\x0f;N6\x97\xd8S\xfeti\x80\xb3\x12\xe0>\x02 l\x93=\x02m\xb4<\x90\xf4%\xf9Z${\xb7\xecO\x19\x15\xa3\xa3S\xf2Q\x81\xdcX\xfb\xd5&\x9e\xc5\x01\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae\x00\x00\x00' + SIGNED_P2WSH_PSBT_SD = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00O\x01\x045\x87\xcf\x04>b\xdf~\x80\x00\x00\x02A+I\x84\xd5I\xba^\xef\x1c\xa6\xe8\xf3u]\x9a\xe0\x16\xdam\x16ir\xca\x0eQ@6~\xddP\xda\x025\xb8K1\xdc8*|\xfbC\xba:{\x17K\xe9AaA\xe8\x16\xf6r[\xd1%\x12\xb5\xb2\xc4\xa5\xac\x14\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\x9d\xb1\xd0\x00\x80\x00\x00\x02?\xd8\xd7;\xc7\xb8\x8c\xa4\x93Z\xa57\xbf8\x94\xd5\xe2\x88\x9f\xab4\x1ca\x8fJWo\x8f\x19\x18\xc2u\x02h\xc3\rV\x9d#j}\xccW\x1b+\xb1\xd2\xadO\xa9\xf9\xb3R\xa8\t6\xa2\x89\n\x99\xaa#\xdbx\xec\x14&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\xba\xc1H9\x80\x00\x00\x02\x1dO\xbe\xbd\xd9g\xe1\xafqL\t\x97\xd3\x8f\xcfg\x0b\\\xe9\xd3\x01\xc0D\x0b\xbc\xc3\xb6\xa2\x0e\xb7r\x1c\x03V\x8e\xa1\xf3`Q\x91n\xd1\xb6\x90\xc3\x9e\x12\xa8\xe7\x06\x03\xb2\x80\xbd0\xce_(\x1f)\x18\xa5Sc\xaa\x14s\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$"\x02\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87G0D\x02 h?m\x19\x04C\x89\x95\x8b\xba\xed\xbb\xba8)\t\xae^\xe3`\x16G\xc8\x8bq\x9c\x0e\xbc\xc5\xb1j\xa2\x02 \x05\rP(\xe0\x9cc])q\xe5\xe2S\x9f\xaf+\xe4_\xa9\xc6\xf9\r"%\xf4\xa2\x00;\xa2\xaf2W\x01\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae"\x06\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5"\x02\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01G0D\x02 ~O\x1b\x8c\xbb\x87x\xa3\xbb\xff\x04\xd8\x10Cq\xc8Y\x0f;N6\x97\xd8S\xfeti\x80\xb3\x12\xe0>\x02 l\x93=\x02m\xb4<\x90\xf4%\xf9Z${\xb7\xecO\x19\x15\xa3\xa3S\xf2Q\x81\xdcX\xfb\xd5&\x9e\xc5\x01\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae"\x06\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01iR!\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe!\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2kS\xae"\x02\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00"\x02\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2k\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' P2WSH_PSBT_B64 = "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAATwEENYfPBD5i336AAAACQStJhNVJul7vHKbo83VdmuAW2m0WaXLKDlFANn7dUNoCNbhLMdw4Knz7Q7o6exdL6UFhQegW9nJb0SUStbLEpawUAgjLdzAAAIABAACAAAAAgAIAAIBPAQQ1h88EnbHQAIAAAAI/2Nc7x7iMpJNapTe/OJTV4oifqzQcYY9KV2+PGRjCdQJoww1WnSNqfcxXGyux0q1PqfmzUqgJNqKJCpmqI9t47BQmu4PEMAAAgAEAAIAAAACAAgAAgE8BBDWHzwS6wUg5gAAAAh1Pvr3ZZ+GvcUwJl9OPz2cLXOnTAcBEC7zDtqIOt3IcA1aOofNgUZFu0baQw54SqOcGA7KAvTDOXygfKRilU2OqFHPF2gowAACAAQAAgAAAAIACAACAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQBBWlSIQJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8iEDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMchA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHU64iBgJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8hwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAABAAAAIgYDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMccAgjLdzAAAIABAACAAAAAgAIAAIAAAAAAAQAAACIGA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHHHPF2gowAACAAQAAgAAAAIACAACAAAAAAAEAAAAAAQErgJaYAAAAAAAiACAzd60wM9EFnPHSNbsSJfyipL8myVLVP2/vwzotVUSNxQEFaVIhAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiIQKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdCEDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFTriIGAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiHAIIy3cwAACAAQAAgAAAAIACAACAAAAAAAAAAAAiBgKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdBwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIgYDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgEcc8XaCjAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAABAWlSIQKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/iEDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYhA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrU64iAgKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/hwCCMt3MAAAgAEAAIAAAACAAgAAgAEAAAAAAAAAIgIDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYcc8XaCjAAAIABAACAAAAAgAIAAIABAAAAAAAAACICA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrHCa7g8QwAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA==" SIGNED_P2WSH_PSBT_B64 = "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQiAgNolXLiiw/tqdaYHAI32eXe2/7BbecUP2gKAu1dFZ91h0cwRAIgaD9tGQRDiZWLuu27ujgpCa5e42AWR8iLcZwOvMWxaqICIAUNUCjgnGNdKXHl4lOfryvkX6nG+Q0iJfSiADuirzJXAQEFaVIhAk6NCAx9fbpcR/62scgSTetiQRfljY1+sUpABE9x3ZfyIQMFYdSCrbk98e8T6GVwGvIkbvCjbLyMpRI9jux3zk44xyEDaJVy4osP7anWmBwCN9nl3tv+wW3nFD9oCgLtXRWfdYdTrgABASuAlpgAAAAAACIAIDN3rTAz0QWc8dI1uxIl/KKkvybJUtU/b+/DOi1VRI3FIgIDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFHMEQCIH5PG4y7h3iju/8E2BBDcchZDztONpfYU/50aYCzEuA+AiBskz0CbbQ8kPQl+Voke7fsTxkVo6NT8lGB3Fj71SaexQEBBWlSIQIigjES5cyIS5EWyyFCDMeSmCTNL+i3I1v5kuiu3hRsIiECg81H5VNty3nnEYMw6OSAQhL2lhnx1uyZDcc177nOxXQhAwuQ7S6GutfypP6XabtBfXupyqESSAfb+zYt++62Xn4BU64AAAA=" P2WPKH_HIGH_FEE_PSBT = "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" @@ -93,13 +97,17 @@ def tdata(mocker): "SPECTER_MULTISIG_WALLET_DATA", "P2WPKH_PSBT", "SIGNED_P2WPKH_PSBT", + "SIGNED_P2WPKH_PSBT_SD", "P2WPKH_PSBT_B64", "P2WPKH_PSBT_B64_ZEROES_FINGERPRINT", "P2TR_PSBT_BIN_ZEROES_FINGERPRINT", "SIGNED_P2WPKH_PSBT_B64", + "SIGNED_P2WPKH_PSBT_B64_SD", "SIGNED_P2TR_PSBT_BIN", + "SIGNED_P2TR_PSBT_BIN_SD", "P2WSH_PSBT", "SIGNED_P2WSH_PSBT", + "SIGNED_P2WSH_PSBT_SD", "P2WSH_PSBT_B64", "SIGNED_P2WSH_PSBT_B64", "P2WPKH_HIGH_FEE_PSBT", @@ -125,13 +133,17 @@ def tdata(mocker): SPECTER_MULTISIG_WALLET_DATA, P2WPKH_PSBT, SIGNED_P2WPKH_PSBT, + SIGNED_P2WPKH_PSBT_SD, P2WPKH_PSBT_B64, P2WPKH_PSBT_B64_ZEROES_FINGERPRINT, P2TR_PSBT_BIN_ZEROES_FINGERPRINT, SIGNED_P2WPKH_PSBT_B64, + SIGNED_P2WPKH_PSBT_B64_SD, SIGNED_P2TR_PSBT_BIN, + SIGNED_P2TR_PSBT_BIN_SD, P2WSH_PSBT, SIGNED_P2WSH_PSBT, + SIGNED_P2WSH_PSBT_SD, P2WSH_PSBT_B64, SIGNED_P2WSH_PSBT_B64, P2WPKH_HIGH_FEE_PSBT, @@ -566,7 +578,7 @@ def test_sign_psbt(mocker, m5stickv, tdata): BUTTON_PAGE, # Move to "Sign to QR SD card" BUTTON_ENTER, # Sign to SD card ], - tdata.SIGNED_P2WPKH_PSBT, # 8 SD avaiable + tdata.SIGNED_P2WPKH_PSBT_SD, # 8 SD avaiable ), # Multisig, not loaded, load from microSD, sign, save to microSD, No print prompt ( @@ -590,7 +602,7 @@ def test_sign_psbt(mocker, m5stickv, tdata): BUTTON_PAGE, # Move to "Sign to QR SD card" BUTTON_ENTER, # Sign to SD card ], - tdata.SIGNED_P2WSH_PSBT, # 8 SD avaiable + tdata.SIGNED_P2WSH_PSBT_SD, # 8 SD avaiable ), # Single-sig base64, not loaded, load from microSD, sign to microSD ( @@ -612,7 +624,7 @@ def test_sign_psbt(mocker, m5stickv, tdata): BUTTON_PAGE, # Move to "Sign to QR SD card" BUTTON_ENTER, # Sign to SD card ], - tdata.SIGNED_P2WPKH_PSBT_B64, # 8 SD avaiable + tdata.SIGNED_P2WPKH_PSBT_B64_SD, # 8 SD avaiable ), ] # Case X @@ -992,7 +1004,7 @@ def test_sign_p2tr_zeroes_fingerprint(mocker, m5stickv, tdata): handle_write = mock_open_write() # # Embit will write the signed PSBT to the output file in chunks. Capture all write calls written_data = b"".join(call.args[0] for call in handle_write.write.call_args_list) - assert written_data == tdata.SIGNED_P2TR_PSBT_BIN + assert written_data == tdata.SIGNED_P2TR_PSBT_BIN_SD def test_sign_high_fee(mocker, m5stickv, tdata): From e4d6530c422c558e6483fc65abfb1fc93bc1d6d3 Mon Sep 17 00:00:00 2001 From: odudex Date: Mon, 30 Dec 2024 11:36:00 -0300 Subject: [PATCH 10/34] bugfix on identification of taproot miniscript descriptors --- src/krux/wallet.py | 9 +++------ tests/pages/home_pages/test_home.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/krux/wallet.py b/src/krux/wallet.py index 6c283a9e..44ac26f0 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -131,7 +131,7 @@ def _validate_descriptor(self, descriptor, descriptor_xpubs): if descriptor.miniscript is None or descriptor.is_basic_multisig: raise ValueError("not P2WSH miniscript") elif self.key.script_type == P2TR: - if descriptor.taptree is None: + if not descriptor.taptree: raise ValueError("not P2TR miniscript") if self.key.xpub() not in descriptor_xpubs: raise ValueError("xpub not a miniscript cosigner") @@ -182,11 +182,8 @@ def load(self, wallet_data, qr_format, allow_assumption=None): "n": n, "cosigners": cosigners, } - elif ( - self.descriptor.miniscript is not None - or self.descriptor.taptree is not None - ): - if self.descriptor.taptree is not None: + elif self.descriptor.miniscript is not None or self.descriptor.taptree: + if self.descriptor.taptree: taproot_txt = "TR " miniscript_type = P2TR else: diff --git a/tests/pages/home_pages/test_home.py b/tests/pages/home_pages/test_home.py index a44f5057..15d07290 100644 --- a/tests/pages/home_pages/test_home.py +++ b/tests/pages/home_pages/test_home.py @@ -62,7 +62,7 @@ def tdata(mocker): SIGNED_P2WPKH_PSBT_B64 = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgIC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxlHMEQCID5l/ztM1H8SH6fJgihGGNuAMUewVtOTlNTsQg79/GuhAiBsvdiKxRhsPy79JCUx7XkXdXZRrCYjdPPTHYXWFs1qgQEAAAA=" SIGNED_P2WPKH_PSBT_B64_SD = "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgIC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxlHMEQCID5l/ztM1H8SH6fJgihGGNuAMUewVtOTlNTsQg79/GuhAiBsvdiKxRhsPy79JCUx7XkXdXZRrCYjdPPTHYXWFs1qgQEiBgLnqyU3tdSelwMJquBunknzbOHJ/rvUTsjg0cygtPnDGRhzxdoKVAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDXUnszVTQCZ5DZ2J3x6bUYl1hHaiKXfSb+VF6d5Gnd6UYc8XaClQAAIABAACAAAAAgAEAAAAAAAAAAAA=" SIGNED_P2TR_PSBT_BIN = b"psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x08B\x01@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca'9\xa6\x00\x00" - SIGNED_P2TR_PSBT_BIN_SD = b'psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00O\x01\x045\x87\xcf\x03\xca\xd0\xf4J\x80\x00\x00\x00\x07\x1aOa\x83\xb1T7u\x18\xe8\xbd|\x9e\x0c\x9c\xa0\t\x8cV\x8a:J\x96\xa3\x9eK\xd9\xb4\xff\x9f4\x02\x8c\xc0\x83\xa0\x96^\x8c@A!\xf6\xd7\xa46#?3E\x89p\xb1E\xd3rk2lDL\x84\xfd\xdf\x10\x00\x00\x00\x00V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x01\x00\x87\x02\x00\x00\x00\x02\xc6\xfcO\x0f\t|9X\x93\xfc\x051\x12\x03(\xe7\xc38\x87\xee\xaf\xf9\x84\x06\xea\xf5\xa6)#\xf7\xa8;\x00\x00\x00\x00\x00\xfd\xff\xff\xff\xe1\xa1U,\x01\n?\x8e:Y1e\xf8\xc7`$\xb0\xa2\xb6V\xa5\x9e\x01\n\xcd0P\xe9%\x18n\xb5\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x01\xe8\xe7\x01\x00\x00\x00\x00\x00"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe\'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\xd4\xc1+\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe\'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x03\x04\x00\x00\x00\x00\x01\x08B\x01@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca\'9\xa6\x01\x13@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca\'9\xa6!\x16\x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x19\x00\xe0\xc5\x95\xc5V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17 \x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x00\x00' + SIGNED_P2TR_PSBT_BIN_SD = b"psbt\xff\x01\x00R\x02\x00\x00\x00\x01\xe6\x02\x02c\xc5\xfdX\xa1\x17[\xf2\xc0Z-\xfd\xa9\x84\xc8H\xf0\x84B)\xa7\x0b\xf6WA\xfaE\xde\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01-\xe1\x01\x00\x00\x00\x00\x00\x16\x00\x14\xb1P%\xed\xdb8\x87^\xc7h\x8d]6srC\x81)gq\x00\x00\x00\x00O\x01\x045\x87\xcf\x03\xca\xd0\xf4J\x80\x00\x00\x00\x07\x1aOa\x83\xb1T7u\x18\xe8\xbd|\x9e\x0c\x9c\xa0\t\x8cV\x8a:J\x96\xa3\x9eK\xd9\xb4\xff\x9f4\x02\x8c\xc0\x83\xa0\x96^\x8c@A!\xf6\xd7\xa46#?3E\x89p\xb1E\xd3rk2lDL\x84\xfd\xdf\x10\x00\x00\x00\x00V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x01\x00\x87\x02\x00\x00\x00\x02\xc6\xfcO\x0f\t|9X\x93\xfc\x051\x12\x03(\xe7\xc38\x87\xee\xaf\xf9\x84\x06\xea\xf5\xa6)#\xf7\xa8;\x00\x00\x00\x00\x00\xfd\xff\xff\xff\xe1\xa1U,\x01\n?\x8e:Y1e\xf8\xc7`$\xb0\xa2\xb6V\xa5\x9e\x01\n\xcd0P\xe9%\x18n\xb5\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x01\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\xd4\xc1+\x00\x01\x01+\xe8\xe7\x01\x00\x00\x00\x00\x00\"Q \x0f\xdf\xc7Q\x92}\xd5t\xbe'\\\xd6m\xb4\x84\xc8t.\x8f\xcc\xaa\xff\x04*\xf8\xc5\xe9(\x83\x0fX\xac\x01\x03\x04\x00\x00\x00\x00\x01\x08B\x01@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca'9\xa6\x01\x13@jRXU\x1f\x0f2\xd2\xd8?\x08a\x089\xfa\x936\x13_\x8d\x0f\xcb\xb9\x04\xc0T\xe4\xea\xc6~\xf2A\xa6K\xc8Lu\r\x1b\x8aN\xca\xf6\x95\xce\xa6p\x0e\xc1\x95\xcbd\t\xfc\xa3^,lQF\xca'9\xa6!\x16\x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x19\x00\xe0\xc5\x95\xc5V\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\x17 \x8b4{\xa5\xc1l\n&\xcd3\xfefv\xbbA~\xe8\x1fB\xf1(\x17\xa6\xe4\x11\xee\x8a\xb2\x00\xdf\xe9`\x00\x00" P2WSH_PSBT = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00O\x01\x045\x87\xcf\x04>b\xdf~\x80\x00\x00\x02A+I\x84\xd5I\xba^\xef\x1c\xa6\xe8\xf3u]\x9a\xe0\x16\xdam\x16ir\xca\x0eQ@6~\xddP\xda\x025\xb8K1\xdc8*|\xfbC\xba:{\x17K\xe9AaA\xe8\x16\xf6r[\xd1%\x12\xb5\xb2\xc4\xa5\xac\x14\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\x9d\xb1\xd0\x00\x80\x00\x00\x02?\xd8\xd7;\xc7\xb8\x8c\xa4\x93Z\xa57\xbf8\x94\xd5\xe2\x88\x9f\xab4\x1ca\x8fJWo\x8f\x19\x18\xc2u\x02h\xc3\rV\x9d#j}\xccW\x1b+\xb1\xd2\xadO\xa9\xf9\xb3R\xa8\t6\xa2\x89\n\x99\xaa#\xdbx\xec\x14&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\xba\xc1H9\x80\x00\x00\x02\x1dO\xbe\xbd\xd9g\xe1\xafqL\t\x97\xd3\x8f\xcfg\x0b\\\xe9\xd3\x01\xc0D\x0b\xbc\xc3\xb6\xa2\x0e\xb7r\x1c\x03V\x8e\xa1\xf3`Q\x91n\xd1\xb6\x90\xc3\x9e\x12\xa8\xe7\x06\x03\xb2\x80\xbd0\xce_(\x1f)\x18\xa5Sc\xaa\x14s\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae"\x06\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae"\x06\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01iR!\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe!\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2kS\xae"\x02\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00"\x02\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2k\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' SIGNED_P2WSH_PSBT = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$"\x02\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87G0D\x02 h?m\x19\x04C\x89\x95\x8b\xba\xed\xbb\xba8)\t\xae^\xe3`\x16G\xc8\x8bq\x9c\x0e\xbc\xc5\xb1j\xa2\x02 \x05\rP(\xe0\x9cc])q\xe5\xe2S\x9f\xaf+\xe4_\xa9\xc6\xf9\r"%\xf4\xa2\x00;\xa2\xaf2W\x01\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5"\x02\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01G0D\x02 ~O\x1b\x8c\xbb\x87x\xa3\xbb\xff\x04\xd8\x10Cq\xc8Y\x0f;N6\x97\xd8S\xfeti\x80\xb3\x12\xe0>\x02 l\x93=\x02m\xb4<\x90\xf4%\xf9Z${\xb7\xecO\x19\x15\xa3\xa3S\xf2Q\x81\xdcX\xfb\xd5&\x9e\xc5\x01\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae\x00\x00\x00' SIGNED_P2WSH_PSBT_SD = b'psbt\xff\x01\x00\xb2\x02\x00\x00\x00\x02\xadC\x87\x14J\xfae\x07\xe1>\xaeP\xda\x1b\xf1\xb5\x1ag\xb3\x0f\xfb\x8e\x0c[\x8f\x98\xf5\xb3\xb1\xa68Y\x00\x00\x00\x00\x00\xfd\xff\xff\xffig%Y\x0f\xb8\xe4r\xab#N\xeb\xf3\xbf\x04\xd9J\xc0\xba\x94\xf6\xa5\xa4\xf8B\xea\xdb\x9a\xd3c`\xd4\x01\x00\x00\x00\x00\xfd\xff\xff\xff\x02@B\x0f\x00\x00\x00\x00\x00"\x00 \xa9\x903\xc3\x86b3>Y\t\xae<=\x03\xbdq\x8d\xb2\x14Y\xfd\xd5P\x1e\xe8\xa0RaMY\xb4\xe2\xd8\xd2!\x01\x00\x00\x00\x00"\x00 \x8d\x02\x85\r\xab\x88^\xc5y\xbbm\xcb\x05\xd6 ;\x05\xf5\x17\x01\x86\xac\xb8\x90}l\xc1\xb4R\x99\xed\xd2\x00\x00\x00\x00O\x01\x045\x87\xcf\x04>b\xdf~\x80\x00\x00\x02A+I\x84\xd5I\xba^\xef\x1c\xa6\xe8\xf3u]\x9a\xe0\x16\xdam\x16ir\xca\x0eQ@6~\xddP\xda\x025\xb8K1\xdc8*|\xfbC\xba:{\x17K\xe9AaA\xe8\x16\xf6r[\xd1%\x12\xb5\xb2\xc4\xa5\xac\x14\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\x9d\xb1\xd0\x00\x80\x00\x00\x02?\xd8\xd7;\xc7\xb8\x8c\xa4\x93Z\xa57\xbf8\x94\xd5\xe2\x88\x9f\xab4\x1ca\x8fJWo\x8f\x19\x18\xc2u\x02h\xc3\rV\x9d#j}\xccW\x1b+\xb1\xd2\xadO\xa9\xf9\xb3R\xa8\t6\xa2\x89\n\x99\xaa#\xdbx\xec\x14&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80O\x01\x045\x87\xcf\x04\xba\xc1H9\x80\x00\x00\x02\x1dO\xbe\xbd\xd9g\xe1\xafqL\t\x97\xd3\x8f\xcfg\x0b\\\xe9\xd3\x01\xc0D\x0b\xbc\xc3\xb6\xa2\x0e\xb7r\x1c\x03V\x8e\xa1\xf3`Q\x91n\xd1\xb6\x90\xc3\x9e\x12\xa8\xe7\x06\x03\xb2\x80\xbd0\xce_(\x1f)\x18\xa5Sc\xaa\x14s\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 \x89\x801pn\xdd\x9e\xb1"g\x85G\x15Q\xce\xa3_\x17\t\xa9o\x85\x96.2\xa0k\xf6~\xc7\x11$"\x02\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87G0D\x02 h?m\x19\x04C\x89\x95\x8b\xba\xed\xbb\xba8)\t\xae^\xe3`\x16G\xc8\x8bq\x9c\x0e\xbc\xc5\xb1j\xa2\x02 \x05\rP(\xe0\x9cc])q\xe5\xe2S\x9f\xaf+\xe4_\xa9\xc6\xf9\r"%\xf4\xa2\x00;\xa2\xaf2W\x01\x01\x05iR!\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2!\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7!\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87S\xae"\x06\x02N\x8d\x08\x0c}}\xba\\G\xfe\xb6\xb1\xc8\x12M\xebbA\x17\xe5\x8d\x8d~\xb1J@\x04Oq\xdd\x97\xf2\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03\x05a\xd4\x82\xad\xb9=\xf1\xef\x13\xe8ep\x1a\xf2$n\xf0\xa3l\xbc\x8c\xa5\x12=\x8e\xecw\xceN8\xc7\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00"\x06\x03h\x95r\xe2\x8b\x0f\xed\xa9\xd6\x98\x1c\x027\xd9\xe5\xde\xdb\xfe\xc1m\xe7\x14?h\n\x02\xed]\x15\x9fu\x87\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x01\x00\x00\x00\x00\x01\x01+\x80\x96\x98\x00\x00\x00\x00\x00"\x00 3w\xad03\xd1\x05\x9c\xf1\xd25\xbb\x12%\xfc\xa2\xa4\xbf&\xc9R\xd5?o\xef\xc3:-UD\x8d\xc5"\x02\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01G0D\x02 ~O\x1b\x8c\xbb\x87x\xa3\xbb\xff\x04\xd8\x10Cq\xc8Y\x0f;N6\x97\xd8S\xfeti\x80\xb3\x12\xe0>\x02 l\x93=\x02m\xb4<\x90\xf4%\xf9Z${\xb7\xecO\x19\x15\xa3\xa3S\xf2Q\x81\xdcX\xfb\xd5&\x9e\xc5\x01\x01\x05iR!\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"!\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t!\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01S\xae"\x06\x02"\x821\x12\xe5\xcc\x88K\x91\x16\xcb!B\x0c\xc7\x92\x98$\xcd/\xe8\xb7#[\xf9\x92\xe8\xae\xde\x14l"\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x02\x83\xcdG\xe5Sm\xcby\xe7\x11\x830\xe8\xe4\x80B\x12\xf6\x96\x19\xf1\xd6\xec\x99\r\xc75\xef\xb9\xce\xc5t\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"\x06\x03\x0b\x90\xed.\x86\xba\xd7\xf2\xa4\xfe\x97i\xbbA}{\xa9\xca\xa1\x12H\x07\xdb\xfb6-\xfb\xee\xb6^~\x01\x1cs\xc5\xda\n0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01iR!\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe!\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2kS\xae"\x02\x02\xad!\xd9\xad(\xab\x99\xac~\xdf\xd9\x1e"!O\x11YS\xab\t\xd1\xd5X\x10\x92\xfbG\xbd\xa5\x92r\xfe\x1c\x02\x08\xcbw0\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00"\x02\x03\xa0};\xe0\xba\xd6<\x805\xd2\x1c\x97\xb4\x10\x89\r=:\x19\xd2\xe4\x03\xaf\xb3\xfc\xfch&\xaa&\x02\xb8\xf4\xcfb\xbc\xc6\xa7\xa2k\x1c&\xbb\x83\xc40\x00\x00\x80\x01\x00\x00\x80\x00\x00\x00\x80\x02\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00' From 136a3464812a022e8b234b0c83e8480a990d425f Mon Sep 17 00:00:00 2001 From: odudex Date: Mon, 30 Dec 2024 16:05:58 -0300 Subject: [PATCH 11/34] tests: adapting to 3 types of policy --- src/krux/pages/home_pages/home.py | 4 +- tests/pages/test_encryption_ui.py | 12 ++--- tests/pages/test_tiny_seed.py | 4 +- tests/test_key.py | 32 ++++++------ tests/test_psbt.py | 83 ++++++++++++++++--------------- tests/test_wallet.py | 26 +++++++--- 6 files changed, 88 insertions(+), 73 deletions(-) diff --git a/src/krux/pages/home_pages/home.py b/src/krux/pages/home_pages/home.py index d3f0c89d..2f067d9d 100644 --- a/src/krux/pages/home_pages/home.py +++ b/src/krux/pages/home_pages/home.py @@ -131,7 +131,7 @@ def customize(self): from ...wallet import Wallet wallet_settings = WalletSettings(self.ctx) - network, multisig, script_type, account = wallet_settings.customize_wallet( + network, policy_type, script_type, account = wallet_settings.customize_wallet( self.ctx.wallet.key ) mnemonic = self.ctx.wallet.key.mnemonic @@ -139,7 +139,7 @@ def customize(self): self.ctx.wallet = Wallet( Key( mnemonic, - multisig, + policy_type, network, passphrase, account, diff --git a/tests/pages/test_encryption_ui.py b/tests/pages/test_encryption_ui.py index 293bc0fd..84196d7e 100644 --- a/tests/pages/test_encryption_ui.py +++ b/tests/pages/test_encryption_ui.py @@ -117,7 +117,7 @@ def test_encrypt_cbc_sd_ui(m5stickv, mocker, mock_file_operations): from krux.krux_settings import Settings from krux.input import BUTTON_ENTER, BUTTON_PAGE from krux.pages.encryption_ui import EncryptMnemonic - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from embit.networks import NETWORKS BTN_SEQUENCE = ( @@ -128,7 +128,7 @@ def test_encrypt_cbc_sd_ui(m5stickv, mocker, mock_file_operations): + [BUTTON_ENTER] # Confirm encryption ID ) ctx = create_ctx(mocker, BTN_SEQUENCE) - ctx.wallet = Wallet(Key(CBC_WORDS, False, NETWORKS["main"])) + ctx.wallet = Wallet(Key(CBC_WORDS, TYPE_SINGLESIG, NETWORKS["main"])) storage_ui = EncryptMnemonic(ctx) mocker.patch( "krux.pages.encryption_ui.EncryptionKey.encryption_key", @@ -152,7 +152,7 @@ def test_encrypt_to_qrcode_ecb_ui(m5stickv, mocker): from krux.krux_settings import Settings from krux.input import BUTTON_ENTER, BUTTON_PAGE from krux.pages.encryption_ui import EncryptMnemonic - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from embit.networks import NETWORKS BTN_SEQUENCE = ( @@ -163,7 +163,7 @@ def test_encrypt_to_qrcode_ecb_ui(m5stickv, mocker): # QR view is mocked here, no press needed ) ctx = create_ctx(mocker, BTN_SEQUENCE) - ctx.wallet = Wallet(Key(ECB_WORDS, False, NETWORKS["main"])) + ctx.wallet = Wallet(Key(ECB_WORDS, TYPE_SINGLESIG, NETWORKS["main"])) ctx.printer = None storage_ui = EncryptMnemonic(ctx) mocker.patch( @@ -188,7 +188,7 @@ def test_encrypt_to_qrcode_cbc_ui(m5stickv, mocker): from krux.krux_settings import Settings from krux.input import BUTTON_ENTER, BUTTON_PAGE from krux.pages.encryption_ui import EncryptMnemonic - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from embit.networks import NETWORKS BTN_SEQUENCE = ( @@ -200,7 +200,7 @@ def test_encrypt_to_qrcode_cbc_ui(m5stickv, mocker): # QR view is mocked here, no press needed ) ctx = create_ctx(mocker, BTN_SEQUENCE) - ctx.wallet = Wallet(Key(CBC_WORDS, False, NETWORKS["main"])) + ctx.wallet = Wallet(Key(CBC_WORDS, TYPE_SINGLESIG, NETWORKS["main"])) ctx.printer = None storage_ui = EncryptMnemonic(ctx) mocker.patch( diff --git a/tests/pages/test_tiny_seed.py b/tests/pages/test_tiny_seed.py index 0f95a1ba..fcf320f5 100644 --- a/tests/pages/test_tiny_seed.py +++ b/tests/pages/test_tiny_seed.py @@ -39,7 +39,7 @@ def test_export_tiny_seed(m5stickv, mocker): from krux.pages.tiny_seed import TinySeed from krux.input import BUTTON_ENTER from krux.wallet import Wallet - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from embit.networks import NETWORKS BTN_SEQUENCE = [ @@ -50,7 +50,7 @@ def test_export_tiny_seed(m5stickv, mocker): TEST_24_WORD_MNEMONIC = "brush badge sing still venue panther kitchen please help panel bundle excess sign couch stove increase human once effort candy goat top tiny major" # Amount of rectangles filled for this mnemonic + menus FILLED_RECTANGLES = 137 - SINGLESIG_24_WORD_KEY = Key(TEST_24_WORD_MNEMONIC, False, NETWORKS["main"]) + SINGLESIG_24_WORD_KEY = Key(TEST_24_WORD_MNEMONIC, TYPE_SINGLESIG, NETWORKS["main"]) ctx = create_ctx(mocker, BTN_SEQUENCE, Wallet(SINGLESIG_24_WORD_KEY), MockPrinter()) tiny_seed = TinySeed(ctx) tiny_seed.export() diff --git a/tests/test_key.py b/tests/test_key.py index 3a324972..40270985 100644 --- a/tests/test_key.py +++ b/tests/test_key.py @@ -92,7 +92,7 @@ def test_init(mocker, m5stickv, tdata): cases = [ ( - [tdata.TEST_12_WORD_MNEMONIC, False], + [tdata.TEST_12_WORD_MNEMONIC, TYPE_SINGLESIG], { "mnemonic": tdata.TEST_12_WORD_MNEMONIC, "policy_type": TYPE_SINGLESIG, @@ -103,7 +103,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_12_WORD_MNEMONIC, True], + [tdata.TEST_12_WORD_MNEMONIC, TYPE_MULTISIG], { "mnemonic": tdata.TEST_12_WORD_MNEMONIC, "policy_type": TYPE_MULTISIG, @@ -114,7 +114,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_12_WORD_MNEMONIC, False, NETWORKS["main"]], + [tdata.TEST_12_WORD_MNEMONIC, TYPE_SINGLESIG, NETWORKS["main"]], { "mnemonic": tdata.TEST_12_WORD_MNEMONIC, "policy_type": TYPE_SINGLESIG, @@ -125,7 +125,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_12_WORD_MNEMONIC, True, NETWORKS["main"]], + [tdata.TEST_12_WORD_MNEMONIC, TYPE_MULTISIG, NETWORKS["main"]], { "mnemonic": tdata.TEST_12_WORD_MNEMONIC, "policy_type": TYPE_MULTISIG, @@ -136,7 +136,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_24_WORD_MNEMONIC, False], + [tdata.TEST_24_WORD_MNEMONIC, TYPE_SINGLESIG], { "mnemonic": tdata.TEST_24_WORD_MNEMONIC, "policy_type": TYPE_SINGLESIG, @@ -147,7 +147,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_24_WORD_MNEMONIC, True], + [tdata.TEST_24_WORD_MNEMONIC, TYPE_MULTISIG], { "mnemonic": tdata.TEST_24_WORD_MNEMONIC, "policy_type": TYPE_MULTISIG, @@ -158,7 +158,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_24_WORD_MNEMONIC, False, NETWORKS["main"]], + [tdata.TEST_24_WORD_MNEMONIC, TYPE_SINGLESIG, NETWORKS["main"]], { "mnemonic": tdata.TEST_24_WORD_MNEMONIC, "policy_type": TYPE_SINGLESIG, @@ -169,7 +169,7 @@ def test_init(mocker, m5stickv, tdata): }, ), ( - [tdata.TEST_24_WORD_MNEMONIC, True, NETWORKS["main"]], + [tdata.TEST_24_WORD_MNEMONIC, TYPE_MULTISIG, NETWORKS["main"]], { "mnemonic": tdata.TEST_24_WORD_MNEMONIC, "policy_type": TYPE_MULTISIG, @@ -195,9 +195,9 @@ def test_init(mocker, m5stickv, tdata): def test_xpub(mocker, m5stickv, tdata): mock_modules(mocker) - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG - key = Key(tdata.TEST_MNEMONIC, False) + key = Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG) mocker.spy(key.account, "to_base58") assert key.xpub() == tdata.TEST_XPUB @@ -207,9 +207,9 @@ def test_xpub(mocker, m5stickv, tdata): def test_key_expression(mocker, m5stickv, tdata): mock_modules(mocker) import krux - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG - key = Key(tdata.TEST_MNEMONIC, False) + key = Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG) mocker.spy(key.account, "to_base58") cases = [ @@ -227,9 +227,9 @@ def test_key_expression(mocker, m5stickv, tdata): def test_sign(mocker, m5stickv, tdata): mock_modules(mocker) from embit import ec - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG - key = Key(tdata.TEST_MNEMONIC, False) + key = Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG) signature = key.sign(tdata.TEST_HASH) assert isinstance(signature, ec.Signature) @@ -238,9 +238,9 @@ def test_sign(mocker, m5stickv, tdata): def test_sign_fails_with_invalid_hash(mocker, m5stickv, tdata): mock_modules(mocker) - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG - key = Key(tdata.TEST_MNEMONIC, False) + key = Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG) with pytest.raises(ValueError): key.sign(tdata.TEST_INVALID_HASH) diff --git a/tests/test_psbt.py b/tests/test_psbt.py index e26bb36d..461e9975 100644 --- a/tests/test_psbt.py +++ b/tests/test_psbt.py @@ -236,11 +236,11 @@ def tdata(mocker): def test_init_singlesig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR, FORMAT_BBQR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ (tdata.P2PKH_PSBT, FORMAT_NONE), (tdata.P2PKH_PSBT_B43, FORMAT_PMOFN), @@ -273,11 +273,11 @@ def test_init_singlesig(mocker, m5stickv, tdata): def test_init_singlesig_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ (tdata.P2PKH_PSBT, FORMAT_NONE), (tdata.P2WPKH_PSBT, FORMAT_NONE), @@ -294,11 +294,11 @@ def test_init_singlesig_from_sdcard(mocker, m5stickv, tdata): def test_init_empty_file_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) mocker.patch("builtins.open", mock_open(MockFile())) with pytest.raises(ValueError): PSBTSigner(wallet, None, FORMAT_NONE, "dummy.psbt") @@ -307,11 +307,11 @@ def test_init_empty_file_from_sdcard(mocker, m5stickv, tdata): def test_init_multisig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ (tdata.P2WSH_PSBT, FORMAT_NONE), (tdata.P2WSH_PSBT_B43, FORMAT_PMOFN), @@ -333,11 +333,11 @@ def test_init_multisig(mocker, m5stickv, tdata): def test_init_multisig_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ (tdata.P2WSH_PSBT, FORMAT_NONE), (tdata.P2SH_P2WSH_PSBT, FORMAT_NONE), @@ -354,11 +354,11 @@ def test_init_fails_on_invalid_psbt(mocker, m5stickv, tdata): from embit.networks import NETWORKS from ur.ur import UR from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_UR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ ("thisisnotavalidpsbt", FORMAT_NONE), @@ -373,11 +373,11 @@ def test_init_fails_on_invalid_psbt_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from ur.ur import UR from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet - from krux.qr import FORMAT_NONE, FORMAT_UR + from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) mock_file = MockFile("thisisnotavalidpsbt") mocker.patch("builtins.open", return_value=mock_file) @@ -388,11 +388,11 @@ def test_init_fails_on_invalid_psbt_from_sdcard(mocker, m5stickv, tdata): def test_sign_singlesig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR, FORMAT_BBQR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ (tdata.P2PKH_PSBT, FORMAT_NONE, tdata.SIGNED_P2PKH_PSBT), (tdata.P2PKH_PSBT_B43, FORMAT_PMOFN, tdata.SIGNED_P2PKH_PSBT_B43), @@ -440,11 +440,11 @@ def test_sign_singlesig(mocker, m5stickv, tdata): def test_sign_singlesig_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ (tdata.P2PKH_PSBT, FORMAT_NONE, tdata.SIGNED_P2PKH_PSBT), (tdata.P2PKH_PSBT_B64, FORMAT_NONE, tdata.SIGNED_P2PKH_PSBT_B64), @@ -480,11 +480,11 @@ def test_sign_singlesig_from_sdcard(mocker, m5stickv, tdata): def test_sign_multisig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ (tdata.P2WSH_PSBT, FORMAT_NONE, tdata.SIGNED_P2WSH_PSBT), (tdata.P2WSH_PSBT_B43, FORMAT_PMOFN, tdata.SIGNED_P2WSH_PSBT_B43), @@ -513,11 +513,11 @@ def test_sign_multisig(mocker, m5stickv, tdata): def test_sign_multisig_from_sdcard(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ (tdata.P2WSH_PSBT, FORMAT_NONE, tdata.SIGNED_P2WSH_PSBT), (tdata.P2SH_P2WSH_PSBT, FORMAT_NONE, tdata.SIGNED_P2SH_P2WSH_PSBT), @@ -536,11 +536,11 @@ def test_sign_multisig_from_sdcard(mocker, m5stickv, tdata): def test_sign_fails_with_0_sigs_added(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) signer = PSBTSigner(wallet, tdata.P2WSH_PSBT, FORMAT_NONE) mocker.patch.object(signer.psbt, "sign_with", mocker.MagicMock(return_value=0)) @@ -552,11 +552,11 @@ def test_sign_fails_with_0_sigs_added(mocker, m5stickv, tdata): def test_outputs_singlesig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ ( tdata.P2PKH_PSBT, @@ -601,11 +601,11 @@ def test_outputs_singlesig(mocker, m5stickv, tdata): def test_outputs_multisig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ ( tdata.P2WSH_PSBT, @@ -634,11 +634,11 @@ def test_outputs_multisig(mocker, m5stickv, tdata): def test_xpubs_fails_with_no_xpubs(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) with pytest.raises(ValueError): signer = PSBTSigner(wallet, tdata.MISSING_GLOBAL_XPUBS_PSBT, FORMAT_NONE) @@ -648,7 +648,7 @@ def test_xpubs_fails_with_no_xpubs(mocker, m5stickv, tdata): def test_sign_single_1_input_1_output_no_change(m5stickv): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_PMOFN @@ -659,7 +659,7 @@ def test_sign_single_1_input_1_output_no_change(m5stickv): "1. Spend: \n\n2MwEP7AfPt8NC65ACmcUhUtDZgGSxYiWUy4\n\n₿ 0.00 006 000", ] - wallet = Wallet(Key(MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) signer = PSBTSigner(wallet, PSBT_B64, FORMAT_PMOFN) outputs, _ = signer.outputs() assert outputs == OUTPUT @@ -668,7 +668,7 @@ def test_sign_single_1_input_1_output_no_change(m5stickv): def test_path_mismatch(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key, P2PKH, P2WPKH, P2SH_P2WPKH, P2TR, P2WSH + from krux.key import Key, P2WPKH, P2SH_P2WPKH, P2TR, TYPE_SINGLESIG from krux.wallet import Wallet from krux.qr import FORMAT_NONE @@ -733,11 +733,16 @@ def test_path_mismatch(mocker, m5stickv, tdata): for case in cases: if len(case) > 3: wallet = Wallet( - Key(tdata.TEST_MNEMONIC, False, case[3], script_type=case[0]) + Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, case[3], script_type=case[0]) ) else: wallet = Wallet( - Key(tdata.TEST_MNEMONIC, False, NETWORKS["test"], script_type=case[0]) + Key( + tdata.TEST_MNEMONIC, + TYPE_SINGLESIG, + NETWORKS["test"], + script_type=case[0], + ) ) signer = PSBTSigner(wallet, case[1], FORMAT_NONE) path_mismatch = signer.path_mismatch() @@ -747,12 +752,12 @@ def test_path_mismatch(mocker, m5stickv, tdata): def test_sign_sats_vB(m5stickv): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key + from krux.key import Key, TYPE_SINGLESIG, TYPE_MULTISIG from krux.wallet import Wallet from krux.qr import FORMAT_PMOFN MNEMONIC = "action action action action action action action action action action action action" - wallet = Wallet(Key(MNEMONIC, False, NETWORKS["test"])) + wallet = Wallet(Key(MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) PSBT_satvB_1_31 = "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" signer = PSBTSigner(wallet, PSBT_satvB_1_31, FORMAT_PMOFN) @@ -810,7 +815,7 @@ def test_sign_sats_vB(m5stickv): == "Inputs (7): ₿ 0.00 002 100\n\nSpend (3): ₿ 0.00 000 997\n\nFee: ₿ 0.00 001 103 (110.7%) ~1.8 sat/vB" ) - wallet = Wallet(Key(MNEMONIC, True, NETWORKS["test"])) + wallet = Wallet(Key(MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) PSBT_satvB_164_83 = "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" signer = PSBTSigner(wallet, PSBT_satvB_164_83, FORMAT_PMOFN) outputs, _ = signer.outputs() diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 074b3779..6d507fba 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -8,7 +8,15 @@ def tdata(mocker): from collections import namedtuple from ur.ur import UR from embit.networks import NETWORKS - from krux.key import Key, P2PKH, P2SH_P2WPKH, P2TR + from krux.key import ( + Key, + P2PKH, + P2SH_P2WPKH, + P2TR, + TYPE_SINGLESIG, + TYPE_MULTISIG, + TYPE_MINISCRIPT, + ) TEST_MNEMONIC1 = ( "olympic term tissue route sense program under choose bean emerge velvet absurd" @@ -17,14 +25,16 @@ def tdata(mocker): TEST_MNEMONIC3 = "range fatigue into stadium endless kitchen royal present rally welcome scatter twice" SINGLESIG_KEY = Key( - TEST_MNEMONIC1, False, NETWORKS["main"] + TEST_MNEMONIC1, TYPE_SINGLESIG, NETWORKS["main"] ) # default account=0, script=P2WPKH - LEGACY1_KEY = Key(TEST_MNEMONIC1, False, NETWORKS["main"], "", 1, P2PKH) - NESTEDSW1_KEY = Key(TEST_MNEMONIC1, False, NETWORKS["main"], "", 1, P2SH_P2WPKH) - TAPROOT1_KEY = Key(TEST_MNEMONIC1, False, NETWORKS["main"], "", 1, P2TR) - MULTISIG_KEY1 = Key(TEST_MNEMONIC1, True, NETWORKS["main"]) - MULTISIG_KEY2 = Key(TEST_MNEMONIC2, True, NETWORKS["main"]) - MULTISIG_KEY3 = Key(TEST_MNEMONIC3, True, NETWORKS["main"]) + LEGACY1_KEY = Key(TEST_MNEMONIC1, TYPE_SINGLESIG, NETWORKS["main"], "", 1, P2PKH) + NESTEDSW1_KEY = Key( + TEST_MNEMONIC1, TYPE_SINGLESIG, NETWORKS["main"], "", 1, P2SH_P2WPKH + ) + TAPROOT1_KEY = Key(TEST_MNEMONIC1, TYPE_SINGLESIG, NETWORKS["main"], "", 1, P2TR) + MULTISIG_KEY1 = Key(TEST_MNEMONIC1, TYPE_MULTISIG, NETWORKS["main"]) + MULTISIG_KEY2 = Key(TEST_MNEMONIC2, TYPE_MULTISIG, NETWORKS["main"]) + MULTISIG_KEY3 = Key(TEST_MNEMONIC3, TYPE_MULTISIG, NETWORKS["main"]) # SINGLESIG_KEY [55f8fc5d/84h/0h/0h]xpub6DPMTPxGMqdtzMwpqT1dDQaVdyaEppEm2qYSaJ7ANsuES7HkNzrXJst1Ed8D7NAnijUdgSDUFgph1oj5LKKAD5gyxWNhNP2AuDqaKYqzphA # MULTISIG_KEY1 [55f8fc5d/48h/0h/0h/2h]xpub6EKmKYGYc1WY6t9d3d9SksR8keSaPZbFa6tqsGiH4xVxx8d2YyxSX7WG6yXEX3CmG54dPCxaapDw1XsjwCmfoqP7tbsAeqMVfKvqSAu4ndy From e9574e224847f6a6e3d607a1cd1b07f33c426480 Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 31 Dec 2024 17:17:13 -0300 Subject: [PATCH 12/34] add some wallet and descriptor tests --- src/krux/wallet.py | 4 + tests/test_wallet.py | 211 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 211 insertions(+), 4 deletions(-) diff --git a/src/krux/wallet.py b/src/krux/wallet.py index 44ac26f0..827c0701 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -215,6 +215,10 @@ def wallet_qr(self): def obtain_addresses(self, i=0, limit=None, branch_index=0): """Returns an iterator deriving addresses (default branch_index is receive) for the wallet up to the provided limit""" + + if self.descriptor is None: + raise ValueError("No descriptor to derive addresses from") + starting_index = i while limit is None or i < starting_index + limit: diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 6d507fba..52670b50 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -35,6 +35,10 @@ def tdata(mocker): MULTISIG_KEY1 = Key(TEST_MNEMONIC1, TYPE_MULTISIG, NETWORKS["main"]) MULTISIG_KEY2 = Key(TEST_MNEMONIC2, TYPE_MULTISIG, NETWORKS["main"]) MULTISIG_KEY3 = Key(TEST_MNEMONIC3, TYPE_MULTISIG, NETWORKS["main"]) + MINISCRIPT_KEY = Key(TEST_MNEMONIC1, TYPE_MINISCRIPT, NETWORKS["main"]) + TAP_MINISCRIPT_KEY = Key( + TEST_MNEMONIC1, TYPE_MINISCRIPT, NETWORKS["main"], script_type=P2TR + ) # SINGLESIG_KEY [55f8fc5d/84h/0h/0h]xpub6DPMTPxGMqdtzMwpqT1dDQaVdyaEppEm2qYSaJ7ANsuES7HkNzrXJst1Ed8D7NAnijUdgSDUFgph1oj5LKKAD5gyxWNhNP2AuDqaKYqzphA # MULTISIG_KEY1 [55f8fc5d/48h/0h/0h/2h]xpub6EKmKYGYc1WY6t9d3d9SksR8keSaPZbFa6tqsGiH4xVxx8d2YyxSX7WG6yXEX3CmG54dPCxaapDw1XsjwCmfoqP7tbsAeqMVfKvqSAu4ndy @@ -167,6 +171,9 @@ def tdata(mocker): UNSORTED_MULTISIG_DESCRIPTOR = "wsh(multi(2,[3e15470d/48h/0h/0h/2h]xpub6F2P6Pz5KLPgCc6pTBd2xxCunaSYWc8CdkL28W5z15pJrN3aCYY7mCUAkCMtqrgT2wdhAGgRnJxAkCCUpGKoXKxQ57yffEGmPwtYA3DEXwu/<0;1>/*,[55f8fc5d/48h/0h/0h/2h]xpub6EKmKYGYc1WY6t9d3d9SksR8keSaPZbFa6tqsGiH4xVxx8d2YyxSX7WG6yXEX3CmG54dPCxaapDw1XsjwCmfoqP7tbsAeqMVfKvqSAu4ndy/<0;1>/*,[d3a80c8b/48h/0h/0h/2h]xpub6FKYY6y3oVi7ihSCszFKRSeZj5SzrfSsUFXhKqjMV4iigrLhxwMX3mrjioNyLTZ5iD3u4wU9S3tyzpJGxhd5geaXoQ68jGz2M6dfh2zJrUv/<0;1>/*))" + LIANA_MINISCRIPT_DESCRIPTOR = "wsh(or_d(pk([55f8fc5d/48'/0'/0'/2']xpub6EKmKYGYc1WY6t9d3d9SksR8keSaPZbFa6tqsGiH4xVxx8d2YyxSX7WG6yXEX3CmG54dPCxaapDw1XsjwCmfoqP7tbsAeqMVfKvqSAu4ndy/<0;1>/*),and_v(v:pkh([3e15470d/48'/0'/0'/2']xpub6F2P6Pz5KLPgCc6pTBd2xxCunaSYWc8CdkL28W5z15pJrN3aCYY7mCUAkCMtqrgT2wdhAGgRnJxAkCCUpGKoXKxQ57yffEGmPwtYA3DEXwu/<0;1>/*),older(6))))#x09nw3rv" + LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR = "tr([55f8fc5d/48'/0'/0'/2']xpub6EKmKYGYc1WY6t9d3d9SksR8keSaPZbFa6tqsGiH4xVxx8d2YyxSX7WG6yXEX3CmG54dPCxaapDw1XsjwCmfoqP7tbsAeqMVfKvqSAu4ndy/<0;1>/*,and_v(v:pk([3e15470d/48'/0'/0'/2']xpub6F2P6Pz5KLPgCc6pTBd2xxCunaSYWc8CdkL28W5z15pJrN3aCYY7mCUAkCMtqrgT2wdhAGgRnJxAkCCUpGKoXKxQ57yffEGmPwtYA3DEXwu/<0;1>/*),older(6)))#qjluv5ue" + return namedtuple( "TestData", [ @@ -180,6 +187,8 @@ def tdata(mocker): "MULTISIG_KEY1", "MULTISIG_KEY2", "MULTISIG_KEY3", + "MINISCRIPT_KEY", + "TAP_MINISCRIPT_KEY", "KRUX_LEGACY1_DESCRIPTOR", "KRUX_LEGACY1_XPUB", "KRUX_NESTEDSW1_DESCRIPTOR", @@ -218,6 +227,8 @@ def tdata(mocker): "UNRELATED_SINGLESIG_DESCRIPTOR", "UNRELATED_MULTISIG_DESCRIPTOR", "UNSORTED_MULTISIG_DESCRIPTOR", + "LIANA_MINISCRIPT_DESCRIPTOR", + "LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR", ], )( TEST_MNEMONIC1, @@ -230,6 +241,8 @@ def tdata(mocker): MULTISIG_KEY1, MULTISIG_KEY2, MULTISIG_KEY3, + MINISCRIPT_KEY, + TAP_MINISCRIPT_KEY, KRUX_LEGACY1_DESCRIPTOR, KRUX_LEGACY1_XPUB, KRUX_NESTEDSW1_DESCRIPTOR, @@ -268,6 +281,8 @@ def tdata(mocker): UNRELATED_SINGLESIG_DESCRIPTOR, UNRELATED_MULTISIG_DESCRIPTOR, UNSORTED_MULTISIG_DESCRIPTOR, + LIANA_MINISCRIPT_DESCRIPTOR, + LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR, ) @@ -323,7 +338,6 @@ def test_init_singlesig(mocker, m5stickv, tdata): def test_init_multisig(mocker, m5stickv, tdata): from krux.wallet import Wallet - from krux.qr import FORMAT_NONE wallet = Wallet(tdata.MULTISIG_KEY1) assert isinstance(wallet, Wallet) @@ -338,22 +352,116 @@ def test_init_multisig(mocker, m5stickv, tdata): assert wallet.policy is None +def test_init_miniscript(mocker, m5stickv, tdata): + from krux.wallet import Wallet + + wallet = Wallet(tdata.MINISCRIPT_KEY) + assert isinstance(wallet, Wallet) + assert wallet.descriptor is None + assert wallet.label is None + assert wallet.policy is None + + def test_is_multisig(mocker, m5stickv, tdata): from krux.wallet import Wallet + from krux.qr import FORMAT_NONE + # Non multisig keys wallet = Wallet(tdata.SINGLESIG_KEY) assert not wallet.is_multisig() + wallet = Wallet(tdata.TAPROOT1_KEY) + assert not wallet.is_multisig() + + wallet = Wallet(tdata.MINISCRIPT_KEY) + assert not wallet.is_multisig() + + # Multisig key wallet = Wallet(tdata.MULTISIG_KEY1) assert wallet.is_multisig() + # Multisig key with loaded descriptor + wallet.load(tdata.SPECTER_MULTISIG_DESCRIPTOR, FORMAT_NONE) + assert wallet.is_multisig() + + # No key, no descriptor wallet = Wallet(None) assert not wallet.is_multisig() - from krux.qr import FORMAT_NONE + # Sans key: Descriptor only, no key loaded + # Multisig descriptor wallet.load(tdata.SPECTER_MULTISIG_DESCRIPTOR, FORMAT_NONE) assert wallet.is_multisig() + # Single-sig descriptor + wallet = Wallet(None) + wallet.load(tdata.UNAMBIGUOUS_SINGLESIG_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_multisig() + + # Taproot single-sig descriptor + wallet = Wallet(None) + wallet.load(tdata.KRUX_TAPROOT1_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_multisig() + + # Miniscript descriptor + wallet = Wallet(None) + wallet.load(tdata.LIANA_MINISCRIPT_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_multisig() + + # Taproot miniscript descriptor + wallet = Wallet(None) + wallet.load(tdata.LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_multisig() + + +def test_is_miniscript(mocker, m5stickv, tdata): + from krux.wallet import Wallet + from krux.qr import FORMAT_NONE + + # Non multisig keys + wallet = Wallet(tdata.SINGLESIG_KEY) + assert not wallet.is_miniscript() + + wallet = Wallet(tdata.TAPROOT1_KEY) + assert not wallet.is_miniscript() + + wallet = Wallet(tdata.MULTISIG_KEY1) + assert not wallet.is_miniscript() + + # Miniscript key + wallet = Wallet(tdata.MINISCRIPT_KEY) + assert wallet.is_miniscript() + + # Miniscript key with loaded descriptor + wallet.load(tdata.LIANA_MINISCRIPT_DESCRIPTOR, FORMAT_NONE) + assert wallet.is_miniscript() + + # Taproot miniscript key with loaded descriptor + wallet = Wallet(tdata.TAP_MINISCRIPT_KEY) + wallet.load(tdata.LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR, FORMAT_NONE) + assert wallet.is_miniscript() + + # No key and no descriptor + wallet = Wallet(None) + assert not wallet.is_miniscript() + + # Non miniscript descriptors + wallet.load(tdata.SPECTER_MULTISIG_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_miniscript() + + wallet = Wallet(None) + wallet.load(tdata.UNAMBIGUOUS_SINGLESIG_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_miniscript() + + wallet = Wallet(None) + wallet.load(tdata.KRUX_TAPROOT1_DESCRIPTOR, FORMAT_NONE) + assert not wallet.is_miniscript() + + # Miniscript descriptor + wallet = Wallet(None) + wallet.load(tdata.LIANA_MINISCRIPT_DESCRIPTOR, FORMAT_NONE) + assert wallet.is_miniscript() + def test_is_loaded(mocker, m5stickv, tdata): from krux.wallet import Wallet @@ -388,9 +496,10 @@ def test_wallet_qr(mocker, m5stickv, tdata): def test_receive_addresses(mocker, m5stickv, tdata): from krux.wallet import Wallet from krux.qr import FORMAT_PMOFN + from krux.key import TYPE_SINGLESIG cases = [ - ( + ( # Native Segwit tdata.SINGLESIG_KEY, tdata.SPECTER_SINGLESIG_WALLET_DATA, FORMAT_PMOFN, @@ -407,7 +516,58 @@ def test_receive_addresses(mocker, m5stickv, tdata): "bc1q6mlxs236fmyeteummv9x5pmxteh86lkln4emag", ], ), - ( + ( # Legacy Single-sig + tdata.LEGACY1_KEY, + tdata.KRUX_LEGACY1_DESCRIPTOR, + FORMAT_PMOFN, + [ + "1GsaC1sVGGnN7KpYCHUMR4Z8c9F3BzfUAL", + "1CSSWcbgAPNsB5twS2TfACtCqC6PfahgnM", + "1E9U3icgCLbDqEhS4xqtopvXYtRU4UmSoU", + "17mTKjHWb1LNKjEdUUmVXsDFUcEmqWzSw6", + "16Sg1PLRfEoGr2YsH93v3xAfXSTf42vcHi", + "13rNEribNgeARh7p58FXKJxSRYcVg3i5VJ", + "1Ler4fyDfDq23dMbJ1WXRoYr9EcL6QX8KN", + "15dihUfEHdC21UaJhneQFUfPywonD9ZuJe", + "1MSN9AjP8GourmFEmtqTjukqV9Jp2vzgVS", + "1CoSqz4K5xEysnBoPPLukp4gmh2dnx11Wj", + ], + ), + ( # Nested Segwit Single-sig + tdata.NESTEDSW1_KEY, + tdata.KRUX_NESTEDSW1_DESCRIPTOR, + FORMAT_PMOFN, + [ + "3HN5XxXfK5kNNu83GSVc1tAu3CVpBjghcS", + "395RjbYLhWFWC2LarYkDk41BxxiBiJSWEb", + "3EuvWJNACMVswiczged3bHnWjywKA3n3WQ", + "3EZtai8s84WG8MvcUw3xnTHnWKiQUJBLr7", + "36eFaNxjFBTZUHFWHRcKacppmdFy4X1MKR", + "38p4zYbK1KC4KEkid1tVkHr2ruARTCKBJK", + "3P8PcnaFeHn8FCoFwoxPKFGCTzmxXnLJEx", + "33aw8rJwuxtexN8nZ6YiYvJSoNUAeaY8FM", + "35V6by4xd9owyHaZiBhueKCGMUecV9K4d8", + "33eCmbdSneaxYarxtX9mdJpvyoG1NWaKPj", + ], + ), + ( # Taproot Single-sig + tdata.TAPROOT1_KEY, + tdata.KRUX_TAPROOT1_DESCRIPTOR, + FORMAT_PMOFN, + [ + "bc1pkusdqe839xltdn5jk62rv0cx6c4nrrw3hr8rmf3478tnn88qs2ls4g77cl", + "bc1pzlnrsqze6lwcl9w9z5n9x8dfejf47mrdhv5t8pp4gjehe535xftqkwp635", + "bc1pxz7j9cakl53a72hg08g0ug8gtsy9cf86fmh5mtjucyp707jm7mjsx8lagh", + "bc1p2aqrr4wctz0mhcnn4tpcrg0r89pnn7yqqnv8xpeqr3sckkp3s3fqnnpkgh", + "bc1p3tdqgfjk2zn88jshtj698vfphzn8t3lr4l6qhpf38yyvcpevkfeql3p2fs", + "bc1paa4rtwsdf7z9p22wassvhj6zk5zs94xp0rpcrq2452xwzmgejxpquat7gs", + "bc1p93pdqw9v2237pn2xzyks0dd3mnpnf0kagx0hl44u2msz23425w0s68s658", + "bc1pmejm3jyp6d287dn4lx68j966y0783rvwfujus8s3h9dmedfdm98s7xglv7", + "bc1p9dvlkmfzl8qmax47lg22rfluztc0ev7lvs3rl838y0s96p65mwusz08lhv", + "bc1p8fue49l0amhqvsau72gmk3dau2w5ktxkh2njck95rej0v538wj4q40gksu", + ], + ), + ( # Multisig WSH tdata.MULTISIG_KEY1, tdata.SPECTER_MULTISIG_WALLET_DATA, FORMAT_PMOFN, @@ -424,10 +584,53 @@ def test_receive_addresses(mocker, m5stickv, tdata): "bc1qgzqffn9f4fal2ld04wdafakvgrl5gexp744gm8eg58j7a633lf9slzcjup", ], ), + ( # Miniscript WSH + tdata.MINISCRIPT_KEY, + tdata.LIANA_MINISCRIPT_DESCRIPTOR, + FORMAT_PMOFN, + [ + "bc1q3qp2kgjxkmh6dptyppvptjyg8wlvvddyec0cfgpwftn4nyjs8jpslyaynm", + "bc1qt2j45nvg7rup8h73t5syuaxuvzqr4wk3lqdpjw3ew2afdc7ggzlqspnqwj", + "bc1qvs22y63jdy0fdmgesht3yn3d7ruvuahf73yqmuhhx5w6trj6fy6syugd43", + "bc1qckd42m2mttn3gmz2my7c3flj9uujq0ekpkuxqjj2m2lnwgkrzwuqq8ke23", + "bc1qq4lhujwh4rhxmu82svy3cz97h7ays9j2wjmtw2ju8sqynx88pxys2fud4h", + "bc1q8jm9g8t96g8m75ce6uvq2lhp5snfnzmumdq3qh04v8jt5w7dlmvqfjs8f2", + "bc1qa8tc0pwljncccdauv37vka3kxfus9pssn62dwvvjjhye3h0egsjsj2eu27", + "bc1q5rehtcjd7xw9pfv72lnpv7rrtr4pjh93afr0y60hrluauzn2y83qc8p4fe", + "bc1qmz9v9z0acnd7qehmvqje2m7qmr9ytus87xyk8nprzkypg8gy4zksem4dfz", + "bc1qncuaqah98hypkxnpp465mqw7srjgm67s70gkzcs693kv9zp3dv8qa5w8fj", + ], + ), + ( # Miniscript TR + tdata.TAP_MINISCRIPT_KEY, + tdata.LIANA_TAPROOT_MINISCRIPT_DESCRIPTOR, + FORMAT_PMOFN, + [ + "bc1pq87pvf85eu3stcxgemm2gppt5w99uxgm75fv3l62lnmjaewuaw0qjpeda6", + "bc1purr3u8x4j0ean426yf09ap2xu5xvehyrlh9s3uqxsf3e2w9pxkjsaf9pum", + "bc1pw83dp6vq7tn7965jxxavkd5fa04y8vpz9njtg7ms8ng9l3u0d0uqnqssfa", + "bc1pef8a3pmsdee6vlup2g3mg5903zxq5lykvpwm4v5f4z0swefdrswsle9sy8", + "bc1pfxmm35spmp9s9jstmvwd0vjksura79ez5f68q6c5gewh6q77jwqs7geafk", + "bc1pwwntw7cplgueq5kulek7mqqqr5s6uatd6psgnclp2a8c3rukxgzqmhszr7", + "bc1pc0m0kvav5sddu5a05chzqk9de9z324teh7cdtem9kly6snw8fz4qpp5uwu", + "bc1pedvnetmy08ekk9j3qyw3zussmxy0lc3tlzmvs9qmnxm9yqq827dqhzjv69", + "bc1p40d9u4g7wgnggrykeghztla4l78k38nkc3ekrghsp6evp2awj72slfa8qc", + "bc1pvpwmch3gjcrer395wzswawnfuer4crz7x6vt9g845jqklvgvydkq0hm8gj", + ], + ), ] for case in cases: wallet = Wallet(case[0]) + # Check addresses before descriptor is loadaded in case it is single-sig + if wallet.key.policy_type == TYPE_SINGLESIG: + assert [addr for addr in wallet.obtain_addresses(0, limit=10)] == case[3] + else: + # Ensure an error is raised as the descriptor is not loaded + with pytest.raises(ValueError): + [addr for addr in wallet.obtain_addresses(0, limit=10)] + + # Check addresses after descriptor is loaded wallet.load(case[1], case[2]) assert [addr for addr in wallet.obtain_addresses(0, limit=10)] == case[3] From eeec7df4aab7a9b30d26943360564ad2ce1a93ba Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 2 Jan 2025 23:04:45 -0300 Subject: [PATCH 13/34] path mismatch only checks matching key --- src/krux/psbt.py | 14 ++++++++++---- tests/pages/home_pages/test_home.py | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 2223530d..f226850f 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -199,7 +199,7 @@ def get_policy_from_psbt_input(self, tx_input, xpubs): return get_policy(tx_input, scriptpubkey, xpubs) def path_mismatch(self): - """Verifies if the PSBT path matches wallet's derivation path""" + """Verifies if the PSBT key path matches loaded keys's derivation path""" mismatched_paths = [] der_path_nodes = len(self.wallet.key.derivation.split("/")) - 1 for _input in self.psbt.inputs: @@ -208,10 +208,16 @@ def path_mismatch(self): else: derivations = _input.bip32_derivations for pubkey in derivations: + # Checks if fingerprint belongs to loaded key if self.policy["type"] == P2TR: - derivation_path = derivations[pubkey][ - 1 - ].derivation # ignore taproot leaf + fingerprint = derivations[pubkey][1].fingerprint + else: + fingerprint = derivations[pubkey].fingerprint + if fingerprint != self.wallet.key.fingerprint: + # Not our key, won't check derivation path mismatch + continue + if self.policy["type"] == P2TR: + derivation_path = derivations[pubkey][1].derivation else: derivation_path = derivations[pubkey].derivation textual_path = "m" diff --git a/tests/pages/home_pages/test_home.py b/tests/pages/home_pages/test_home.py index 15d07290..696824d2 100644 --- a/tests/pages/home_pages/test_home.py +++ b/tests/pages/home_pages/test_home.py @@ -856,7 +856,6 @@ def test_sign_wrong_key(mocker, m5stickv, tdata): btn_seq = [ BUTTON_ENTER, # Load from QR code - BUTTON_ENTER, # Path mismatch ACK BUTTON_ENTER, # PSBT resume BUTTON_ENTER, # output 1 BUTTON_ENTER, # output 2 @@ -898,7 +897,6 @@ def test_sign_zeroes_fingerprint(mocker, m5stickv, tdata): btn_seq = [ BUTTON_ENTER, # Load from QR code - BUTTON_ENTER, # Path mismatch ACK BUTTON_ENTER, # Confirm fingerprint missing BUTTON_ENTER, # PSBT resume BUTTON_ENTER, # output 1 From 965dbd7227cd0242320df66b427f8ff4bd46e314 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 2 Jan 2025 23:07:14 -0300 Subject: [PATCH 14/34] wallet descriptor loading - highlight loaded key among cosigners --- .../pages/home_pages/wallet_descriptor.py | 78 ++++++++++++------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index 2d8c2dbd..42794c0a 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -26,12 +26,12 @@ LOAD_FROM_CAMERA, LOAD_FROM_SD, ) -from ...display import DEFAULT_PADDING, BOTTOM_PROMPT_LINE +from ...display import DEFAULT_PADDING, BOTTOM_PROMPT_LINE, FONT_HEIGHT from ...krux_settings import t from ...qr import FORMAT_NONE from ...sd_card import DESCRIPTOR_FILE_EXTENSION, JSON_FILE_EXTENSION from ...themes import theme -from ...key import FINGERPRINT_SYMBOL +from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL, TYPE_SINGLESIG class WalletDescriptor(Page): @@ -151,43 +151,69 @@ def display_wallet(self, wallet, is_loading=False): which will contain the same data as was originally loaded, in the same QR format """ - import binascii - about = [wallet.label] - fingerprints = [] + offset_y = DEFAULT_PADDING + self.ctx.display.draw_hcentered_text(wallet.label, offset_y) + offset_y += (3 * FONT_HEIGHT) // 2 for i, key in enumerate(wallet.descriptor.keys): - label = str(i + 1) + ". " if wallet.is_multisig() else "" - fingerprints.append( - label - + FINGERPRINT_SYMBOL - + " " - + ( - binascii.hexlify(key.origin.fingerprint).decode() - if key.origin - else t("unknown") - ) + label_color = theme.fg_color + if wallet.is_multisig() or wallet.is_miniscript(): + label = chr(65 + i) + ": " + else: + label = "" + key_fingerprint = FINGERPRINT_SYMBOL + " " + if key.origin: + key_origin_str = str(key.origin) + key_fingerprint += key_origin_str[:8] + else: + key_fingerprint += t("unknown") + if ( + self.ctx.wallet.key + and key.fingerprint == self.ctx.wallet.key.fingerprint + ): + label_color = theme.highlight_color + self.ctx.display.draw_string( + DEFAULT_PADDING, + offset_y, + label + key_fingerprint, + label_color, ) - about.extend(fingerprints) + offset_y += FONT_HEIGHT + if key.origin: + key_derivation_str = " " * 3 + key_derivation_str += DERIVATION_PATH_SYMBOL + key_derivation_str += " " + key_derivation_str += key_origin_str[8:] + self.ctx.display.draw_string( + DEFAULT_PADDING, + offset_y, + key_derivation_str, + label_color, + ) + offset_y += (FONT_HEIGHT * 3) // 2 + if not wallet.is_multisig() and not wallet.is_miniscript(): - about.append(self.fit_to_line(str(wallet.descriptor.keys[0].key))) + about = self.fit_to_line(str(wallet.descriptor.keys[0].key)) if is_loading: - self.ctx.display.draw_hcentered_text(about, offset_y=DEFAULT_PADDING) + self.ctx.display.draw_hcentered_text(about, offset_y) else: wallet_data, qr_format = wallet.wallet_qr() self.display_qr_codes(wallet_data, qr_format, title=about) else: - # Display fingerprints - self.ctx.display.draw_hcentered_text(about, offset_y=DEFAULT_PADDING) + + # Wait user acknowledge fingerprints self.ctx.input.wait_for_button() # Display XPUBs - about = [wallet.label] - xpubs = [] - for i, xpub in enumerate(wallet.policy["cosigners"]): - xpubs.append(self.fit_to_line(xpub, str(i + 1) + ". ")) - about.extend(xpubs) self.ctx.display.clear() - self.ctx.display.draw_hcentered_text(about, offset_y=DEFAULT_PADDING) + offset_y = DEFAULT_PADDING + self.ctx.display.draw_hcentered_text(wallet.label, offset_y) + for i, xpub in enumerate(wallet.policy["cosigners"]): + offset_y += FONT_HEIGHT + self.ctx.display.draw_hcentered_text( + self.fit_to_line(xpub, chr(65 + i) + ". "), offset_y + ) + if is_loading: # Skip the QR code if we're loading the wallet return From 2f4d97232c726be61f59e2a0caacbc265da99362 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 3 Jan 2025 17:07:16 -0300 Subject: [PATCH 15/34] miniscript descriptos vis - fix fingerprint and xpubs correlation --- .../pages/home_pages/wallet_descriptor.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index 42794c0a..22c5fd14 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -31,7 +31,7 @@ from ...qr import FORMAT_NONE from ...sd_card import DESCRIPTOR_FILE_EXTENSION, JSON_FILE_EXTENSION from ...themes import theme -from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL, TYPE_SINGLESIG +from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL class WalletDescriptor(Page): @@ -106,6 +106,8 @@ def _load_wallet(self): else: # Cancel return MENU_CONTINUE + self.ctx.display.clear() + self.ctx.display.draw_centered_text(t("Processing..")) if wallet_data is None: # Camera or SD card loading failed! self.flash_error(t("Failed to load")) @@ -155,6 +157,7 @@ def display_wallet(self, wallet, is_loading=False): offset_y = DEFAULT_PADDING self.ctx.display.draw_hcentered_text(wallet.label, offset_y) offset_y += (3 * FONT_HEIGHT) // 2 + our_key_index = None for i, key in enumerate(wallet.descriptor.keys): label_color = theme.fg_color if wallet.is_multisig() or wallet.is_miniscript(): @@ -172,6 +175,7 @@ def display_wallet(self, wallet, is_loading=False): and key.fingerprint == self.ctx.wallet.key.fingerprint ): label_color = theme.highlight_color + our_key_index = i self.ctx.display.draw_string( DEFAULT_PADDING, offset_y, @@ -192,6 +196,14 @@ def display_wallet(self, wallet, is_loading=False): ) offset_y += (FONT_HEIGHT * 3) // 2 + # Checks if there's room for another key + if offset_y + (FONT_HEIGHT * 3) > self.ctx.display.height(): + self.ctx.input.wait_for_button() + self.ctx.display.clear() + offset_y = DEFAULT_PADDING + self.ctx.display.draw_hcentered_text(wallet.label, offset_y) + offset_y += (3 * FONT_HEIGHT) // 2 + if not wallet.is_multisig() and not wallet.is_miniscript(): about = self.fit_to_line(str(wallet.descriptor.keys[0].key)) if is_loading: @@ -208,11 +220,24 @@ def display_wallet(self, wallet, is_loading=False): self.ctx.display.clear() offset_y = DEFAULT_PADDING self.ctx.display.draw_hcentered_text(wallet.label, offset_y) - for i, xpub in enumerate(wallet.policy["cosigners"]): + for i, key in enumerate(wallet.descriptor.keys): offset_y += FONT_HEIGHT + # Checks is xpub belongs to the current wallet + if i == our_key_index: + label_color = theme.highlight_color + else: + label_color = theme.fg_color self.ctx.display.draw_hcentered_text( - self.fit_to_line(xpub, chr(65 + i) + ". "), offset_y + self.fit_to_line(key.key.to_base58(), chr(65 + i) + ". "), + offset_y, + label_color, ) + # Checks if there's room for another xpub + if offset_y + FONT_HEIGHT > self.ctx.display.height(): + self.ctx.input.wait_for_button() + self.ctx.display.clear() + offset_y = DEFAULT_PADDING + self.ctx.display.draw_hcentered_text(wallet.label, offset_y) if is_loading: # Skip the QR code if we're loading the wallet From bbb1d4d7610b8433cd21fb7b6bec9b267c9e6037 Mon Sep 17 00:00:00 2001 From: odudex Date: Sun, 5 Jan 2025 15:41:55 -0300 Subject: [PATCH 16/34] miniscript - allow custom derivations --- src/krux/key.py | 15 ++++-- src/krux/pages/home_pages/home.py | 5 +- src/krux/pages/login.py | 21 +++++--- src/krux/pages/wallet_settings.py | 88 ++++++++++++++++++++++++------- 4 files changed, 97 insertions(+), 32 deletions(-) diff --git a/src/krux/key.py b/src/krux/key.py index e0237992..e4fa8bba 100644 --- a/src/krux/key.py +++ b/src/krux/key.py @@ -42,7 +42,6 @@ DER_SINGLE = "m/%dh/%dh/%dh" DER_MULTI = "m/%dh/%dh/%dh/2h" DER_MINISCRIPT = "m/%dh/%dh/%dh/2h" -HARDENED_STR_REPLACE = "'" # Pay To Public Key Hash - 44' Legacy single-sig # address starts with 1 (mainnet) or m (testnet) @@ -114,6 +113,7 @@ def __init__( passphrase="", account_index=0, script_type=P2WPKH, + custom_derivation="", ): self.mnemonic = mnemonic self.policy_type = policy_type @@ -129,9 +129,14 @@ def __init__( bip39.mnemonic_to_seed(mnemonic, passphrase), version=network["xprv"] ) self.fingerprint = self.root.child(0).fingerprint - self.derivation = self.get_default_derivation( - self.policy_type, self.network, self.account_index, self.script_type - ) + if not custom_derivation: + self.derivation = self.get_default_derivation( + self.policy_type, self.network, self.account_index, self.script_type + ) + self.custom_derivation = False + else: + self.derivation = custom_derivation + self.custom_derivation = True self.account = self.root.derive(self.derivation).to_public() def xpub(self, version=None): @@ -218,7 +223,7 @@ def get_default_derivation(policy_type, network, account=0, script_type=P2WPKH): def format_derivation(derivation, pretty=False): """Helper method to display the derivation path formatted""" formatted_txt = DERIVATION_PATH_SYMBOL + THIN_SPACE + "%s" if pretty else "%s" - return (formatted_txt % derivation).replace("h", HARDENED_STR_REPLACE) + return formatted_txt % derivation @staticmethod def format_fingerprint(fingerprint, pretty=False): diff --git a/src/krux/pages/home_pages/home.py b/src/krux/pages/home_pages/home.py index 2f067d9d..ed764e59 100644 --- a/src/krux/pages/home_pages/home.py +++ b/src/krux/pages/home_pages/home.py @@ -131,8 +131,8 @@ def customize(self): from ...wallet import Wallet wallet_settings = WalletSettings(self.ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - self.ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(self.ctx.wallet.key) ) mnemonic = self.ctx.wallet.key.mnemonic passphrase = self.ctx.wallet.key.passphrase @@ -144,6 +144,7 @@ def customize(self): passphrase, account, script_type, + derivation_path, ) ) return MENU_CONTINUE diff --git a/src/krux/pages/login.py b/src/krux/pages/login.py index c0e4dcd9..234f5818 100644 --- a/src/krux/pages/login.py +++ b/src/krux/pages/login.py @@ -45,6 +45,7 @@ TYPE_MULTISIG, TYPE_MINISCRIPT, POLICY_TYPE_IDS, + DERIVATION_PATH_SYMBOL, ) from ..krux_settings import t from ..settings import NAME_SINGLE_SIG, NAME_MULTISIG, NAME_MINISCRIPT @@ -296,11 +297,21 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): script_type = P2TR else: script_type = P2WSH + derivation_path = "" from ..wallet import Wallet while True: - key = Key(mnemonic, policy_type, network, passphrase, account, script_type) - + key = Key( + mnemonic, + policy_type, + network, + passphrase, + account, + script_type, + derivation_path, + ) + if not derivation_path: + derivation_path = key.derivation wallet_info = key.fingerprint_hex_str(True) + "\n" wallet_info += network["name"] + "\n" if policy_type == TYPE_SINGLESIG: @@ -311,9 +322,7 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): if script_type == P2TR: wallet_info += "TR " wallet_info += NAME_MINISCRIPT + "\n" - wallet_info += ( - self.fit_to_line(key.derivation_str(True), crop_middle=False) + "\n" - ) + wallet_info += key.derivation_str(True) + "\n" wallet_info += ( t("No Passphrase") if not passphrase else t("Passphrase") + ": *..*" ) @@ -349,7 +358,7 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): from .wallet_settings import WalletSettings wallet_settings = WalletSettings(self.ctx) - network, policy_type, script_type, account = ( + network, policy_type, script_type, account, derivation_path = ( wallet_settings.customize_wallet(key) ) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index 10e2bd22..567f26c1 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -43,6 +43,7 @@ TYPE_SINGLESIG, TYPE_MULTISIG, TYPE_MINISCRIPT, + DERIVATION_PATH_SYMBOL, ) from ..settings import ( MAIN_TXT, @@ -54,6 +55,9 @@ from ..key import P2PKH, P2SH_P2WPKH, P2WPKH, P2WSH, P2TR PASSPHRASE_MAX_LEN = 200 +DERIVATION_KEYPAD = "123456789/0h" + +MINISCRIPT_DEFAULT_DERIVATION = "m/48h/0h/0h/2h" class PassphraseEditor(Page): @@ -122,6 +126,7 @@ def customize_wallet(self, key): policy_type = key.policy_type script_type = key.script_type account = key.account_index + custom_derivation = key.derivation if key.custom_derivation else None while True: wallet_info = network["name"] + "\n" if policy_type == TYPE_SINGLESIG: @@ -131,20 +136,24 @@ def customize_wallet(self, key): elif policy_type == TYPE_MINISCRIPT: wallet_info += NAME_MINISCRIPT + "\n" wallet_info += str(script_type).upper() + "\n" - derivation_path = "m/" - if policy_type == TYPE_SINGLESIG: - derivation_path += str(SINGLESIG_SCRIPT_PURPOSE[script_type]) - elif policy_type == TYPE_MULTISIG: - derivation_path += str(MULTISIG_SCRIPT_PURPOSE) - elif policy_type == TYPE_MINISCRIPT: - # For now, miniscript is the same as multisig - derivation_path += str(MINISCRIPT_PURPOSE) - derivation_path += "'/" - derivation_path += "0'" if network == NETWORKS[MAIN_TXT] else "1'" - derivation_path += "/" + str(account) + "'" - if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): - derivation_path += "/2'" - wallet_info += derivation_path + if policy_type != TYPE_MINISCRIPT or not custom_derivation: + derivation_path = "m/" + if policy_type == TYPE_SINGLESIG: + derivation_path += str(SINGLESIG_SCRIPT_PURPOSE[script_type]) + elif policy_type == TYPE_MULTISIG: + derivation_path += str(MULTISIG_SCRIPT_PURPOSE) + elif policy_type == TYPE_MINISCRIPT: + # For now, miniscript is the same as multisig + derivation_path += str(MINISCRIPT_PURPOSE) + derivation_path += "h/" + derivation_path += "0h" if network == NETWORKS[MAIN_TXT] else "1h" + derivation_path += "/" + str(account) + "h" + if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): + derivation_path += "/2h" + custom_derivation = "" + else: + derivation_path = custom_derivation + wallet_info += DERIVATION_PATH_SYMBOL + " " + derivation_path self.ctx.display.clear() derivation_path = self.fit_to_line(derivation_path, crop_middle=False) @@ -158,7 +167,14 @@ def customize_wallet(self, key): t("Script Type"), (lambda: None) if policy_type != TYPE_MULTISIG else None, ), - (t("Account"), lambda: None), + ( + ( + t("Account") + if policy_type != TYPE_MINISCRIPT + else t("Derivation Path") + ), + lambda: None, + ), ], offset=info_len * FONT_HEIGHT + DEFAULT_PADDING, ) @@ -198,10 +214,15 @@ def customize_wallet(self, key): if new_script_type is not None: script_type = new_script_type elif index == 3: - account_temp = self._account(account) - if account_temp is not None: - account = account_temp - return network, policy_type, script_type, account + if policy_type != TYPE_MINISCRIPT: + new_account = self._account(account) + if new_account is not None: + account = new_account + else: + new_derivation_path = self._derivation_path(derivation_path) + if new_derivation_path is not None: + custom_derivation = new_derivation_path + return network, policy_type, script_type, account, derivation_path def _coin_type(self): """Network selection menu""" @@ -287,3 +308,32 @@ def _account(self, initial_account=None): ) return None return account + + def _derivation_path(self, derivation): + """Derivation path input""" + while True: + derivation = self.capture_from_keypad( + t("Derivation Path"), + [DERIVATION_KEYPAD], + starting_buffer=(str(derivation) if derivation is not None else ""), + delete_key_fn=lambda x: x[:-1] if len(x) > 2 else x, + ) + if derivation == ESC_KEY: + return None + nodes = derivation.split("/")[1:] + not_hardened_txt = "" + for i, node in enumerate(nodes): + if node[-1] != "h": + not_hardened_txt += "Node {}: {}\n".format(i, node) + if not_hardened_txt: + self.ctx.display.clear() + if not self.prompt( + t("Some nodes are not hardened:") + + "\n" + + not_hardened_txt + + t("Proceed?"), + self.ctx.display.height() // 2, + ): + # Allow user to edit the derivation path + continue + return derivation From 25fc02152e8ead5dd0baf32fef1f6d924ffb092b Mon Sep 17 00:00:00 2001 From: odudex Date: Mon, 6 Jan 2025 14:10:08 -0300 Subject: [PATCH 17/34] detail wallet descriptor attributes while loading --- src/krux/key.py | 3 +- .../pages/home_pages/wallet_descriptor.py | 126 +++++++++--------- src/krux/pages/login.py | 1 - src/krux/pages/wallet_settings.py | 14 +- 4 files changed, 71 insertions(+), 73 deletions(-) diff --git a/src/krux/key.py b/src/krux/key.py index e4fa8bba..84e7ef01 100644 --- a/src/krux/key.py +++ b/src/krux/key.py @@ -42,6 +42,7 @@ DER_SINGLE = "m/%dh/%dh/%dh" DER_MULTI = "m/%dh/%dh/%dh/2h" DER_MINISCRIPT = "m/%dh/%dh/%dh/2h" +HARDENED_STR_REPLACE = "'" # Pay To Public Key Hash - 44' Legacy single-sig # address starts with 1 (mainnet) or m (testnet) @@ -223,7 +224,7 @@ def get_default_derivation(policy_type, network, account=0, script_type=P2WPKH): def format_derivation(derivation, pretty=False): """Helper method to display the derivation path formatted""" formatted_txt = DERIVATION_PATH_SYMBOL + THIN_SPACE + "%s" if pretty else "%s" - return formatted_txt % derivation + return (formatted_txt % derivation).replace("'", HARDENED_STR_REPLACE) @staticmethod def format_fingerprint(fingerprint, pretty=False): diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index 22c5fd14..cb9b699f 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -26,7 +26,7 @@ LOAD_FROM_CAMERA, LOAD_FROM_SD, ) -from ...display import DEFAULT_PADDING, BOTTOM_PROMPT_LINE, FONT_HEIGHT +from ...display import DEFAULT_PADDING, BOTTOM_PROMPT_LINE, FONT_HEIGHT, FONT_WIDTH from ...krux_settings import t from ...qr import FORMAT_NONE from ...sd_card import DESCRIPTOR_FILE_EXTENSION, JSON_FILE_EXTENSION @@ -140,42 +140,52 @@ def _load_wallet(self): if wallet.is_loaded(): self.ctx.display.clear() - self.display_wallet(wallet, is_loading=True) + self.display_loading_wallet(wallet) if self.prompt(t("Load?"), BOTTOM_PROMPT_LINE): self.ctx.wallet = wallet self.flash_text(t("Wallet output descriptor loaded!")) return MENU_CONTINUE - def display_wallet(self, wallet, is_loading=False): - """Displays a wallet, including its label and abbreviated xpubs. - If include_qr is True, a QR code of the wallet will be shown - which will contain the same data as was originally loaded, in - the same QR format - """ + def display_wallet(self, wallet): + """Try to show the wallet output descriptor as a QRCode""" + try: + wallet_data, qr_format = wallet.wallet_qr() + self.display_qr_codes(wallet_data, qr_format, title=wallet.label) + except Exception as e: + self.ctx.display.clear() + self.ctx.display.draw_centered_text( + t("Error:") + "\n%s" % repr(e), theme.error_color + ) + self.ctx.input.wait_for_button() + + def display_loading_wallet(self, wallet): + """Displays wallet descriptor attrbutes while loading""" offset_y = DEFAULT_PADDING self.ctx.display.draw_hcentered_text(wallet.label, offset_y) offset_y += (3 * FONT_HEIGHT) // 2 - our_key_index = None + our_key_indexes_chars = [] for i, key in enumerate(wallet.descriptor.keys): label_color = theme.fg_color if wallet.is_multisig() or wallet.is_miniscript(): label = chr(65 + i) + ": " else: - label = "" + label = " " * 3 key_fingerprint = FINGERPRINT_SYMBOL + " " if key.origin: key_origin_str = str(key.origin) key_fingerprint += key_origin_str[:8] else: key_fingerprint += t("unknown") + # Check if the key is the one loaded in the wallet if ( self.ctx.wallet.key + and len(wallet.descriptor.keys) > 1 and key.fingerprint == self.ctx.wallet.key.fingerprint ): label_color = theme.highlight_color - our_key_index = i + our_key_indexes_chars.append(chr(65 + i)) self.ctx.display.draw_string( DEFAULT_PADDING, offset_y, @@ -184,72 +194,60 @@ def display_wallet(self, wallet, is_loading=False): ) offset_y += FONT_HEIGHT if key.origin: - key_derivation_str = " " * 3 - key_derivation_str += DERIVATION_PATH_SYMBOL - key_derivation_str += " " - key_derivation_str += key_origin_str[8:] + key_derivation_str = DERIVATION_PATH_SYMBOL + key_derivation_str += " m" + key_derivation_str += key_origin_str[8:].replace("h", "'") self.ctx.display.draw_string( - DEFAULT_PADDING, + DEFAULT_PADDING + 3 * FONT_WIDTH, offset_y, key_derivation_str, label_color, ) + offset_y += FONT_HEIGHT + self.ctx.display.draw_hcentered_text( + self.fit_to_line(key.key.to_base58(), " " * 3), + offset_y, + label_color, + ) offset_y += (FONT_HEIGHT * 3) // 2 - # Checks if there's room for another key - if offset_y + (FONT_HEIGHT * 3) > self.ctx.display.height(): + # Checks if there's another key and room for it + if ( + i + 1 < len(wallet.descriptor.keys) + and offset_y + (FONT_HEIGHT * 4) > self.ctx.display.height() + ): + # If there's no room for another key, create a new page self.ctx.input.wait_for_button() self.ctx.display.clear() offset_y = DEFAULT_PADDING self.ctx.display.draw_hcentered_text(wallet.label, offset_y) offset_y += (3 * FONT_HEIGHT) // 2 - if not wallet.is_multisig() and not wallet.is_miniscript(): - about = self.fit_to_line(str(wallet.descriptor.keys[0].key)) - if is_loading: - self.ctx.display.draw_hcentered_text(about, offset_y) - else: - wallet_data, qr_format = wallet.wallet_qr() - self.display_qr_codes(wallet_data, qr_format, title=about) - else: - - # Wait user acknowledge fingerprints - self.ctx.input.wait_for_button() + # Display miniscrip policies + if wallet.is_miniscript(): + # TODO: Replace to_lines with a better wrapper + miniscript_policy = self.ctx.display.to_lines(wallet.descriptor.full_policy) + lines_left = (self.ctx.display.height() - offset_y) // FONT_HEIGHT + if len(miniscript_policy) > lines_left: + # If there's no room for the policy, create a new page + self.ctx.input.wait_for_button() + self.ctx.display.clear() + offset_y = DEFAULT_PADDING + self.ctx.display.draw_hcentered_text(wallet.label, offset_y) + offset_y += (3 * FONT_HEIGHT) // 2 - # Display XPUBs - self.ctx.display.clear() - offset_y = DEFAULT_PADDING - self.ctx.display.draw_hcentered_text(wallet.label, offset_y) - for i, key in enumerate(wallet.descriptor.keys): - offset_y += FONT_HEIGHT - # Checks is xpub belongs to the current wallet - if i == our_key_index: - label_color = theme.highlight_color - else: - label_color = theme.fg_color - self.ctx.display.draw_hcentered_text( - self.fit_to_line(key.key.to_base58(), chr(65 + i) + ". "), + for line in miniscript_policy: + self.ctx.display.draw_string( + DEFAULT_PADDING, offset_y, - label_color, - ) - # Checks if there's room for another xpub - if offset_y + FONT_HEIGHT > self.ctx.display.height(): - self.ctx.input.wait_for_button() - self.ctx.display.clear() - offset_y = DEFAULT_PADDING - self.ctx.display.draw_hcentered_text(wallet.label, offset_y) - - if is_loading: - # Skip the QR code if we're loading the wallet - return - self.ctx.input.wait_for_button() - # Try to show the wallet output descriptor as a QRCode - try: - wallet_data, qr_format = wallet.wallet_qr() - self.display_qr_codes(wallet_data, qr_format, title=wallet.label) - except Exception as e: - self.ctx.display.clear() - self.ctx.display.draw_centered_text( - t("Error:") + "\n%s" % repr(e), theme.error_color + line, ) - self.ctx.input.wait_for_button() + for i, char in enumerate(line): + if char in our_key_indexes_chars: + self.ctx.display.draw_string( + DEFAULT_PADDING + i * FONT_WIDTH, + offset_y, + char, + theme.highlight_color, + ) + offset_y += FONT_HEIGHT diff --git a/src/krux/pages/login.py b/src/krux/pages/login.py index 234f5818..3f187a1f 100644 --- a/src/krux/pages/login.py +++ b/src/krux/pages/login.py @@ -45,7 +45,6 @@ TYPE_MULTISIG, TYPE_MINISCRIPT, POLICY_TYPE_IDS, - DERIVATION_PATH_SYMBOL, ) from ..krux_settings import t from ..settings import NAME_SINGLE_SIG, NAME_MULTISIG, NAME_MINISCRIPT diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index 567f26c1..ffe65256 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -55,9 +55,9 @@ from ..key import P2PKH, P2SH_P2WPKH, P2WPKH, P2WSH, P2TR PASSPHRASE_MAX_LEN = 200 -DERIVATION_KEYPAD = "123456789/0h" +DERIVATION_KEYPAD = "123456789/0'" -MINISCRIPT_DEFAULT_DERIVATION = "m/48h/0h/0h/2h" +MINISCRIPT_DEFAULT_DERIVATION = "m/48'/0'/0'/2'" class PassphraseEditor(Page): @@ -145,11 +145,11 @@ def customize_wallet(self, key): elif policy_type == TYPE_MINISCRIPT: # For now, miniscript is the same as multisig derivation_path += str(MINISCRIPT_PURPOSE) - derivation_path += "h/" - derivation_path += "0h" if network == NETWORKS[MAIN_TXT] else "1h" - derivation_path += "/" + str(account) + "h" + derivation_path += "'/" + derivation_path += "0'" if network == NETWORKS[MAIN_TXT] else "1'" + derivation_path += "/" + str(account) + "'" if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): - derivation_path += "/2h" + derivation_path += "/2'" custom_derivation = "" else: derivation_path = custom_derivation @@ -323,7 +323,7 @@ def _derivation_path(self, derivation): nodes = derivation.split("/")[1:] not_hardened_txt = "" for i, node in enumerate(nodes): - if node[-1] != "h": + if node[-1] != "'": not_hardened_txt += "Node {}: {}\n".format(i, node) if not_hardened_txt: self.ctx.display.clear() From 082d51e056e25deb5c2fff5bccf1db7f9f85c155 Mon Sep 17 00:00:00 2001 From: odudex Date: Mon, 6 Jan 2025 14:51:12 -0300 Subject: [PATCH 18/34] return path mismatch hardened char from h to ' --- src/krux/psbt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index f226850f..4925cdc0 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -223,7 +223,7 @@ def path_mismatch(self): textual_path = "m" for index in derivation_path[:der_path_nodes]: if index >= 2**31: - textual_path += "/{}h".format(index - 2**31) + textual_path += "/{}'".format(index - 2**31) else: textual_path += "/{}".format(index) if textual_path != self.wallet.key.derivation: From 9409bc8be6411943df27a5f80e9ee6be2cd3cc2a Mon Sep 17 00:00:00 2001 From: odudex Date: Mon, 6 Jan 2025 15:06:50 -0300 Subject: [PATCH 19/34] enable tap miniscript signing via QR code --- src/krux/psbt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 4925cdc0..33a3e7b1 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -546,6 +546,10 @@ def sign(self, trim=True): if inp.witness_script: trimmed_psbt.inputs[i].witness_script = inp.witness_script + # Preserve taproot_key_sig for Taproot inputs + if inp.taproot_key_sig: + trimmed_psbt.inputs[i].taproot_key_sig = inp.taproot_key_sig + self.psbt = trimmed_psbt def psbt_qr(self): From fbb747d38cd07b7b1c37b0d495595d23d06523dd Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 7 Jan 2025 16:26:32 -0300 Subject: [PATCH 20/34] better check custom derivations --- src/krux/pages/wallet_settings.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index ffe65256..fcc35341 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -321,6 +321,28 @@ def _derivation_path(self, derivation): if derivation == ESC_KEY: return None nodes = derivation.split("/")[1:] + + # Check node numbers are valid + valid_nodes = True + for node in nodes: + if not node: + valid_nodes = False + break + try: + if node[-1] == "'": + node = node[:-1] + if not 0 <= int(node) < HARDENED_INDEX: + raise ValueError + except ValueError: + valid_nodes = False + break + if not valid_nodes: + self.flash_error( + t("Invalid derivation path"), + ) + continue + + # Check if all nodes are hardened not_hardened_txt = "" for i, node in enumerate(nodes): if node[-1] != "'": From 7c28041554839a8d24e93920592fd18554950cad Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 7 Jan 2025 16:27:48 -0300 Subject: [PATCH 21/34] miniscript related lint refactors --- src/krux/key.py | 2 +- src/krux/pages/login.py | 7 +++-- src/krux/pages/wallet_settings.py | 43 +++++++++++++++++-------------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/krux/key.py b/src/krux/key.py index 84e7ef01..c9710fa9 100644 --- a/src/krux/key.py +++ b/src/krux/key.py @@ -224,7 +224,7 @@ def get_default_derivation(policy_type, network, account=0, script_type=P2WPKH): def format_derivation(derivation, pretty=False): """Helper method to display the derivation path formatted""" formatted_txt = DERIVATION_PATH_SYMBOL + THIN_SPACE + "%s" if pretty else "%s" - return (formatted_txt % derivation).replace("'", HARDENED_STR_REPLACE) + return (formatted_txt % derivation).replace("h", HARDENED_STR_REPLACE) @staticmethod def format_fingerprint(fingerprint, pretty=False): diff --git a/src/krux/pages/login.py b/src/krux/pages/login.py index 3f187a1f..0f6f9f98 100644 --- a/src/krux/pages/login.py +++ b/src/krux/pages/login.py @@ -266,12 +266,11 @@ def _load_key_from_words(self, words, charset=LETTERS, new=False): return MENU_CONTINUE self.ctx.display.clear() - from .mnemonic_editor import MnemonicEditor - # If the mnemonic is not hidden, show the mnemonic editor if not Settings().security.hide_mnemonic: - mnemonic_editor = MnemonicEditor(self.ctx, mnemonic, new) - mnemonic = mnemonic_editor.edit() + from .mnemonic_editor import MnemonicEditor + + mnemonic = MnemonicEditor(self.ctx, mnemonic, new).edit() if mnemonic is None: return MENU_CONTINUE self.ctx.display.clear() diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index fcc35341..6ac9f714 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -44,6 +44,7 @@ TYPE_MULTISIG, TYPE_MINISCRIPT, DERIVATION_PATH_SYMBOL, + POLICY_TYPE_IDS, ) from ..settings import ( MAIN_TXT, @@ -129,27 +130,15 @@ def customize_wallet(self, key): custom_derivation = key.derivation if key.custom_derivation else None while True: wallet_info = network["name"] + "\n" - if policy_type == TYPE_SINGLESIG: - wallet_info += NAME_SINGLE_SIG + "\n" - elif policy_type == TYPE_MULTISIG: - wallet_info += NAME_MULTISIG + "\n" - elif policy_type == TYPE_MINISCRIPT: - wallet_info += NAME_MINISCRIPT + "\n" + policy_type_str = next( + (k for k, v in POLICY_TYPE_IDS.items() if v == policy_type), "" + ) + wallet_info += policy_type_str + "\n" wallet_info += str(script_type).upper() + "\n" if policy_type != TYPE_MINISCRIPT or not custom_derivation: - derivation_path = "m/" - if policy_type == TYPE_SINGLESIG: - derivation_path += str(SINGLESIG_SCRIPT_PURPOSE[script_type]) - elif policy_type == TYPE_MULTISIG: - derivation_path += str(MULTISIG_SCRIPT_PURPOSE) - elif policy_type == TYPE_MINISCRIPT: - # For now, miniscript is the same as multisig - derivation_path += str(MINISCRIPT_PURPOSE) - derivation_path += "'/" - derivation_path += "0'" if network == NETWORKS[MAIN_TXT] else "1'" - derivation_path += "/" + str(account) + "'" - if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): - derivation_path += "/2'" + derivation_path = self._derivation_path_str( + policy_type, script_type, network, account + ) custom_derivation = "" else: derivation_path = custom_derivation @@ -309,6 +298,22 @@ def _account(self, initial_account=None): return None return account + def _derivation_path_str(self, policy_type, script_type, network, account): + derivation_path = "m/" + if policy_type == TYPE_SINGLESIG: + derivation_path += str(SINGLESIG_SCRIPT_PURPOSE[script_type]) + elif policy_type == TYPE_MULTISIG: + derivation_path += str(MULTISIG_SCRIPT_PURPOSE) + elif policy_type == TYPE_MINISCRIPT: + # For now, miniscript is the same as multisig + derivation_path += str(MINISCRIPT_PURPOSE) + derivation_path += "'/" + derivation_path += "0'" if network == NETWORKS[MAIN_TXT] else "1'" + derivation_path += "/" + str(account) + "'" + if policy_type in (TYPE_MULTISIG, TYPE_MINISCRIPT): + derivation_path += "/2'" + return derivation_path + def _derivation_path(self, derivation): """Derivation path input""" while True: From 0c46f9e02933f5c71892c581e14685a1fc7cda90 Mon Sep 17 00:00:00 2001 From: odudex Date: Tue, 7 Jan 2025 16:45:22 -0300 Subject: [PATCH 22/34] custom derivations - add translations --- i18n/translations/de-DE.json | 3 +++ i18n/translations/es-MX.json | 3 +++ i18n/translations/fr-FR.json | 3 +++ i18n/translations/ja-JP.json | 3 +++ i18n/translations/ko-KR.json | 3 +++ i18n/translations/nl-NL.json | 3 +++ i18n/translations/pt-BR.json | 3 +++ i18n/translations/ru-RU.json | 3 +++ i18n/translations/tr-TR.json | 3 +++ i18n/translations/vi-VN.json | 3 +++ i18n/translations/zh-CN.json | 3 +++ src/krux/translations/__init__.py | 3 +++ src/krux/translations/de.py | 3 +++ src/krux/translations/es.py | 3 +++ src/krux/translations/fr.py | 3 +++ src/krux/translations/ja.py | 3 +++ src/krux/translations/ko.py | 3 +++ src/krux/translations/nl.py | 3 +++ src/krux/translations/pt.py | 3 +++ src/krux/translations/ru.py | 3 +++ src/krux/translations/tr.py | 3 +++ src/krux/translations/vi.py | 3 +++ src/krux/translations/zh.py | 3 +++ 23 files changed, 69 insertions(+) diff --git a/i18n/translations/de-DE.json b/i18n/translations/de-DE.json index ae76038c..ecde7d6b 100644 --- a/i18n/translations/de-DE.json +++ b/i18n/translations/de-DE.json @@ -58,6 +58,7 @@ "Decrypt?": "Entschlüsseln?", "Default Wallet": "Standard-Wallet", "Depth Per Pass": "Tiefe pro Durchgang", + "Derivation Path": "Derivation-Pfad", "Derive BIP85 entropy?": "BIP85-Entropie ableiten?", "Descriptor Addresses": "Deskriptor-Adressen", "Display": "Bildschirm", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Unzureichende Entropie!", "Invalid Tamper Check Code": "Ungültiger Tamper Check Code", "Invalid address": "Ungültige Adresse", + "Invalid derivation path": "Ungültiger Derivation-Pfad", "Invalid mnemonic length": "Ungültige mnemonische Lange", "Invalid wallet:": "Ungültige Wallet:", "Invert": "Umkehren", @@ -253,6 +255,7 @@ "Single-sig": "Single-Sig", "Size:": "Größe:", "Some checks cannot be performed.": "Einige Schecks können nicht durchgeführt werden.", + "Some nodes are not hardened:": "Einige Knoten sind nicht gehärtet:", "Spend (%d):": "Ausgabe (%d):", "Spend:": "Ausgaben:", "Stats for Nerds": "Statistiken für Nerds", diff --git a/i18n/translations/es-MX.json b/i18n/translations/es-MX.json index 05998691..e0200a84 100644 --- a/i18n/translations/es-MX.json +++ b/i18n/translations/es-MX.json @@ -58,6 +58,7 @@ "Decrypt?": "¿Descifrar?", "Default Wallet": "Cartera Predeterminada", "Depth Per Pass": "Profundidad por Pasada", + "Derivation Path": "Ruta de derivación", "Derive BIP85 entropy?": "¿Derivar entropía BIP85?", "Descriptor Addresses": "Direcciones del descriptor", "Display": "Pantalla", @@ -122,6 +123,7 @@ "Insufficient entropy!": "¡Entropía Insuficiente!", "Invalid Tamper Check Code": "Código de verificación no válido", "Invalid address": "Dirección inválida", + "Invalid derivation path": "Ruta de derivación no válida", "Invalid mnemonic length": "Longitud mnemónica no válida", "Invalid wallet:": "Cartera inválida:", "Invert": "Invertir", @@ -253,6 +255,7 @@ "Single-sig": "Single-sig", "Size:": "Tamaño:", "Some checks cannot be performed.": "Algunas comprobaciones no se pueden realizar.", + "Some nodes are not hardened:": "Algunos nodos no están endurecidos:", "Spend (%d):": "Gastos (%d):", "Spend:": "Gasto:", "Stats for Nerds": "Estadísticas para Entendidos", diff --git a/i18n/translations/fr-FR.json b/i18n/translations/fr-FR.json index a1eb5acc..82e6d808 100644 --- a/i18n/translations/fr-FR.json +++ b/i18n/translations/fr-FR.json @@ -58,6 +58,7 @@ "Decrypt?": "Déchiffrer ?", "Default Wallet": "Portefeuille par défaut", "Depth Per Pass": "Profondeur par passage", + "Derivation Path": "Chemin de dérivation", "Derive BIP85 entropy?": "Dériver l'entropie BIP85 ?", "Descriptor Addresses": "Adresses du descripteur", "Display": "Affichage", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Entropie insuffisante !", "Invalid Tamper Check Code": "Code de non compromis non valide", "Invalid address": "Adresse invalide", + "Invalid derivation path": "Chemin de dérivation non valide", "Invalid mnemonic length": "Longueur mnémonique invalide", "Invalid wallet:": "Portefeuille invalide :", "Invert": "Inverser", @@ -253,6 +255,7 @@ "Single-sig": "Clé unique", "Size:": "Capacité :", "Some checks cannot be performed.": "Certains vérifications ne peuvent pas être effectués.", + "Some nodes are not hardened:": "Certains nœuds ne sont pas durcis :", "Spend (%d):": "Dépense (%d) :", "Spend:": "Dépense :", "Stats for Nerds": "Statistiques pour les geeks", diff --git a/i18n/translations/ja-JP.json b/i18n/translations/ja-JP.json index 124e8b37..de4692c8 100644 --- a/i18n/translations/ja-JP.json +++ b/i18n/translations/ja-JP.json @@ -58,6 +58,7 @@ "Decrypt?": "デクリプト?", "Default Wallet": "デフォルトの財布", "Depth Per Pass": "パスごとの深さ", + "Derivation Path": "導出パス", "Derive BIP85 entropy?": "BIP85エントロピーを導出しますか?", "Descriptor Addresses": "ディスクリプタアドレス", "Display": "ディスプレイ", @@ -122,6 +123,7 @@ "Insufficient entropy!": "不十分なエントロピー!", "Invalid Tamper Check Code": "無効な改ざんチェックコード", "Invalid address": "無効なアドレス", + "Invalid derivation path": "無効な導出パス", "Invalid mnemonic length": "無効なニーモニックの長さ", "Invalid wallet:": "無効なウォレット:", "Invert": "反転する", @@ -253,6 +255,7 @@ "Single-sig": "シングルサイン", "Size:": "サイズ:", "Some checks cannot be performed.": "一部のチェックを実行できません.", + "Some nodes are not hardened:": "一部のノードは硬化されていません:", "Spend (%d):": "支出(%d):", "Spend:": "支出:", "Stats for Nerds": "オタクのための統計", diff --git a/i18n/translations/ko-KR.json b/i18n/translations/ko-KR.json index 2ccd4f9e..9f7e860f 100644 --- a/i18n/translations/ko-KR.json +++ b/i18n/translations/ko-KR.json @@ -58,6 +58,7 @@ "Decrypt?": "복호화하시겠습니까?", "Default Wallet": "지갑 기본설정", "Depth Per Pass": "Depth Per Pass", + "Derivation Path": "파생 경로", "Derive BIP85 entropy?": "BIP85 엔트로피를 유독하시겠습니까?", "Descriptor Addresses": "디스크립터 주소", "Display": "디스플레이", @@ -122,6 +123,7 @@ "Insufficient entropy!": "엔트로피가 충분하지 않습니다!", "Invalid Tamper Check Code": "유효하지 않은 탬퍼 체크 코드", "Invalid address": "주소가 잘못되었습니다", + "Invalid derivation path": "잘못된 파생 경로", "Invalid mnemonic length": "니모닉 길이가 잘못되었습니다", "Invalid wallet:": "지갑이 잘못되었습니다:", "Invert": "반전", @@ -253,6 +255,7 @@ "Single-sig": "단일서명", "Size:": "크기:", "Some checks cannot be performed.": "일부 검사를 수행할 수 없습니다.", + "Some nodes are not hardened:": "일부 노드가 경화되지 않습니다:", "Spend (%d):": "Spend (%d):", "Spend:": "지출", "Stats for Nerds": "전문가를 위한 통계", diff --git a/i18n/translations/nl-NL.json b/i18n/translations/nl-NL.json index 2400de46..d32865a8 100644 --- a/i18n/translations/nl-NL.json +++ b/i18n/translations/nl-NL.json @@ -58,6 +58,7 @@ "Decrypt?": "Ontsleutelen?", "Default Wallet": "Standaard portemonnee", "Depth Per Pass": "Diepte per pas", + "Derivation Path": "Afleidingspad", "Derive BIP85 entropy?": "BIP85-entropie afleiden?", "Descriptor Addresses": "Descriptoradressen", "Display": "Weergave", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Onvoldoende Entropie!", "Invalid Tamper Check Code": "Ongeldige sabotagecontrolecode", "Invalid address": "Ongeldig adres", + "Invalid derivation path": "Ongeldige afleidingspad", "Invalid mnemonic length": "Ongeldige geheugensteun lengte", "Invalid wallet:": "Ongeldige portemonnee:", "Invert": "Omkeren", @@ -253,6 +255,7 @@ "Single-sig": "Enkele sleutel", "Size:": "Grootte:", "Some checks cannot be performed.": "Sommige controles kunnen niet worden uitgevoerd.", + "Some nodes are not hardened:": "Sommige knooppunten zijn niet gehard:", "Spend (%d):": "Uitgaven (%d):", "Spend:": "Uitgaven:", "Stats for Nerds": "Statistieken voor nerds", diff --git a/i18n/translations/pt-BR.json b/i18n/translations/pt-BR.json index 73236695..f43c40d3 100644 --- a/i18n/translations/pt-BR.json +++ b/i18n/translations/pt-BR.json @@ -58,6 +58,7 @@ "Decrypt?": "Descriptografar?", "Default Wallet": "Carteira Padrão", "Depth Per Pass": "Profundidade da Passagem", + "Derivation Path": "Caminho de Derivação", "Derive BIP85 entropy?": "Derivar entropia BIP85?", "Descriptor Addresses": "Endereços do Descritor", "Display": "Display", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Entropia insuficiente!", "Invalid Tamper Check Code": "Código de verificação inválido", "Invalid address": "Endereço inválido", + "Invalid derivation path": "Caminho de derivação inválido", "Invalid mnemonic length": "Comprimento de mnemônico inválido", "Invalid wallet:": "Carteira inválida:", "Invert": "Invertido", @@ -253,6 +255,7 @@ "Single-sig": "Single-sig", "Size:": "Total:", "Some checks cannot be performed.": "Algumas verificações não podem ser realizadas.", + "Some nodes are not hardened:": "Alguns nós não são hardened:", "Spend (%d):": "Gastos (%d):", "Spend:": "Gasto:", "Stats for Nerds": "Estatísticas para Nerds", diff --git a/i18n/translations/ru-RU.json b/i18n/translations/ru-RU.json index e9f78351..9cddcb65 100644 --- a/i18n/translations/ru-RU.json +++ b/i18n/translations/ru-RU.json @@ -58,6 +58,7 @@ "Decrypt?": "Расшифровать?", "Default Wallet": "Кошелек по умолчанию", "Depth Per Pass": "Глубина за Проход", + "Derivation Path": "Путь деривации", "Derive BIP85 entropy?": "Вывести энтропию BIP85?", "Descriptor Addresses": "Адреса дескрипторов", "Display": "Дисплеи", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Недостаточная Энтропия!", "Invalid Tamper Check Code": "Недействительный код проверки вскрытия", "Invalid address": "Неверный адрес", + "Invalid derivation path": "Недопустимый путь деривации", "Invalid mnemonic length": "Неверная длина мнемоники", "Invalid wallet:": "Неверный кошелек:", "Invert": "Инвертировать", @@ -253,6 +255,7 @@ "Single-sig": "Одна подпись", "Size:": "Размер:", "Some checks cannot be performed.": "Некоторые проверки не могут быть выполнены.", + "Some nodes are not hardened:": "Некоторые узлы не укреплены:", "Spend (%d):": "Расход (%d):", "Spend:": "Расход:", "Stats for Nerds": "Статистика для Гиков", diff --git a/i18n/translations/tr-TR.json b/i18n/translations/tr-TR.json index 25104988..401baaf0 100644 --- a/i18n/translations/tr-TR.json +++ b/i18n/translations/tr-TR.json @@ -58,6 +58,7 @@ "Decrypt?": "Şifre çözülsün mü?", "Default Wallet": "Varsayılan Cüzdan", "Depth Per Pass": "Geçiş Başına Derinlik", + "Derivation Path": "Türetim Yolu", "Derive BIP85 entropy?": "BIP85 entropisi türetilsin mi?", "Descriptor Addresses": "Tanımlayıcı Adresler", "Display": "Ekran", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Yetersiz entropi!", "Invalid Tamper Check Code": "Geçersiz Kurcalama Kontrol Kodu", "Invalid address": "Geçersiz adres", + "Invalid derivation path": "Geçersiz türetim yolu", "Invalid mnemonic length": "Geçersiz mnemonic uzunluğu", "Invalid wallet:": "Geçersiz cüzdan:", "Invert": "Ters Çevir", @@ -253,6 +255,7 @@ "Single-sig": "Tek-imza", "Size:": "Boyut:", "Some checks cannot be performed.": "Bazı kontroller yerine getirilemedi.", + "Some nodes are not hardened:": "Bazı düğümler sertleştirilmemiş:", "Spend (%d):": "Harcama (%d):", "Spend:": "Harcama:", "Stats for Nerds": "İnekler İçin İstatistikler", diff --git a/i18n/translations/vi-VN.json b/i18n/translations/vi-VN.json index bee1fec4..92c72b24 100644 --- a/i18n/translations/vi-VN.json +++ b/i18n/translations/vi-VN.json @@ -58,6 +58,7 @@ "Decrypt?": "Giải mã?", "Default Wallet": "Ví mặc định", "Depth Per Pass": "Độ sâu mỗi lần cắt CNC", + "Derivation Path": "Đường dẫn phái sinh", "Derive BIP85 entropy?": "Suy ra entropy BIP85?", "Descriptor Addresses": "Địa chỉ người mô tả", "Display": "Hiển thị", @@ -122,6 +123,7 @@ "Insufficient entropy!": "Entropy không đủ!", "Invalid Tamper Check Code": "Mã kiểm tra giả mạo không hợp lệ", "Invalid address": "Địa chỉ không hợp lệ", + "Invalid derivation path": "Đường dẫn phái sinh không hợp lệ", "Invalid mnemonic length": "Độ dài mã Mnemonic không hợp lệ", "Invalid wallet:": "Ví không hợp lệ:", "Invert": "Đảo ngược", @@ -253,6 +255,7 @@ "Single-sig": "Khóa đơn", "Size:": "Dung lượng:", "Some checks cannot be performed.": "Một số kiểm tra không thể được thực hiện.", + "Some nodes are not hardened:": "Một số nút không được làm cứng:", "Spend (%d):": "Chi tiêu (%d):", "Spend:": "Chi tiêu:", "Stats for Nerds": "Số liệu thống kê cho Mọt sách", diff --git a/i18n/translations/zh-CN.json b/i18n/translations/zh-CN.json index 0ed7b9ae..6a9f227b 100644 --- a/i18n/translations/zh-CN.json +++ b/i18n/translations/zh-CN.json @@ -58,6 +58,7 @@ "Decrypt?": "解密?", "Default Wallet": "默认钱包", "Depth Per Pass": "每次通过的深度", + "Derivation Path": "源路径", "Derive BIP85 entropy?": "导出BIP85熵?", "Descriptor Addresses": "描述符地址", "Display": "显示", @@ -122,6 +123,7 @@ "Insufficient entropy!": "熵不足!", "Invalid Tamper Check Code": "无效的防篡改检查码", "Invalid address": "无效地址", + "Invalid derivation path": "无效的源路径", "Invalid mnemonic length": "助记词长度无效", "Invalid wallet:": "无效钱包:", "Invert": "反转", @@ -253,6 +255,7 @@ "Single-sig": "单签", "Size:": "大小:", "Some checks cannot be performed.": "无法执行某些检查。", + "Some nodes are not hardened:": "有些节点未硬化:", "Spend (%d):": "花费 (%d):", "Spend:": "花费", "Stats for Nerds": "极客统计数据", diff --git a/src/krux/translations/__init__.py b/src/krux/translations/__init__.py index b790f3a0..3257a502 100644 --- a/src/krux/translations/__init__.py +++ b/src/krux/translations/__init__.py @@ -92,6 +92,7 @@ 2751113454, 1272005728, 4102535566, + 3200465747, 2940689088, 1712856005, 3278654271, @@ -156,6 +157,7 @@ 649035497, 3248804547, 2585599782, + 870847764, 4093416954, 237577215, 4122897393, @@ -287,6 +289,7 @@ 2281377987, 2019512665, 2344747135, + 2863098142, 2090568351, 1260825919, 1232757391, diff --git a/src/krux/translations/de.py b/src/krux/translations/de.py index f22d081f..c28602e4 100644 --- a/src/krux/translations/de.py +++ b/src/krux/translations/de.py @@ -80,6 +80,7 @@ "Entschlüsseln?", "Standard-Wallet", "Tiefe pro Durchgang", + "Derivation-Pfad", "BIP85-Entropie ableiten?", "Deskriptor-Adressen", "Bildschirm", @@ -144,6 +145,7 @@ "Unzureichende Entropie!", "Ungültiger Tamper Check Code", "Ungültige Adresse", + "Ungültiger Derivation-Pfad", "Ungültige mnemonische Lange", "Ungültige Wallet:", "Umkehren", @@ -275,6 +277,7 @@ "Single-Sig", "Größe:", "Einige Schecks können nicht durchgeführt werden.", + "Einige Knoten sind nicht gehärtet:", "Ausgabe (%d):", "Ausgaben:", "Statistiken für Nerds", diff --git a/src/krux/translations/es.py b/src/krux/translations/es.py index 89336a8f..6e8eec70 100644 --- a/src/krux/translations/es.py +++ b/src/krux/translations/es.py @@ -80,6 +80,7 @@ "¿Descifrar?", "Cartera Predeterminada", "Profundidad por Pasada", + "Ruta de derivación", "¿Derivar entropía BIP85?", "Direcciones del descriptor", "Pantalla", @@ -144,6 +145,7 @@ "¡Entropía Insuficiente!", "Código de verificación no válido", "Dirección inválida", + "Ruta de derivación no válida", "Longitud mnemónica no válida", "Cartera inválida:", "Invertir", @@ -275,6 +277,7 @@ "Single-sig", "Tamaño:", "Algunas comprobaciones no se pueden realizar.", + "Algunos nodos no están endurecidos:", "Gastos (%d):", "Gasto:", "Estadísticas para Entendidos", diff --git a/src/krux/translations/fr.py b/src/krux/translations/fr.py index ad1696fd..0ca3ac25 100644 --- a/src/krux/translations/fr.py +++ b/src/krux/translations/fr.py @@ -80,6 +80,7 @@ "Déchiffrer\u2009?", "Portefeuille par défaut", "Profondeur par passage", + "Chemin de dérivation", "Dériver l'entropie BIP85\u2009?", "Adresses du descripteur", "Affichage", @@ -144,6 +145,7 @@ "Entropie insuffisante\u2009!", "Code de non compromis non valide", "Adresse invalide", + "Chemin de dérivation non valide", "Longueur mnémonique invalide", "Portefeuille invalide\u2009:", "Inverser", @@ -275,6 +277,7 @@ "Clé unique", "Capacité\u2009:", "Certains vérifications ne peuvent pas être effectués.", + "Certains nœuds ne sont pas durcis :", "Dépense (%d)\u2009:", "Dépense\u2009:", "Statistiques pour les geeks", diff --git a/src/krux/translations/ja.py b/src/krux/translations/ja.py index f8188c42..ab15a8dc 100644 --- a/src/krux/translations/ja.py +++ b/src/krux/translations/ja.py @@ -80,6 +80,7 @@ "デクリプト?", "デフォルトの財布", "パスごとの深さ", + "導出パス", "BIP85エントロピーを導出しますか?", "ディスクリプタアドレス", "ディスプレイ", @@ -144,6 +145,7 @@ "不十分なエントロピー!", "無効な改ざんチェックコード", "無効なアドレス", + "無効な導出パス", "無効なニーモニックの長さ", "無効なウォレット:", "反転する", @@ -275,6 +277,7 @@ "シングルサイン", "サイズ:", "一部のチェックを実行できません.", + "一部のノードは硬化されていません:", "支出(%d):", "支出:", "オタクのための統計", diff --git a/src/krux/translations/ko.py b/src/krux/translations/ko.py index cd322cf3..5321d1ec 100644 --- a/src/krux/translations/ko.py +++ b/src/krux/translations/ko.py @@ -80,6 +80,7 @@ "복호화하시겠습니까?", "지갑 기본설정", "Depth Per Pass", + "파생 경로", "BIP85 엔트로피를 유독하시겠습니까?", "디스크립터 주소", "디스플레이", @@ -144,6 +145,7 @@ "엔트로피가 충분하지 않습니다!", "유효하지 않은 탬퍼 체크 코드", "주소가 잘못되었습니다", + "잘못된 파생 경로", "니모닉 길이가 잘못되었습니다", "지갑이 잘못되었습니다:", "반전", @@ -275,6 +277,7 @@ "단일서명", "크기:", "일부 검사를 수행할 수 없습니다.", + "일부 노드가 경화되지 않습니다:", "Spend (%d):", "지출", "전문가를 위한 통계", diff --git a/src/krux/translations/nl.py b/src/krux/translations/nl.py index a4fe5962..13651069 100644 --- a/src/krux/translations/nl.py +++ b/src/krux/translations/nl.py @@ -80,6 +80,7 @@ "Ontsleutelen?", "Standaard portemonnee", "Diepte per pas", + "Afleidingspad", "BIP85-entropie afleiden?", "Descriptoradressen", "Weergave", @@ -144,6 +145,7 @@ "Onvoldoende Entropie!", "Ongeldige sabotagecontrolecode", "Ongeldig adres", + "Ongeldige afleidingspad", "Ongeldige geheugensteun lengte", "Ongeldige portemonnee:", "Omkeren", @@ -275,6 +277,7 @@ "Enkele sleutel", "Grootte:", "Sommige controles kunnen niet worden uitgevoerd.", + "Sommige knooppunten zijn niet gehard:", "Uitgaven (%d):", "Uitgaven:", "Statistieken voor nerds", diff --git a/src/krux/translations/pt.py b/src/krux/translations/pt.py index a60394dd..4bb63be5 100644 --- a/src/krux/translations/pt.py +++ b/src/krux/translations/pt.py @@ -80,6 +80,7 @@ "Descriptografar?", "Carteira Padrão", "Profundidade da Passagem", + "Caminho de Derivação", "Derivar entropia BIP85?", "Endereços do Descritor", "Display", @@ -144,6 +145,7 @@ "Entropia insuficiente!", "Código de verificação inválido", "Endereço inválido", + "Caminho de derivação inválido", "Comprimento de mnemônico inválido", "Carteira inválida:", "Invertido", @@ -275,6 +277,7 @@ "Single-sig", "Total:", "Algumas verificações não podem ser realizadas.", + "Alguns nós não são hardened:", "Gastos (%d):", "Gasto:", "Estatísticas para Nerds", diff --git a/src/krux/translations/ru.py b/src/krux/translations/ru.py index a33bf25a..78e45044 100644 --- a/src/krux/translations/ru.py +++ b/src/krux/translations/ru.py @@ -80,6 +80,7 @@ "Расшифровать?", "Кошелек по умолчанию", "Глубина за Проход", + "Путь деривации", "Вывести энтропию BIP85?", "Адреса дескрипторов", "Дисплеи", @@ -144,6 +145,7 @@ "Недостаточная Энтропия!", "Недействительный код проверки вскрытия", "Неверный адрес", + "Недопустимый путь деривации", "Неверная длина мнемоники", "Неверный кошелек:", "Инвертировать", @@ -275,6 +277,7 @@ "Одна подпись", "Размер:", "Некоторые проверки не могут быть выполнены.", + "Некоторые узлы не укреплены:", "Расход (%d):", "Расход:", "Статистика для Гиков", diff --git a/src/krux/translations/tr.py b/src/krux/translations/tr.py index 83dc19a5..f7cc8a8d 100644 --- a/src/krux/translations/tr.py +++ b/src/krux/translations/tr.py @@ -80,6 +80,7 @@ "Şifre çözülsün mü?", "Varsayılan Cüzdan", "Geçiş Başına Derinlik", + "Türetim Yolu", "BIP85 entropisi türetilsin mi?", "Tanımlayıcı Adresler", "Ekran", @@ -144,6 +145,7 @@ "Yetersiz entropi!", "Geçersiz Kurcalama Kontrol Kodu", "Geçersiz adres", + "Geçersiz türetim yolu", "Geçersiz mnemonic uzunluğu", "Geçersiz cüzdan:", "Ters Çevir", @@ -275,6 +277,7 @@ "Tek-imza", "Boyut:", "Bazı kontroller yerine getirilemedi.", + "Bazı düğümler sertleştirilmemiş:", "Harcama (%d):", "Harcama:", "İnekler İçin İstatistikler", diff --git a/src/krux/translations/vi.py b/src/krux/translations/vi.py index ed87f213..09e1173d 100644 --- a/src/krux/translations/vi.py +++ b/src/krux/translations/vi.py @@ -80,6 +80,7 @@ "Giải mã?", "Ví mặc định", "Độ sâu mỗi lần cắt CNC", + "Đường dẫn phái sinh", "Suy ra entropy BIP85?", "Địa chỉ người mô tả", "Hiển thị", @@ -144,6 +145,7 @@ "Entropy không đủ!", "Mã kiểm tra giả mạo không hợp lệ", "Địa chỉ không hợp lệ", + "Đường dẫn phái sinh không hợp lệ", "Độ dài mã Mnemonic không hợp lệ", "Ví không hợp lệ:", "Đảo ngược", @@ -275,6 +277,7 @@ "Khóa đơn", "Dung lượng:", "Một số kiểm tra không thể được thực hiện.", + "Một số nút không được làm cứng:", "Chi tiêu (%d):", "Chi tiêu:", "Số liệu thống kê cho Mọt sách", diff --git a/src/krux/translations/zh.py b/src/krux/translations/zh.py index c2d2383b..31cc966d 100644 --- a/src/krux/translations/zh.py +++ b/src/krux/translations/zh.py @@ -80,6 +80,7 @@ "解密?", "默认钱包", "每次通过的深度", + "源路径", "导出BIP85熵?", "描述符地址", "显示", @@ -144,6 +145,7 @@ "熵不足!", "无效的防篡改检查码", "无效地址", + "无效的源路径", "助记词长度无效", "无效钱包:", "反转", @@ -275,6 +277,7 @@ "单签", "大小:", "无法执行某些检查。", + "有些节点未硬化:", "花费 (%d):", "花费", "极客统计数据", From a1d74b9e765410047cb5e5daa509a3e9d90afb4c Mon Sep 17 00:00:00 2001 From: odudex Date: Wed, 8 Jan 2025 16:04:51 -0300 Subject: [PATCH 23/34] wallet customization bugfix simplify sentence for correct uPython interpretation --- src/krux/pages/wallet_settings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/krux/pages/wallet_settings.py b/src/krux/pages/wallet_settings.py index 6ac9f714..d3ded914 100644 --- a/src/krux/pages/wallet_settings.py +++ b/src/krux/pages/wallet_settings.py @@ -130,9 +130,12 @@ def customize_wallet(self, key): custom_derivation = key.derivation if key.custom_derivation else None while True: wallet_info = network["name"] + "\n" - policy_type_str = next( - (k for k, v in POLICY_TYPE_IDS.items() if v == policy_type), "" - ) + # Find the policy type string from the POLICY_TYPE_IDS dictionary + policy_type_str = "" + for k, v in POLICY_TYPE_IDS.items(): + if v == policy_type: + policy_type_str = k + break wallet_info += policy_type_str + "\n" wallet_info += str(script_type).upper() + "\n" if policy_type != TYPE_MINISCRIPT or not custom_derivation: From 8e429b3105fe5f854842c1a3f7fc5c08b4fa6671 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 9 Jan 2025 14:19:32 -0300 Subject: [PATCH 24/34] tap miniscript - better identify Liana taptree only scripts --- .../pages/home_pages/wallet_descriptor.py | 34 +++++++++++++++-- src/krux/psbt.py | 38 ++++++++++++------- .../home_pages/test_wallet_descriptor.py | 10 ++--- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index cb9b699f..25265e1b 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -31,7 +31,7 @@ from ...qr import FORMAT_NONE from ...sd_card import DESCRIPTOR_FILE_EXTENSION, JSON_FILE_EXTENSION from ...themes import theme -from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL +from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL, TYPE_MINISCRIPT, P2TR class WalletDescriptor(Page): @@ -166,6 +166,7 @@ def display_loading_wallet(self, wallet): self.ctx.display.draw_hcentered_text(wallet.label, offset_y) offset_y += (3 * FONT_HEIGHT) // 2 our_key_indexes_chars = [] + unused_key_index = None for i, key in enumerate(wallet.descriptor.keys): label_color = theme.fg_color if wallet.is_multisig() or wallet.is_miniscript(): @@ -177,7 +178,16 @@ def display_loading_wallet(self, wallet): key_origin_str = str(key.origin) key_fingerprint += key_origin_str[:8] else: - key_fingerprint += t("unknown") + if ( + i == 0 + and wallet.key.policy_type == TYPE_MINISCRIPT + and wallet.key.script_type == P2TR + ): + key_fingerprint = t("TR internal key") + label_color = theme.disabled_color + unused_key_index = chr(65 + i) + else: + key_fingerprint += t("unknown") # Check if the key is the one loaded in the wallet if ( self.ctx.wallet.key @@ -203,6 +213,17 @@ def display_loading_wallet(self, wallet): key_derivation_str, label_color, ) + elif ( + i == 0 + and wallet.key.policy_type == TYPE_MINISCRIPT + and wallet.key.script_type == P2TR + ): + self.ctx.display.draw_string( + DEFAULT_PADDING + 3 * FONT_WIDTH, + offset_y, + t("unused"), + label_color, + ) offset_y += FONT_HEIGHT self.ctx.display.draw_hcentered_text( self.fit_to_line(key.key.to_base58(), " " * 3), @@ -243,7 +264,14 @@ def display_loading_wallet(self, wallet): line, ) for i, char in enumerate(line): - if char in our_key_indexes_chars: + if char == unused_key_index: + self.ctx.display.draw_string( + DEFAULT_PADDING + i * FONT_WIDTH, + offset_y, + char, + theme.disabled_color, + ) + elif char in our_key_indexes_chars: self.ctx.display.draw_string( DEFAULT_PADDING + i * FONT_WIDTH, offset_y, diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 33a3e7b1..de91f3b3 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -151,15 +151,18 @@ def validate(self): """Validates the PSBT""" # From: https://github.com/diybitcoinhardware/embit/blob/master/examples/change.py#L110 xpubs = [] + origin_less_xpub = None try: - xpubs = self.xpubs() + xpubs, origin_less_xpub = self.xpubs() except: # Expected to fail to get xpubs from Miniscript PSBT pass for inp in self.psbt.inputs: # get policy of the input try: - inp_policy = self.get_policy_from_psbt_input(inp, xpubs) + inp_policy = self.get_policy_from_psbt_input( + inp, xpubs, origin_less_xpub + ) except: raise ValueError("Unable to get policy") # if policy is None - assign current @@ -186,7 +189,7 @@ def validate(self): if self.wallet.policy != self.policy: raise ValueError("policy mismatch") - def get_policy_from_psbt_input(self, tx_input, xpubs): + def get_policy_from_psbt_input(self, tx_input, xpubs, origin_less_xpub=None): """Extracts the scriptPubKey from an input's UTXO and determines the policy.""" if tx_input.witness_utxo: scriptpubkey = tx_input.witness_utxo.script_pubkey @@ -196,7 +199,7 @@ def get_policy_from_psbt_input(self, tx_input, xpubs): else: raise ValueError("No UTXO information available in the input.") - return get_policy(tx_input, scriptpubkey, xpubs) + return get_policy(tx_input, scriptpubkey, xpubs, origin_less_xpub) def path_mismatch(self): """Verifies if the PSBT key path matches loaded keys's derivation path""" @@ -226,7 +229,7 @@ def path_mismatch(self): textual_path += "/{}'".format(index - 2**31) else: textual_path += "/{}".format(index) - if textual_path != self.wallet.key.derivation: + if textual_path != self.wallet.key.derivation.replace("h", "'"): if textual_path not in mismatched_paths: mismatched_paths.append(textual_path) if mismatched_paths: @@ -352,12 +355,12 @@ def outputs(self): xpubs = [] try: - xpubs = self.xpubs() + xpubs, origin_less_xpub = self.xpubs() except: # Expected to fail to get xpubs from Miniscript PSBT pass for i, out in enumerate(self.psbt.outputs): - out_policy = get_policy(out, self.psbt.tx.vout[i].script_pubkey, xpubs) + out_policy = get_policy(out, self.psbt.tx.vout[i].script_pubkey, xpubs, origin_less_xpub) output_policy_count[out_policy["type"]] += 1 output_type = self._classify_output(out_policy, i, out) @@ -598,13 +601,19 @@ def xpubs(self): else [self.wallet.descriptor.key] ) xpubs = {} + origin_less_xpub = None for descriptor_key in descriptor_keys: if descriptor_key.origin: # Pure xpub descriptors (Blue Wallet) don't have origin data xpubs[descriptor_key.key] = DerivationPath( descriptor_key.origin.fingerprint, descriptor_key.origin.derivation ) - return xpubs + elif len(descriptor_keys) > 1: + # Allow one descriptor key without origin data for taproot + # Pure taptree descriptors won't have origin data for internal key + origin_less_xpub = descriptor_key.key + + return xpubs, origin_less_xpub def psbt_policy_string(self): """Returns the policy string containing script type and cosigners' fingerprints""" @@ -710,15 +719,13 @@ def get_cosigners_miniscript(derivations, xpubs): return sorted(cosigners) -def get_cosigners_taproot_miniscript(taproot_derivations, xpubs): +def get_cosigners_taproot_miniscript(taproot_derivations, xpubs, origin_less_xpub=None): """ Compares the taproot derivations with the xpubs to check get the cosigners """ cosigners = [] - loop_count = 0 for xonly_pubkey, der_info in taproot_derivations.items(): - loop_count += 1 _, der = der_info # tap_leaf_hashes are not used fp = der.fingerprint full_path = der.derivation @@ -738,6 +745,11 @@ def get_cosigners_taproot_miniscript(taproot_derivations, xpubs): cosigners.append(xpub.to_base58()) break + if origin_less_xpub: + # Pocicies which don't cover internal key spending (e.g. taptree only) + # can have an origin-less xpub for internal key derivation + cosigners.append(origin_less_xpub.to_base58()) + # Ensure all pubkeys have a matching xpub if len(cosigners) != len(taproot_derivations): raise ValueError("cannot get all cosigners") @@ -746,7 +758,7 @@ def get_cosigners_taproot_miniscript(taproot_derivations, xpubs): # Modified from: https://github.com/diybitcoinhardware/embit/blob/master/examples/change.py#L64 -def get_policy(scope, scriptpubkey, xpubs): +def get_policy(scope, scriptpubkey, xpubs, origin_less_xpub=None): """Parse scope and get policy""" from embit.finalizer import parse_multisig @@ -791,7 +803,7 @@ def get_policy(scope, scriptpubkey, xpubs): # Will succeed to verify cosigners only if the descriptor is loaded cosigners = get_cosigners_taproot_miniscript( - scope.taproot_bip32_derivations, xpubs + scope.taproot_bip32_derivations, xpubs, origin_less_xpub ) # Only add cosigners if is miniscript (multiple cosigners), # otherwise it probably is single-sig taproot diff --git a/tests/pages/home_pages/test_wallet_descriptor.py b/tests/pages/home_pages/test_wallet_descriptor.py index 2f43e6e5..f3825baa 100644 --- a/tests/pages/home_pages/test_wallet_descriptor.py +++ b/tests/pages/home_pages/test_wallet_descriptor.py @@ -18,7 +18,7 @@ def test_wallet(mocker, m5stickv, tdata): None, [BUTTON_PAGE], ), - # 1 Load, from camera, good data, accept + # 1 Load, from camera, good data - accept ( False, tdata.SINGLESIG_12_WORD_KEY, @@ -154,7 +154,7 @@ def test_wallet(mocker, m5stickv, tdata): "display_qr_codes", new=lambda data, qr_format, title=None: ctx.input.wait_for_button(), ) - mocker.spy(wallet_descriptor, "display_wallet") + mocker.spy(wallet_descriptor, "display_loading_wallet") # Mock SD card descriptor loading if case[4][:3] == [BUTTON_ENTER, BUTTON_PAGE, BUTTON_ENTER]: @@ -164,15 +164,15 @@ def test_wallet(mocker, m5stickv, tdata): wallet_descriptor.wallet() if case[0]: - wallet_descriptor.display_wallet.assert_called_once() + wallet_descriptor.display_loading_wallet.assert_called_once() else: # If accepted the message and choose to load from camera if case[4][:2] == [BUTTON_ENTER, BUTTON_ENTER]: qr_capturer.assert_called_once() if case[2] is not None and case[2] != "{}": - wallet_descriptor.display_wallet.assert_called_once() + wallet_descriptor.display_loading_wallet.assert_called_once() # If accepted the message and choose to load from SD elif case[4][:3] == [BUTTON_ENTER, BUTTON_PAGE, BUTTON_ENTER]: if case[2] is not None and case[2] != "{}": - wallet_descriptor.display_wallet.assert_called_once() + wallet_descriptor.display_loading_wallet.assert_called_once() assert ctx.input.wait_for_button.call_count == len(case[4]) From 0a47a130cffb60f4a5ab2f0524879ecbbfede285 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 9 Jan 2025 16:29:14 -0300 Subject: [PATCH 25/34] tap miniscript - tap tree only adjusts --- mkdocs.yml | 2 +- pyproject.toml | 2 +- src/krux/metadata.py | 2 +- src/krux/psbt.py | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index bc19fc38..d7b9ba62 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,7 +51,7 @@ edit_uri: edit/main/docs docs_dir: docs site_dir: public extra: - latest_krux: krux-v25.01.beta5 + latest_krux: krux-v25.01.beta6 latest_installer: v0.0.20-beta latest_installer_rpm: krux-installer-0.0.20_beta-1.x86_64.rpm latest_installer_deb: krux-installer_0.0.20-beta_amd64.deb diff --git a/pyproject.toml b/pyproject.toml index 03b7b1dc..4bf9569f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ [tool.poetry] name = "krux" -version = "25.01.beta5" +version = "25.01.beta6" description = "Open-source signing device firmware for Bitcoin" authors = ["Jeff S "] diff --git a/src/krux/metadata.py b/src/krux/metadata.py index 6037afb8..9b71f067 100644 --- a/src/krux/metadata.py +++ b/src/krux/metadata.py @@ -19,5 +19,5 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -VERSION = "25.01.beta5" +VERSION = "25.01.beta6" SIGNER_PUBKEY = "03339e883157e45891e61ca9df4cd3bb895ef32d475b8e793559ea10a36766689b" diff --git a/src/krux/psbt.py b/src/krux/psbt.py index de91f3b3..8780fe2e 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -354,13 +354,16 @@ def outputs(self): output_policy_count = Counter() xpubs = [] + origin_less_xpub = None try: xpubs, origin_less_xpub = self.xpubs() except: # Expected to fail to get xpubs from Miniscript PSBT pass for i, out in enumerate(self.psbt.outputs): - out_policy = get_policy(out, self.psbt.tx.vout[i].script_pubkey, xpubs, origin_less_xpub) + out_policy = get_policy( + out, self.psbt.tx.vout[i].script_pubkey, xpubs, origin_less_xpub + ) output_policy_count[out_policy["type"]] += 1 output_type = self._classify_output(out_policy, i, out) @@ -590,7 +593,7 @@ def xpubs(self): from embit.psbt import DerivationPath if self.psbt.xpubs: - return self.psbt.xpubs + return self.psbt.xpubs, None if not self.wallet.descriptor: raise ValueError("missing xpubs") From 9fe40fa8e73e4a569c4f3f61a21f2c4cdb1bb871 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 9 Jan 2025 16:53:24 -0300 Subject: [PATCH 26/34] tap miniscript - tap tree only adjusts II --- src/krux/pages/home_pages/wallet_descriptor.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index 25265e1b..7fe388a6 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -31,7 +31,7 @@ from ...qr import FORMAT_NONE from ...sd_card import DESCRIPTOR_FILE_EXTENSION, JSON_FILE_EXTENSION from ...themes import theme -from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL, TYPE_MINISCRIPT, P2TR +from ...key import FINGERPRINT_SYMBOL, DERIVATION_PATH_SYMBOL, P2TR class WalletDescriptor(Page): @@ -180,8 +180,8 @@ def display_loading_wallet(self, wallet): else: if ( i == 0 - and wallet.key.policy_type == TYPE_MINISCRIPT - and wallet.key.script_type == P2TR + and wallet.is_miniscript() + and wallet.policy.get("type") == P2TR ): key_fingerprint = t("TR internal key") label_color = theme.disabled_color @@ -214,9 +214,7 @@ def display_loading_wallet(self, wallet): label_color, ) elif ( - i == 0 - and wallet.key.policy_type == TYPE_MINISCRIPT - and wallet.key.script_type == P2TR + i == 0 and wallet.is_miniscript() and wallet.policy.get("type") == P2TR ): self.ctx.display.draw_string( DEFAULT_PADDING + 3 * FONT_WIDTH, From 8a571007c517976e32876cd7d8ba0d3b283310c0 Mon Sep 17 00:00:00 2001 From: odudex Date: Thu, 9 Jan 2025 23:28:16 -0300 Subject: [PATCH 27/34] Tap miniscript - Check NUMS in case internal key is disabled --- src/krux/wallet.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/krux/wallet.py b/src/krux/wallet.py index 827c0701..e3457ddb 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -38,6 +38,11 @@ TYPE_MINISCRIPT, ) +# Liana's NUMS (Nothing-Up-My-Sleeve) +# for Taproot descriptors to make them unspendable from internal key +# https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304/21 +LIANA_TR_NUMS = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + class AssumptionWarning(Exception): """An exception for assumptions that require user acceptance""" @@ -184,6 +189,16 @@ def load(self, wallet_data, qr_format, allow_assumption=None): } elif self.descriptor.miniscript is not None or self.descriptor.taptree: if self.descriptor.taptree: + if not descriptor.keys[0].origin: + import binascii + + # In case internal key is disabled, check if NUMS is known + if ( + binascii.hexlify(descriptor.keys[0].sec()).decode() + != LIANA_TR_NUMS + ): + raise ValueError("Unregistered NUMS") + taproot_txt = "TR " miniscript_type = P2TR else: From 87ee6bad2edefffd8ae746a095511d4ce88a2703 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 10 Jan 2025 15:19:32 -0300 Subject: [PATCH 28/34] tap miniscript - fix broken tests --- .../home_pages/test_wallet_descriptor.py | 6 ++-- tests/pages/test_wallet_settings.py | 32 +++++++++---------- tests/test_psbt.py | 8 ++--- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/pages/home_pages/test_wallet_descriptor.py b/tests/pages/home_pages/test_wallet_descriptor.py index f3825baa..b393ceb8 100644 --- a/tests/pages/home_pages/test_wallet_descriptor.py +++ b/tests/pages/home_pages/test_wallet_descriptor.py @@ -74,7 +74,7 @@ def test_wallet(mocker, m5stickv, tdata): tdata.MULTISIG_12_WORD_KEY, tdata.SPECTER_MULTISIG_WALLET_DATA, None, - [BUTTON_ENTER, BUTTON_ENTER, BUTTON_ENTER], + [BUTTON_ENTER], ), # 9 vague BlueWallet-ish p2pkh, requires allow_assumption ( @@ -155,6 +155,7 @@ def test_wallet(mocker, m5stickv, tdata): new=lambda data, qr_format, title=None: ctx.input.wait_for_button(), ) mocker.spy(wallet_descriptor, "display_loading_wallet") + mocker.spy(wallet_descriptor, "display_wallet") # Mock SD card descriptor loading if case[4][:3] == [BUTTON_ENTER, BUTTON_PAGE, BUTTON_ENTER]: @@ -164,7 +165,8 @@ def test_wallet(mocker, m5stickv, tdata): wallet_descriptor.wallet() if case[0]: - wallet_descriptor.display_loading_wallet.assert_called_once() + # If wallet is already loaded + wallet_descriptor.display_wallet.assert_called_once() else: # If accepted the message and choose to load from camera if case[4][:2] == [BUTTON_ENTER, BUTTON_ENTER]: diff --git a/tests/pages/test_wallet_settings.py b/tests/pages/test_wallet_settings.py index 52dce7de..fcdf7242 100644 --- a/tests/pages/test_wallet_settings.py +++ b/tests/pages/test_wallet_settings.py @@ -110,8 +110,8 @@ def test_change_multisig_changes(m5stickv, mocker, tdata): ctx = create_ctx(mocker, BTN_SEQUENCE_1, Wallet(tdata.SINGLESIG_12_WORD_KEY)) mnemonic = ctx.wallet.key.mnemonic wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -138,8 +138,8 @@ def test_change_multisig_changes(m5stickv, mocker, tdata): ] ctx = create_ctx(mocker, BTN_SEQUENCE_2, ctx.wallet) wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -172,8 +172,8 @@ def test_change_script_type(m5stickv, mocker, tdata): ctx = create_ctx(mocker, BTN_SEQUENCE_1, Wallet(tdata.SINGLESIG_12_WORD_KEY)) mnemonic = ctx.wallet.key.mnemonic wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -198,8 +198,8 @@ def test_change_script_type(m5stickv, mocker, tdata): ] ctx = create_ctx(mocker, BTN_SEQUENCE_2, ctx.wallet) wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -234,8 +234,8 @@ def test_change_account(m5stickv, mocker, tdata): ctx = create_ctx(mocker, BTN_SEQUENCE_1, Wallet(tdata.SINGLESIG_12_WORD_KEY)) mnemonic = ctx.wallet.key.mnemonic wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -265,8 +265,8 @@ def test_change_account(m5stickv, mocker, tdata): ] ctx = create_ctx(mocker, BTN_SEQUENCE_2, ctx.wallet) wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -300,8 +300,8 @@ def test_change_account_esc(m5stickv, mocker, tdata): ctx = create_ctx(mocker, BTN_SEQUENCE_1, Wallet(tdata.SINGLESIG_12_WORD_KEY)) mnemonic = ctx.wallet.key.mnemonic wallet_settings = WalletSettings(ctx) - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( @@ -337,8 +337,8 @@ def test_account_out_of_range(m5stickv, mocker, tdata): mnemonic = ctx.wallet.key.mnemonic wallet_settings = WalletSettings(ctx) wallet_settings.flash_error = mocker.MagicMock() - network, policy_type, script_type, account = wallet_settings.customize_wallet( - ctx.wallet.key + network, policy_type, script_type, account, derivation_path = ( + wallet_settings.customize_wallet(ctx.wallet.key) ) ctx.wallet = Wallet( Key( diff --git a/tests/test_psbt.py b/tests/test_psbt.py index 461e9975..e73d3b68 100644 --- a/tests/test_psbt.py +++ b/tests/test_psbt.py @@ -61,10 +61,10 @@ def tdata(mocker): P2TR_PSBT_B64 = "cHNidP8BAKgCAAAAAfn+phUWoAfkdjJXSEFxjcDaXBr22RczfwaOVNq+Sb4/AAAAAAD9////AzoeAAAAAAAAIlEgdeZfiD3lhycx2Y6ob18IYvCSOdDpsA9J9ZIGnBhNAqLoAwAAAAAAACJRIJ0tm23mDNzdWQfBwJYxSRvdQ07uqoqvOzvxnWPVPkCZ6AMAAAAAAAAWABSuzR7cPv9lqiCdAhXnPXCQXcFobIO/KwBPAQQ1h88D4BfRu4AAAAABg1wLUSGDdsYUVUKKn0e/zOH2zoOX6tewA4fp2epWgwLPvXEAMR4OhYRMNzhygxQ5TrgwKmtQcNaS5BsUuoGAkBBzxdoKVgAAgAEAAIAAAACAAAEBK38nAAAAAAAAIlEgBqyC/Dbbtg1IeR2Bw8HrOX+pc+RWyMb7VMEEIR8EexoBAwQAAAAAIRZFxrHkNII86ea0SPvB5AUnIDg6DSEogry6dlNNd1KKrRkAc8XaClYAAIABAACAAAAAgAAAAAAJAAAAARcgRcax5DSCPOnmtEj7weQFJyA4Og0hKIK8unZTTXdSiq0AIQcg65BQBosBO949IE21msXbKhx4qKolBz+/LnrUnQUVxhkAc8XaClYAAIABAACAAAAAgAEAAAABAAAAAQUgIOuQUAaLATvePSBNtZrF2yoceKiqJQc/vy561J0FFcYAIQfcq45CM9kU8pgInl2NOydhigf1IT+Jmiu9unj9uQLFzxkAc8XaClYAAIABAACAAAAAgAAAAAAKAAAAAQUg3KuOQjPZFPKYCJ5djTsnYYoH9SE/iZorvbp4/bkCxc8AAA==" P2TR_PSBT_UR_PSBT = UR("crypto-psbt", PSBT(P2TR_PSBT).to_cbor()) - SIGNED_P2TR_PSBT = b'psbt\xff\x01\x00\xa8\x02\x00\x00\x00\x01\xf9\xfe\xa6\x15\x16\xa0\x07\xe4v2WHAq\x8d\xc0\xda\\\x1a\xf6\xd9\x173\x7f\x06\x8eT\xda\xbeI\xbe?\x00\x00\x00\x00\x00\xfd\xff\xff\xff\x03:\x1e\x00\x00\x00\x00\x00\x00"Q u\xe6_\x88=\xe5\x87\'1\xd9\x8e\xa8o_\x08b\xf0\x929\xd0\xe9\xb0\x0fI\xf5\x92\x06\x9c\x18M\x02\xa2\xe8\x03\x00\x00\x00\x00\x00\x00"Q \x9d-\x9bm\xe6\x0c\xdc\xddY\x07\xc1\xc0\x961I\x1b\xddCN\xee\xaa\x8a\xaf;;\xf1\x9dc\xd5>@\x99\xe8\x03\x00\x00\x00\x00\x00\x00\x16\x00\x14\xae\xcd\x1e\xdc>\xffe\xaa \x9d\x02\x15\xe7=p\x90]\xc1hl\x83\xbf+\x00\x00\x01\x01+\x7f\'\x00\x00\x00\x00\x00\x00"Q \x06\xac\x82\xfc6\xdb\xb6\rHy\x1d\x81\xc3\xc1\xeb9\x7f\xa9s\xe4V\xc8\xc6\xfbT\xc1\x04!\x1f\x04{\x1a\x01\x08B\x01@(\xb0\x7f\x8d\x19\x9a\xa5\xa3\xae_ ti\x10G\x95\xc3)\x18\x1e\xca=Lq^\x9ah\xe7\xb58=\x87\x80\xd8\x1a\xbd\x0bb\x06@}\xd2\x0c?\xd9G\xac\xe9!%{\xe0E\x8bY\xca\xedH\xebh`\xbc\xbb\x13\x00\x00\x00\x00' - SIGNED_P2TR_PSBT_B43 = "1.W6Z+4SFE49JJT-J.9MUIM8BKT/3M25COGC72:/-9L4Z*6IXB2KK$H2IC/44*CKWH2801.OB1F+5M*MTBOXC8*$8NXVL40OY41IL*F5QJ1AT4ZL4G8J.8X7IFBM-:XE+QQOY*4ODVY-67NBDJ.ZKY:KQ1S3O.ZJV*14GS7Z/KU+.9G8SBDR1NWSCJWD*7$/63DQ+ZW$4BU7.00/QBF$VXSTW0A9LK52C7RBVSQ4+S4+HSC2159G:4HJ8+QWX-DU.LK43L-7CHLHHKWRL/7ZK8S.A-T578YCDHTCH5O/3N/96SV$5ADQ+S82KECF-T7868W*INCWZ3U4G$V8$ON-/KWV3Z+4N37DNH.8U:-9:KJ5WW*7U67S-0V9K3T:GO19R-/Z*XBW$N9OE7P.I912.1JN+EM5KJXR*2NAB/WON6RP03U6YIP0C" - SIGNED_P2TR_PSBT_B58 = "297QokMrvCwY2Pjf3b1GUWQsM8EhryP2qMWTDeTDRo6WNptEQH54WkFSybF6uYEbxSJja2nwMM9mHqEbtkxYqriFdRs7fvU2tEHXK2Hx2Xycz7AEx2LRvADfUGtycCxvVV155wRPwruzecqi5pTAbYnh8Ds5pznRzTkgcnCVaoJbLvmjbE7b6BkWiY5LEYUvZ1wBdzaVhewCS32CXfQ516AC1Ky4G7Xf1rvoQeAdfoD4YV13zV8ifoWWz6A9avCZMREzXartA7igxQZSwZS3o36yrr6MTkfKgqRYs46fHNKud78PUugnEA3kDDKrTRUhioptPQ52hFrN6iswVE2EuHGttaFKZmtQa6Z2gBDrydus7iwrqx14EdzSVzKmY54AHWn23u6u9F1StHsW3sj7m" - SIGNED_P2TR_PSBT_B64 = "cHNidP8BAKgCAAAAAfn+phUWoAfkdjJXSEFxjcDaXBr22RczfwaOVNq+Sb4/AAAAAAD9////AzoeAAAAAAAAIlEgdeZfiD3lhycx2Y6ob18IYvCSOdDpsA9J9ZIGnBhNAqLoAwAAAAAAACJRIJ0tm23mDNzdWQfBwJYxSRvdQ07uqoqvOzvxnWPVPkCZ6AMAAAAAAAAWABSuzR7cPv9lqiCdAhXnPXCQXcFobIO/KwAAAQErfycAAAAAAAAiUSAGrIL8Ntu2DUh5HYHDwes5f6lz5FbIxvtUwQQhHwR7GgEIQgFAKLB/jRmapaOuXyB0aRBHlcMpGB7KPUxxXppo57U4PYeA2Bq9C2IGQH3SDD/ZR6zpISV74EWLWcrtSOtoYLy7EwAAAAA=" + SIGNED_P2TR_PSBT = b'psbt\xff\x01\x00\xa8\x02\x00\x00\x00\x01\xf9\xfe\xa6\x15\x16\xa0\x07\xe4v2WHAq\x8d\xc0\xda\\\x1a\xf6\xd9\x173\x7f\x06\x8eT\xda\xbeI\xbe?\x00\x00\x00\x00\x00\xfd\xff\xff\xff\x03:\x1e\x00\x00\x00\x00\x00\x00"Q u\xe6_\x88=\xe5\x87\'1\xd9\x8e\xa8o_\x08b\xf0\x929\xd0\xe9\xb0\x0fI\xf5\x92\x06\x9c\x18M\x02\xa2\xe8\x03\x00\x00\x00\x00\x00\x00"Q \x9d-\x9bm\xe6\x0c\xdc\xddY\x07\xc1\xc0\x961I\x1b\xddCN\xee\xaa\x8a\xaf;;\xf1\x9dc\xd5>@\x99\xe8\x03\x00\x00\x00\x00\x00\x00\x16\x00\x14\xae\xcd\x1e\xdc>\xffe\xaa \x9d\x02\x15\xe7=p\x90]\xc1hl\x83\xbf+\x00\x00\x01\x01+\x7f\'\x00\x00\x00\x00\x00\x00"Q \x06\xac\x82\xfc6\xdb\xb6\rHy\x1d\x81\xc3\xc1\xeb9\x7f\xa9s\xe4V\xc8\xc6\xfbT\xc1\x04!\x1f\x04{\x1a\x01\x08B\x01@(\xb0\x7f\x8d\x19\x9a\xa5\xa3\xae_ ti\x10G\x95\xc3)\x18\x1e\xca=Lq^\x9ah\xe7\xb58=\x87\x80\xd8\x1a\xbd\x0bb\x06@}\xd2\x0c?\xd9G\xac\xe9!%{\xe0E\x8bY\xca\xedH\xebh`\xbc\xbb\x13\x01\x13@(\xb0\x7f\x8d\x19\x9a\xa5\xa3\xae_ ti\x10G\x95\xc3)\x18\x1e\xca=Lq^\x9ah\xe7\xb58=\x87\x80\xd8\x1a\xbd\x0bb\x06@}\xd2\x0c?\xd9G\xac\xe9!%{\xe0E\x8bY\xca\xedH\xebh`\xbc\xbb\x13\x00\x00\x00\x00' + SIGNED_P2TR_PSBT_B43 = "$JBJ$DT-D-U/1AEWDTQ*PH4J+H:1+I467L27$UINP.QOHP:ZYLDCPD8/LI+DN/A.X7-PC4UCX-K$/OQO-G-H.CGX.+9:5JKUVI:PVG+C37HW*MVZDUS$J-CLTWQ6WQ.A0MGXBUZF:X:276$9AZTBLV82BDNXAV1XQ6..R3I+.LV4OLAXU0MO:KY3KB.:-2PFJOYQ/*95CD:5HPWA/HJ2OW1ZPNB*S39+EAP+60B73/9W9KU.A:DTLVNCOCV1/BBLE9O14Z5ACDF0N$PHZ-B:IEJHV17V91KA/WPGFPSZNSIB/-WNYY./+5VQV.DUAIILF88LFSGJ/E36.B6WJ1$I/FWPOI/L*3.8F2B1TNQS-24N8QJAF**+S13/RFDR9ZZ3/N4CP7S9Y9HKJ-$/B43SC*+BRP44+DUH+$79DJB1-9AY7N/SZPZSHJP6+KYW+4/GIWNVRW$DUE08UMY-7/FH3*ZPPAL2PWR$1+R996Y.E9H:PIB*SJQY.JOEQL/X+2Q5V91UL*/WNCH$$DY*GK.I9XN" + SIGNED_P2TR_PSBT_B58 = "9eehoGbwQ78EoHBL2mrxCSrMophCYTK9PxBW3WqL69ZFwj5F1ESdYXt9GdSKA3xxCgfDKhQt9PqBtSkNoKRFgnm6WyTASro6r5za7Nd2JtywPgYe1JdErssp47WvjrbkBBYV5jvWrzPTJhFrpyis8LLyKExp1XY4VwfSTrcFXfDEM7QoRDehks7s7w7CbLHg8zJEDqRjCdcZpzbVunb3FR7ypZHv41S8VKvhiGHFq1nW99F3dErPYbEs8Pc7gVwhJ6XuhmkpHArZp8pyFd3pQJhyBYQwqUR4QU41buYuFQ465SyMuCLyXhcevALrcjJRJVJsCpsEEZgqTk5z2oqsNPXNVHweNjeHBUdN9uJBgtQ6v5mVWRdsYUfmwFeQqyvDEaioqAnNNSpSe2ByNfcv8ftSfzocbNHLahUqTbjSvxThW84LpEwMUitaUQoguJweVscdRRitkx1kCmsaMC5JwrRH13zHVoFnJqPEUHZwqYTzAJCf" + SIGNED_P2TR_PSBT_B64 = "cHNidP8BAKgCAAAAAfn+phUWoAfkdjJXSEFxjcDaXBr22RczfwaOVNq+Sb4/AAAAAAD9////AzoeAAAAAAAAIlEgdeZfiD3lhycx2Y6ob18IYvCSOdDpsA9J9ZIGnBhNAqLoAwAAAAAAACJRIJ0tm23mDNzdWQfBwJYxSRvdQ07uqoqvOzvxnWPVPkCZ6AMAAAAAAAAWABSuzR7cPv9lqiCdAhXnPXCQXcFobIO/KwAAAQErfycAAAAAAAAiUSAGrIL8Ntu2DUh5HYHDwes5f6lz5FbIxvtUwQQhHwR7GgEIQgFAKLB/jRmapaOuXyB0aRBHlcMpGB7KPUxxXppo57U4PYeA2Bq9C2IGQH3SDD/ZR6zpISV74EWLWcrtSOtoYLy7EwETQCiwf40ZmqWjrl8gdGkQR5XDKRgeyj1McV6aaOe1OD2HgNgavQtiBkB90gw/2Ues6SEle+BFi1nK7UjraGC8uxMAAAAA" SIGNED_P2TR_PSBT_UR_PSBT = UR("crypto-psbt", PSBT(SIGNED_P2TR_PSBT).to_cbor()) # Native Segwit Multisig From ce1816b2c5738090c6e728bed95a715eb76084d9 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 10 Jan 2025 15:39:26 -0300 Subject: [PATCH 29/34] tap miniscript - update translations --- i18n/translations/de-DE.json | 1 + i18n/translations/es-MX.json | 1 + i18n/translations/fr-FR.json | 1 + i18n/translations/ja-JP.json | 1 + i18n/translations/ko-KR.json | 1 + i18n/translations/nl-NL.json | 1 + i18n/translations/pt-BR.json | 1 + i18n/translations/ru-RU.json | 1 + i18n/translations/tr-TR.json | 1 + i18n/translations/vi-VN.json | 1 + i18n/translations/zh-CN.json | 1 + src/krux/pages/home_pages/wallet_descriptor.py | 2 +- src/krux/translations/__init__.py | 1 + src/krux/translations/de.py | 1 + src/krux/translations/es.py | 1 + src/krux/translations/fr.py | 1 + src/krux/translations/ja.py | 1 + src/krux/translations/ko.py | 1 + src/krux/translations/nl.py | 1 + src/krux/translations/pt.py | 1 + src/krux/translations/ru.py | 1 + src/krux/translations/tr.py | 1 + src/krux/translations/vi.py | 1 + src/krux/translations/zh.py | 1 + 24 files changed, 24 insertions(+), 1 deletion(-) diff --git a/i18n/translations/de-DE.json b/i18n/translations/de-DE.json index ecde7d6b..7c39d8ba 100644 --- a/i18n/translations/de-DE.json +++ b/i18n/translations/de-DE.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash-Hash", "TC Flash Hash at Boot": "TC Flash-Hash beim Start", "TOUCH or ENTER to capture": "TOUCH oder ENTER zum Erfassen", + "TR internal key": "TR interner Schlüssel", "TX Pin": "TX Pin", "Tamper Check Code": "Tamper Check Code", "Tamper check code set successfully": "Tamper Check Code erfolgreich gesetzt", diff --git a/i18n/translations/es-MX.json b/i18n/translations/es-MX.json index e0200a84..025f2807 100644 --- a/i18n/translations/es-MX.json +++ b/i18n/translations/es-MX.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Hash Flash", "TC Flash Hash at Boot": "TC Flash Hash al arranque", "TOUCH or ENTER to capture": "TOCA o ENTER para capturar", + "TR internal key": "Clave interna TR", "TX Pin": "TX Pin", "Tamper Check Code": "Código de verificación", "Tamper check code set successfully": "Código de verificación establecido con éxito", diff --git a/i18n/translations/fr-FR.json b/i18n/translations/fr-FR.json index 82e6d808..75097df7 100644 --- a/i18n/translations/fr-FR.json +++ b/i18n/translations/fr-FR.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "TC Flash Hash au démarrage", "TOUCH or ENTER to capture": "TOUCHEZ ou ENTRER pour capturer", + "TR internal key": "Clé interne TR", "TX Pin": "TX Fiche", "Tamper Check Code": "Code de non compromis", "Tamper check code set successfully": "Code de non compromis défini avec succès", diff --git a/i18n/translations/ja-JP.json b/i18n/translations/ja-JP.json index de4692c8..f59563bd 100644 --- a/i18n/translations/ja-JP.json +++ b/i18n/translations/ja-JP.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TCフラッシュハッシュ", "TC Flash Hash at Boot": "起動時のTCフラッシュハッシュ", "TOUCH or ENTER to capture": "タッチまたはENTERでキャプチャする", + "TR internal key": "TR内部キー", "TX Pin": "TXピン", "Tamper Check Code": "改ざんチェックコード", "Tamper check code set successfully": "改ざんチェックコードが正常に設定されました", diff --git a/i18n/translations/ko-KR.json b/i18n/translations/ko-KR.json index 9f7e860f..d6bd088e 100644 --- a/i18n/translations/ko-KR.json +++ b/i18n/translations/ko-KR.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "부팅 시 플래시 탬퍼 확인 해시", "TOUCH or ENTER to capture": "터치하거나 엔터를 눌러 캡처하십시오", + "TR internal key": "TR 내부 키", "TX Pin": "TX 핀", "Tamper Check Code": "탬퍼 체크 코드", "Tamper check code set successfully": "탬퍼 검사 코드가 성공적으로 설정되었습니다", diff --git a/i18n/translations/nl-NL.json b/i18n/translations/nl-NL.json index d32865a8..6dfd1b8a 100644 --- a/i18n/translations/nl-NL.json +++ b/i18n/translations/nl-NL.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "Hash Flash bij het opstarten", "TOUCH or ENTER to capture": "TIK of ENTER voor opname", + "TR internal key": "TR interne sleutel", "TX Pin": "TX pin", "Tamper Check Code": "Sabotagecontrolecode", "Tamper check code set successfully": "Sabotagecontrolecode succesvol ingesteld", diff --git a/i18n/translations/pt-BR.json b/i18n/translations/pt-BR.json index f43c40d3..c9fefba4 100644 --- a/i18n/translations/pt-BR.json +++ b/i18n/translations/pt-BR.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "TC Hash Flash na Inicialização", "TOUCH or ENTER to capture": "TOQUE ou ENTER para capturar", + "TR internal key": "Chave interna TR", "TX Pin": "Pino TX", "Tamper Check Code": "Código de Verificação", "Tamper check code set successfully": "Código de verificação definido com sucesso", diff --git a/i18n/translations/ru-RU.json b/i18n/translations/ru-RU.json index 9cddcb65..50adf9f1 100644 --- a/i18n/translations/ru-RU.json +++ b/i18n/translations/ru-RU.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "Проверка хэша Flash при загрузке", "TOUCH or ENTER to capture": "ПРИКОСНИТЕСЬ или нажмите ВВОД, чтобы захватить", + "TR internal key": "внутренний ключ", "TX Pin": "TX Пин", "Tamper Check Code": "Код проверки вскрытия", "Tamper check code set successfully": "Код проверки вскрытия успешно установлен", diff --git a/i18n/translations/tr-TR.json b/i18n/translations/tr-TR.json index 401baaf0..dd4484a6 100644 --- a/i18n/translations/tr-TR.json +++ b/i18n/translations/tr-TR.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "Önyüklemede TC Flash Hash", "TOUCH or ENTER to capture": "Yakalamak için DOKUN veya GİR", + "TR internal key": "TR iç anahtar", "TX Pin": "TX Pini", "Tamper Check Code": "Kurcalama Kontrol Kodu", "Tamper check code set successfully": "Kurcalama kontrol kodu başarıyla ayarlandı", diff --git a/i18n/translations/vi-VN.json b/i18n/translations/vi-VN.json index 92c72b24..775cc384 100644 --- a/i18n/translations/vi-VN.json +++ b/i18n/translations/vi-VN.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "Hash Flash TC khi khởi động", "TOUCH or ENTER to capture": "Chạm màn hình hoặc nhấn nút ENTER để chụp", + "TR internal key": "Khóa nội bộ TR", "TX Pin": "TX Pin", "Tamper Check Code": "Mã kiểm tra giả mạo", "Tamper check code set successfully": "Đã đặt mã kiểm tra giả mạo thành công", diff --git a/i18n/translations/zh-CN.json b/i18n/translations/zh-CN.json index 6a9f227b..a3aabb4a 100644 --- a/i18n/translations/zh-CN.json +++ b/i18n/translations/zh-CN.json @@ -265,6 +265,7 @@ "TC Flash Hash": "TC Flash Hash", "TC Flash Hash at Boot": "启动时的 TC Flash Hash", "TOUCH or ENTER to capture": "点击或按下 ENTER 截图", + "TR internal key": "TR内部密钥", "TX Pin": "TX 引脚", "Tamper Check Code": "防篡改检查码", "Tamper check code set successfully": "防篡改检查码设置成功", diff --git a/src/krux/pages/home_pages/wallet_descriptor.py b/src/krux/pages/home_pages/wallet_descriptor.py index 7fe388a6..aa4bf25b 100644 --- a/src/krux/pages/home_pages/wallet_descriptor.py +++ b/src/krux/pages/home_pages/wallet_descriptor.py @@ -219,7 +219,7 @@ def display_loading_wallet(self, wallet): self.ctx.display.draw_string( DEFAULT_PADDING + 3 * FONT_WIDTH, offset_y, - t("unused"), + "NUMS", label_color, ) offset_y += FONT_HEIGHT diff --git a/src/krux/translations/__init__.py b/src/krux/translations/__init__.py index 3257a502..eca6e60a 100644 --- a/src/krux/translations/__init__.py +++ b/src/krux/translations/__init__.py @@ -299,6 +299,7 @@ 2596024031, 2440924821, 1898550184, + 2270688958, 4228215415, 2336603177, 3679411849, diff --git a/src/krux/translations/de.py b/src/krux/translations/de.py index c28602e4..50cfc72a 100644 --- a/src/krux/translations/de.py +++ b/src/krux/translations/de.py @@ -287,6 +287,7 @@ "TC Flash-Hash", "TC Flash-Hash beim Start", "TOUCH oder ENTER zum Erfassen", + "TR interner Schlüssel", "TX Pin", "Tamper Check Code", "Tamper Check Code erfolgreich gesetzt", diff --git a/src/krux/translations/es.py b/src/krux/translations/es.py index 6e8eec70..5f85389e 100644 --- a/src/krux/translations/es.py +++ b/src/krux/translations/es.py @@ -287,6 +287,7 @@ "TC Hash Flash", "TC Flash Hash al arranque", "TOCA o ENTER para capturar", + "Clave interna TR", "TX Pin", "Código de verificación", "Código de verificación establecido con éxito", diff --git a/src/krux/translations/fr.py b/src/krux/translations/fr.py index 0ca3ac25..8feaf086 100644 --- a/src/krux/translations/fr.py +++ b/src/krux/translations/fr.py @@ -287,6 +287,7 @@ "TC Flash Hash", "TC Flash Hash au démarrage", "TOUCHEZ ou ENTRER pour capturer", + "Clé interne TR", "TX Fiche", "Code de non compromis", "Code de non compromis défini avec succès", diff --git a/src/krux/translations/ja.py b/src/krux/translations/ja.py index ab15a8dc..a849cae8 100644 --- a/src/krux/translations/ja.py +++ b/src/krux/translations/ja.py @@ -287,6 +287,7 @@ "TCフラッシュハッシュ", "起動時のTCフラッシュハッシュ", "タッチまたはENTERでキャプチャする", + "TR内部キー", "TXピン", "改ざんチェックコード", "改ざんチェックコードが正常に設定されました", diff --git a/src/krux/translations/ko.py b/src/krux/translations/ko.py index 5321d1ec..e0bf5f98 100644 --- a/src/krux/translations/ko.py +++ b/src/krux/translations/ko.py @@ -287,6 +287,7 @@ "TC Flash Hash", "부팅 시 플래시 탬퍼 확인 해시", "터치하거나 엔터를 눌러 캡처하십시오", + "TR 내부 키", "TX 핀", "탬퍼 체크 코드", "탬퍼 검사 코드가 성공적으로 설정되었습니다", diff --git a/src/krux/translations/nl.py b/src/krux/translations/nl.py index 13651069..12307fbd 100644 --- a/src/krux/translations/nl.py +++ b/src/krux/translations/nl.py @@ -287,6 +287,7 @@ "TC Flash Hash", "Hash Flash bij het opstarten", "TIK of ENTER voor opname", + "TR interne sleutel", "TX pin", "Sabotagecontrolecode", "Sabotagecontrolecode succesvol ingesteld", diff --git a/src/krux/translations/pt.py b/src/krux/translations/pt.py index 4bb63be5..aee3015b 100644 --- a/src/krux/translations/pt.py +++ b/src/krux/translations/pt.py @@ -287,6 +287,7 @@ "TC Flash Hash", "TC Hash Flash na Inicialização", "TOQUE ou ENTER para capturar", + "Chave interna TR", "Pino TX", "Código de Verificação", "Código de verificação definido com sucesso", diff --git a/src/krux/translations/ru.py b/src/krux/translations/ru.py index 78e45044..f4b6a4a7 100644 --- a/src/krux/translations/ru.py +++ b/src/krux/translations/ru.py @@ -287,6 +287,7 @@ "TC Flash Hash", "Проверка хэша Flash при загрузке", "ПРИКОСНИТЕСЬ или нажмите ВВОД, чтобы захватить", + "внутренний ключ", "TX Пин", "Код проверки вскрытия", "Код проверки вскрытия успешно установлен", diff --git a/src/krux/translations/tr.py b/src/krux/translations/tr.py index f7cc8a8d..f0d6f6b2 100644 --- a/src/krux/translations/tr.py +++ b/src/krux/translations/tr.py @@ -287,6 +287,7 @@ "TC Flash Hash", "Önyüklemede TC Flash Hash", "Yakalamak için DOKUN veya GİR", + "TR iç anahtar", "TX Pini", "Kurcalama Kontrol Kodu", "Kurcalama kontrol kodu başarıyla ayarlandı", diff --git a/src/krux/translations/vi.py b/src/krux/translations/vi.py index 09e1173d..d0715694 100644 --- a/src/krux/translations/vi.py +++ b/src/krux/translations/vi.py @@ -287,6 +287,7 @@ "TC Flash Hash", "Hash Flash TC khi khởi động", "Chạm màn hình hoặc nhấn nút ENTER để chụp", + "Khóa nội bộ TR", "TX Pin", "Mã kiểm tra giả mạo", "Đã đặt mã kiểm tra giả mạo thành công", diff --git a/src/krux/translations/zh.py b/src/krux/translations/zh.py index 31cc966d..34f807e0 100644 --- a/src/krux/translations/zh.py +++ b/src/krux/translations/zh.py @@ -287,6 +287,7 @@ "TC Flash Hash", "启动时的 TC Flash Hash", "点击或按下 ENTER 截图", + "TR内部密钥", "TX 引脚", "防篡改检查码", "防篡改检查码设置成功", From 33e74a76195a3b9fabc400fa085d3067b34d80f4 Mon Sep 17 00:00:00 2001 From: odudex Date: Sat, 11 Jan 2025 09:42:06 -0300 Subject: [PATCH 30/34] better reference BIP341 NUMS example --- src/krux/wallet.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/krux/wallet.py b/src/krux/wallet.py index e3457ddb..649e3ad2 100644 --- a/src/krux/wallet.py +++ b/src/krux/wallet.py @@ -38,10 +38,13 @@ TYPE_MINISCRIPT, ) -# Liana's NUMS (Nothing-Up-My-Sleeve) -# for Taproot descriptors to make them unspendable from internal key +# Liana uses a example NUMS (Nothing-Up-My-Sleeve) key from BIP341 to create unspendable keys +# https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs # https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304/21 -LIANA_TR_NUMS = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +# H = lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) +BIP_341_NUMS_EXAMPLE = ( + "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +) class AssumptionWarning(Exception): @@ -195,7 +198,7 @@ def load(self, wallet_data, qr_format, allow_assumption=None): # In case internal key is disabled, check if NUMS is known if ( binascii.hexlify(descriptor.keys[0].sec()).decode() - != LIANA_TR_NUMS + != BIP_341_NUMS_EXAMPLE ): raise ValueError("Unregistered NUMS") From e3a60276c6c4126f60d8197ef861ff0f0379dca1 Mon Sep 17 00:00:00 2001 From: odudex Date: Sat, 11 Jan 2025 11:52:00 -0300 Subject: [PATCH 31/34] tap miniscript - add taproot_sigs field to QR Code exported PSBTs --- src/krux/psbt.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 8780fe2e..41fcb514 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -556,6 +556,10 @@ def sign(self, trim=True): if inp.taproot_key_sig: trimmed_psbt.inputs[i].taproot_key_sig = inp.taproot_key_sig + # Preserve taproot script path sigs + if inp.taproot_sigs: + trimmed_psbt.inputs[i].taproot_sigs = inp.taproot_sigs + self.psbt = trimmed_psbt def psbt_qr(self): From 5251d1050fe75c85360eb19d68084a39b73b37a4 Mon Sep 17 00:00:00 2001 From: odudex Date: Sat, 11 Jan 2025 11:53:37 -0300 Subject: [PATCH 32/34] update beta version --- mkdocs.yml | 2 +- pyproject.toml | 2 +- src/krux/metadata.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index d7b9ba62..32bb5f36 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,7 +51,7 @@ edit_uri: edit/main/docs docs_dir: docs site_dir: public extra: - latest_krux: krux-v25.01.beta6 + latest_krux: krux-v25.01.beta7 latest_installer: v0.0.20-beta latest_installer_rpm: krux-installer-0.0.20_beta-1.x86_64.rpm latest_installer_deb: krux-installer_0.0.20-beta_amd64.deb diff --git a/pyproject.toml b/pyproject.toml index 4bf9569f..a69e9f73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ [tool.poetry] name = "krux" -version = "25.01.beta6" +version = "25.01.beta7" description = "Open-source signing device firmware for Bitcoin" authors = ["Jeff S "] diff --git a/src/krux/metadata.py b/src/krux/metadata.py index 9b71f067..b4343dbc 100644 --- a/src/krux/metadata.py +++ b/src/krux/metadata.py @@ -19,5 +19,5 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -VERSION = "25.01.beta6" +VERSION = "25.01.beta7" SIGNER_PUBKEY = "03339e883157e45891e61ca9df4cd3bb895ef32d475b8e793559ea10a36766689b" From 298bd53d996a72e82861fd88ccefa7e10d15aa87 Mon Sep 17 00:00:00 2001 From: odudex Date: Sat, 11 Jan 2025 14:31:11 -0300 Subject: [PATCH 33/34] bugfix - confusion with "h" and "'" --- src/krux/psbt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 41fcb514..36d1fb8a 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -250,7 +250,7 @@ def is_our_miniscript_output(self, psbt_output): expected_path_str = self.wallet.key.derivation.split("/")[1:] expected_path_nodes = [] for p in expected_path_str: - if "h" in p: + if "'" in p: expected_path_nodes.append(int(p[:-1]) + HARDENED_INDEX) else: expected_path_nodes.append(int(p)) From 8e996901a0f0a455e59bf9112cdd8047278932d6 Mon Sep 17 00:00:00 2001 From: odudex Date: Sun, 12 Jan 2025 08:18:45 -0300 Subject: [PATCH 34/34] use Embit's Descriptor.owns() to detect slef-transfer --- src/krux/psbt.py | 67 +++++++--------------------------------------- tests/test_psbt.py | 64 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/krux/psbt.py b/src/krux/psbt.py index 36d1fb8a..cdd9d8c7 100644 --- a/src/krux/psbt.py +++ b/src/krux/psbt.py @@ -21,7 +21,6 @@ # THE SOFTWARE. import gc from embit.psbt import PSBT, CompressMode -from embit.bip32 import HARDENED_INDEX from ur.ur import UR import urtypes from urtypes.crypto import CRYPTO_PSBT @@ -29,7 +28,7 @@ from .krux_settings import t from .settings import THIN_SPACE from .qr import FORMAT_PMOFN, FORMAT_BBQR -from .key import Key, P2PKH, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR +from .key import Key, P2SH, P2SH_P2WPKH, P2SH_P2WSH, P2WPKH, P2WSH, P2TR from .sats_vb import SatsVB # PSBT Output Types: @@ -236,32 +235,15 @@ def path_mismatch(self): return Key.format_derivation(", ".join(mismatched_paths)) return "" - def is_our_miniscript_output(self, psbt_output): - """Check if the output is from our wallet""" - - # TODO: Is comparing fingerprints and paths a trustable method? - derivations = psbt_output.bip32_derivations - if not derivations: - return False - for _, derivation in derivations.items(): - # Check if the fingerprint matches - if derivation.fingerprint == self.wallet.key.fingerprint: - # Verify the derivation path - expected_path_str = self.wallet.key.derivation.split("/")[1:] - expected_path_nodes = [] - for p in expected_path_str: - if "'" in p: - expected_path_nodes.append(int(p[:-1]) + HARDENED_INDEX) - else: - expected_path_nodes.append(int(p)) + def address_belongs_to_descriptor(self, psbt_output): + """Check if the output is from our wallet descriptor""" - if derivation.derivation[:4] == expected_path_nodes: - return True + if self.wallet.descriptor: + return self.wallet.descriptor.owns(psbt_output) return False - def _classify_output(self, out_policy, i, out): + def _classify_output(self, out_policy, out): """Classify the output based on its properties and policy""" - from embit import script address_from_my_wallet = False address_is_change = False @@ -274,45 +256,14 @@ def _classify_output(self, out_policy, i, out): # so we only need to check that scriptpubkey is generated from # witness script - # empty script by default - sc = script.Script(b"") - if self.policy["type"] == P2WSH: - try: - # if multisig, we know witness script - sc = script.p2wsh(out.witness_script) - except: - # Expected to fail with Miniscript PSBT - pass - elif self.policy["type"] == P2SH_P2WSH: - sc = script.p2sh(script.p2wsh(out.witness_script)) - # single-sig - elif "pkh" in self.policy["type"]: - if len(list(out.bip32_derivations.values())) > 0: - der = list(out.bip32_derivations.values())[0].derivation - my_hd_prvkey = self.wallet.key.root.derive(der) - if self.policy["type"] == P2WPKH: - sc = script.p2wpkh(my_hd_prvkey) - elif self.policy["type"] == P2SH_P2WPKH: - sc = script.p2sh(script.p2wpkh(my_hd_prvkey)) - elif self.policy["type"] == P2PKH: - sc = script.p2pkh(my_hd_prvkey) + address_from_my_wallet = self.address_belongs_to_descriptor(out) if self.policy["type"] == P2TR: - address_from_my_wallet = ( - len(list(out.taproot_bip32_derivations.values())) > 0 - ) if address_from_my_wallet: # _ = leafs _, der = list(out.taproot_bip32_derivations.values())[0] - address_is_change = der.derivation[3] == 1 + address_is_change = der.derivation[-2] == 1 else: - if sc.data: - address_from_my_wallet = ( - sc.data == self.psbt.tx.vout[i].script_pubkey.data - ) - else: - # If the script is empty, we compare fingerprints and paths - address_from_my_wallet = self.is_our_miniscript_output(out) if address_from_my_wallet: address_is_change = ( len(list(out.bip32_derivations.values())) > 0 @@ -365,7 +316,7 @@ def outputs(self): out, self.psbt.tx.vout[i].script_pubkey, xpubs, origin_less_xpub ) output_policy_count[out_policy["type"]] += 1 - output_type = self._classify_output(out_policy, i, out) + output_type = self._classify_output(out_policy, out) if output_type == CHANGE: change_list.append( diff --git a/tests/test_psbt.py b/tests/test_psbt.py index e73d3b68..a34dfa89 100644 --- a/tests/test_psbt.py +++ b/tests/test_psbt.py @@ -552,11 +552,10 @@ def test_sign_fails_with_0_sigs_added(mocker, m5stickv, tdata): def test_outputs_singlesig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key, TYPE_SINGLESIG + from krux.key import Key, TYPE_SINGLESIG, P2PKH, P2WPKH, P2SH_P2WPKH, P2TR from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_SINGLESIG, NETWORKS["test"])) cases = [ ( tdata.P2PKH_PSBT, @@ -564,6 +563,14 @@ def test_outputs_singlesig(mocker, m5stickv, tdata): "Inputs (3): ₿ 0.00 001 856\n\nSpend (1): ₿ 0.00 001 000\n\nFee: ₿ 0.00 000 856 (85.6%) ~1.8 sat/vB", "1. Spend: \n\ntb1q4mx3ahp7laj65gyaqg27w0tsjpwuz6rvaxx3tl\n\n₿ 0.00 001 000", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_SINGLESIG, + NETWORKS["test"], + script_type=P2PKH, + ) + ), ), ( tdata.P2WPKH_PSBT, @@ -572,6 +579,14 @@ def test_outputs_singlesig(mocker, m5stickv, tdata): "1. Spend: \n\ntb1que40al7rsw88ru9z0vr78vqwme4w3ctqj694kx\n\n₿ 0.10 000 000", "1. Change: \n\ntb1q9u62588spffmq4dzjxsr5l297znf3z6j5p2688\n\n₿ 0.89 997 180", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_SINGLESIG, + NETWORKS["test"], + script_type=P2WPKH, + ) + ), ), ( tdata.P2SH_P2WPKH_PSBT, @@ -580,6 +595,14 @@ def test_outputs_singlesig(mocker, m5stickv, tdata): "1. Spend: \n\ntb1que40al7rsw88ru9z0vr78vqwme4w3ctqj694kx\n\n₿ 0.10 000 000", "1. Change: \n\n2MvdUi5o3f2tnEFh9yGvta6FzptTZtkPJC8\n\n₿ 0.89 996 700", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_SINGLESIG, + NETWORKS["test"], + script_type=P2SH_P2WPKH, + ) + ), ), ( tdata.P2TR_PSBT, @@ -589,23 +612,32 @@ def test_outputs_singlesig(mocker, m5stickv, tdata): "1. Self-transfer: \n\ntb1pn5kekm0xpnwd6kg8c8qfvv2fr0w5xnhw42927wem7xwk84f7gzvsvctkhp\n\n₿ 0.00 001 000", "1. Change: \n\ntb1pwhn9lzpaukrjwvwe365x7hcgvtcfywwsaxcq7j04jgrfcxzdq23qhzr7wt\n\n₿ 0.00 007 738", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_SINGLESIG, + NETWORKS["test"], + script_type=P2TR, + ) + ), ), ] - + case_num = 0 for case in cases: - signer = PSBTSigner(wallet, case[0], FORMAT_NONE) + print("test_outputs_singlesig case: ", case_num) + signer = PSBTSigner(case[2], case[0], FORMAT_NONE) outputs, _ = signer.outputs() assert outputs == case[1] + case_num += 1 def test_outputs_multisig(mocker, m5stickv, tdata): from embit.networks import NETWORKS from krux.psbt import PSBTSigner - from krux.key import Key, TYPE_MULTISIG + from krux.key import Key, TYPE_MULTISIG, P2WSH, P2SH_P2WSH from krux.wallet import Wallet from krux.qr import FORMAT_NONE - wallet = Wallet(Key(tdata.TEST_MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) cases = [ ( tdata.P2WSH_PSBT, @@ -614,6 +646,14 @@ def test_outputs_multisig(mocker, m5stickv, tdata): "1. Spend: \n\ntb1q35pg2rdt3p0v27dmdh9st43q8vzl29cps6kt3yradnqmg55eahfqfgn83n\n\n₿ 0.18 993 880", "1. Change: \n\ntb1q4xgr8suxvgenukgf4c7r6qaawxxmy9zelh24q8hg5pfxzn2ekn3qfw808t\n\n₿ 0.01 000 000", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_MULTISIG, + NETWORKS["test"], + script_type=P2WSH, + ) + ), ), ( tdata.P2SH_P2WSH_PSBT, @@ -622,11 +662,20 @@ def test_outputs_multisig(mocker, m5stickv, tdata): "1. Spend: \n\ntb1que40al7rsw88ru9z0vr78vqwme4w3ctqj694kx\n\n₿ 0.10 000 000", "1. Change: \n\n2N3vYfcg14Axr4NN33ADUorE2kEGEchFJpC\n\n₿ 0.89 995 740", ], + Wallet( + Key( + tdata.TEST_MNEMONIC, + TYPE_MULTISIG, + NETWORKS["test"], + script_type=P2SH_P2WSH, + ) + ), + # TODO: Add a descriptor so change can be deteted ), ] for case in cases: - signer = PSBTSigner(wallet, case[0], FORMAT_NONE) + signer = PSBTSigner(case[2], case[0], FORMAT_NONE) outputs, _ = signer.outputs() assert outputs == case[1] @@ -815,6 +864,7 @@ def test_sign_sats_vB(m5stickv): == "Inputs (7): ₿ 0.00 002 100\n\nSpend (3): ₿ 0.00 000 997\n\nFee: ₿ 0.00 001 103 (110.7%) ~1.8 sat/vB" ) + # TODO: Add a descriptor so change can be deteted wallet = Wallet(Key(MNEMONIC, TYPE_MULTISIG, NETWORKS["test"])) PSBT_satvB_164_83 = "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" signer = PSBTSigner(wallet, PSBT_satvB_164_83, FORMAT_PMOFN)