mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 23:18:25 +00:00
Merge branch 'dev' into export_xpub_eliminate
This commit is contained in:
+57
-13
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user