Merge branch dev into messaging toast

This commit is contained in:
alvroble
2025-02-01 11:26:34 +01:00
21 changed files with 373 additions and 269 deletions
+28 -12
View File
@@ -31,7 +31,7 @@ jobs:
target: [ "pi0", "pi2", "pi02w", "pi4" ]
steps:
- name: checkout seedsigner-os
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: "seedsigner/seedsigner-os"
# use the os-ref input parameter in case of workflow_dispatch or default to main in case of cron triggers
@@ -42,7 +42,7 @@ jobs:
fetch-depth: 0
- name: checkout source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# ref defaults to repo default-branch=dev (cron) or SHA of event (workflow_dispatch)
path: "seedsigner-os/opt/rootfs-overlay/opt"
@@ -78,7 +78,7 @@ jobs:
ls -la src
- name: restore build cache
uses: actions/cache@v3
uses: actions/cache@v4
# Caching reduces the build time to ~50% (currently: ~30 mins instead of ~1 hour,
# while consuming ~850 MB storage space).
with:
@@ -89,10 +89,25 @@ jobs:
restore-keys: |
build-cache-${{ matrix.target }}-
- name: Create build container
run: |
cd seedsigner-os
docker build -t seedsigner-os-build .
- name: build
run: |
cd seedsigner-os/opt
./build.sh --${{ matrix.target }} --skip-repo --no-clean
mkdir -p \
~/.buildroot-ccache \
seedsigner-os/buildroot_dl
docker run \
--rm \
-v "$(pwd)/seedsigner-os/opt:/opt" \
-v "$(pwd)/seedsigner-os/images:/images" \
-v "$(pwd)/seedsigner-os/buildroot_dl:/buildroot_dl" \
-v "${HOME}/.buildroot-ccache:/root/.buildroot-ccache" \
seedsigner-os-build \
--${{ matrix.target }} --skip-repo --no-clean
sudo chown -R $USER:$USER seedsigner-os/images seedsigner-os/buildroot_dl ~/.buildroot-ccache/
- name: list image (before rename)
run: |
@@ -113,9 +128,9 @@ jobs:
ls -la seedsigner-os/images
- name: upload images
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: seedsigner_os_images
name: seedsigner_os_images-${{ matrix.target }}
path: "seedsigner-os/images/*.img"
if-no-files-found: error
# maximum 90 days retention
@@ -127,14 +142,13 @@ jobs:
needs: build
steps:
- name: download images
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: seedsigner_os_images
path: images
- name: list images
run: |
ls -la images
ls -lRa images
- name: get seedsigner latest commit hash
id: get-seedsigner-hash
@@ -145,12 +159,14 @@ jobs:
- name: write sha256sum
run: |
cd images
# each downloaded image is in its own subfolder
find . -name "*.img" -exec mv {} . \;
sha256sum *.img > seedsigner_os.${{ env.source_hash }}.sha256
- name: upload checksums
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: seedsigner_os_images
name: seedsigner_os_images_sha256
path: "images/*.sha256"
if-no-files-found: error
# maximum 90 days retention
+4 -4
View File
@@ -26,12 +26,12 @@ jobs:
python-version: ["3.10", "3.12"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
# Needs to also pull the seedsigner-translations repo
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -63,9 +63,9 @@ jobs:
- name: Coverage report
run: coverage report
- name: Archive CI Artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ci-artifacts
name: ci-artifacts-${{ matrix.python-version }}
path: artifacts/**
retention-days: 10
# Upload also when tests fail. The workflow result (red/green) will
+29 -37
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: seedsigner 0.8.5-rc1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-01-15 10:45-0600\n"
"POT-Creation-Date: 2025-01-28 13:28-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"
@@ -36,16 +36,6 @@ msgstr ""
msgid "sats"
msgstr ""
#. The abbreviated label for the special key <del> on a standard keyboard.
#: src/seedsigner/gui/keyboard.py
msgid "del"
msgstr ""
#. The abbreviated label for the special key <space> on a standard keyboard.
#: src/seedsigner/gui/keyboard.py
msgid "space"
msgstr ""
#: src/seedsigner/gui/toast.py
msgid ""
"You can remove\n"
@@ -215,7 +205,7 @@ msgstr ""
msgid "OK"
msgstr ""
#: src/seedsigner/gui/screens/screen.py src/seedsigner/views/psbt_views.py
#: src/seedsigner/gui/screens/screen.py
msgid "Caution"
msgstr ""
@@ -231,6 +221,11 @@ msgstr ""
msgid "Classified Info!"
msgstr ""
#: src/seedsigner/gui/screens/screen.py src/seedsigner/views/scan_views.py
#: src/seedsigner/views/view.py
msgid "Error"
msgstr ""
#: src/seedsigner/gui/screens/screen.py
msgid "Restarting"
msgstr ""
@@ -516,7 +511,7 @@ msgid "Home"
msgstr ""
#: src/seedsigner/gui/screens/tools_screens.py
msgid "click joystick"
msgid "click a button"
msgstr ""
#. A prompt to the user to either accept or reshoot the image
@@ -901,13 +896,7 @@ msgstr ""
#. Variable is either "change" or "self-transfer".
#: src/seedsigner/views/psbt_views.py
msgid ""
"PSBT's {} address could not be verified with your multisig wallet "
"descriptor."
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Suspicious PSBT"
msgid "PSBT's {} address could not be verified from wallet descriptor."
msgstr ""
#. Variable is either "change" or "self-transfer".
@@ -915,6 +904,10 @@ msgstr ""
msgid "PSBT's {} address could not be generated from your seed."
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Suspicious PSBT"
msgstr ""
#: src/seedsigner/views/psbt_views.py
msgid "Address Verification Failed"
msgstr ""
@@ -955,23 +948,6 @@ msgstr ""
msgid "Wrong QR Type"
msgstr ""
#: src/seedsigner/views/scan_views.py src/seedsigner/views/view.py
msgid "Error"
msgstr ""
#: src/seedsigner/views/scan_views.py
msgid "Unknown QR Type"
msgstr ""
#: src/seedsigner/views/scan_views.py
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"
msgstr ""
#: src/seedsigner/views/scan_views.py src/seedsigner/views/seed_views.py
msgid "Scan PSBT"
msgstr ""
@@ -1004,6 +980,19 @@ msgstr ""
msgid "Expected an address QR"
msgstr ""
#: src/seedsigner/views/scan_views.py
msgid "Unknown QR Type"
msgstr ""
#: src/seedsigner/views/scan_views.py
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"
msgstr ""
#. This is on the opening splash screen, displayed above the HRF logo
#: src/seedsigner/views/screensaver.py
msgid "With support from:"
@@ -1459,10 +1448,13 @@ msgstr ""
msgid "Back to Main Menu"
msgstr ""
#. The network setting (mainnet/testnet/regtest) doesn't match the provided
#. derivation path
#: src/seedsigner/views/view.py
msgid "Network Mismatch"
msgstr ""
#. Button option to alter a setting
#: src/seedsigner/views/view.py
msgid "Change Setting"
msgstr ""
+1 -1
View File
@@ -13,7 +13,7 @@ description = "Build an offline, airgapped Bitcoin signing device for less than
name = "seedsigner"
readme = "README.md"
requires-python = ">=3.10"
version = "0.8.5-rc1"
version = "0.8.5"
[project.urls]
"Bug Tracker" = "https://github.com/SeedSigner/seedsigner/issues"
+1 -1
View File
@@ -99,7 +99,7 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""
VERSION = "0.8.5-rc1"
VERSION = "0.8.5"
# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
+5 -15
View File
@@ -217,9 +217,9 @@ class SeedSignerIconConstants:
# Messaging icons
INFO = "\ue912"
ERROR = "\ue913"
SUCCESS = "\ue914"
WARNING = "\ue915"
SUCCESS = "\ue913"
WARNING = "\ue914"
ERROR = "\ue915"
# Informational icons
ADDRESS = "\ue916"
@@ -241,8 +241,9 @@ class SeedSignerIconConstants:
DELETE = "\ue922"
SPACE = "\ue923"
# Must be updated whenever new icons are added. See usage in `Icon` class below.
MIN_VALUE = SCAN
MAX_VALUE = QRCODE
MAX_VALUE = SPACE
@@ -280,17 +281,6 @@ def calc_text_centering(font: ImageFont,
def load_icon(icon_name: str, load_selected_variant: bool = False):
icon_url = os.path.join(pathlib.Path(__file__).parent.resolve(), "..", "resources", "icons", icon_name)
icon = Image.open(icon_url + ".png").convert("RGB")
if not load_selected_variant:
return icon
else:
icon_selected = Image.open(icon_url + "_selected.png").convert("RGB")
return (icon, icon_selected)
def load_image(image_name: str) -> Image.Image:
image_url = os.path.join(pathlib.Path(__file__).parent.resolve(), "..", "resources", "img", image_name)
image = Image.open(image_url).convert("RGB")
+32 -29
View File
@@ -3,7 +3,7 @@ from PIL import Image, ImageDraw, ImageFont
from typing import Tuple
from gettext import gettext as _
from seedsigner.gui.components import Fonts, GUIConstants
from seedsigner.gui.components import Fonts, GUIConstants, SeedSignerIconConstants
from seedsigner.hardware.buttons import HardwareButtonsConstants
@@ -30,58 +30,55 @@ class Keyboard:
ENTER_RIGHT = "enter_right"
REGULAR_KEY_FONT = "regular"
COMPACT_KEY_FONT = "compact"
ICON_KEY_FONT = GUIConstants.ICON_FONT_NAME__SEEDSIGNER
# TRANSLATOR_NOTE: The abbreviated label for the special key <del> on a standard keyboard.
del_label = _("del")
KEY_BACKSPACE = {
"code": "DEL",
"letter": del_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.DELETE,
"font": ICON_KEY_FONT,
"size": 2,
}
# TRANSLATOR_NOTE: The abbreviated label for the special key <space> on a standard keyboard.
space_label = _("space")
KEY_SPACE = {
"code": "SPACE",
"letter": space_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.SPACE,
"font": ICON_KEY_FONT,
"size": 1,
}
KEY_SPACE_2 = {
"code": "SPACE",
"letter": space_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.SPACE,
"font": ICON_KEY_FONT,
"size": 2,
}
KEY_SPACE_3 = {
"code": "SPACE",
"letter": space_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.SPACE,
"font": ICON_KEY_FONT,
"size": 3,
}
KEY_SPACE_4 = {
"code": "SPACE",
"letter": space_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.SPACE,
"font": ICON_KEY_FONT,
"size": 4,
}
KEY_SPACE_5 = {
"code": "SPACE",
"letter": space_label,
"font": COMPACT_KEY_FONT,
"letter": SeedSignerIconConstants.SPACE,
"font": ICON_KEY_FONT,
"size": 5,
}
KEY_CURSOR_LEFT = {
"code": "CURSOR_LEFT",
"letter": "<",
"font": REGULAR_KEY_FONT,
"letter": SeedSignerIconConstants.CHEVRON_LEFT,
"font": ICON_KEY_FONT,
"size": 1,
}
KEY_CURSOR_RIGHT = {
"code": "CURSOR_RIGHT",
"letter": ">",
"font": REGULAR_KEY_FONT,
"letter": SeedSignerIconConstants.CHEVRON_RIGHT,
"font": ICON_KEY_FONT,
"size": 1,
}
KEY_PREVIOUS_PAGE = {
@@ -123,9 +120,11 @@ class Keyboard:
def render_key(self):
font = self.keyboard.font
text_height = self.keyboard.text_height
if self.is_additional_key:
if Keyboard.ADDITIONAL_KEYS[self.code]["font"] == Keyboard.COMPACT_KEY_FONT:
font = self.keyboard.additonal_key_compact_font
if Keyboard.ADDITIONAL_KEYS[self.code]["font"] == Keyboard.ICON_KEY_FONT:
font = self.keyboard.icon_key_font
text_height = self.keyboard.icon_key_height
outline_color = "#333"
if not self.is_active:
@@ -159,15 +158,12 @@ class Keyboard:
radius=4
)
# Fixed-width fonts will all have same height, ignoring below baseline (e.g. "Q" or "q")
(left, top, right, bottom) = font.getbbox("X", anchor="ls")
text_height = -1 * top
self.keyboard.draw.text(
(
self.screen_x + int(self.keyboard.key_width * self.size / 2),
self.screen_y + self.keyboard.key_height - int((self.keyboard.key_height - text_height)/2)
),
_(self.letter),
self.letter,
fill=font_color,
font=font,
anchor="ms"
@@ -217,8 +213,15 @@ class Keyboard:
# Set up the rendering and state params
self.active_keys = list(self.charset)
self.icon_key_font = Fonts.get_font(GUIConstants.ICON_FONT_NAME__SEEDSIGNER, 26)
# Fixed-width fonts will all have same height, ignoring below baseline (e.g. "Q" or "q")
(left, top, right, bottom) = self.font.getbbox("X", anchor="ls")
self.text_height = -1 * top
(left, top, right, bottom) = self.icon_key_font.getbbox(SeedSignerIconConstants.DELETE + SeedSignerIconConstants.SPACE, anchor="ls")
self.icon_key_height = -1 * top
self.additonal_key_compact_font = Fonts.get_font("RobotoCondensed-Bold", 18)
self.x_start = rect[0]
self.y_start = rect[1]
self.x_gap = 2
+19 -2
View File
@@ -309,7 +309,7 @@ class ButtonListScreen(BaseTopNavScreen):
if len(self.button_data) == 1:
button_list_height = button_height
else:
button_list_height = (len(self.button_data) * button_height) + (GUIConstants.COMPONENT_PADDING * (len(self.button_data) - 1))
button_list_height = (len(self.button_data) * button_height) + (GUIConstants.LIST_ITEM_PADDING * (len(self.button_data) - 1))
if self.is_bottom_list:
button_list_y = self.canvas_height - (button_list_height + GUIConstants.EDGE_PADDING)
@@ -1013,9 +1013,12 @@ class WarningEdgesMixin:
@dataclass
class WarningScreen(WarningEdgesMixin, LargeIconStatusScreen):
"""
Exclamation point icon + yellow WARNING color
"""
title: str = _mft("Caution")
status_icon_name: str = SeedSignerIconConstants.WARNING
status_color: str = "yellow"
status_color: str = GUIConstants.WARNING_COLOR
status_headline: str = _mft("Privacy Leak!") # The colored text under the alert icon
button_data: list = field(default_factory=lambda: [ButtonOption("I Understand")])
@@ -1023,11 +1026,25 @@ class WarningScreen(WarningEdgesMixin, LargeIconStatusScreen):
@dataclass
class DireWarningScreen(WarningScreen):
"""
Exclamation point icon + orange DIRE_WARNING color
"""
status_headline: str = _mft("Classified Info!") # The colored text under the alert icon
status_color: str = GUIConstants.DIRE_WARNING_COLOR
@dataclass
class ErrorScreen(WarningScreen):
"""
X icon + red ERROR color
"""
title: str = _mft("Error")
status_icon_name: str = SeedSignerIconConstants.ERROR
status_color: str = GUIConstants.ERROR_COLOR
@dataclass
class ResetScreen(BaseTopNavScreen):
def __post_init__(self):
@@ -6,7 +6,7 @@ from PIL.ImageOps import autocontrast
from typing import List
from seedsigner.helpers.l10n import mark_for_translation as _mft
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, Fonts, GUIConstants, Icon, IconButton, IconTextLine, TextArea
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, Fonts, GUIConstants, Icon, IconButton, IconTextLine, SeedSignerIconConstants, TextArea
from seedsigner.gui.screens.scan_screens import ScanScreen
from seedsigner.gui.screens.screen import BaseScreen, BaseTopNavScreen, ButtonListScreen, ButtonOption
from seedsigner.hardware.buttons import HardwareButtonsConstants
@@ -82,7 +82,7 @@ class IOTestScreen(BaseTopNavScreen):
self.components.append(self.joystick_click_button)
self.joystick_up_button = IconButton(
icon_name=FontAwesomeIconConstants.ANGLE_UP,
icon_name=SeedSignerIconConstants.CHEVRON_UP,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
width=input_button_width,
height=input_button_height,
@@ -93,7 +93,7 @@ class IOTestScreen(BaseTopNavScreen):
self.components.append(self.joystick_up_button)
self.joystick_down_button = IconButton(
icon_name=FontAwesomeIconConstants.ANGLE_DOWN,
icon_name=SeedSignerIconConstants.CHEVRON_DOWN,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
width=input_button_width,
height=input_button_height,
@@ -104,9 +104,8 @@ class IOTestScreen(BaseTopNavScreen):
self.components.append(self.joystick_down_button)
self.joystick_left_button = IconButton(
text=FontAwesomeIconConstants.ANGLE_LEFT,
font_name=GUIConstants.ICON_FONT_NAME__FONT_AWESOME,
font_size=GUIConstants.ICON_INLINE_FONT_SIZE,
icon_name=SeedSignerIconConstants.CHEVRON_LEFT,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
width=input_button_width,
height=input_button_height,
screen_x=dpad_center_x - input_button_width - GUIConstants.COMPONENT_PADDING,
@@ -116,7 +115,7 @@ class IOTestScreen(BaseTopNavScreen):
self.components.append(self.joystick_left_button)
self.joystick_right_button = IconButton(
icon_name=FontAwesomeIconConstants.ANGLE_RIGHT,
icon_name=SeedSignerIconConstants.CHEVRON_RIGHT,
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
width=input_button_width,
height=input_button_height,
+6 -5
View File
@@ -43,8 +43,8 @@ class ToolsImageEntropyLivePreviewScreen(BaseScreen):
time.sleep(0.01)
continue
# Check for joystick click to take final entropy image
if self.hw_inputs.check_for_low(HardwareButtonsConstants.KEY_PRESS):
# Check for ANYCLICK to take final entropy image
if self.hw_inputs.check_for_low(keys=HardwareButtonsConstants.KEYS__ANYCLICK):
# Have to manually update last input time since we're not in a wait_for loop
self.hw_inputs.update_last_input_time()
self.camera.stop_video_stream_mode()
@@ -77,7 +77,7 @@ class ToolsImageEntropyLivePreviewScreen(BaseScreen):
int(self.renderer.canvas_width/2),
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
),
text="< " + _("back") + " | " + _("click joystick"), # TODO: Render with UI elements instead of text
text="< " + _("back") + " | " + _("click a button"), # TODO: Render with UI elements instead of text
fill=GUIConstants.BODY_FONT_COLOR,
font=instructions_font,
stroke_width=4,
@@ -123,7 +123,8 @@ class ToolsImageEntropyFinalImageScreen(BaseScreen):
)
self.renderer.show_image()
input = self.hw_inputs.wait_for([HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT])
# LEFT = reshoot, RIGHT / ANYCLICK = accept
input = self.hw_inputs.wait_for([HardwareButtonsConstants.KEY_LEFT, HardwareButtonsConstants.KEY_RIGHT] + HardwareButtonsConstants.KEYS__ANYCLICK)
if input == HardwareButtonsConstants.KEY_LEFT:
return RET_CODE__BACK_BUTTON
@@ -140,7 +141,7 @@ class ToolsDiceEntropyEntryScreen(KeyboardScreen):
self.rows = 3
self.cols = 3
self.keyboard_font_name = GUIConstants.ICON_FONT_NAME__FONT_AWESOME
self.keyboard_font_size = None # Force auto-scaling to Key height
self.keyboard_font_size = 36
self.keys_charset = "".join([
FontAwesomeIconConstants.DICE_ONE,
FontAwesomeIconConstants.DICE_TWO,
+57 -47
View File
@@ -507,8 +507,7 @@ class DecodeQR:
def is_bitcoin_address(s):
if re.search(r'^bitcoin\:.*', s, re.IGNORECASE):
return True
elif re.search(r'^((bc1|tb1|bcr|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,62})$', s):
# TODO: Handle regtest bcrt?
elif re.search(r'^((bc1|tb1|bcr|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,62})$', s, re.IGNORECASE):
return True
else:
return False
@@ -940,61 +939,72 @@ class BitcoinAddressQrDecoder(BaseSingleFrameQrDecoder):
def add(self, segment, qr_type=QRType.BITCOIN_ADDRESS):
r = re.search(r'((bc1q|tb1q|bcrt1q|bc1p|tb1p|bcrt1p|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,64})', segment)
if r != None:
self.address = r.group(1)
if re.search(r'^((bc1q|tb1q|bcrt1q|bc1p|tb1p|bcrt1p|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,64})$', self.address) != None:
self.complete = True
self.collected_segments = 1
# get address type
r = re.search(r'^((bc1q|tb1q|bcrt1q|bc1p|tb1p|bcrt1p|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,64})$', self.address)
if r != None:
r = r.group(2)
if r == "1":
# Legacy P2PKH. mainnet
self.address_type = (SettingsConstants.LEGACY_P2PKH, SettingsConstants.MAINNET)
"""
Input may be prefixed with "bitcoin:" but will be ignored.
elif r == "m" or r == "n":
self.address_type = (SettingsConstants.LEGACY_P2PKH, SettingsConstants.TESTNET)
RegEx searches for a recognizable bitcoin address.
* The `^` ensures that the specified address prefixes can only match at
the beginning of the address.
elif r == "3":
# Nested segwit single sig (p2sh-p2wpkh), nested segwit multisig (p2sh-p2wsh), or legacy multisig (p2sh); mainnet
# TODO: Would be more correct to use a P2SH constant
self.address_type = (SettingsConstants.NESTED_SEGWIT, SettingsConstants.MAINNET)
Result will yield the following match groups:
* group 1: complete address
* group 2: address prefix
"""
address_match = re.search(r'^((bc1q|tb1q|bcrt1q|bc1p|tb1p|bcrt1p|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,64})', segment.split(":")[-1], re.IGNORECASE)
if address_match != None:
self.address = address_match.group(1)
self.complete = True
self.collected_segments = 1
# Have to handle wallets that uppercase bech32 addresses.
# Note that it's safe to lowercase the prefix for ALL addr formats.
addr_prefix = address_match.group(2).lower()
if addr_prefix == "1":
# Legacy P2PKH. mainnet
self.address_type = (SettingsConstants.LEGACY_P2PKH, SettingsConstants.MAINNET)
elif r == "2":
# Nested segwit single sig (p2sh-p2wpkh), nested segwit multisig (p2sh-p2wsh), or legacy multisig (p2sh); testnet / regtest
self.address_type = (SettingsConstants.NESTED_SEGWIT, SettingsConstants.TESTNET)
elif addr_prefix in ["m", "n"]:
self.address_type = (SettingsConstants.LEGACY_P2PKH, SettingsConstants.TESTNET)
elif r == "bc1q":
# Native Segwit (single sig or multisig), mainnet
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.MAINNET)
elif addr_prefix == "3":
# Nested segwit single sig (p2sh-p2wpkh), nested segwit multisig (p2sh-p2wsh), or legacy multisig (p2sh); mainnet
# TODO: Would be more correct to use a P2SH constant
self.address_type = (SettingsConstants.NESTED_SEGWIT, SettingsConstants.MAINNET)
elif r == "tb1q":
# Native Segwit (single sig or multisig), testnet
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.TESTNET)
elif addr_prefix == "2":
# Nested segwit single sig (p2sh-p2wpkh), nested segwit multisig (p2sh-p2wsh), or legacy multisig (p2sh); testnet / regtest
self.address_type = (SettingsConstants.NESTED_SEGWIT, SettingsConstants.TESTNET)
elif r == "bcrt1q":
# Native Segwit (single sig or multisig), regtest
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.REGTEST)
elif addr_prefix == "bc1q":
# Native Segwit (single sig or multisig), mainnet
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.MAINNET)
elif r == "bc1p":
# Native Segwit (single sig or multisig), mainnet
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.MAINNET)
elif addr_prefix == "tb1q":
# Native Segwit (single sig or multisig), testnet
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.TESTNET)
elif r == "tb1p":
# Native Segwit (single sig or multisig), testnet
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.TESTNET)
elif addr_prefix == "bcrt1q":
# Native Segwit (single sig or multisig), regtest
self.address_type = (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.REGTEST)
elif r == "bcrt1p":
# Native Segwit (single sig or multisig), regtest
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.REGTEST)
return DecodeQRStatus.COMPLETE
elif addr_prefix == "bc1p":
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.MAINNET)
elif addr_prefix == "tb1p":
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.TESTNET)
elif addr_prefix == "bcrt1p":
self.address_type = (SettingsConstants.TAPROOT, SettingsConstants.REGTEST)
# Note: there is no final "else" here because the regex won't return any other matches.
# If the addr type is case-insensitive, ensure we return it lowercase
if self.address_type[0] in [SettingsConstants.NATIVE_SEGWIT, SettingsConstants.TAPROOT]:
self.address = self.address.lower()
return DecodeQRStatus.COMPLETE
logger.debug(f"Invalid address: {segment}")
return DecodeQRStatus.INVALID
+2 -4
View File
@@ -460,16 +460,14 @@ class PSBTAddressVerificationFailedView(View):
def run(self):
if self.is_multisig:
title = _("Caution")
# TRANSLATOR_NOTE: Variable is either "change" or "self-transfer".
text = _("PSBT's {} address could not be verified with your multisig wallet descriptor.").format(_("change") if self.is_change else _("self-transfer"))
text = _("PSBT's {} address could not be verified from wallet descriptor.").format(_("change") if self.is_change else _("self-transfer"))
else:
title = _("Suspicious PSBT")
# TRANSLATOR_NOTE: Variable is either "change" or "self-transfer".
text = _("PSBT's {} address could not be generated from your seed.").format(_("change") if self.is_change else _("self-transfer"))
DireWarningScreen(
title=title,
title=_("Suspicious PSBT"),
status_headline=_("Address Verification Failed"),
text=text,
button_data=[ButtonOption("Discard PSBT")],
+22 -7
View File
@@ -5,6 +5,7 @@ from gettext import gettext as _
from seedsigner.helpers.l10n import mark_for_translation as _mft
from seedsigner.models.settings import SettingsConstants
from seedsigner.views.view import BackStackView, ErrorView, MainMenuView, NotYetImplementedView, View, Destination
from seedsigner.gui.screens.screen import ButtonOption
logger = logging.getLogger(__name__)
@@ -163,13 +164,7 @@ class ScanView(View):
# For now, don't even try to re-do the attempted operation, just reset and
# start everything over.
self.controller.resume_main_flow = None
return Destination(ErrorView, view_args=dict(
title=_("Error"),
status_headline=_("Unknown QR Type"),
text=_("QRCode is invalid or is a data format not yet supported."),
button_text=_("Done"),
next_destination=Destination(MainMenuView, clear_history=True),
))
return Destination(ScanInvalidQRTypeView)
return Destination(MainMenuView)
@@ -212,3 +207,23 @@ class ScanAddressView(ScanView):
@property
def is_valid_qr_type(self):
return self.decoder.is_address
class ScanInvalidQRTypeView(View):
def run(self):
from seedsigner.gui.screens import WarningScreen
# TODO: This screen says "Error" but is intentionally using the WarningScreen in
# order to avoid the perception that something is broken on our end. This should
# either change to use the red ErrorScreen or the "Error" title should be
# changed to something softer.
self.run_screen(
WarningScreen,
title=_("Error"),
status_headline=_("Unknown QR Type"),
text=_("QRCode is invalid or is a data format not yet supported."),
button_data=[ButtonOption("Done")],
)
return Destination(MainMenuView, clear_history=True)
+6 -7
View File
@@ -279,8 +279,9 @@ class SeedMnemonicInvalidView(View):
def run(self):
button_data = [self.EDIT, self.DISCARD]
selected_menu_num = self.run_screen(
WarningScreen,
DireWarningScreen,
title=_("Invalid Mnemonic!"),
status_icon_name=SeedSignerIconConstants.ERROR,
status_headline=None,
text=_("Checksum failure; not a valid seed phrase."),
show_back_button=False,
@@ -1206,6 +1207,7 @@ class SeedBIP85InvalidChildIndexView(View):
DireWarningScreen(
title=_("BIP-85 Index Error"),
show_back_button=False,
status_icon_name=SeedSignerIconConstants.ERROR,
status_headline=_("Invalid Child Index"),
text=_("BIP-85 Child Index must be between 0 and 2^31-1."),
button_data=[ButtonOption("Try Again")]
@@ -1366,6 +1368,7 @@ class SeedWordsBackupTestMistakeView(View):
selected_menu_num = DireWarningScreen(
title=_("Verification Error"),
show_back_button=False,
status_icon_name=SeedSignerIconConstants.ERROR,
status_headline=status_headline,
button_data=button_data,
text=text,
@@ -1722,8 +1725,8 @@ class AddressVerificationStartView(View):
destination = Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__VERIFY_SINGLESIG_ADDR), skip_current_view=True)
elif self.controller.unverified_address["script_type"] == SettingsConstants.TAPROOT:
# TODO: add Taproot support
return Destination(NotYetImplementedView)
sig_type = SettingsConstants.SINGLE_SIG
destination = Destination(SeedSelectSeedView, view_args=dict(flow=Controller.FLOW__VERIFY_SINGLESIG_ADDR), skip_current_view=True)
derivation_path = embit_utils.get_standard_derivation_path(
network=self.controller.unverified_address["network"],
@@ -1816,10 +1819,6 @@ class SeedAddressVerificationView(View):
self.sig_type = self.controller.unverified_address["sig_type"]
self.network = self.controller.unverified_address["network"]
if self.script_type == SettingsConstants.TAPROOT:
# TODO: Taproot addr verification
return Destination(NotYetImplementedView)
# TODO: This should be in `Seed` or `PSBT` utility class
embit_network = SettingsConstants.map_network_to_embit(self.network)
+15 -7
View File
@@ -5,7 +5,7 @@ from typing import Type
from seedsigner.helpers.l10n import mark_for_translation as _mft
from seedsigner.gui.components import SeedSignerIconConstants
from seedsigner.gui.screens import RET_CODE__POWER_BUTTON, RET_CODE__BACK_BUTTON
from seedsigner.gui.screens.screen import BaseScreen, ButtonOption, DireWarningScreen, LargeButtonScreen, PowerOffScreen, PowerOffNotRequiredScreen, ResetScreen, WarningScreen
from seedsigner.gui.screens.screen import BaseScreen, ButtonOption, LargeButtonScreen, WarningScreen, ErrorScreen
from seedsigner.models.settings import Settings, SettingsConstants
from seedsigner.models.settings_definition import SettingsDefinition
from seedsigner.models.threads import BaseThread
@@ -245,6 +245,7 @@ class PowerOptionsView(View):
class RestartView(View):
def run(self):
from seedsigner.gui.screens.screen import ResetScreen
thread = RestartView.DoResetThread()
thread.start()
self.run_screen(ResetScreen)
@@ -270,6 +271,7 @@ class RestartView(View):
class PowerOffView(View):
def run(self):
from seedsigner.gui.screens.screen import PowerOffNotRequiredScreen
self.run_screen(PowerOffNotRequiredScreen)
return Destination(BackStackView)
@@ -300,6 +302,7 @@ class NotYetImplementedView(View):
class ErrorView(View):
title: str = _mft("Error")
show_back_button: bool = True
status_icon_name: str = SeedSignerIconConstants.ERROR
status_headline: str = None
text: str = None
button_text: str = None
@@ -307,8 +310,9 @@ class ErrorView(View):
def run(self):
self.run_screen(
WarningScreen,
ErrorScreen,
title=self.title,
status_icon_name=self.status_icon_name,
status_headline=self.status_headline,
text=self.text,
button_data=[ButtonOption(self.button_text)],
@@ -324,9 +328,14 @@ class NetworkMismatchErrorView(ErrorView):
def __post_init__(self):
from seedsigner.views.settings_views import SettingsEntryUpdateSelectionView
self.title: str = _("Network Mismatch")
self.show_back_button: bool = False
self.button_text: str = _("Change Setting")
# TRANSLATOR_NOTE: The network setting (mainnet/testnet/regtest) doesn't match the provided derivation path
self.title = _("Network Mismatch")
self.status_icon_name = SeedSignerIconConstants.WARNING
self.show_back_button = False
# TRANSLATOR_NOTE: Button option to alter a setting
self.button_text = _("Change Setting")
self.next_destination = Destination(SettingsEntryUpdateSelectionView, view_args=dict(attr_name=SettingsConstants.SETTING__NETWORK), clear_history=True)
super().__post_init__()
@@ -342,10 +351,9 @@ class NetworkMismatchErrorView(ErrorView):
class UnhandledExceptionView(View):
error: list[str]
def run(self):
self.run_screen(
DireWarningScreen,
ErrorScreen,
title=_("System Error"),
status_headline=self.error[0],
text=self.error[1] + "\n" + self.error[2],
+4 -9
View File
@@ -26,7 +26,7 @@ patch('PIL.ImageFont.core.HAVE_RAQM', False).start()
from seedsigner.controller import Controller
from seedsigner.gui.renderer import Renderer
from seedsigner.gui.screens.seed_screens import SeedAddPassphraseScreen
from seedsigner.gui.toast import BaseToastOverlayManagerThread, RemoveSDCardToastManagerThread, SDCardStateChangeToastManagerThread
from seedsigner.gui.toast import RemoveSDCardToastManagerThread, SDCardStateChangeToastManagerThread
from seedsigner.gui.toast import DefaultToast, InfoToast, SuccessToast, WarningToast, ErrorToast, DireWarningToast
from seedsigner.hardware.microsd import MicroSD
from seedsigner.helpers import embit_utils
@@ -37,9 +37,9 @@ from seedsigner.models.seed import Seed
from seedsigner.models.settings import Settings
from seedsigner.models.settings_definition import SettingsConstants, SettingsDefinition
from seedsigner.views import (MainMenuView, PowerOptionsView, RestartView, NotYetImplementedView, UnhandledExceptionView,
psbt_views, seed_views, settings_views, tools_views)
psbt_views, seed_views, settings_views, tools_views, scan_views)
from seedsigner.views.screensaver import OpeningSplashView
from seedsigner.views.view import ErrorView, NetworkMismatchErrorView, OptionDisabledView, PowerOffView, View
from seedsigner.views.view import NetworkMismatchErrorView, OptionDisabledView, PowerOffView
from .utils import ScreenshotComplete, ScreenshotConfig, ScreenshotRenderer
@@ -366,12 +366,7 @@ def generate_screenshots(locale):
ScreenshotConfig(UnhandledExceptionView, dict(error=["IndexError", "line 1, in some_buggy_code.py", "list index out of range"])),
ScreenshotConfig(NetworkMismatchErrorView, dict(derivation_path="m/84'/1'/0'")),
ScreenshotConfig(OptionDisabledView, dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING)),
ScreenshotConfig(ErrorView, dict(
title="Error",
status_headline="Unknown QR Type",
text="QRCode is invalid or is a data format not yet supported.",
button_text="Back",
)),
ScreenshotConfig(scan_views.ScanInvalidQRTypeView)
]
}
+113 -61
View File
@@ -281,69 +281,121 @@ def test_short_4_letter_mnemonic_qr():
assert d.get_seed_phrase() == ["height", "demise", "useless", "trap", "grow", "lion", "found", "off", "key", "clown", "transfer", "enroll"]
def test_bitcoin_address():
bad1 = "loremipsum"
bad2 = "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae"
bad3 = "121802020768124106400009195602431595117715840445"
legacy_address1 = "1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY"
legacy_address2 = "16ftSEQ4ctQFDtVZiUBusQUjRrGhM3JYwe"
main_bech32_address = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
test_bech32_address = "tb1qkurj377gtlmu0j5flcykcsh2xagexh9h3jk06a"
main_nested_segwit_address = "3Nu78Cqcf6hsD4sUBAN9nP13tYiHU9QPFX"
test_nested_segwit_address = "2N6JbrvPMMwbBhu2KxqXyyHUQz3XKspvyfm"
main_bech32_address2 = "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?amount=12000"
main_bech32_address3 = "BITCOIN:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?junk"
d = DecodeQR()
d.add_data(bad1)
assert d.qr_type == QRType.INVALID
d = DecodeQR()
d.add_data(legacy_address1)
assert d.get_address() == legacy_address1
assert d.get_address_type() == (SettingsConstants.LEGACY_P2PKH, SettingsConstants.MAINNET)
d = DecodeQR()
d.add_data(legacy_address2)
assert d.get_address() == legacy_address2
assert d.get_address_type() == (SettingsConstants.LEGACY_P2PKH, SettingsConstants.MAINNET)
d = DecodeQR()
d.add_data(main_bech32_address)
assert d.get_address() == main_bech32_address
assert d.get_address_type() == (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.MAINNET)
d = DecodeQR()
d.add_data(test_bech32_address)
assert d.get_address() == test_bech32_address
assert d.get_address_type() == (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.TESTNET)
d = DecodeQR()
d.add_data(main_nested_segwit_address)
assert d.get_address() == main_nested_segwit_address
assert d.get_address_type() == (SettingsConstants.NESTED_SEGWIT, SettingsConstants.MAINNET)
d = DecodeQR()
d.add_data(test_nested_segwit_address)
# Test data for bitcoin address decoding. All generated from test key: ["abandon"] * 11 + ["about"]
legacy_address_mainnet = "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
legacy_address_testnet = "mkpZhYtJu2r87Js3pDiWJDmPte2NRZ8bJV"
assert d.get_address() == test_nested_segwit_address
assert d.get_address_type() == (SettingsConstants.NESTED_SEGWIT, SettingsConstants.TESTNET)
nested_segwit_address_mainnet = "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
nested_segwit_address_testnet = "2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2"
d = DecodeQR()
d.add_data(main_bech32_address2)
assert d.get_address() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
assert d.get_address_type() == (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.MAINNET)
native_segwit_address_mainnet = "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
native_segwit_address_testnet = "tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl"
native_segwit_address_regtest = "bcrt1q6rz28mcfaxtmd6v789l9rrlrusdprr9pz3cppk"
taproot_address_mainnet = "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"
taproot_address_testnet = "tb1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqlqt9zj"
taproot_address_regtest = "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg"
def test_bitcoin_address():
"""
Decoder should parse various types of valid bitcoin addresses with or without the
"bitcoin:" prefix and optional query params.
"""
def decode(address, expected_script_type, expected_network=SettingsConstants.MAINNET):
for data in [address, "bitcoin:" + address, "bitcoin:" + address + "?amount=12000"]:
d = DecodeQR()
d.add_data(data)
assert d.get_address() == address
assert d.get_address_type() == (expected_script_type, expected_network)
decode(legacy_address_mainnet, SettingsConstants.LEGACY_P2PKH)
decode(legacy_address_testnet, SettingsConstants.LEGACY_P2PKH, SettingsConstants.TESTNET)
decode(nested_segwit_address_mainnet, SettingsConstants.NESTED_SEGWIT)
decode(nested_segwit_address_testnet, SettingsConstants.NESTED_SEGWIT, SettingsConstants.TESTNET)
decode(native_segwit_address_mainnet, SettingsConstants.NATIVE_SEGWIT)
decode(native_segwit_address_testnet, SettingsConstants.NATIVE_SEGWIT, SettingsConstants.TESTNET)
decode(native_segwit_address_regtest, SettingsConstants.NATIVE_SEGWIT, SettingsConstants.REGTEST)
decode(taproot_address_mainnet, SettingsConstants.TAPROOT)
decode(taproot_address_testnet, SettingsConstants.TAPROOT, SettingsConstants.TESTNET)
decode(taproot_address_regtest, SettingsConstants.TAPROOT, SettingsConstants.REGTEST)
def test_invalid_bitcoin_address():
"""
Decoder should fail to parse invalid address data.
* Test incorrect "bitcoin:" prefix.
* Test invalid addresses.
* Test valid addresses w/additional prefixes (to ensure regexp is not finding
mid-string matches) which make the data invalid.
"""
bad_inputs = [
# wrong separator char
"bitcoin=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
# Unrecognized addr prefix
"bitcoin:bcfakehrp1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
# valid addr w/garbage addr prefix
"abcbc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
# valid "bitcoin:" prefix w/garbage addr prefix
"bitcoin:abcbc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
# typo in "bitcoin:" prefix
"bitcon:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
]
for bad_input in bad_inputs:
d = DecodeQR()
status = d.add_data(bad_input)
assert status == DecodeQRStatus.INVALID
def test_bitcoin_address_ignores_case_where_allowed():
"""
Decoder should ignore case in QR data prefix and in the address itself (for the
address types where case is ignored).
"""
def decode(address, expected_script_type, is_case_sensitive, expected_network=SettingsConstants.MAINNET):
addr_variations = [address]
if not is_case_sensitive:
# Test as-is and all uppercase
addr_variations.append(address.upper())
for addr_variation in addr_variations:
# First add prefix capitalizations
variations_1 = ["bitcoin:" + addr_variation, "BITCOIN:" + addr_variation]
# Now add query params
variations_2 = [v + "?amount=12000" for v in variations_1]
variations_3 = [v + "?AMOUNT=12000" for v in variations_1]
for data in [addr_variation] + variations_1 + variations_2 + variations_3:
d = DecodeQR()
d.add_data(data)
assert d.get_address_type() == (expected_script_type, expected_network)
if not is_case_sensitive:
assert d.get_address() == addr_variation.lower()
else:
assert d.get_address() == addr_variation
# Case sensitive address types
decode(legacy_address_mainnet, SettingsConstants.LEGACY_P2PKH, is_case_sensitive=True)
decode(legacy_address_testnet, SettingsConstants.LEGACY_P2PKH, is_case_sensitive=True, expected_network=SettingsConstants.TESTNET)
decode(nested_segwit_address_mainnet, SettingsConstants.NESTED_SEGWIT, is_case_sensitive=True)
decode(nested_segwit_address_testnet, SettingsConstants.NESTED_SEGWIT, is_case_sensitive=True, expected_network=SettingsConstants.TESTNET)
# Case insensitive address types
decode(native_segwit_address_mainnet, SettingsConstants.NATIVE_SEGWIT, is_case_sensitive=False)
decode(native_segwit_address_testnet, SettingsConstants.NATIVE_SEGWIT, is_case_sensitive=False, expected_network=SettingsConstants.TESTNET)
decode(native_segwit_address_regtest, SettingsConstants.NATIVE_SEGWIT, is_case_sensitive=False, expected_network=SettingsConstants.REGTEST)
decode(taproot_address_mainnet, SettingsConstants.TAPROOT, is_case_sensitive=False)
decode(taproot_address_testnet, SettingsConstants.TAPROOT, is_case_sensitive=False, expected_network=SettingsConstants.TESTNET)
decode(taproot_address_regtest, SettingsConstants.TAPROOT, is_case_sensitive=False, expected_network=SettingsConstants.REGTEST)
+1 -1
View File
@@ -646,7 +646,7 @@ class TestMessageSigningFlows(FlowTest):
FlowStep(seed_views.SeedFinalizeView, button_data_selection=seed_views.SeedFinalizeView.FINALIZE),
FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.SIGN_MESSAGE),
FlowStep(scan_views.ScanView, before_run=load_invalid_signmessage_qr), # simulate read message QR; ret val is ignored
FlowStep(ErrorView),
FlowStep(scan_views.ScanInvalidQRTypeView),
FlowStep(MainMenuView),
])
+20 -11
View File
@@ -257,16 +257,25 @@ class TestToolsFlows(FlowTest):
settings = controller.settings
settings.set_value(SettingsConstants.SETTING__NETWORK, SettingsConstants.REGTEST)
def load_address_into_decoder(view: scan_views.ScanView):
addrs = [
# Native segwit regtest receive addr @ index 6
view.decoder.add_data("bcrt1q4e9q5taxnsvc6m0uxv6h75mkzvnkxeqk6l90u2")
"bcrt1q4e9q5taxnsvc6m0uxv6h75mkzvnkxeqk6l90u2",
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.TOOLS),
FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.VERIFY_ADDRESS),
FlowStep(scan_views.ScanAddressView, before_run=load_address_into_decoder), # simulate read address QR
FlowStep(seed_views.AddressVerificationStartView, is_redirect=True),
FlowStep(seed_views.SeedSelectSeedView, screen_return_value=0),
FlowStep(seed_views.SeedAddressVerificationView),
FlowStep(seed_views.SeedAddressVerificationSuccessView),
])
# Taproot regtest change addr @ index 48
"bcrt1pj5v8ean2hc5lh2djsgfx4j9uc0n67942ngv6q9r49qv88ex5mrwsn3u4f7",
]
for test_addr in addrs:
def load_address_into_decoder(view: scan_views.ScanView):
# Native segwit regtest receive addr @ index 6
view.decoder.add_data(test_addr)
self.run_sequence([
FlowStep(MainMenuView, button_data_selection=MainMenuView.TOOLS),
FlowStep(tools_views.ToolsMenuView, button_data_selection=tools_views.ToolsMenuView.VERIFY_ADDRESS),
FlowStep(scan_views.ScanAddressView, before_run=load_address_into_decoder), # simulate read address QR
FlowStep(seed_views.AddressVerificationStartView, is_redirect=True),
FlowStep(seed_views.SeedSelectSeedView, screen_return_value=0),
FlowStep(seed_views.SeedAddressVerificationView),
FlowStep(seed_views.SeedAddressVerificationSuccessView),
])