Test suite fixes; DonateView

* Updated custom SeedSigner icon font pack
* Removing debugging print statements
* Default button handling for BaseTopNavScreen
This commit is contained in:
kdmukai
2022-04-01 07:38:59 -05:00
parent c178cc96e0
commit 477d44f2a0
19 changed files with 179 additions and 238 deletions
+1 -10
View File
@@ -49,7 +49,7 @@ class Controller(Singleton):
rather than at the top in order avoid circular imports.
"""
VERSION = "0.5.0 Pre-Release 3"
VERSION = "0.5.0"
# Declare class member vars with type hints to enable richer IDE support throughout
# the code.
@@ -185,15 +185,6 @@ class Controller(Singleton):
opening_splash = OpeningSplashView()
opening_splash.start()
# TODO: Remove for v0.5.0 production release
WarningScreen(
title="Warning",
status_headline="Pre-Release Code",
text="Do not use this with real funds or to create new secure keys!",
show_back_button=False,
).display()
""" Class references can be stored as variables in python!
This loop receives a View class to execute and stores it in the `View_cls`
+19 -20
View File
@@ -1,5 +1,4 @@
import math
from operator import is_
import os
import pathlib
@@ -33,7 +32,7 @@ class GUIConstants:
ICON_FONT_SIZE = 22
ICON_INLINE_FONT_SIZE = 24
ICON_LARGE_BUTTON_SIZE = 36
ICON_PRIMARY_SCREEN_SIZE = 44
ICON_PRIMARY_SCREEN_SIZE = 60
TOP_NAV_TITLE_FONT_NAME = "OpenSans-SemiBold"
TOP_NAV_TITLE_FONT_SIZE = 20
@@ -109,18 +108,19 @@ class FontAwesomeIconConstants:
class SeedSignerCustomIconConstants:
LARGE_CHEVRON_LEFT = "\ue900"
SMALL_CHEVRON_RIGHT = "\ue901"
PAGE_DOWN = "\ue902"
PAGE_UP = "\ue903"
CIRCLE_X = "\ue904"
CIRCLE_EXCLAMATION = "\ue905"
CIRCLE_CHECK = "\ue906"
FINGERPRINT = "\ue907"
PATH = "\ue908"
BITCOIN_LOGO = "\ue909"
BITCOIN_LOGO_2 = "\ue90a"
PAGE_DOWN = "\ue902"
PLUS = "\ue904"
CIRCLE_CHECK = "\ue907"
CIRCLE_EXCLAMATION = "\ue908"
CIRCLE_X = "\ue909"
FINGERPRINT = "\ue90a"
PATH = "\ue90b"
BITCOIN_LOGO_STRAIGHT = "\ue90c"
BITCOIN_LOGO_TILTED = "\ue90d"
MIN_VALUE = LARGE_CHEVRON_LEFT
MAX_VALUE = BITCOIN_LOGO_2
MAX_VALUE = BITCOIN_LOGO_TILTED
@@ -419,7 +419,7 @@ class TextArea(BaseComponent):
class Icon(BaseComponent):
screen_x: int = 0
screen_y: int = 0
icon_name: str = SeedSignerCustomIconConstants.BITCOIN_LOGO
icon_name: str = SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED
icon_size: int = GUIConstants.ICON_FONT_SIZE
icon_color: str = GUIConstants.BODY_FONT_COLOR
@@ -438,11 +438,11 @@ class Icon(BaseComponent):
def render(self):
self.image_draw.text(
(self.screen_x, self.screen_y),
(self.screen_x, self.screen_y + self.height),
text=self.icon_name,
font=self.icon_font,
fill=self.icon_color,
anchor="lt", # left, top anchor to avoid "ascender" gap space
anchor="ls",
)
@@ -539,7 +539,8 @@ class IconTextLine(BaseComponent):
if self.icon_name:
icon_y = self.screen_y + int((self.height - self.icon.height)/2)
self.icon.screen_y = icon_y
self.height = self.icon.height
self.height = max(self.icon.height, self.height)
if self.is_text_centered:
if self.icon_name:
@@ -762,8 +763,6 @@ class BtcAmount(BaseComponent):
denomination = Settings.get_instance().get_value(SettingsConstants.SETTING__BTC_DENOMINATION)
network = Settings.get_instance().get_value(SettingsConstants.SETTING__NETWORK)
print(self.total_sats)
btc_unit = "tBtc"
sats_unit = "tSats"
if network == SettingsConstants.MAINNET:
@@ -789,7 +788,7 @@ class BtcAmount(BaseComponent):
btc_icon = Icon(
image_draw=draw,
canvas=self.paste_image,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED,
icon_color=btc_color,
icon_size=self.icon_size,
screen_x=0,
@@ -877,7 +876,7 @@ class BtcAmount(BaseComponent):
btc_icon = Icon(
image_draw=draw,
canvas=self.paste_image,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO,
icon_name=SeedSignerCustomIconConstants.BITCOIN_LOGO_TILTED,
icon_color=btc_color,
icon_size=self.icon_size,
screen_x=0,
@@ -1047,7 +1046,6 @@ class Button(BaseComponent):
self.icon_selected = Icon(icon_name=self.icon_name, icon_size=self.icon_size, icon_color=self.selected_icon_color)
if self.is_icon_inline:
# TODO: Only apply screen_* at render
if self.is_text_centered:
# Shift the text's centering
if self.text:
@@ -1077,6 +1075,7 @@ class Button(BaseComponent):
self.right_icon_y = math.ceil((self.height - self.right_icon.height)/2)
def render(self):
if self.is_selected:
background_color = self.selected_color
+1 -5
View File
@@ -18,17 +18,13 @@ class Renderer(ConfigurableSingleton):
@classmethod
def configure_instance(cls, config={}):
def configure_instance(cls):
from seedsigner.models.settings import Settings
super().configure_instance(config)
# Instantiate the one and only Renderer instance
renderer = cls.__new__(cls)
cls._instance = renderer
# TODO: Use Settings values to wire up diff hardware params
settings = Settings.get_instance()
# Eventually we'll be able to plug in other display controllers
renderer.disp = ST7789()
renderer.canvas_width = renderer.disp.width
+3 -3
View File
@@ -38,7 +38,6 @@ class PSBTOverviewScreen(ButtonListScreen):
# icon_text_lines_y = self.components[-1].screen_y + self.components[-1].height
icon_text_lines_y = self.top_nav.height + GUIConstants.COMPONENT_PADDING
if not self.destination_addresses:
# This is a self-transfer
spend_amount = self.change_amount
@@ -52,7 +51,7 @@ class PSBTOverviewScreen(ButtonListScreen):
# Prep the transaction flow chart
self.chart_x = 0
self.chart_y = self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING
self.chart_y = self.components[-1].screen_y + self.components[-1].height + int(GUIConstants.COMPONENT_PADDING/2)
chart_height = self.buttons[0].screen_y - self.chart_y - GUIConstants.COMPONENT_PADDING
# We need to supersample the whole panel so that small/thin elements render
@@ -70,7 +69,8 @@ class PSBTOverviewScreen(ButtonListScreen):
font_size = GUIConstants.BODY_FONT_MIN_SIZE * ssf
font = Fonts.get_font(GUIConstants.BODY_FONT_NAME, font_size)
tw, chart_text_height = font.getsize("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") # All possible chars for max range
# Measure from anchor "top" to include font pixels below baseline (e.g. the "g" in "change")
(left, top, right, chart_text_height) = font.getbbox(text="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", anchor="lt")
vertical_center = int(image.height/2)
# Supersampling renders thin elements poorly if they land on an even line before scaling down
if vertical_center % 2 == 1:
+37 -11
View File
@@ -56,8 +56,6 @@ class BaseScreen(BaseComponent):
return self._run()
except Exception as e:
print(e)
print("------")
repr(e)
raise e
finally:
@@ -214,7 +212,42 @@ class BaseTopNavScreen(BaseScreen):
def _run(self):
raise Exception("Must implement in a child class")
while True:
if not self.top_nav.show_back_button and not self.top_nav.show_power_button:
# There's no navigation away from this screen; nothing to do here
time.sleep(0.1)
continue
user_input = self.hw_inputs.wait_for(
HardwareButtonsConstants.ALL_KEYS,
check_release=True,
release_keys=HardwareButtonsConstants.KEYS__ANYCLICK
)
with self.renderer.lock:
if not self.top_nav.is_selected and user_input in [
HardwareButtonsConstants.KEY_LEFT,
HardwareButtonsConstants.KEY_UP
]:
self.top_nav.is_selected = True
self.top_nav.render_buttons()
elif self.top_nav.is_selected and user_input in [
HardwareButtonsConstants.KEY_DOWN,
HardwareButtonsConstants.KEY_RIGHT
]:
self.top_nav.is_selected = False
self.top_nav.render_buttons()
elif self.top_nav.is_selected and user_input in HardwareButtonsConstants.KEYS__ANYCLICK:
return self.top_nav.selected_button
else:
# Nothing to do with this input
continue
# Write the screen updates
self.renderer.show_image()
@@ -810,6 +843,7 @@ class DireWarningScreen(WarningScreen):
status_color: str = GUIConstants.DIRE_WARNING_COLOR
@dataclass
class ResetScreen(BaseTopNavScreen):
def __post_init__(self):
@@ -822,10 +856,6 @@ class ResetScreen(BaseTopNavScreen):
screen_y=self.top_nav.height,
height=self.canvas_height - self.top_nav.height,
))
def _run(self):
while True:
pass
@@ -841,7 +871,3 @@ class PowerOffScreen(BaseTopNavScreen):
screen_y=self.top_nav.height,
height=self.canvas_height - self.top_nav.height,
))
def _run(self):
while True:
pass
+22 -1
View File
@@ -5,7 +5,7 @@ from typing import List
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, GUIConstants, IconButton, TextArea
from seedsigner.gui.screens.scan_screens import ScanScreen
from seedsigner.gui.screens.screen import ButtonListScreen
from seedsigner.gui.screens.screen import BaseTopNavScreen, ButtonListScreen
from seedsigner.hardware.buttons import HardwareButtonsConstants
from seedsigner.models.settings import SettingsConstants
@@ -190,3 +190,24 @@ class IOTestScreen(ScanScreen):
cur_selected_button.is_selected = True
time.sleep(0.1)
@dataclass
class DonateScreen(BaseTopNavScreen):
def __post_init__(self):
self.title = "Donate"
super().__post_init__()
self.components.append(TextArea(
text="SeedSigner is 100% free & open source, funded solely by the Bitcoin community.\n\nDonate onchain or LN at:",
screen_y=self.top_nav.height + 3*GUIConstants.COMPONENT_PADDING,
))
self.components.append(TextArea(
text="seedsigner.com",
font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE + 8,
font_color=GUIConstants.ACCENT_COLOR,
supersampling_factor=1,
screen_y=self.components[-1].screen_y + self.components[-1].height + GUIConstants.COMPONENT_PADDING
))
+4 -2
View File
@@ -60,8 +60,6 @@ class DecodeQR:
qr_type = DecodeQR.detect_segment_type(data, wordlist_language_code=self.wordlist_language_code)
print(qr_type)
if self.qr_type == None:
self.qr_type = qr_type
@@ -97,6 +95,10 @@ class DecodeQR:
elif self.qr_type != qr_type:
raise Exception('QR Fragment Unexpected Type Change')
if not self.decoder:
# Did not find any recognizable format
return DecodeQRStatus.INVALID
# Process the binary formats first
if self.qr_type == QRType.SEED__COMPACTSEEDQR:
+1 -3
View File
@@ -58,7 +58,6 @@ class PSBTParser():
def _set_root(self):
self.root = bip32.HDKey.from_seed(self.seed.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(self.network)]["xprv"])
print(f"root: {self.root}")
def parse(self):
@@ -85,7 +84,6 @@ class PSBTParser():
def _parse_inputs(self):
self.input_amount = 0
print(f"psbt.inputs: {self.psbt.inputs}")
self.num_inputs = len(self.psbt.inputs)
for inp in self.psbt.inputs:
if inp.witness_utxo:
@@ -343,5 +341,5 @@ class PSBTParser():
i = change_data["output_index"]
output = self.psbt.outputs[i]
is_owner = descriptor.owns(output)
print(f"{self.psbt.tx.vout[i].script_pubkey.address()} | {output.value} | {is_owner}")
# print(f"{self.psbt.tx.vout[i].script_pubkey.address()} | {output.value} | {is_owner}")
return is_owner
@@ -513,8 +513,6 @@ class SettingsDefinition:
if __name__ == "__main__":
import json
import os
print("Exporting SettingsDefinition to json")
output_file = "settings_definition.json"
with open(output_file, 'w') as json_file:
Binary file not shown.
+1 -3
View File
@@ -109,7 +109,7 @@ class PSBTOverviewView(View):
num_change_outputs = 0
num_self_transfer_outputs = 0
for change_output in change_data:
print(f"""{change_output["derivation_path"][0]}""")
# print(f"""{change_output["derivation_path"][0]}""")
if change_output["derivation_path"][0].split("/")[-2] == "1":
num_change_outputs += 1
else:
@@ -277,8 +277,6 @@ class PSBTChangeDetailsView(View):
# Single-sig verification is easy. We expect to find a single fingerprint
# and derivation path.
seed_fingerprint = self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
print(f"seed fingerprint: {seed_fingerprint}")
print(change_data)
if seed_fingerprint not in change_data.get("fingerprint"):
# TODO: Something is wrong with this psbt(?). Reroute to warning?
+2 -12
View File
@@ -256,7 +256,6 @@ class SeedReviewPassphraseView(View):
def __init__(self):
super().__init__()
self.seed = self.controller.storage.get_pending_seed()
print(f"SeedReviewPassphraseView self.seed: {self.seed}")
def run(self):
@@ -348,7 +347,7 @@ class SeedOptionsView(View):
REVIEW_PSBT = "Review PSBT"
VERIFY_ADDRESS = "Verify Addr"
EXPORT_XPUB = "Export Xpub"
BACKUP = ("Backup Seed", None, None, None, FontAwesomeIconConstants.CIRCLE_CHEVRON_RIGHT)
BACKUP = ("Backup Seed", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT)
DISCARD = ("Discard Seed", None, None, "red")
button_data = []
@@ -366,7 +365,7 @@ class SeedOptionsView(View):
if self.controller.psbt:
if PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
if self.controller.resume_main_flow and self.controller.resume_main_flow == Controller.FLOW__PSBT:
# Re-route us directly back to the start of the PSBT flow
# Re-route us directly back to the start of the PSBT flow
self.controller.resume_main_flow = None
self.controller.psbt_seed = self.seed
return Destination(PSBTOverviewView, skip_current_view=True)
@@ -1122,9 +1121,6 @@ class AddressVerificationStartView(View):
self.controller.unverified_address["sig_type"] = sig_type
self.controller.unverified_address["derivation_path"] = derivation_path
import json
print(json.dumps(self.controller.unverified_address, indent=4))
return destination
@@ -1332,8 +1328,6 @@ class SeedAddressVerificationView(View):
# Successfully verified the addr; update the data
self.controller.unverified_address["verified_index"] = self.verified_index.cur_count
self.controller.unverified_address["verified_index_is_change"] = self.verified_index_is_change.cur_count == 1
import json
print(json.dumps(self.controller.unverified_address, indent=4))
return Destination(AddressVerificationSuccessView, view_args=dict(seed_num=self.seed_num))
else:
@@ -1379,14 +1373,12 @@ class SeedAddressVerificationView(View):
(receive_address, change_address) = self.derive_single_sig(i)
if self.address == receive_address:
print(f"Verified receive addr #{i}!")
self.verified_index.set_value(i)
self.verified_index_is_change.set_value(0)
self.keep_running = False
break
elif self.address == change_address:
print(f"Verified change addr #{i}!")
self.verified_index.set_value(i)
self.verified_index_is_change.set_value(1)
self.keep_running = False
@@ -1490,8 +1482,6 @@ class MultisigWalletDescriptorView(View):
policy = descriptor.brief_policy.split("multisig")[0].strip()
print(fingerprints)
RETURN = "Return to PSBT"
VERIFY = "Verify Addr"
OK = "OK"
+20 -5
View File
@@ -1,4 +1,4 @@
from seedsigner.gui.components import FontAwesomeIconConstants
from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerCustomIconConstants
from seedsigner.models.decode_qr import DecodeQR
from .view import View, Destination, BackStackView, MainMenuView
@@ -16,6 +16,9 @@ class SettingsMenuView(View):
def run(self):
IO_TEST = "I/O test"
DONATE = "Donate"
settings_entries = SettingsDefinition.get_settings_entries(
visibiilty=self.visibility
)
@@ -32,16 +35,17 @@ class SettingsMenuView(View):
title = "Settings"
# Set up the next nested level of menuing
button_data.append(("Advanced", None, None, None, FontAwesomeIconConstants.CIRCLE_CHEVRON_RIGHT))
button_data.append(("Advanced", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT))
next = Destination(SettingsMenuView, view_args={"visibility": SettingsConstants.VISIBILITY__ADVANCED})
button_data.append("I/O test")
button_data.append(IO_TEST)
button_data.append(DONATE)
elif self.visibility == SettingsConstants.VISIBILITY__ADVANCED:
title = "Advanced"
# So far there are no real Developer options; disabling for now
# button_data.append(("Developer Options", None, None, None, FontAwesomeIconConstants.CIRCLE_CHEVRON_RIGHT))
# button_data.append(("Developer Options", None, None, None, SeedSignerCustomIconConstants.SMALL_CHEVRON_RIGHT))
# next = Destination(SettingsMenuView, view_args={"visibility": SettingsConstants.VISIBILITY__DEVELOPER})
next = None
@@ -67,9 +71,12 @@ class SettingsMenuView(View):
elif selected_menu_num == len(settings_entries):
return next
elif selected_menu_num == len(settings_entries) + 1:
elif len(button_data) > selected_menu_num and button_data[selected_menu_num] == IO_TEST:
return Destination(IOTestView)
elif len(button_data) > selected_menu_num and button_data[selected_menu_num] == DONATE:
return Destination(DonateView)
else:
# TODO: Free-entry types (are there any?) will need their own SettingsEntryUpdateFreeEntryView(?).
return Destination(SettingsEntryUpdateSelectionView, view_args={"attr_name": settings_entries[selected_menu_num].attr_name})
@@ -179,3 +186,11 @@ class IOTestView(View):
).display()
return Destination(SettingsMenuView)
class DonateView(View):
def run(self):
settings_screens.DonateScreen().display()
return Destination(SettingsMenuView)
+4 -5
View File
@@ -4,7 +4,6 @@ import time
from PIL import Image
from PIL.ImageOps import autocontrast
from seedsigner.gui.screens.screen import LargeButtonScreen
from seedsigner.hardware.camera import Camera
from seedsigner.gui.components import FontAwesomeIconConstants
@@ -13,9 +12,9 @@ from seedsigner.gui.screens.tools_screens import ToolsDiceEntropyEntryScreen, To
from seedsigner.helpers import mnemonic_generation
from seedsigner.models.seed import Seed
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedWordsView, SeedWordsWarningView
from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedWordsWarningView
from .view import NotYetImplementedView, View, Destination, BackStackView, MainMenuView
from .view import View, Destination, BackStackView
@@ -207,9 +206,9 @@ class ToolsDiceEntropyEntryView(View):
if ret == RET_CODE__BACK_BUTTON:
return Destination(BackStackView)
print(ret)
print(f"Dice rolls: {ret}")
dice_seed_phrase = mnemonic_generation.generate_mnemonic_from_dice(ret)
print(dice_seed_phrase)
print(f"""Mnemonic: "{dice_seed_phrase}" """)
# Add the mnemonic as an in-memory Seed
seed = Seed(dice_seed_phrase, wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE))
+7 -52
View File
@@ -3,6 +3,7 @@ import pytest
from mock import MagicMock
from seedsigner.controller import Controller
from seedsigner.models.settings import Settings
from seedsigner.models.settings_definition import SettingsConstants
@@ -11,10 +12,6 @@ def test_singleton_init_fails():
with pytest.raises(Exception):
c = Controller()
def test_singleton_get_instance_without_configure_fails():
""" Calling get_instance() without first calling configure_instance() should fail """
with pytest.raises(Exception):
c = Controller.get_instance()
def test_singleton_get_instance_preserves_state():
""" Changes to the Controller singleton should be preserved across calls to get_instance() """
@@ -25,38 +22,17 @@ def test_singleton_get_instance_preserves_state():
Settings._instance = None
Controller._instance = None
settings = """
[system]
debug = False
default_language = en
persistent_settings = False
[display]
text_color = ORANGE
qr_background_color = FFFFFF
camera_rotation = 0
[wallet]
network = main
software = Prompt
qr_density = 2
custom_derivation = m/0/0
compact_seedqr_enabled = False
"""
config = configparser.ConfigParser()
config.read_string(settings)
# Initialize the instance and verify that it read the config settings
Controller.configure_instance(config, disable_hardware=True)
Controller.configure_instance(disable_hardware=True)
controller = Controller.get_instance()
assert controller.color == "ORANGE"
assert controller.unverified_address is None
# Change a value in the instance...
controller.color = "purple"
controller.unverified_address = "123abc"
# ...get a new copy of the instance and confirm change
controller = Controller.get_instance()
assert controller.color == "purple"
assert controller.unverified_address == "123abc"
def test_missing_settings_get_defaults():
@@ -69,30 +45,9 @@ def test_missing_settings_get_defaults():
Settings._instance = None
Controller._instance = None
# Intentionally omit `compact_seedqr_enabled` from settings:
settings = """
[system]
debug = False
default_language = en
persistent_settings = False
[display]
text_color = ORANGE
qr_background_color = FFFFFF
camera_rotation = 0
[wallet]
network = main
software = Prompt
qr_density = 2
custom_derivation = m/0/0
"""
config = configparser.ConfigParser()
config.read_string(settings)
# Controller should parse the settings fine, even though a field is missing
Controller.configure_instance(config, disable_hardware=True)
Controller.configure_instance(disable_hardware=True)
# Controller should still have a default value
controller = Controller.get_instance()
assert controller.settings.compact_seedqr_enabled is False
assert controller.settings.get_value(SettingsConstants.SETTING__COMPACT_SEEDQR) == SettingsConstants.OPTION__DISABLED
+25 -84
View File
@@ -3,6 +3,8 @@ from mock import MagicMock
from seedsigner.models import Seed, DecodeQR, DecodeQRStatus, QRType, PSBTParser
from embit import psbt, bip39
from seedsigner.models.settings_definition import SettingsConstants
# this is an of this bug: https://github.com/Foundation-Devices/foundation-ur-py/issues/3
@@ -78,15 +80,15 @@ def test_base64_single_frame_singlsig():
assert str(tx) == base64_psbt
mnemonic = "height demise useless trap grow lion found off key clown transfer enroll"
mnemonic = "height demise useless trap grow lion found off key clown transfer enroll".split()
pw = ""
seed = Seed(mnemonic, passphrase=pw)
assert seed.mnemonic_str == mnemonic
assert seed.mnemonic_str == " ".join(mnemonic)
assert seed.seed_bytes != None
pp = PSBTParser(tx,seed,"test")
pp = PSBTParser(p=tx, seed=seed, network=SettingsConstants.TESTNET)
assert tx.inputs[0].witness_utxo.value == 25000 # input amount in psbt
@@ -135,8 +137,8 @@ def test_base64_2_input_p2wsh():
assert str(tx) == base64_psbt
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
pp = PSBTParser(tx, Seed(mnemonic), "test")
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".split()
pp = PSBTParser(p=tx, seed=Seed(mnemonic), network=SettingsConstants.TESTNET)
assert tx.inputs[0].witness_utxo.value == 10000000 # input amount 1 in psbt
@@ -162,8 +164,8 @@ def test_base64_1_input_p2sh_p2wsh():
assert str(tx) == base64_psbt
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
pp = PSBTParser(tx,Seed(mnemonic, wordlist=bip39.WORDLIST),"test")
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about".split()
pp = PSBTParser(p=tx, seed=Seed(mnemonic), network=SettingsConstants.TESTNET)
assert tx.inputs[0].witness_utxo.value == 100000000 # input amount 1 in psbt
@@ -175,62 +177,6 @@ def test_base64_1_input_p2sh_p2wsh():
assert len(pp.destination_addresses) == 1
def test_ur_legacy():
qrcodes = [
"UR:BYTES/2OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3DJV07U94KMSQQQQQQQQZQ8A0CQSYQQQQQQQZQHA78FG5TANFV9Z2TPDWE57AUVCN2T06TDGCAJNYPAT9LVG5QFMU5QSQQQQQRLLLLLL3NZ3KK6TMJ2TTRH6SKU05V76GHEANAT2QSDCDPFG67YZQQPFFZKSZQQQQQQ0LLLLLUPYQRGRQQQQQQQQYGQZP4XCCZ3WFNWK"
, "UR:BYTES/2OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3DJV07U94KMSQQQQQQQQZQ8A0CQSYQQQQQQQZQHA78FG5TANFV9Z2TPDWE57AUVCN2T06TDGCAJNYPAT9LVG5QFMU5QSQQQQQRLLLLLL3NZ3KK6TMJ2TTRH6SKU05V76GHEANAT2QSDCDPFG67YZQQPFFZKSZQQQQQQ0LLLLLUPYQRGRQQQQQQQQYGQZP4XCCZ3WFNWK"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/4OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3Y0PYQFPQW6EJAC7ZGZUYVSCVLPJUS8CVFT8389KFXX82VM3YQMT26FRVHLVUQJ8XPZQYGR9FH3H26HSK99ZH3MQ4D7EHCS7Y752EP37HGG2X6RU5V3PKD7PMSPZQF9L7DESNZNS0H4V56978HCMAEG9D99CNY5LYCV247F58T7M66QUQYSS907LVPMZLKYAWMDGRRKT"
, "UR:BYTES/4OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3Y0PYQFPQW6EJAC7ZGZUYVSCVLPJUS8CVFT8389KFXX82VM3YQMT26FRVHLVUQJ8XPZQYGR9FH3H26HSK99ZH3MQ4D7EHCS7Y752EP37HGG2X6RU5V3PKD7PMSPZQF9L7DESNZNS0H4V56978HCMAEG9D99CNY5LYCV247F58T7M66QUQYSS907LVPMZLKYAWMDGRRKT"
, "UR:BYTES/5OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/GX8KAW6FYVRQ2HKD5T4WUJJ7K68T5262QQQQQQQPQY45QRGRQQQQQQQQYGQZP4XCCZ3WFNWK0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GQYZ5W53PQFMWVTS2CUAWJ43C87WR6ZZ705V5LU2AEKGEF8AM8FWP6TQFTD7AZGGZMKAVTUW8HDRTKMEET55FK6MX"
, "UR:BYTES/5OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/GX8KAW6FYVRQ2HKD5T4WUJJ7K68T5262QQQQQQQPQY45QRGRQQQQQQQQYGQZP4XCCZ3WFNWK0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GQYZ5W53PQFMWVTS2CUAWJ43C87WR6ZZ705V5LU2AEKGEF8AM8FWP6TQFTD7AZGGZMKAVTUW8HDRTKMEET55FK6MX"
, "UR:BYTES/5OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/GX8KAW6FYVRQ2HKD5T4WUJJ7K68T5262QQQQQQQPQY45QRGRQQQQQQQQYGQZP4XCCZ3WFNWK0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GQYZ5W53PQFMWVTS2CUAWJ43C87WR6ZZ705V5LU2AEKGEF8AM8FWP6TQFTD7AZGGZMKAVTUW8HDRTKMEET55FK6MX"
, "UR:BYTES/6OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/KTS8U3XQP95WYR8VQ9V2KWHCTS399T3ZQCP8DE3WPTRN462K8QLEC0GGTE73JNL3THXER9YLHVA9C8FVP9DHM5GUKUGYR2PSQQQGQQGQQZQQQQQQSQPQQQYQQQQQQQQRQQQQQGSXQTWM4303C7A5DWM089WJ3XMTV6EWQLJYCQYK3CSVASQ432E6LPWZY8XYUTHDXVQQ"
, "UR:BYTES/6OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/KTS8U3XQP95WYR8VQ9V2KWHCTS399T3ZQCP8DE3WPTRN462K8QLEC0GGTE73JNL3THXER9YLHVA9C8FVP9DHM5GUKUGYR2PSQQQGQQGQQZQQQQQQSQPQQQYQQQQQQQQRQQQQQGSXQTWM4303C7A5DWM089WJ3XMTV6EWQLJYCQYK3CSVASQ432E6LPWZY8XYUTHDXVQQ"
, "UR:BYTES/7OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/QZQQZQQQSQQQQQYQQGQQPQQQQQQQQQCQQQQQQQQPQ9R4YGGZDYQESZAAQHLY3L9L5VMSYRCQAXPWR6ES83NEC5XTMD4HPKVNGF4ZZQ4XNF7SKKG5KVH0UZPD3XXVNDWVP2CQAP0GLNRGQP2DHAD0U9HYLAF2UGSZQF5SRXQTH5ZLUJ8UH73NWQS0QR5C9C0TXQ7X08ZS"
, "UR:BYTES/7OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/QZQQZQQQSQQQQQYQQGQQPQQQQQQQQQCQQQQQQQQPQ9R4YGGZDYQESZAAQHLY3L9L5VMSYRCQAXPWR6ES83NEC5XTMD4HPKVNGF4ZZQ4XNF7SKKG5KVH0UZPD3XXVNDWVP2CQAP0GLNRGQP2DHAD0U9HYLAF2UGSZQF5SRXQTH5ZLUJ8UH73NWQS0QR5C9C0TXQ7X08ZS"
, "UR:BYTES/1OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/TYPUUURNVF607QGQ05PQQQQQQ9VXGQQDUMWH3LD9GH8XF4ZYJQNMQ80L6MC0KMV6JMVV22RDPZP0SQQQQQQQPLLLLLLSYS8ZQYQQQQQQQQTQQ9P6MG599TND0NJ54AZSE0ZHSHLY7Q4P94C79GQSQQQQQQQZYQPQ68Q0NLXPZYTUVURY6X4G8U7T5H4QVCHY3KKXAW0R"
, "UR:BYTES/1OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/TYPUUURNVF607QGQ05PQQQQQQ9VXGQQDUMWH3LD9GH8XF4ZYJQNMQ80L6MC0KMV6JMVV22RDPZP0SQQQQQQQPLLLLLLSYS8ZQYQQQQQQQQTQQ9P6MG599TND0NJ54AZSE0ZHSHLY7Q4P94C79GQSQQQQQQQZYQPQ68Q0NLXPZYTUVURY6X4G8U7T5H4QVCHY3KKXAW0R"
, "UR:BYTES/2OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3DJV07U94KMSQQQQQQQQZQ8A0CQSYQQQQQQQZQHA78FG5TANFV9Z2TPDWE57AUVCN2T06TDGCAJNYPAT9LVG5QFMU5QSQQQQQRLLLLLL3NZ3KK6TMJ2TTRH6SKU05V76GHEANAT2QSDCDPFG67YZQQPFFZKSZQQQQQQ0LLLLLUPYQRGRQQQQQQQQYGQZP4XCCZ3WFNWK"
, "UR:BYTES/2OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3DJV07U94KMSQQQQQQQQZQ8A0CQSYQQQQQQQZQHA78FG5TANFV9Z2TPDWE57AUVCN2T06TDGCAJNYPAT9LVG5QFMU5QSQQQQQRLLLLLL3NZ3KK6TMJ2TTRH6SKU05V76GHEANAT2QSDCDPFG67YZQQPFFZKSZQQQQQQ0LLLLLUPYQRGRQQQQQQQQYGQZP4XCCZ3WFNWK"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/3OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/0QXPCDECDVJ8PS0DPW59YDWSH262DUZV6VAT4E4GMPEQQQQQQQQQQ9SQZSRR3CY9AYW5V4Y0A5DMWD57JD7FXX9G95PYWVZYQGSX2Y0YENNVNLEHXVMTDS86QM4VNDHG9V0K3PNDMGAJLWLSTS6504SZYP80TGGDPFZR8AM7XZNM77XWKRHP0D0Y540FLPTA5C2HRUQ0"
, "UR:BYTES/4OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3Y0PYQFPQW6EJAC7ZGZUYVSCVLPJUS8CVFT8389KFXX82VM3YQMT26FRVHLVUQJ8XPZQYGR9FH3H26HSK99ZH3MQ4D7EHCS7Y752EP37HGG2X6RU5V3PKD7PMSPZQF9L7DESNZNS0H4V56978HCMAEG9D99CNY5LYCV247F58T7M66QUQYSS907LVPMZLKYAWMDGRRKT"
, "UR:BYTES/4OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/3Y0PYQFPQW6EJAC7ZGZUYVSCVLPJUS8CVFT8389KFXX82VM3YQMT26FRVHLVUQJ8XPZQYGR9FH3H26HSK99ZH3MQ4D7EHCS7Y752EP37HGG2X6RU5V3PKD7PMSPZQF9L7DESNZNS0H4V56978HCMAEG9D99CNY5LYCV247F58T7M66QUQYSS907LVPMZLKYAWMDGRRKT"
, "UR:BYTES/6OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/KTS8U3XQP95WYR8VQ9V2KWHCTS399T3ZQCP8DE3WPTRN462K8QLEC0GGTE73JNL3THXER9YLHVA9C8FVP9DHM5GUKUGYR2PSQQQGQQGQQZQQQQQQSQPQQQYQQQQQQQQRQQQQQGSXQTWM4303C7A5DWM089WJ3XMTV6EWQLJYCQYK3CSVASQ432E6LPWZY8XYUTHDXVQQ"
, "UR:BYTES/7OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/QZQQZQQQSQQQQQYQQGQQPQQQQQQQQQCQQQQQQQQPQ9R4YGGZDYQESZAAQHLY3L9L5VMSYRCQAXPWR6ES83NEC5XTMD4HPKVNGF4ZZQ4XNF7SKKG5KVH0UZPD3XXVNDWVP2CQAP0GLNRGQP2DHAD0U9HYLAF2UGSZQF5SRXQTH5ZLUJ8UH73NWQS0QR5C9C0TXQ7X08ZS"
, "UR:BYTES/7OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/QZQQZQQQSQQQQQYQQGQQPQQQQQQQQQCQQQQQQQQPQ9R4YGGZDYQESZAAQHLY3L9L5VMSYRCQAXPWR6ES83NEC5XTMD4HPKVNGF4ZZQ4XNF7SKKG5KVH0UZPD3XXVNDWVP2CQAP0GLNRGQP2DHAD0U9HYLAF2UGSZQF5SRXQTH5ZLUJ8UH73NWQS0QR5C9C0TXQ7X08ZS"
, "UR:BYTES/8OF8/MMH5LDF9RK3QKCUL3VM26M6270FFJU50CU6YT5WM65783GL5EJMQPG3WV3/E0DKKUXEJDPX589HZPQ6SVQQQZQQZQQQSQQQQQYQQGQQPQQPQQQQQPQQQQQZYQSZ56D86Z6EZJEJALSG9KYCEJD4ES9TQR59AR7VDQQ9FKL44LSKUNL3E38ZAMFNQQQQSQQSQQYQQQQQPQQZQQQGQQGQQQQQGQQQQQQQ0P6SEH"
]
d = DecodeQR()
for i in qrcodes:
if d.add_data(i) == DecodeQRStatus.COMPLETE:
break
assert d.qr_type == QRType.PSBT__LEGACY_UR
#complete should be true
assert d.is_complete
tx = d.get_psbt()
mnemonic = "zone zone zone zone zone abandon ability able abandon ability able abstract"
pp = PSBTParser(tx,Seed(mnemonic, wordlist=bip39.WORDLIST),"test")
assert pp.input_amount == 200000
assert pp.fee_amount == 226
assert pp.spend_amount == 123456
assert pp.change_amount == (pp.input_amount - pp.spend_amount - pp.fee_amount)
assert pp.change_amount == 76318
def test_specter_multisig_animated_qr():
qrcodes = [
@@ -252,8 +198,8 @@ def test_specter_multisig_animated_qr():
tx = d.get_psbt()
mnemonic = "zone zone zone zone zone abandon ability able abandon ability able abstract"
pp = PSBTParser(tx,Seed(mnemonic, wordlist=bip39.WORDLIST),"test")
mnemonic = "zone zone zone zone zone abandon ability able abandon ability able abstract".split()
pp = PSBTParser(p=tx, seed=Seed(mnemonic), network=SettingsConstants.TESTNET)
assert pp.input_amount == 1052818
@@ -290,8 +236,8 @@ def test_specter_multisig_animated_qr():
tx2 = d2.get_psbt()
mnemonic2 = "able bacon cable able bacon cable abandon abandon abandon abandon abandon access"
pp2 = PSBTParser(tx2,Seed(mnemonic2, wordlist=bip39.WORDLIST),"test")
mnemonic2 = "able bacon cable able bacon cable abandon abandon abandon abandon abandon access".split()
pp2 = PSBTParser(p=tx2, seed=Seed(mnemonic2), network=SettingsConstants.TESTNET)
assert pp2.input_amount == 1052818
@@ -334,8 +280,8 @@ def test_short_4_letter_mnemonic_qr():
assert d.is_complete
assert d.get_seed_phrase() == ["height", "demise", "useless", "trap", "grow", "lion", "found", "off", "key", "clown", "transfer", "enroll"]
def test_bitcoin_address():
def test_bitcoin_address():
bad1 = "loremipsum"
bad2 = "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae"
bad3 = "121802020768124106400009195602431595117715840445"
@@ -361,49 +307,44 @@ def test_bitcoin_address():
d.add_data(legacy_address1)
assert d.get_address() == legacy_address1
assert d.get_address_type() == "P2PKH-main"
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() == "P2PKH-main"
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() == "Bech32-main"
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() == "Bech32-test"
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() == "P2SH-main"
assert d.get_address_type() == (SettingsConstants.NESTED_SEGWIT, SettingsConstants.MAINNET)
d = DecodeQR()
d.add_data(test_nested_segwit_address)
assert d.get_address() == test_nested_segwit_address
assert d.get_address_type() == "P2SH-test"
assert d.get_address_type() == (SettingsConstants.NESTED_SEGWIT, SettingsConstants.TESTNET)
d = DecodeQR()
d.add_data(main_bech32_address2)
assert d.get_address() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
assert d.get_address_type() == "Bech32-main"
d = DecodeQR()
d.add_data(main_bech32_address3)
assert d.get_address() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
assert d.get_address_type() == "Bech32-main"
assert d.get_address_type() == (SettingsConstants.NATIVE_SEGWIT, SettingsConstants.MAINNET)
def test_seed_qr():
File diff suppressed because one or more lines are too long
+5 -4
View File
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -9,6 +9,7 @@ from seedsigner.models.qr_type import QRType
from seedsigner.models.settings import SettingsConstants
def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
""" Helper method to re-run multiple variations of the same encode/decode test """
print(entropy)
@@ -16,7 +17,7 @@ def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
print(seed_phrase)
assert len(seed_phrase) == mnemonic_length
e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type, wordlist=bip39.WORDLIST)
e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type)
data = e.next_part()
print(data)
@@ -28,7 +29,7 @@ def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
border=3
)
decoder = DecodeQR(wordlist_language_code=SettingsConstants.LANGUAGE__ENGLISH)
decoder = DecodeQR()
status = decoder.add_image(image)
assert status == DecodeQRStatus.COMPLETE