Merge branch 'dev' into export_xpub_eliminate

This commit is contained in:
Nick Klockenga
2026-01-19 21:39:53 -05:00
committed by GitHub
17 changed files with 250 additions and 235 deletions
+57 -13
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: seedsigner 0.8.6\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-11-21 14:39-0600\n"
"POT-Creation-Date: 2025-12-14 05:46-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -597,6 +597,10 @@ msgstr ""
msgid "Next {}"
msgstr ""
#: src/seedsigner/hardware/camera.py
msgid "Camera error. Check camera connections."
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Enabled"
msgstr ""
@@ -613,6 +617,21 @@ msgstr ""
msgid "Required"
msgstr ""
#. QR code format option; "default" = this is the format most wallets use
#: src/seedsigner/models/settings_definition.py
msgid "Animated (default)"
msgstr ""
#. QR code format option (static = single frame, not animated)
#: src/seedsigner/models/settings_definition.py
msgid "Static"
msgstr ""
#. QR code format option: old format that Specter Desktop used to use
#: src/seedsigner/models/settings_definition.py
msgid "Specter legacy"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "BTC"
msgstr ""
@@ -718,10 +737,6 @@ msgstr ""
msgid "Persistent settings"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Coordinator software"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Denomination display"
msgstr ""
@@ -746,6 +761,10 @@ msgstr ""
msgid "Script types"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Xpub QR format"
msgstr ""
#: src/seedsigner/models/settings_definition.py
msgid "Show xpub details"
msgstr ""
@@ -853,7 +872,7 @@ msgid ""
"addresses."
msgstr ""
#: src/seedsigner/views/psbt_views.py
#: src/seedsigner/views/psbt_views.py src/seedsigner/views/view.py
msgid "Continue"
msgstr ""
@@ -1003,9 +1022,8 @@ msgstr ""
msgid "QRCode is invalid or is a data format not yet supported."
msgstr ""
#: src/seedsigner/views/scan_views.py src/seedsigner/views/seed_views.py
#: src/seedsigner/views/view.py
msgid "Done"
#: src/seedsigner/views/scan_views.py src/seedsigner/views/view.py
msgid "Back to Main Menu"
msgstr ""
#. This is on the opening splash screen, displayed above the HRF logo
@@ -1074,6 +1092,10 @@ msgstr ""
msgid "Checksum failure; not a valid seed phrase."
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Done"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Edit passphrase"
msgstr ""
@@ -1159,6 +1181,10 @@ msgstr ""
msgid "Export Xpub"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Xpub QR Format"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Xpub can be used to view all future transactions."
msgstr ""
@@ -1321,10 +1347,6 @@ msgstr ""
msgid "Cancel"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Can't validate a single sig addr without specifying a seed"
msgstr ""
#: src/seedsigner/views/seed_views.py
msgid "Return to transaction"
msgstr ""
@@ -1518,6 +1540,18 @@ msgstr ""
msgid "System Error"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Hardware Error"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Cannot access camera"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Disconnect power and check for a loose camera connection."
msgstr ""
#: src/seedsigner/views/view.py
msgid "Update setting"
msgstr ""
@@ -1532,3 +1566,13 @@ msgstr ""
msgid "Option Disabled"
msgstr ""
#: src/seedsigner/views/view.py
msgid "Action Required"
msgstr ""
#: src/seedsigner/views/view.py
msgid ""
"You must remove the\n"
"MicroSD card to continue."
msgstr ""
+78 -36
View File
@@ -52,7 +52,7 @@ class GUIConstants:
"default": "OpenSans-Regular",
SettingsConstants.LOCALE__ARABIC: "NotoSansAR-Regular",
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: "NotoSansSC-Regular",
# SettingsConstants.LOCALE__CHINESE_TRADITIONAL: "NotoSansTC-Regular",
SettingsConstants.LOCALE__HINDI: "NotoSansDevanagari-Regular",
SettingsConstants.LOCALE__JAPANESE: "NotoSansJP-Regular",
SettingsConstants.LOCALE__KOREAN: "NotoSansKR-Regular",
SettingsConstants.LOCALE__PERSIAN: "NotoSansAR-Regular", # Uses the Arabic font
@@ -63,9 +63,10 @@ class GUIConstants:
TOP_NAV_TITLE_FONT_NAME["default"] = "OpenSans-SemiBold"
TOP_NAV_TITLE_FONT_SIZE = {
"default": 20,
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 23, # Some chars won't render below 23px
SettingsConstants.LOCALE__HINDI: 22,
SettingsConstants.LOCALE__JAPANESE: 22, # Titles won't render below 22px
SettingsConstants.LOCALE__KOREAN: 23, # Titles won't render below 23px
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 23, # Some chars won't render below 23px
}
TOP_NAV_HEIGHT = 48
TOP_NAV_BUTTON_SIZE = 32
@@ -73,9 +74,10 @@ class GUIConstants:
BODY_FONT_NAME = BASE_LOCALE_FONTS.copy()
BODY_FONT_SIZE = {
"default": 17,
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 18,
SettingsConstants.LOCALE__HINDI: 18,
SettingsConstants.LOCALE__JAPANESE: 18,
SettingsConstants.LOCALE__KOREAN: 18,
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 18,
}
BODY_FONT_MAX_SIZE = TOP_NAV_TITLE_FONT_SIZE["default"]
BODY_FONT_MIN_SIZE = 15
@@ -95,6 +97,7 @@ class GUIConstants:
"default": 18,
"ar": 18,
"fa": 18,
SettingsConstants.LOCALE__HINDI: 20,
SettingsConstants.LOCALE__JAPANESE: 20,
SettingsConstants.LOCALE__KOREAN: 20,
SettingsConstants.LOCALE__CHINESE_SIMPLIFIED: 20,
@@ -389,7 +392,13 @@ class TextArea(BaseComponent):
# Guaranteed to be a single line of text, possibly wider than self.width
self.text_lines = [{"text": self.text, "text_width": full_text_width}]
self.text_width = full_text_width
if self.text_width > self.visible_width:
# Technically, the math says that we should enable scrolling for as little as
# 1px beyond the available width, but it's not worth having the text twitch
# back and forth by such a small amount.
min_scrollable_diff = 1
if self.text_width > self.visible_width + min_scrollable_diff:
# We'll have to left justify the text and scroll it (if scrolling is enabled,
# otherwise it'll just run off the right edge).
self.is_text_centered = False
@@ -566,7 +575,7 @@ class TextArea(BaseComponent):
self.horizontal_scroll_position = 0
self.scroll_increment_sign = 1 # flip to negative to scroll text to the right
self.renderer = Renderer.get_instance()
self.renderer = Renderer.get_instance()
def stop_scrolling(self):
@@ -587,45 +596,57 @@ class TextArea(BaseComponent):
readability. 45px/sec is better but still perceptually a bit stuttery.
"""
max_scroll = self.rendered_text_img.width - self.visible_width
last_render_time = None
# The scrolling pauses at the start and end of the text line. These vars track
# when we started holding and how long we should hold for.
hold_started_at = None
cur_hold_duration = None
while self.keep_running:
if not self.scrolling_active:
time.sleep(0.1)
continue
with self.renderer.lock:
if not self.scrolling_active:
# We were stopped while waiting for the lock
if cur_hold_duration is not None:
# We're currently holding; see if we've held long enough
hold_time_elapsed = time.time() - hold_started_at
if hold_time_elapsed < cur_hold_duration:
# Still have to hold longer; skip scrolling logic
time.sleep(0.1)
continue
else:
# We've held long enough; reset the vars and resume scrolling
hold_started_at = None
cur_hold_duration = None
else:
# We're not holding, but if we've reached either end, we need to start
# holding.
if self.horizontal_scroll_position == 0:
# Pause on initial (left-justified) position...
hold_started_at = time.time()
cur_hold_duration = self.begin_hold_secs
# Next scroll direction will be left
self.scroll_increment_sign = 1
# Don't count those pause seconds
last_render_time = None
continue
img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height))
self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y))
self.renderer.show_image()
elif self.horizontal_scroll_position == max_scroll:
# ...and slight pause at end of scroll
hold_started_at = time.time()
cur_hold_duration = self.end_hold_secs
if self.horizontal_scroll_position == 0:
# Pause on initial (left-justified) position...
time.sleep(self.begin_hold_secs)
# Don't count those pause seconds
last_render_time = None
# Don't count those pause seconds
last_render_time = None
# Scroll will be to the right
self.scroll_increment_sign = -1
continue
# Scroll the text left
self.scroll_increment_sign = 1
elif self.horizontal_scroll_position == max_scroll:
# ...and slight pause at end of scroll
time.sleep(self.end_hold_secs)
# Don't count those pause seconds
last_render_time = None
# Scroll the text right
self.scroll_increment_sign = -1
else:
# No need to CPU limit when running in its own thread?
time.sleep(0.02)
next_render_time = time.time()
if not last_render_time:
@@ -633,18 +654,39 @@ class TextArea(BaseComponent):
# "get off zero" for the real increment calc logic to kick in.
scroll_position_increment = 1 * self.scroll_increment_sign
else:
# Calculate how far to scroll based on time elapsed since last render
scroll_position_increment = int(self.horizontal_scroll_speed * (next_render_time - last_render_time) * self.scroll_increment_sign)
# Only render an update if we're going to move at least 1px
if abs(scroll_position_increment) > 0:
self.horizontal_scroll_position += scroll_position_increment
self.horizontal_scroll_position = max(0, min(self.horizontal_scroll_position, max_scroll))
# max: Don't over-scroll when returning to the left edge (0)
# min: Don't over-scroll when revealing the right edge (max_scroll)
self.horizontal_scroll_position = max(
0,
min(self.horizontal_scroll_position + scroll_position_increment, max_scroll)
)
# Render the scroll update
with self.renderer.lock:
if not self.scrolling_active:
# We were stopped while waiting for the lock
continue
# The pre-rendered text img slides within a cropping window
img = self.rendered_text_img.crop((self.horizontal_scroll_position, 0, self.horizontal_scroll_position + self.visible_width, self.rendered_text_img.height))
self.renderer.canvas.paste(img, (self.screen_x, self.screen_y - self.scroll_y))
self.renderer.show_image()
last_render_time = next_render_time
else:
# Wait to accumulate more time before scrolling
# Wait to accumulate more time so we can scroll at least 1px
pass
# Free up the processor for a bit each loop
time.sleep(0.02)
def render(self):
"""
Even if we need to animate for scrolling, all instances should explicitly render
+3 -25
View File
@@ -62,7 +62,6 @@ class ScanScreen(BaseScreen):
self.camera.start_video_stream_mode(resolution=self.resolution, framerate=self.framerate, format="rgb")
self.frames_decode_status = ThreadsafeCounter()
self.frames_decoded_counter = ThreadsafeCounter()
self.threads.append(ScanScreen.LivePreviewThread(
decoder=self.decoder,
@@ -70,12 +69,11 @@ class ScanScreen(BaseScreen):
instructions_text=self.instructions_text,
render_rect=self.render_rect,
frame_decode_status=self.frames_decode_status,
frames_decoded_counter=self.frames_decoded_counter,
))
class LivePreviewThread(BaseThread):
def __init__(self, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: tuple[int,int,int,int], frame_decode_status: ThreadsafeCounter, frames_decoded_counter: ThreadsafeCounter):
def __init__(self, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: tuple[int,int,int,int], frame_decode_status: ThreadsafeCounter):
from seedsigner.hardware.camera import Camera
self.camera = Camera.get_instance()
@@ -87,8 +85,6 @@ class ScanScreen(BaseScreen):
else:
self.render_rect = (0, 0, self.renderer.canvas_width, self.renderer.canvas_height)
self.frame_decode_status = frame_decode_status
self.frames_decoded_counter = frames_decoded_counter
self.last_frame_decoded_count = self.frames_decoded_counter.cur_count
self.render_width = self.render_rect[2] - self.render_rect[0]
self.render_height = self.render_rect[3] - self.render_rect[1]
self.decoder_fps = "0.0"
@@ -97,39 +93,23 @@ class ScanScreen(BaseScreen):
def run(self):
from timeit import default_timer as timer
instructions_font = Fonts.get_font(GUIConstants.get_body_font_name(), GUIConstants.get_button_font_size())
# pre-calculate how big the animated QR percent display can be
left, top, right, bottom = instructions_font.getbbox("100%")
(left, top, right, bottom) = instructions_font.getbbox("100%")
progress_text_width = right - left
start_time = time.time()
num_frames = 0
debug = False
show_framerate = False # enable for debugging / testing
while self.keep_running:
frame = self.camera.read_video_stream(as_image=True)
if frame is not None:
num_frames += 1
cur_time = time.time()
cur_fps = num_frames / (cur_time - start_time)
scan_text = None
progress_percentage = self.decoder.get_percent_complete()
if progress_percentage == 0:
# We've just started scanning, no results yet
if show_framerate:
scan_text = f"{cur_fps:0.2f} | {self.decoder_fps}"
else:
scan_text = self.instructions_text
elif debug:
# Special debugging output for animated QRs
scan_text = f"{self.decoder.get_percent_complete()}% | {self.decoder.get_percent_complete(weight_mixed_frames=True)}% (new)"
if show_framerate:
scan_text += f" {cur_fps:0.2f} | {self.decoder_fps}"
scan_text = self.instructions_text
with self.renderer.lock:
# Use nearest neighbor resizing for max speed
@@ -216,7 +196,6 @@ class ScanScreen(BaseScreen):
# Render the dot to indicate successful QR frame read
indicator_size = 10
self.last_frame_decoded_count = self.frames_decoded_counter.cur_count
status_color_map = {
ScanScreen.FRAME__ADDED_PART: GUIConstants.SUCCESS_COLOR,
ScanScreen.FRAME__REPEATED_PART: GUIConstants.INACTIVE_COLOR,
@@ -267,7 +246,6 @@ class ScanScreen(BaseScreen):
self.camera.stop_video_stream_mode()
break
self.frames_decoded_counter.increment()
# Notify the live preview thread how our most recent decode went
if status == DecodeQRStatus.FALSE:
# Did not find anything to decode in the current frame
-16
View File
@@ -389,7 +389,6 @@ class ButtonListScreen(BaseTopNavScreen):
if self.has_scroll_arrows:
self.arrow_half_width = 10
self.cur_scroll_y = self.scroll_y_initial_offset if self.scroll_y_initial_offset is not None else 0
self.up_arrow_img = Image.new("RGBA", size=(2 * self.arrow_half_width, 8), color="black")
self.up_arrow_img_y = self.top_nav.height - 12
arrow_draw = ImageDraw.Draw(self.up_arrow_img)
@@ -1087,21 +1086,6 @@ class ResetScreen(BaseTopNavScreen):
@dataclass
class PowerOffScreen(BaseTopNavScreen):
def __post_init__(self):
self.title = _("Powering Off")
self.show_back_button = False
super().__post_init__()
self.components.append(TextArea(
text=_("Please wait about 30 seconds before disconnecting power."),
screen_y=self.top_nav.height,
height=self.canvas_height - self.top_nav.height,
))
@dataclass
class PowerOffNotRequiredScreen(BaseTopNavScreen):
def __post_init__(self):
+1 -3
View File
@@ -9,9 +9,7 @@ from seedsigner.models.singleton import Singleton
class CameraConnectionError(Exception):
def __init__(self, *args, **kwargs):
message = _("Camera error. Check camera connections.")
super().__init__(message)
pass
@@ -110,14 +110,3 @@ def get_partial_final_word(coin_flips: str, wordlist_language_code: str = Settin
wordlist_index = int(binary_string, 2)
return Seed.get_wordlist(wordlist_language_code)[wordlist_index]
# Note: This currently isn't being used since we're now chaining hashed bytes for the
# image-based entropy and aren't just ingesting a single image.
def generate_mnemonic_from_image(image, wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH) -> list[str]:
import hashlib
hash = hashlib.sha256(image.tobytes())
# Return as a list
return bip39.mnemonic_from_bytes(hash.digest(), wordlist=Seed.get_wordlist(wordlist_language_code)).split()
+5 -1
View File
@@ -238,7 +238,11 @@ class BaseSimpleAnimatedQREncoder(BaseQrEncoder):
@dataclass
class SpecterXPubQrEncoder(BaseSimpleAnimatedQREncoder, BaseXpubQrEncoder):
class SpecterLegacyXPubQrEncoder(BaseSimpleAnimatedQREncoder, BaseXpubQrEncoder):
"""
Legacy "pXofY" format. Included here for compatibility with much older versions of
Specter Desktop. Can probably eventually be removed.
"""
@property
def qr_max_fragment_size(self):
density_mapping = {
+25 -26
View File
@@ -38,17 +38,18 @@ class SettingsConstants:
]
# User-facing selection options
COORDINATOR__BLUE_WALLET = "bw"
COORDINATOR__NUNCHUK = "nun"
COORDINATOR__SPARROW = "spa"
COORDINATOR__SPECTER_DESKTOP = "spd"
COORDINATOR__KEEPER = "kpr"
ALL_COORDINATORS = [
(COORDINATOR__BLUE_WALLET, "BlueWallet"),
(COORDINATOR__NUNCHUK, "Nunchuk"),
(COORDINATOR__SPARROW, "Sparrow"),
(COORDINATOR__SPECTER_DESKTOP, "Specter Desktop"),
(COORDINATOR__KEEPER, "Keeper"),
XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT = "urca"
XPUB_QR_FORMAT__STATIC = "sta"
XPUB_QR_FORMAT__SPECTER_LEGACY = "spl"
ALL_XPUB_QR_FORMATS = [
# TRANSLATOR_NOTE: QR code format option; "default" = this is the format most wallets use
(XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT, _mft("Animated (default)")),
# TRANSLATOR_NOTE: QR code format option (static = single frame, not animated)
(XPUB_QR_FORMAT__STATIC, _mft("Static")),
# TRANSLATOR_NOTE: QR code format option: old format that Specter Desktop used to use
(XPUB_QR_FORMAT__SPECTER_LEGACY, _mft("Specter legacy")),
]
# Over-specifying current and possible future locales to reduce/eliminate main repo
@@ -129,6 +130,7 @@ class SettingsConstants:
# --------- Beta languages ------------------------------------------------------
LOCALE__CHINESE_SIMPLIFIED: "(beta) 简体中文 (Chinese Simplified)",
LOCALE__HINDI: "(beta) हिन्दी (Hindi)",
LOCALE__JAPANESE: "(beta) 日本語 (Japanese)",
LOCALE__KOREAN: "(beta) 한국어 (Korean)",
LOCALE__THAI: "(beta) ไทย (Thai)",
@@ -149,7 +151,6 @@ class SettingsConstants:
# LOCALE__GUJARATI: "ગુજરાતી (Gujarati)",
LOCALE__HAUSA: "Hausa",
# LOCALE__HEBREW: "עברית (Hebrew)",
# LOCALE__HINDI: "हिन्दी (Hindi)",
LOCALE__CROATIAN: "Hrvatski",
LOCALE__INDONESIAN: "Indonesia",
LOCALE__JAVANESE: "Jawa (Javanese)",
@@ -326,7 +327,7 @@ class SettingsConstants:
SETTING__LOCALE = "locale"
SETTING__WORDLIST_LANGUAGE = "wordlist_language"
SETTING__PERSISTENT_SETTINGS = "persistent_settings"
SETTING__COORDINATORS = "coordinators"
SETTING__XPUB_QR_FORMAT = "xpub_qr"
SETTING__BTC_DENOMINATION = "denomination"
SETTING__DISPLAY_CONFIGURATION = "display_config"
@@ -568,19 +569,6 @@ class SettingsDefinition:
help_text=SettingsConstants.PERSISTENT_SETTINGS__SD_INSERTED__HELP_TEXT,
default_value=SettingsConstants.OPTION__DISABLED),
SettingsEntry(category=SettingsConstants.CATEGORY__WALLET,
attr_name=SettingsConstants.SETTING__COORDINATORS,
abbreviated_name="coords",
display_name=_mft("Coordinator software"),
type=SettingsConstants.TYPE__MULTISELECT,
selection_options=SettingsConstants.ALL_COORDINATORS,
default_value=[
SettingsConstants.COORDINATOR__BLUE_WALLET,
SettingsConstants.COORDINATOR__NUNCHUK,
SettingsConstants.COORDINATOR__SPARROW,
SettingsConstants.COORDINATOR__SPECTER_DESKTOP,
]),
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
attr_name=SettingsConstants.SETTING__BTC_DENOMINATION,
abbreviated_name="denom",
@@ -625,6 +613,17 @@ class SettingsDefinition:
selection_options=SettingsConstants.ALL_SCRIPT_TYPES,
default_value=[SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT, SettingsConstants.TAPROOT]),
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
attr_name=SettingsConstants.SETTING__XPUB_QR_FORMAT,
display_name=_mft("Xpub QR format"),
visibility=SettingsConstants.VISIBILITY__ADVANCED,
type=SettingsConstants.TYPE__MULTISELECT,
selection_options=SettingsConstants.ALL_XPUB_QR_FORMATS,
default_value=[
SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT,
SettingsConstants.XPUB_QR_FORMAT__STATIC,
]),
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
attr_name=SettingsConstants.SETTING__XPUB_DETAILS,
display_name=_mft("Show xpub details"),
+23 -25
View File
@@ -11,7 +11,7 @@ from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconCo
from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen,
WarningScreen, DireWarningScreen, seed_screens)
from seedsigner.gui.screens.screen import ButtonOption, ButtonOptionWithoutTranslation
from seedsigner.models.encode_qr import CompactSeedQrEncoder, GenericStaticQrEncoder, SeedQrEncoder, SpecterXPubQrEncoder, StaticXpubQrEncoder, UrXpubQrEncoder
from seedsigner.models.encode_qr import CompactSeedQrEncoder, GenericStaticQrEncoder, SeedQrEncoder, SpecterLegacyXPubQrEncoder, StaticXpubQrEncoder, UrXpubQrEncoder
from seedsigner.models.qr_type import QRType
from seedsigner.models.seed import Seed
from seedsigner.models.settings import Settings, SettingsConstants
@@ -719,7 +719,7 @@ class SeedExportXpubScriptTypeView(View):
del args["sig_type"]
return Destination(ToolsAddressExplorerAddressTypeView, view_args=args, skip_current_view=True)
else:
return Destination(SeedExportXpubCoordinatorView, view_args=args, skip_current_view=True)
return Destination(SeedExportXpubQRFormatView, view_args=args, skip_current_view=True)
title = _("Export Xpub")
if self.controller.resume_main_flow == Controller.FLOW__ADDRESS_EXPLORER:
@@ -754,7 +754,7 @@ class SeedExportXpubScriptTypeView(View):
del args["sig_type"]
return Destination(ToolsAddressExplorerAddressTypeView, view_args=args)
else:
return Destination(SeedExportXpubCoordinatorView, view_args=args)
return Destination(SeedExportXpubQRFormatView, view_args=args)
@@ -785,7 +785,7 @@ class SeedExportXpubCustomDerivationView(View):
return Destination(ToolsAddressExplorerAddressTypeView, view_args=dict(seed_num=self.seed_num, script_type=self.script_type, custom_derivation=custom_derivation))
return Destination(
SeedExportXpubCoordinatorView,
SeedExportXpubQRFormatView,
view_args={
"seed_num": self.seed_num,
"sig_type": self.sig_type,
@@ -796,7 +796,7 @@ class SeedExportXpubCustomDerivationView(View):
class SeedExportXpubCoordinatorView(View):
class SeedExportXpubQRFormatView(View):
def __init__(self, seed_num: int, sig_type: str, script_type: str, custom_derivation: str = None):
super().__init__()
self.seed_num = seed_num
@@ -812,41 +812,39 @@ class SeedExportXpubCoordinatorView(View):
"script_type": self.script_type,
"custom_derivation": self.custom_derivation,
}
if len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)) == 1:
if len(self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)) == 1:
# Nothing to select; skip this screen
args["coordinator"] = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0]
args["xpub_qr_format"] = self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)[0]
return Destination(SeedExportXpubWarningView, view_args=args, skip_current_view=True)
button_data = []
for display_name, setting_option in zip(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS), self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)):
for display_name, setting_option in zip(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT), self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)):
button_data.append(ButtonOption(display_name, return_data=setting_option))
selected_menu_num = self.run_screen(
ButtonListScreen,
title=_("Export Xpub"),
title=_("Xpub QR Format"),
is_button_text_centered=False,
button_data=button_data,
is_bottom_list=True,
)
if selected_menu_num == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
# coordinators_settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__COORDINATORS)
# selected_display_name = button_data[selected_menu_num]
# args["coordinator"] = coordinators_settings_entry.get_selection_option_value_by_display_name(selected_display_name)
args["coordinator"] = button_data[selected_menu_num].return_data
args["xpub_qr_format"] = button_data[selected_menu_num].return_data
return Destination(SeedExportXpubWarningView, view_args=args)
class SeedExportXpubWarningView(View):
def __init__(self, seed_num: int, sig_type: str, script_type: str, coordinator: str, custom_derivation: str):
def __init__(self, seed_num: int, sig_type: str, script_type: str, xpub_qr_format: str, custom_derivation: str):
super().__init__()
self.seed_num = seed_num
self.sig_type = sig_type
self.script_type = script_type
self.coordinator = coordinator
self.xpub_qr_format = xpub_qr_format
self.custom_derivation = custom_derivation
@@ -857,7 +855,7 @@ class SeedExportXpubWarningView(View):
"seed_num": self.seed_num,
"sig_type": self.sig_type,
"script_type": self.script_type,
"coordinator": self.coordinator,
"xpub_qr_format": self.xpub_qr_format,
"custom_derivation": self.custom_derivation,
},
skip_current_view=True, # Prevent going BACK to WarningViews
@@ -887,11 +885,11 @@ class SeedExportXpubDetailsView(View):
Collects the user input from all the previous screens leading up to this and
finally calculates the xpub and displays the summary view to the user.
"""
def __init__(self, seed_num: int, sig_type: str, script_type: str, coordinator: str, custom_derivation: str):
def __init__(self, seed_num: int, sig_type: str, script_type: str, xpub_qr_format: str, custom_derivation: str):
super().__init__()
self.sig_type = sig_type
self.script_type = script_type
self.coordinator = coordinator
self.xpub_qr_format = xpub_qr_format
self.custom_derivation = custom_derivation
self.seed_num = seed_num
@@ -954,7 +952,7 @@ class SeedExportXpubDetailsView(View):
return Destination(
SeedExportXpubQRDisplayView,
dict(seed_num=self.seed_num,
coordinator=self.coordinator,
xpub_qr_format=self.xpub_qr_format,
derivation_path=derivation_path,
sig_type=self.sig_type
)
@@ -966,7 +964,7 @@ class SeedExportXpubDetailsView(View):
class SeedExportXpubQRDisplayView(View):
def __init__(self, seed_num: int, coordinator: str, derivation_path: str, sig_type: str = SettingsConstants.SINGLE_SIG):
def __init__(self, seed_num: int, xpub_qr_format: str, derivation_path: str, sig_type: str = SettingsConstants.SINGLE_SIG):
super().__init__()
self.seed = self.controller.get_seed(seed_num)
@@ -978,14 +976,14 @@ class SeedExportXpubQRDisplayView(View):
sig_type=sig_type
)
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
self.qr_encoder = SpecterXPubQrEncoder(**encoder_args)
elif coordinator in [SettingsConstants.COORDINATOR__BLUE_WALLET,
SettingsConstants.COORDINATOR__KEEPER]:
if xpub_qr_format == SettingsConstants.XPUB_QR_FORMAT__STATIC:
self.qr_encoder = StaticXpubQrEncoder(**encoder_args)
elif xpub_qr_format == SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY:
self.qr_encoder = SpecterLegacyXPubQrEncoder(**encoder_args)
else:
# Default: UR crypto-address
self.qr_encoder = UrXpubQrEncoder(**encoder_args)
+6 -6
View File
@@ -237,8 +237,8 @@ def generate_screenshots(locale):
)
add_settings_entries(SettingsConstants.VISIBILITY__HARDWARE)
settingsqr_data_persistent = f"settings::v1 name=English_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
settingsqr_data_not_persistent = f"settings::v1 name=Mode_Ephemeral persistent=D coords=spa,spd denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
settingsqr_data_persistent = f"settings::v1 name=English_noob_mode persistent=E xpub_qr=urca,sta denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
settingsqr_data_not_persistent = f"settings::v1 name=Mode_Ephemeral persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E locale={locale}"
# Set up screenshot-specific callbacks to inject data before the View is run and
# reset data after the View is run.
@@ -334,10 +334,10 @@ def generate_screenshots(locale):
ScreenshotConfig(seed_views.SeedExportXpubSigTypeView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedExportXpubScriptTypeView, dict(seed_num=0, sig_type="msig")),
ScreenshotConfig(seed_views.SeedExportXpubCustomDerivationView, dict(seed_num=0, sig_type="ss", script_type="")),
ScreenshotConfig(seed_views.SeedExportXpubCoordinatorView, dict(seed_num=0, sig_type="ss", script_type="nat")),
ScreenshotConfig(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", coordinator="spd", custom_derivation="")),
ScreenshotConfig(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", coordinator="bw", custom_derivation="")),
ScreenshotConfig(SeedExportXpubQR_ScreenBrightnessView, dict(seed_num=0, coordinator="bw", derivation_path="m/84'/0'/0'")),
ScreenshotConfig(seed_views.SeedExportXpubQRFormatView, dict(seed_num=0, sig_type="ss", script_type="nat")),
ScreenshotConfig(seed_views.SeedExportXpubWarningView, dict(seed_num=0, sig_type="msig", script_type="nes", xpub_qr_format="urca", custom_derivation="")),
ScreenshotConfig(seed_views.SeedExportXpubDetailsView, dict(seed_num=0, sig_type="ss", script_type="nat", xpub_qr_format="urca", custom_derivation="")),
ScreenshotConfig(SeedExportXpubQR_ScreenBrightnessView, dict(seed_num=0, xpub_qr_format="urca", derivation_path="m/84'/0'/0'")),
ScreenshotConfig(seed_views.SeedWordsWarningView, dict(seed_num=0)),
ScreenshotConfig(seed_views.SeedWordsView, dict(seed_num=0)),
-35
View File
@@ -4,7 +4,6 @@ import pytest
from base import BaseTest
from seedsigner.controller import Controller
from seedsigner.models.settings_definition import SettingsConstants
class TestController(BaseTest):
@@ -80,37 +79,3 @@ class TestController(BaseTest):
# ...get a new copy of the instance and confirm change
controller = Controller.get_instance()
assert controller.unverified_address == "123abc"
def test_missing_settings_get_defaults(self):
""" Should gracefully handle all missing fields from `settings.json` """
controller = Controller.get_instance()
# Settings defaults
assert controller.settings.get_value(SettingsConstants.SETTING__LOCALE) == SettingsConstants.LOCALE__ENGLISH
assert controller.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE) == SettingsConstants.WORDLIST_LANGUAGE__ENGLISH
assert controller.settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED
assert controller.settings.get_value(SettingsConstants.SETTING__COORDINATORS) == [i for i,j in SettingsConstants.ALL_COORDINATORS if i!="kpr"]
assert controller.settings.get_value(SettingsConstants.SETTING__BTC_DENOMINATION) == SettingsConstants.BTC_DENOMINATION__THRESHOLD
# Advanced Settings defaults
assert controller.settings.get_value(SettingsConstants.SETTING__NETWORK) == SettingsConstants.MAINNET
assert controller.settings.get_value(SettingsConstants.SETTING__QR_DENSITY) == SettingsConstants.DENSITY__MEDIUM
assert controller.settings.get_value(SettingsConstants.SETTING__SIG_TYPES) == [i for i,j in SettingsConstants.ALL_SIG_TYPES]
assert controller.settings.get_value(SettingsConstants.SETTING__SCRIPT_TYPES) == [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.NESTED_SEGWIT, SettingsConstants.TAPROOT]
assert controller.settings.get_value(SettingsConstants.SETTING__XPUB_DETAILS) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__PASSPHRASE) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__CAMERA_ROTATION) == SettingsConstants.CAMERA_ROTATION__180
assert controller.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__BIP85_CHILD_SEEDS) == SettingsConstants.OPTION__DISABLED
assert controller.settings.get_value(SettingsConstants.SETTING__MESSAGE_SIGNING) == SettingsConstants.OPTION__DISABLED
assert controller.settings.get_value(SettingsConstants.SETTING__PRIVACY_WARNINGS) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS_TIPS) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__PARTNER_LOGOS) == SettingsConstants.OPTION__ENABLED
assert controller.settings.get_value(SettingsConstants.SETTING__MICROSD_TOAST_TIMER) == SettingsConstants.MICROSD_TOAST_TIMER_FIVE_SECONDS
# Hidden Settings defaults
assert controller.settings.get_value(SettingsConstants.SETTING__QR_BRIGHTNESS) == 62
+2 -2
View File
@@ -1,4 +1,4 @@
from seedsigner.models.encode_qr import CompactSeedQrEncoder, SeedQrEncoder, SpecterXPubQrEncoder, StaticXpubQrEncoder, UrPsbtQrEncoder, UrXpubQrEncoder
from seedsigner.models.encode_qr import CompactSeedQrEncoder, SeedQrEncoder, SpecterLegacyXPubQrEncoder, StaticXpubQrEncoder, UrPsbtQrEncoder, UrXpubQrEncoder
from embit import psbt
from binascii import a2b_base64
@@ -54,7 +54,7 @@ def test_xpub_qr():
def test_specter_xpub_qr():
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
e = SpecterXPubQrEncoder(seed=Seed(mnemonic.split(" "), passphrase="pass"), network=SettingsConstants.TESTNET, derivation="m/48h/1h/0h/2h", qr_density=SettingsConstants.DENSITY__LOW)
e = SpecterLegacyXPubQrEncoder(seed=Seed(mnemonic.split(" "), passphrase="pass"), network=SettingsConstants.TESTNET, derivation="m/48h/1h/0h/2h", qr_density=SettingsConstants.DENSITY__LOW)
assert e.next_part() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
assert e.next_part() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
+25 -25
View File
@@ -184,7 +184,7 @@ class TestSeedFlows(FlowTest):
"""
Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView
"""
def flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple):
def flowtest_standard_xpub(sig_tuple, script_tuple, xpub_qr_tuple):
if sig_tuple[0] == SettingsConstants.SINGLE_SIG:
sig_selection = seed_views.SeedExportXpubSigTypeView.SINGLE_SIG
else:
@@ -195,7 +195,7 @@ class TestSeedFlows(FlowTest):
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_selection),
FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=ButtonOption(script_tuple[1], return_data=script_tuple[0])),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=ButtonOption(coord_tuple[1], return_data=coord_tuple[0])),
FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(xpub_qr_tuple[1], return_data=xpub_qr_tuple[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),
@@ -211,17 +211,17 @@ class TestSeedFlows(FlowTest):
# these are lists of (constant_value, display_name) tuples
sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES
script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES
coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS
xpub_qr_formats: list[tuple[str, str]] = SettingsConstants.ALL_XPUB_QR_FORMATS
# enable non-defaults so they're available in views
self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types])
self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types])
self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators])
self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in xpub_qr_formats])
# exhaustively test flows thru standard sig_types, script_types, and coordinators
# exhaustively test flows thru standard sig_types, script_types, and xpub_qr_formats
for sig_tuple in sig_types:
for script_tuple in script_types:
for coord_tuple in coordinators:
for xpub_qr_tuple in xpub_qr_formats:
# skip custom derivation
if script_tuple[0] == SettingsConstants.CUSTOM_DERIVATION:
continue
@@ -229,13 +229,13 @@ class TestSeedFlows(FlowTest):
elif sig_tuple[0] == SettingsConstants.MULTISIG and script_tuple[0] == SettingsConstants.TAPROOT:
continue
else:
print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple))
flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple)
print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, xpub_qr_tuple))
flowtest_standard_xpub(sig_tuple, script_tuple, xpub_qr_tuple)
def test_export_xpub_disabled_not_available_flow(self):
"""
If sig_type/script_type/coordinator disabled, then these options are not available
If sig_type/script_type/xpub_qr_format disabled, then these options are not available
"""
# Load a finalized Seed into the Controller
mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split()
@@ -245,17 +245,17 @@ class TestSeedFlows(FlowTest):
# these are lists of (constant_value, display_name) tuples
sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES
script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES
coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS
xpub_qr_formats: list[tuple[str, str]] = SettingsConstants.ALL_XPUB_QR_FORMATS
# these are the disabled types that we will be testing
disabled_sig = SettingsConstants.MULTISIG
disabled_script = SettingsConstants.TAPROOT
disabled_coord = SettingsConstants.COORDINATOR__NUNCHUK
disabled_xpub_qr_format = SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY
# enable all but our target disabled type
self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types if x!=disabled_sig])
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])
self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in xpub_qr_formats if x!=disabled_xpub_qr_format])
# If multisig isn't an option, then the sig type selection is skipped altogether
self.run_sequence(
@@ -286,7 +286,7 @@ class TestSeedFlows(FlowTest):
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubScriptTypeView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=disabled_coord),
FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=disabled_xpub_qr_format),
]
)
@@ -307,8 +307,8 @@ class TestSeedFlows(FlowTest):
SettingsConstants.CUSTOM_DERIVATION
])
# Ensure that all coordinators are enabled
self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x, y in SettingsConstants.ALL_COORDINATORS])
# Ensure that all xpub_qr_formats are enabled
self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x, y in SettingsConstants.ALL_XPUB_QR_FORMATS])
# Set up button_data selections
sig_type = seed_views.SeedExportXpubSigTypeView.SINGLE_SIG
@@ -316,9 +316,9 @@ class TestSeedFlows(FlowTest):
custom_derivation = SettingsConstants.CUSTOM_DERIVATION
script_type = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES)[2], return_data=custom_derivation)
specter = SettingsConstants.COORDINATOR__SPECTER_DESKTOP
assert SettingsConstants.ALL_COORDINATORS[3][0] == specter
coordinator = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[3], return_data=specter)
specter_legacy = SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY
assert SettingsConstants.ALL_XPUB_QR_FORMATS[2][0] == specter_legacy
xpub_qr_format = ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT)[2], return_data=specter_legacy)
self.run_sequence(
initial_destination_view_args=dict(seed_num=0),
@@ -327,7 +327,7 @@ class TestSeedFlows(FlowTest):
FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_type),
FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=script_type),
FlowStep(seed_views.SeedExportXpubCustomDerivationView, screen_return_value="m/0'/0'"),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=coordinator),
FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=xpub_qr_format),
FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0),
@@ -338,18 +338,18 @@ class TestSeedFlows(FlowTest):
def test_export_xpub_skip_non_option_flow(self):
"""
Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or coordinators
Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or xpub_qr_formats
"""
# Load a finalized Seed into the Controller
mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split()
self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic))
self.controller.storage.finalize_pending_seed()
# exclusively set only one choice for each of sig_types, script_types and coordinators
# exclusively set only one choice for each of sig_types, script_types and xpub_qr_formats
self.settings.update({
SettingsConstants.SETTING__SIG_TYPES: SettingsConstants.MULTISIG,
SettingsConstants.SETTING__SCRIPT_TYPES: SettingsConstants.NESTED_SEGWIT,
SettingsConstants.SETTING__COORDINATORS: SettingsConstants.COORDINATOR__SPECTER_DESKTOP,
SettingsConstants.SETTING__XPUB_QR_FORMAT: SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT,
})
self.run_sequence(
@@ -358,7 +358,7 @@ class TestSeedFlows(FlowTest):
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB),
FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubCoordinatorView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubQRFormatView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0),
@@ -379,7 +379,7 @@ class TestSeedFlows(FlowTest):
# 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.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [x for x,y in SettingsConstants.ALL_XPUB_QR_FORMATS])
self.run_sequence(
initial_destination_view_args=dict(seed_num=0),
@@ -389,7 +389,7 @@ class TestSeedFlows(FlowTest):
# Skips past the script type options via redirect
FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[0], return_data=SettingsConstants.ALL_COORDINATORS[0][0])),
FlowStep(seed_views.SeedExportXpubQRFormatView, button_data_selection=ButtonOption(self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__XPUB_QR_FORMAT)[0], return_data=SettingsConstants.ALL_XPUB_QR_FORMATS[0][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),
+4 -3
View File
@@ -39,10 +39,11 @@ class TestSettingsFlows(FlowTest):
def test_multiselect(self):
""" Multiselect Settings options should stay in-place; requires BACK to exit. """
# Which option are we testing?
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__COORDINATORS)
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__XPUB_QR_FORMAT)
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.SETTINGS),
FlowStep(settings_views.SettingsMenuView, button_data_selection=settings_views.SettingsMenuView.ADVANCED),
FlowStep(settings_views.SettingsMenuView, button_data_selection=ButtonOption(settings_entry.display_name)),
FlowStep(settings_views.SettingsEntryUpdateSelectionView, screen_return_value=0), # select/deselect first option
FlowStep(settings_views.SettingsEntryUpdateSelectionView, screen_return_value=1), # select/deselect second option
@@ -78,11 +79,11 @@ class TestSettingsFlows(FlowTest):
MainMenuView.
"""
def load_persistent_settingsqr_into_decoder(view: scan_views.ScanView):
settingsqr_data_persistent: str = "settings::v1 name=Total_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"
settingsqr_data_persistent: str = "settings::v1 name=Total_noob_mode persistent=E xpub_qr=urca,sta denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"
view.decoder.add_data(settingsqr_data_persistent)
def load_not_persistent_settingsqr_into_decoder(view: scan_views.ScanView):
settingsqr_data_not_persistent: str = "settings::v1 name=Ephemeral_noob_mode persistent=D coords=spa,spd denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"
settingsqr_data_not_persistent: str = "settings::v1 name=Ephemeral_noob_mode persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"
view.decoder.add_data(settingsqr_data_not_persistent)
def _run_test(initial_setting_state: str, load_settingsqr_into_decoder: Callable, expected_setting_state: str):
+20 -7
View File
@@ -1,7 +1,7 @@
import pytest
from base import BaseTest
from seedsigner.models.settings import InvalidSettingsQRData, Settings
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition
@@ -14,6 +14,10 @@ class TestSettings(BaseTest):
def test_reset_settings(self):
""" BaseTest.reset_settings() should wipe out any previous Settings changes """
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__PERSISTENT_SETTINGS)
assert settings_entry.default_value == SettingsConstants.OPTION__DISABLED
# Change the setting from its default
settings = Settings.get_instance()
settings.set_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS, SettingsConstants.OPTION__ENABLED)
assert settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__ENABLED
@@ -23,18 +27,27 @@ class TestSettings(BaseTest):
assert settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == SettingsConstants.OPTION__DISABLED
def test_settings_defaults(self):
""" Settings should initialize to their default values """
BaseTest.reset_settings()
settings = Settings.get_instance()
for settings_entry in SettingsDefinition.get_settings_entries():
assert settings.get_value(settings_entry.attr_name) == settings_entry.default_value
def test_parse_settingsqr_data(self):
"""
SettingsQR parser should successfully parse a valid settingsqr input string and
return the resulting config_name and formatted settings_update_dict.
"""
settings_name = "Test SettingsQR"
settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"""
settingsqr_data = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D denom=thr network=M qr_density=M sigs=ss,ms scripts=nat,nes,tr xpub_qr=urca,sta xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"""
# First explicitly set settings that differ from the settingsqr_data
self.settings.set_value(SettingsConstants.SETTING__COMPACT_SEEDQR, SettingsConstants.OPTION__DISABLED)
self.settings.set_value(SettingsConstants.SETTING__DIRE_WARNINGS, SettingsConstants.OPTION__DISABLED)
self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [SettingsConstants.COORDINATOR__BLUE_WALLET, SettingsConstants.COORDINATOR__SPARROW])
self.settings.set_value(SettingsConstants.SETTING__XPUB_QR_FORMAT, [SettingsConstants.XPUB_QR_FORMAT__STATIC, SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY])
# Now parse the settingsqr_data
config_name, settings_update_dict = Settings.parse_settingsqr(settingsqr_data)
@@ -45,10 +58,10 @@ class TestSettings(BaseTest):
assert self.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__ENABLED
assert self.settings.get_value(SettingsConstants.SETTING__DIRE_WARNINGS) == SettingsConstants.OPTION__ENABLED
coordinators = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)
assert SettingsConstants.COORDINATOR__BLUE_WALLET not in coordinators
assert SettingsConstants.COORDINATOR__SPARROW in coordinators
assert SettingsConstants.COORDINATOR__SPECTER_DESKTOP in coordinators
xpub_qr_formats = self.settings.get_value(SettingsConstants.SETTING__XPUB_QR_FORMAT)
assert SettingsConstants.XPUB_QR_FORMAT__UR_CRYPTO_ACCOUNT in xpub_qr_formats
assert SettingsConstants.XPUB_QR_FORMAT__STATIC in xpub_qr_formats
assert SettingsConstants.XPUB_QR_FORMAT__SPECTER_LEGACY not in xpub_qr_formats
def test_settingsqr_version(self):
+1 -1
View File
@@ -9,7 +9,7 @@ class TestSettingsQRDecoder:
with parsing the result.
"""
settings_name = "Test SettingsQR"
settings_qr_str = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D coords=spa,spd denom=thr network=M qr_density=M sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"""
settings_qr_str = f"""settings::v1 name={ settings_name.replace(" ", "_") } persistent=D xpub_qr=urca,sta denom=thr network=M qr_density=M sigs=ss,ms scripts=nat,nes,tr xpub_details=E passphrase=E camera=180 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E"""
# Now parse the settings_qr_str
decoder = DecodeQR()