Skip to content

Commit

Permalink
Routing bugfixes, additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmukai committed Jul 14, 2024
1 parent e078a60 commit 21d4363
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/seedsigner/helpers/embit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# 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'"
Expand Down
18 changes: 9 additions & 9 deletions src/seedsigner/models/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,15 @@ def set_wordlist_language_code(self, language_code: str):


@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)

Expand Down Expand Up @@ -205,17 +205,17 @@ def normalize_electrum_passphrase(passphrase : str) -> str:


@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
Expand Down
5 changes: 2 additions & 3 deletions src/seedsigner/views/psbt_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ def run(self):
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)



Expand Down
12 changes: 10 additions & 2 deletions src/seedsigner/views/seed_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,8 +653,16 @@ def __init__(self, seed_num: int, sig_type: str):
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]
Expand Down Expand Up @@ -1694,7 +1702,7 @@ def __init__(self, seed_num: int = None):
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"]
Expand Down
6 changes: 3 additions & 3 deletions src/seedsigner/views/tools_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ def run(self):
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)



Expand Down Expand Up @@ -532,7 +532,7 @@ def __init__(self, seed_num: int = None, script_type: str = None, custom_derivat
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
Expand Down
45 changes: 44 additions & 1 deletion tests/test_flows_psbt.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -57,7 +58,49 @@ def load_seed_into_decoder(view: scan_views.ScanView):
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):
Expand Down
53 changes: 42 additions & 11 deletions tests/test_flows_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

# 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

Expand Down Expand Up @@ -238,15 +238,15 @@ def test_export_xpub_disabled_not_available_flow(self):
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:
Expand Down Expand Up @@ -340,6 +340,37 @@ def test_export_xpub_skip_non_option_flow(self):
)


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
Expand Down
41 changes: 36 additions & 5 deletions tests/test_flows_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ def test__address_explorer__loadseed__sideflow(self):
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")

Expand All @@ -55,11 +53,11 @@ def load_seed_into_decoder(view: scan_views.ScanView):
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(
Expand All @@ -73,6 +71,39 @@ def load_seed_into_decoder(view: scan_views.ScanView):
)


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
Expand Down

0 comments on commit 21d4363

Please sign in to comment.