Routing bugfixes, additional tests

This commit is contained in:
kdmukai
2024-07-14 13:21:23 -05:00
parent 8c17ef1b63
commit 3b50309a9e
8 changed files with 147 additions and 34 deletions
+1
View File
@@ -19,6 +19,7 @@ from seedsigner.models.settings_definition import SettingsConstants
# TODO: PR these directly into `embit`? Or replace with new/existing methods already in `embit`?
# TODO: Refactor `wallet_type` to conform to our `sig_type` naming convention
def get_standard_derivation_path(network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG, script_type: str = SettingsConstants.NATIVE_SEGWIT) -> str:
if network == SettingsConstants.MAINNET:
network_path = "0'"
+9 -9
View File
@@ -109,15 +109,15 @@ class Seed:
@property
def script_override(self) -> list:
def script_override(self) -> str:
return None
def derivation_override(self, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
def derivation_override(self, sig_type: str = SettingsConstants.SINGLE_SIG) -> str:
return None
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, sig_type: str = SettingsConstants.SINGLE_SIG) -> str:
embit_network = NETWORKS[SettingsConstants.map_network_to_embit(network)]
return bip32.detect_version(derivation_path, default="xpub", network=embit_network)
@@ -205,17 +205,17 @@ class ElectrumSeed(Seed):
@property
def script_override(self) -> list:
return [SettingsConstants.NATIVE_SEGWIT]
def script_override(self) -> str:
return SettingsConstants.NATIVE_SEGWIT
def derivation_override(self, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
return "m/0h" if SettingsConstants.SINGLE_SIG == wallet_type else "m/1h"
def derivation_override(self, sig_type: str = SettingsConstants.SINGLE_SIG) -> str:
return "m/0h" if sig_type == SettingsConstants.SINGLE_SIG else "m/1h"
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, sig_type: str = SettingsConstants.SINGLE_SIG) -> str:
embit_network = NETWORKS[SettingsConstants.map_network_to_embit(network)]
return embit_network["zpub"] if SettingsConstants.SINGLE_SIG == wallet_type else embit_network["Zpub"]
return embit_network["zpub"] if sig_type == SettingsConstants.SINGLE_SIG else embit_network["Zpub"]
@property
+2 -3
View File
@@ -79,9 +79,8 @@ class PSBTSelectSeedView(View):
return Destination(SeedMnemonicEntryView)
elif button_data[selected_menu_num] == self.TYPE_ELECTRUM:
from seedsigner.views.seed_views import SeedMnemonicEntryView
self.controller.storage.init_pending_mnemonic(num_words=12, is_electrum=True)
return Destination(SeedMnemonicEntryView)
from seedsigner.views.seed_views import SeedElectrumMnemonicStartView
return Destination(SeedElectrumMnemonicStartView)
+10 -2
View File
@@ -688,8 +688,16 @@ class SeedExportXpubScriptTypeView(View):
def run(self):
from .tools_views import ToolsAddressExplorerAddressTypeView
args = {"seed_num": self.seed_num, "sig_type": self.sig_type}
script_types = self.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES)
seed = self.controller.storage.seeds[self.seed_num]
script_types = seed.script_override if seed.script_override else self.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES)
if seed.script_override:
# This seed only allows one script type
# TODO: Does it matter if the Settings don't have the override script type
# enabled?
script_types = [seed.script_override]
if len(script_types) == 1:
# Nothing to select; skip this screen
args["script_type"] = script_types[0]
@@ -1729,7 +1737,7 @@ class SeedAddressVerificationView(View):
raise Exception("Can't validate a single sig addr without specifying a seed")
self.seed_num = seed_num
self.seed = self.controller.get_seed(seed_num)
self.seed_derivation_override = self.seed.derivation_override(wallet_type=SettingsConstants.SINGLE_SIG)
self.seed_derivation_override = self.seed.derivation_override(sig_type=SettingsConstants.SINGLE_SIG)
else:
self.seed = None
self.address = self.controller.unverified_address["address"]
+3 -3
View File
@@ -496,8 +496,8 @@ class ToolsAddressExplorerSelectSourceView(View):
return Destination(SeedMnemonicEntryView)
elif button_data[selected_menu_num] == self.TYPE_ELECTRUM:
from seedsigner.views.seed_views import SeedElectrumWarningView
return Destination(SeedElectrumWarningView)
from seedsigner.views.seed_views import SeedElectrumMnemonicStartView
return Destination(SeedElectrumMnemonicStartView)
@@ -532,7 +532,7 @@ class ToolsAddressExplorerAddressTypeView(View):
if self.seed_num is not None:
self.seed = self.controller.storage.seeds[seed_num]
data["seed_num"] = self.seed
seed_derivation_override = self.seed.derivation_override(wallet_type=SettingsConstants.SINGLE_SIG)
seed_derivation_override = self.seed.derivation_override(sig_type=SettingsConstants.SINGLE_SIG)
if self.script_type == SettingsConstants.CUSTOM_DERIVATION:
derivation_path = self.custom_derivation
+44 -1
View File
@@ -1,5 +1,6 @@
from base import FlowTest, FlowStep
from seedsigner.controller import Controller
from seedsigner.views.view import MainMenuView
from seedsigner.views import scan_views, seed_views, psbt_views
from seedsigner.models.settings import SettingsConstants
@@ -57,7 +58,49 @@ class TestPSBTFlows(FlowTest):
FlowStep(MainMenuView)
])
def test_scan_psbt_first_then_load_electrum_seed(self):
"""
Should be able to load an Electrum mnemonic after first loading in a psbt.
"""
def load_psbt_into_decoder(view: scan_views.ScanView):
# Same psbt as above, but we don't care about the details here
view.decoder.add_data("cHNidP8BANgCAAAAAsTXZs3fz/dmGb6M80+jjvJZdYya+cw5bT/dGuhZFdSlAAAAAAD9////qo6xg/UZAvUkcbse1F+C9zbP/FeZNjThx7SCIn6eMCgBAAAAAP3///8EQOIBAAAAAAAWABSkZPM7kLcTRE2En1t33/0RCHgMjQXYnnYAAAAAFgAUKMaPRKXdY4m8iKrE9j+rycskJU1A4gEAAAAAABYAFPYc9wiHRrYKAZYLLztREAwpPBIwipVcAwAAAAAWABSiFuiJIa4NrxLUBVQNS0NIun6DDtoRAABPAQQ1h88DBcQGZIAAAAA+0J+jlNL3dpWwlnBi8Dx+Ipg4e6uvB3HdjzFPX7r9CAOOlAIxgII+/xCcj+XoEenKH7wj5s5wlu7Q7CCZWFLGLhA5Su0UVAAAgAEAAIAAAACAAAEA7QIAAAAEE6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0BAAAAAP3///8TqeNf9+e+fuFuQohFzHM1gU5J9sJ015ad3sV7/VRxDQMAAAAA/f///xOp41/3575+4W5CiEXMczWBTkn2wnTXlp3exXv9VHENBAAAAAD9////E6njX/fnvn7hbkKIRcxzNYFOSfbCdNeWnd7Fe/1UcQ0GAAAAAP3///8CUnheAwAAAAAWABRCfygPJ+Fjsx4BknYvvm3A3qKn2xJ/XQcAAAAAF6kU1I4TAst5nAj15ey7vwe5cM3OFq+HlhEAAAEBH1J4XgMAAAAAFgAUQn8oDyfhY7MeAZJ2L75twN6ip9sBAwQBAAAAIgYCo7sfm78RQY3B5n0ac/QF8VtMAzFnci+h5D1MtpgRY7oYOUrtFFQAAIABAACAAAAAgAEAAAAGAAAAAAEAcQIAAAABxY7wh0nsfJQfzWrD/9rN9BYsM+iOmPaO6I0ANFgO/PcAAAAAAP3///8CptiUAAAAAAAWABRIm4HhQY/TzOjeWSPRrbuJo9MlW826oHYAAAAAFgAU0z+0L2QSLGtyQTn8FhbCpcI7jbliAQAAAQEfzbqgdgAAAAAWABTTP7QvZBIsa3JBOfwWFsKlwjuNuQEDBAEAAAAiBgITHmebEANk81CraV4xZIpqkNjjw0tIvezl1Ism1NRH3Rg5Su0UVAAAgAEAAIAAAACAAQAAAAAAAAAAIgICuTT7WnuiUTpObjWnZFHzIeEvW9PTB+1LLVFNQJVFeIIYOUrtFFQAAIABAACAAAAAgAEAAAAHAAAAACICAk8f3hpc5C35chgSg+Pe2zZ9IhHREd4aKW2+yAMRIFeqGDlK7RRUAACAAQAAgAAAAIABAAAACQAAAAAAIgIDjt1CjvrnMMnjbmTNKUAYoKEDRbmKjNjbq+6Ppqj3bqQYOUrtFFQAAIABAACAAAAAgAEAAAAIAAAAAA==")
self.settings.set_value(SettingsConstants.SETTING__ELECTRUM_SEEDS, SettingsConstants.OPTION__ENABLED)
sequence = [
FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN),
FlowStep(scan_views.ScanView, before_run=load_psbt_into_decoder), # simulate read PSBT; ret val is ignored
FlowStep(psbt_views.PSBTSelectSeedView, button_data_selection=psbt_views.PSBTSelectSeedView.TYPE_ELECTRUM),
FlowStep(seed_views.SeedElectrumMnemonicStartView),
]
# Load an Electrum mnemonic during the flow (same one used in test_seed.py)
# This seed can't actually sign the psbt.
for word in "regular reject rare profit once math fringe chase until ketchup century escape".split():
sequence += [
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=word),
]
sequence += [
FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE),
# TODO: Change the PSBT to one that this Electrum seed can actually sign in
# order to test the full flow:
# FlowStep(seed_views.SeedOptionsView, is_redirect=True),
# FlowStep(psbt_views.PSBTOverviewView),
# Until then, the View won't actually auto-route us back into the PSBT flow
FlowStep(seed_views.SeedOptionsView),
]
self.run_sequence(sequence)
# But we can at least verify that we're still in the PSBT flow
assert self.controller.resume_main_flow == Controller.FLOW__PSBT
def test_scan_multisig_psbt_seed_already_signed_flow(self):
def load_psbt_into_decoder(view: scan_views.ScanView):
+42 -11
View File
@@ -3,11 +3,11 @@ import pytest
# Must import test base before the Controller
from base import BaseTest, FlowTest, FlowStep
from base import FlowTestRunScreenNotExecutedException, FlowTestInvalidButtonDataSelectionException
from base import FlowTestInvalidButtonDataSelectionException
from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON
from seedsigner.models.settings import Settings, SettingsConstants
from seedsigner.models.seed import Seed
from seedsigner.models.seed import ElectrumSeed, Seed
from seedsigner.views.view import ErrorView, MainMenuView, OptionDisabledView, RemoveMicroSDWarningView, View, NetworkMismatchErrorView, NotYetImplementedView
from seedsigner.views import seed_views, scan_views, settings_views, tools_views
@@ -243,15 +243,15 @@ class TestSeedFlows(FlowTest):
self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types if x!=disabled_script])
self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators if x!=disabled_coord])
# test that multisig is not an option via exception raised when redirected to next step instead of having a choice
with pytest.raises(FlowTestRunScreenNotExecutedException) as e:
self.run_sequence(
initial_destination_view_args=dict(seed_num=0),
sequence=[
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=disabled_sig),
]
)
# If multisig isn't an option, then the sig type selection is skipped altogether
self.run_sequence(
initial_destination_view_args=dict(seed_num=0),
sequence=[
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubScriptTypeView),
]
)
# test that taproot is not an option via exception raised when choice is taproot
with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e:
@@ -345,6 +345,37 @@ class TestSeedFlows(FlowTest):
)
def test_export_xpub_electrum_seed_flow(self):
"""
Electrum seeds should skip script type selection
"""
# Load a finalized Seed into the Controller
self.controller.storage.init_pending_mnemonic(num_words=12, is_electrum=True)
self.controller.storage.set_pending_seed(ElectrumSeed("regular reject rare profit once math fringe chase until ketchup century escape".split()))
self.controller.storage.finalize_pending_seed()
# Make sure all options are enabled
self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in SettingsConstants.ALL_SIG_TYPES])
self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in SettingsConstants.ALL_SCRIPT_TYPES])
self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in SettingsConstants.ALL_COORDINATORS])
self.run_sequence(
initial_destination_view_args=dict(seed_num=0),
sequence=[
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=seed_views.SeedExportXpubSigTypeView.SINGLE_SIG),
# Skips past the script type options via redirect
FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[0]),
FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0),
FlowStep(MainMenuView),
]
)
def test_discard_seed_flow(self):
"""
Selecting "Discard Seed" from the SeedOptionsView should enter the Discard Seed flow and
+36 -5
View File
@@ -39,8 +39,6 @@ class TestToolsFlows(FlowTest):
Finalizing a seed during the Address Explorer flow should return to the next
Address Explorer step upon completion.
"""
controller = Controller.get_instance()
def load_seed_into_decoder(view: scan_views.ScanView):
view.decoder.add_data("0000" * 11 + "0003")
@@ -55,11 +53,11 @@ class TestToolsFlows(FlowTest):
FlowStep(seed_views.SeedExportXpubScriptTypeView),
])
assert controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER
assert self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER
# Reset
controller.storage.seeds.clear()
controller.storage.set_pending_seed(Seed(mnemonic=["abandon "* 11 + "about"]))
self.controller.storage.seeds.clear()
self.controller.storage.set_pending_seed(Seed(mnemonic=["abandon "* 11 + "about"]))
# Finalize the new seed w/passphrase
self.run_sequence(
@@ -73,6 +71,39 @@ class TestToolsFlows(FlowTest):
)
def test__address_explorer__load_electrum_seed__sideflow(self):
"""
Loading an Electrum seed during the Address Explorer flow should return to
the Address Explorer flow upon completion, skip the script type selection,
and successfully generate receive or change addresses.
"""
self.settings.set_value(SettingsConstants.SETTING__ELECTRUM_SEEDS, SettingsConstants.OPTION__ENABLED)
sequence = [
FlowStep(MainMenuView, button_data_selection=MainMenuView.TOOLS),
FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.ADDRESS_EXPLORER),
FlowStep(tools_views.ToolsAddressExplorerSelectSourceView, button_data_selection=tools_views.ToolsAddressExplorerSelectSourceView.TYPE_ELECTRUM),
FlowStep(seed_views.SeedElectrumMnemonicStartView),
]
# Load an Electrum mnemonic during the flow (same one used in test_seed.py)
for word in "regular reject rare profit once math fringe chase until ketchup century escape".split():
sequence += [
FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=word),
]
sequence += [
FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE),
FlowStep(seed_views.SeedOptionsView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True),
FlowStep(tools_views.ToolsAddressExplorerAddressTypeView, button_data_selection=tools_views.ToolsAddressExplorerAddressTypeView.RECEIVE),
FlowStep(tools_views.ToolsAddressExplorerAddressListView),
]
self.run_sequence(sequence)
def test__address_explorer__scan_wrong_qrtype__flow(self):
"""
Scanning the wrong type of QR code when a SeedQR is expected should route to ErrorView