mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
Work in progress after merge w/main; Lots of breaking changes in Settings
This commit is contained in:
+3
-3
@@ -2,9 +2,9 @@ embit==0.4.10
|
||||
numpy==1.21.1
|
||||
picamera==1.13
|
||||
Pillow==8.2.0
|
||||
pyzbar==0.1.8
|
||||
qrcode==6.1
|
||||
-e git+https://github.com/kdmukai/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
|
||||
qrcode==7.3.1
|
||||
RPi.GPIO==0.7.0
|
||||
six==1.16.0
|
||||
spidev==3.5
|
||||
-e src/vendor/urtypes
|
||||
urtypes @ git+https://github.com/jreesun/urtypes.git@e0d0db277ec2339650343eaf7b220fffb9233241
|
||||
|
||||
@@ -5,6 +5,7 @@ from picamera import PiCamera
|
||||
from PIL import Image
|
||||
from seedsigner.models import Singleton
|
||||
from seedsigner.helpers import PiVideoStream
|
||||
from seedsigner.models.settings import SettingsConstants
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +20,7 @@ class Camera(Singleton):
|
||||
from seedsigner.models import Settings
|
||||
if cls._instance is None:
|
||||
cls._instance = cls.__new__(cls)
|
||||
cls._instance._camera_rotation = Settings.get_instance().camera_rotation
|
||||
cls._instance._camera_rotation = Settings.get_instance().get_value(SettingsConstants.SETTING__CAMERA_ROTATION)
|
||||
return cls._instance
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
from multiprocessing import Process, Queue
|
||||
from subprocess import call
|
||||
import os, sys
|
||||
from embit import bip32, script, ec
|
||||
from embit.networks import NETWORKS
|
||||
from embit.descriptor import Descriptor
|
||||
from binascii import hexlify
|
||||
from threading import Thread
|
||||
|
||||
from embit.psbt import PSBT
|
||||
from seedsigner.gui.renderer import Renderer
|
||||
|
||||
from .models import (EncodeQRDensity, Seed, SeedStorage, Settings,
|
||||
Singleton, DecodeQR, DecodeQRStatus, EncodeQR, PSBTParser)
|
||||
from .models import Seed, SeedStorage, Settings, Singleton, PSBTParser
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +24,7 @@ class Controller(Singleton):
|
||||
"""
|
||||
from .helpers import Buttons
|
||||
|
||||
VERSION = "0.5.0"
|
||||
VERSION = "0.5.0 prerelease 1"
|
||||
|
||||
# Declare class member vars with type hints to enable richer IDE support throughout
|
||||
# the code.
|
||||
@@ -67,30 +53,16 @@ class Controller(Singleton):
|
||||
# models
|
||||
# TODO: Rename "storage" to something more indicative of its temp, in-memory state
|
||||
controller.storage = SeedStorage()
|
||||
|
||||
controller.settings = Settings.get_instance()
|
||||
|
||||
# Store one working psbt in memory
|
||||
controller.psbt = None
|
||||
controller.psbt_parser = None
|
||||
|
||||
# settings
|
||||
# TODO: Remove these
|
||||
controller.DEBUG = controller.settings.debug
|
||||
controller.color = controller.settings.text_color
|
||||
controller.current_bg_qr_color = controller.settings.qr_background_color
|
||||
|
||||
# Configure the Renderer
|
||||
Renderer.configure_instance({"text_color": controller.color})
|
||||
Renderer.configure_instance()
|
||||
|
||||
# TODO: Refactor so that we don't need the Renderer here
|
||||
controller.renderer = Renderer.get_instance()
|
||||
|
||||
# Views
|
||||
# controller.menu_view = MenuView()
|
||||
# controller.seed_tools_view = SeedToolsView()
|
||||
# controller.io_test_view = IOTestView()
|
||||
# controller.signing_tools_view = SigningToolsView(controller.storage)
|
||||
# controller.settings_tools_view = SettingsToolsView()
|
||||
controller.screensaver = ScreensaverView(controller.buttons)
|
||||
|
||||
controller.back_stack = []
|
||||
@@ -134,8 +106,8 @@ class Controller(Singleton):
|
||||
from .views import View, Destination, OpeningSplashView, MainMenuView, BackStackView
|
||||
|
||||
# Disabled to save some time during dev...
|
||||
# opening_splash = OpeningSplashView()
|
||||
# opening_splash.start()
|
||||
opening_splash = OpeningSplashView()
|
||||
opening_splash.start()
|
||||
|
||||
""" Class references can be stored as variables in python!
|
||||
|
||||
@@ -202,13 +174,14 @@ class Controller(Singleton):
|
||||
|
||||
finally:
|
||||
# Clear the screen when exiting
|
||||
self.renderer.display_blank_screen()
|
||||
Renderer.get_instance().display_blank_screen()
|
||||
|
||||
|
||||
def start_screensaver(self):
|
||||
self.screensaver.start()
|
||||
|
||||
|
||||
"""
|
||||
### Menu
|
||||
### Menu View handles navigation within the menu
|
||||
### Sub Menu's like Seed Tools, Signing Tools, Settings are all in the Menu View
|
||||
@@ -965,7 +938,7 @@ class Controller(Singleton):
|
||||
|
||||
def show_qr_density_tool(self):
|
||||
r = self.settings_tools_view.display_qr_density_selection()
|
||||
if r in (EncodeQRDensity.LOW, EncodeQRDensity.MEDIUM, EncodeQRDensity.HIGH):
|
||||
if r in (EncodeQR.DENSITY__LOW, EncodeQR.DENSITY__MEDIUM, EncodeQR.DENSITY__HIGH):
|
||||
self.settings.qr_density = r
|
||||
|
||||
return Path.SETTINGS_SUB_MENU
|
||||
@@ -1084,3 +1057,4 @@ class Controller(Singleton):
|
||||
|
||||
return Path.MAIN_MENU
|
||||
|
||||
"""
|
||||
@@ -18,25 +18,25 @@ class Renderer(ConfigurableSingleton):
|
||||
|
||||
|
||||
@classmethod
|
||||
def configure_instance(cls, config=None):
|
||||
def configure_instance(cls, config={}):
|
||||
from seedsigner.models.settings import Settings
|
||||
super().configure_instance(config)
|
||||
|
||||
# Instantiate the one and only Renderer instance
|
||||
renderer = cls.__new__(cls)
|
||||
cls._instance = renderer
|
||||
|
||||
renderer.color = config["text_color"]
|
||||
# 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
|
||||
renderer.canvas_height = renderer.disp.height
|
||||
|
||||
renderer.canvas = Image.new('RGB', (renderer.canvas_width, renderer.canvas_height))
|
||||
renderer.draw = ImageDraw.Draw(renderer.canvas)
|
||||
|
||||
draw = ImageDraw.Draw(renderer.canvas)
|
||||
|
||||
|
||||
def show_image(self, image=None, alpha_overlay=None):
|
||||
if image == None:
|
||||
|
||||
@@ -2,7 +2,7 @@ import time
|
||||
|
||||
from dataclasses import dataclass
|
||||
from PIL import Image, ImageDraw, ImageColor
|
||||
from typing import List, Tuple
|
||||
from typing import Any, List, Tuple
|
||||
from seedsigner.gui.renderer import Renderer
|
||||
|
||||
from seedsigner.helpers.threads import BaseThread
|
||||
@@ -41,7 +41,7 @@ class BaseScreen(BaseComponent):
|
||||
self.paste_images: List[Tuple] = []
|
||||
|
||||
|
||||
def display(self):
|
||||
def display(self) -> Any:
|
||||
try:
|
||||
with self.renderer.lock:
|
||||
self._render()
|
||||
|
||||
@@ -7,7 +7,7 @@ from seedsigner.models.settings import SettingsConstants
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingsEntryUpdateScreen(ButtonListScreen):
|
||||
class SettingsEntryUpdateSelectionScreen(ButtonListScreen):
|
||||
display_name: str = None
|
||||
help_text: str = None
|
||||
checked_buttons: List[int] = None
|
||||
|
||||
@@ -3,7 +3,6 @@ from .singleton import Singleton, ConfigurableSingleton
|
||||
|
||||
from .seed import *
|
||||
from .qr_type import *
|
||||
from .encode_qr_density import *
|
||||
from .decode_qr import *
|
||||
from .encode_qr import *
|
||||
from .psbt_parser import *
|
||||
|
||||
@@ -698,6 +698,7 @@ class SeedQR:
|
||||
return False
|
||||
|
||||
|
||||
# TODO: Refactor this to work with the new SettingsDefinition
|
||||
class SettingsQR:
|
||||
def __init__(self, wordlist=None):
|
||||
self.total_segments = 1
|
||||
|
||||
@@ -7,18 +7,22 @@ from seedsigner.helpers.ur2.cbor_lite import CBOREncoder
|
||||
from seedsigner.helpers.ur2.ur import UR
|
||||
from seedsigner.helpers.bcur import (bc32encode, cbor_encode, bcur_encode)
|
||||
from seedsigner.helpers.qr import QR
|
||||
from seedsigner.models import Seed, QRType, EncodeQRDensity
|
||||
from seedsigner.models import Seed, QRType
|
||||
|
||||
from urtypes.crypto import PSBT as UR_PSBT
|
||||
from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIPT_EXPRESSION_TAG_MAP
|
||||
|
||||
###
|
||||
### EncodeQR Class
|
||||
### Purpose: used to encode psbt for displaying as qr image
|
||||
###
|
||||
|
||||
|
||||
class EncodeQR:
|
||||
|
||||
"""
|
||||
Encode psbt for displaying as qr image
|
||||
"""
|
||||
DENSITY__LOW = "Low"
|
||||
DENSITY__MEDIUM = "Medium"
|
||||
DENSITY__HIGH = "High"
|
||||
ALL_DENSITIES = [DENSITY__LOW, DENSITY__MEDIUM, DENSITY__HIGH]
|
||||
|
||||
WORDLIST = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -57,7 +61,7 @@ class EncodeQR:
|
||||
raise Exception('Encoder Type Required')
|
||||
|
||||
if self.qr_density == None:
|
||||
self.qr_density = EncodeQRDensity.MEDIUM
|
||||
self.qr_density = EncodeQR.DENSITY__MEDIUM
|
||||
|
||||
self.encoder = None
|
||||
if self.qr_type == QRType.PSBTSPECTER:
|
||||
@@ -108,11 +112,11 @@ class UREncodePSBTQR:
|
||||
|
||||
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
|
||||
|
||||
if qr_density == EncodeQRDensity.LOW:
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 10
|
||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 30
|
||||
elif qr_density == EncodeQRDensity.HIGH:
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 120
|
||||
|
||||
self.ur2_encode = UREncoder(qr_ur_bytes,self.qr_max_fragement_size,0)
|
||||
@@ -135,11 +139,11 @@ class SpecterEncodePSBTQR:
|
||||
self.part_num_sent = 0
|
||||
self.sent_complete = False
|
||||
|
||||
if qr_density == EncodeQRDensity.LOW:
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 40
|
||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 65
|
||||
elif qr_density == EncodeQRDensity.HIGH:
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 90
|
||||
|
||||
self.__createParts()
|
||||
@@ -263,11 +267,11 @@ class SpecterXPubQR(XPubQR):
|
||||
|
||||
def __init__(self, seed_phrase, passphrase, derivation, network, qr_density, wordlist):
|
||||
self.qr_max_fragement_size = 65
|
||||
if qr_density == EncodeQRDensity.LOW:
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 40
|
||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 65
|
||||
elif qr_density == EncodeQRDensity.HIGH:
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 90
|
||||
|
||||
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
||||
@@ -323,11 +327,11 @@ class URXPubQR(XPubQR):
|
||||
|
||||
XPubQR.__init__(self, seed_phrase, passphrase, derivation, network, wordlist)
|
||||
|
||||
if qr_density == EncodeQRDensity.LOW:
|
||||
if qr_density == EncodeQR.DENSITY__LOW:
|
||||
self.qr_max_fragement_size = 10
|
||||
elif qr_density == EncodeQRDensity.MEDIUM:
|
||||
elif qr_density == EncodeQR.DENSITY__MEDIUM:
|
||||
self.qr_max_fragement_size = 30
|
||||
elif qr_density == EncodeQRDensity.HIGH:
|
||||
elif qr_density == EncodeQR.DENSITY__HIGH:
|
||||
self.qr_max_fragement_size = 120
|
||||
|
||||
def derivation_to_keypath(path: str) -> list:
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from enum import IntEnum
|
||||
|
||||
###
|
||||
### QRType Class IntEum
|
||||
### Purpose: used with DecodeQR and EncodeQR to communicate qr encoding type
|
||||
###
|
||||
|
||||
class EncodeQRDensity(IntEnum):
|
||||
LOW = 1
|
||||
MEDIUM = 2
|
||||
HIGH = 3
|
||||
@@ -5,6 +5,7 @@ from embit.psbt import PSBT
|
||||
from io import BytesIO
|
||||
from typing import List
|
||||
from seedsigner.models import Seed
|
||||
from seedsigner.models.seed import SeedConstants
|
||||
|
||||
class PSBTParser():
|
||||
|
||||
@@ -178,7 +179,40 @@ class PSBTParser():
|
||||
|
||||
return cnt
|
||||
|
||||
# checks that all inputs are from the same wallet
|
||||
|
||||
@staticmethod
|
||||
def calc_derivation(network, wallet_type, script_type):
|
||||
# TODO: Move this to Seed?
|
||||
if network == SeedConstants.MAINNET:
|
||||
network_path = "0'"
|
||||
elif network == SeedConstants.TESTNET:
|
||||
network_path = "1'"
|
||||
else:
|
||||
raise Exception("Unexpected network")
|
||||
|
||||
if wallet_type == SeedConstants.SINGLE_SIG:
|
||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||
return f"m/84'/{network_path}/0'"
|
||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||
return f"m/49'/{network_path}/0'"
|
||||
elif script_type == SeedConstants.TAPROOT:
|
||||
return f"m/86'/{network_path}/0'"
|
||||
else:
|
||||
raise Exception("Unexpected script type")
|
||||
|
||||
elif wallet_type == SeedConstants.MULTISIG:
|
||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||
return f"m/48'/{network_path}/0'/2'"
|
||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||
return f"m/48'/{network_path}/0'/1'"
|
||||
elif script_type == SeedConstants.TAPROOT:
|
||||
raise Exception("Taproot multisig/musig not yet supported")
|
||||
else:
|
||||
raise Exception("Unexpected script type")
|
||||
else:
|
||||
raise Exception("Unexpected wallet type") # checks that all inputs are from the same wallet
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __get_policy(scope, scriptpubkey, xpubs):
|
||||
"""Parse scope and get policy"""
|
||||
|
||||
@@ -7,9 +7,10 @@ from typing import List
|
||||
|
||||
|
||||
class SeedConstants:
|
||||
MAINNET = "main"
|
||||
TESTNET = "test"
|
||||
REGTEST = "regtest"
|
||||
MAINNET = ("main", "Mainnet")
|
||||
TESTNET = ("test", "Testnet")
|
||||
REGTEST = ("regtest", "Regtest")
|
||||
ALL_NETWORKS = [MAINNET, TESTNET, REGTEST]
|
||||
|
||||
SINGLE_SIG = "single_sig"
|
||||
MULTISIG = "multisig"
|
||||
|
||||
+133
-173
@@ -3,12 +3,12 @@ import json
|
||||
import os
|
||||
|
||||
from embit import bip39
|
||||
from typing import List
|
||||
from typing import Any, List
|
||||
|
||||
from .seed import SeedConstants
|
||||
from .singleton import Singleton
|
||||
from .qr_type import QRType
|
||||
from .encode_qr_density import EncodeQRDensity
|
||||
from .encode_qr import EncodeQR
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ class SettingsConstants:
|
||||
COORDINATOR__BLUE_WALLET = "BlueWallet"
|
||||
COORDINATOR__SPARROW = "Sparrow"
|
||||
COORDINATOR__SPECTER_DESKTOP = "Specter Desktop"
|
||||
|
||||
ALL_COORDINATORS = [
|
||||
COORDINATOR__BLUE_WALLET,
|
||||
COORDINATOR__SPARROW,
|
||||
@@ -28,7 +27,6 @@ class SettingsConstants:
|
||||
OPTION__DISABLED = "Disabled"
|
||||
OPTION__PROMPT = "Prompt"
|
||||
OPTION__REQUIRED = "Required"
|
||||
|
||||
ALL_OPTIONS = [
|
||||
OPTION__ENABLED,
|
||||
OPTION__DISABLED,
|
||||
@@ -36,18 +34,28 @@ class SettingsConstants:
|
||||
OPTION__REQUIRED,
|
||||
]
|
||||
|
||||
LANGUAGE__ENGLISH = "English"
|
||||
|
||||
LANGUAGE__ENGLISH = ("en", "English")
|
||||
ALL_LANGUAGES = [
|
||||
LANGUAGE__ENGLISH,
|
||||
]
|
||||
|
||||
ROTATION__0 = (0, "0°")
|
||||
ROTATION__90 = (90, "90°")
|
||||
ROTATION__180 = (180, "180°")
|
||||
ROTATION__270 = (270, "270°")
|
||||
ALL_ROTATIONS = [
|
||||
ROTATION__0, ROTATION__90, ROTATION__180, ROTATION__270
|
||||
]
|
||||
|
||||
# Individual settings entry attr_names
|
||||
SETTING__LANGUAGE = "language"
|
||||
SETTING__PERSISTENT_SETTINGS = "persistent_settings"
|
||||
SETTING__COORDINATORS = "coordinators"
|
||||
|
||||
SETTING__NETWORK = "network"
|
||||
SETTING__QR_DENSITY = "qr_density"
|
||||
SETTING__XPUB_EXPORT = "xpub_export"
|
||||
SETTING__CAMERA_ROTATION = "camera_rotation"
|
||||
SETTING__PRIVACY_WARNINGS = "privacy_warnings"
|
||||
SETTING__DIRE_WARNINGS = "dire_warnings"
|
||||
|
||||
@@ -93,22 +101,57 @@ class SettingsEntry:
|
||||
visibility: str = SettingsConstants.VISIBILITY__GENERAL
|
||||
type: str = SettingsConstants.TYPE__ENABLED_DISABLED
|
||||
help_text: str = None
|
||||
possible_values: List[str] = None
|
||||
possible_values_abbreviated: List[str] = None
|
||||
selection_options: List[str] = None
|
||||
selection_options_abbreviated: List[str] = None
|
||||
default_value: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.type == SettingsConstants.TYPE__ENABLED_DISABLED:
|
||||
self.possible_values = [SettingsConstants.OPTION__ENABLED,
|
||||
SettingsConstants.OPTION__DISABLED]
|
||||
self.selection_options = [SettingsConstants.OPTION__ENABLED,
|
||||
SettingsConstants.OPTION__DISABLED]
|
||||
|
||||
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT:
|
||||
self.possible_values = [SettingsConstants.OPTION__ENABLED,
|
||||
SettingsConstants.OPTION__DISABLED,
|
||||
SettingsConstants.OPTION__PROMPT]
|
||||
self.selection_options = [SettingsConstants.OPTION__ENABLED,
|
||||
SettingsConstants.OPTION__DISABLED,
|
||||
SettingsConstants.OPTION__PROMPT]
|
||||
|
||||
elif self.type == SettingsConstants.TYPE__ENABLED_DISABLED_PROMPT_REQURIED:
|
||||
self.possible_values = [SettingsConstants.ALL_OPTIONS]
|
||||
self.selection_options = [SettingsConstants.ALL_OPTIONS]
|
||||
|
||||
|
||||
@property
|
||||
def selection_options_display_names(self) -> List[str]:
|
||||
if type(self.selection_options[0]) == tuple:
|
||||
return [v[1] for v in self.selection_options]
|
||||
else:
|
||||
# Always return a copy so the original can't be altered
|
||||
return list(self.selection_options)
|
||||
|
||||
|
||||
def get_selection_option_value(self, i: int):
|
||||
value = self.selection_options[i]
|
||||
if type(value) == tuple:
|
||||
value = value[0]
|
||||
return value
|
||||
|
||||
|
||||
def get_selection_option_display_name(self, i: int) -> str:
|
||||
value = self.selection_options[i]
|
||||
if type(value) == tuple:
|
||||
value = value[1]
|
||||
return value
|
||||
|
||||
|
||||
def get_selection_option_display_name_by_value(self, value) -> str:
|
||||
for option in self.selection_options:
|
||||
if type(option) == tuple:
|
||||
option_value = option[0]
|
||||
display_name = option[1]
|
||||
else:
|
||||
option_value = option
|
||||
display_name = option
|
||||
if option_value == value:
|
||||
return display_name
|
||||
|
||||
|
||||
|
||||
@@ -131,32 +174,62 @@ class SettingsDefinition:
|
||||
attr_name=SettingsConstants.SETTING__LANGUAGE,
|
||||
display_name="Language",
|
||||
type=SettingsConstants.TYPE__SELECT_1,
|
||||
possible_values=SettingsConstants.ALL_LANGUAGES,
|
||||
selection_options=SettingsConstants.ALL_LANGUAGES,
|
||||
default_value=SettingsConstants.LANGUAGE__ENGLISH),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__SYSTEM,
|
||||
attr_name=SettingsConstants.SETTING__PERSISTENT_SETTINGS,
|
||||
display_name="Persistent Settings",
|
||||
display_name="Persistent settings",
|
||||
help_text="Store Settings on SD card",
|
||||
default_value=SettingsConstants.OPTION__DISABLED),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__WALLET,
|
||||
attr_name=SettingsConstants.SETTING__COORDINATORS,
|
||||
display_name="Coordinator Software",
|
||||
display_name="Coordinator software",
|
||||
type=SettingsConstants.TYPE__MULTISELECT,
|
||||
possible_values=SettingsConstants.ALL_COORDINATORS,
|
||||
selection_options=SettingsConstants.ALL_COORDINATORS,
|
||||
default_value=SettingsConstants.ALL_COORDINATORS),
|
||||
|
||||
# Advanced options
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__NETWORK,
|
||||
display_name="Bitcoin network",
|
||||
type=SettingsConstants.TYPE__SELECT_1,
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
selection_options=SeedConstants.ALL_NETWORKS,
|
||||
default_value=SeedConstants.MAINNET[0]),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__QR_DENSITY,
|
||||
display_name="QR code density",
|
||||
type=SettingsConstants.TYPE__SELECT_1,
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
selection_options=EncodeQR.ALL_DENSITIES,
|
||||
default_value=EncodeQR.DENSITY__MEDIUM),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__XPUB_EXPORT,
|
||||
display_name="Xpub export",
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
default_value=SettingsConstants.OPTION__ENABLED),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__QR_DENSITY,
|
||||
display_name="QR code density",
|
||||
type=SettingsConstants.TYPE__SELECT_1,
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
selection_options=SettingsConstants.ALL_ROTATIONS,
|
||||
default_value=SettingsConstants.ROTATION__0),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__PRIVACY_WARNINGS,
|
||||
display_name="Show Privacy Warnings",
|
||||
display_name="Show privacy warnings",
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
default_value=SettingsConstants.OPTION__ENABLED),
|
||||
|
||||
SettingsEntry(category=SettingsConstants.CATEGORY__FEATURES,
|
||||
attr_name=SettingsConstants.SETTING__DIRE_WARNINGS,
|
||||
display_name="Show Dire Warnings",
|
||||
display_name="Show dire warnings",
|
||||
visibility=SettingsConstants.VISIBILITY__ADVANCED,
|
||||
default_value=SettingsConstants.OPTION__ENABLED),
|
||||
|
||||
@@ -187,7 +260,7 @@ class SettingsDefinition:
|
||||
|
||||
@classmethod
|
||||
def parse_abbreviated_ini(cls, abbreviated_ini: str) -> dict:
|
||||
return False
|
||||
raise Exception("Not implemented, maybe not needed")
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -205,12 +278,14 @@ class SettingsDefinition:
|
||||
|
||||
@classmethod
|
||||
def to_json(cls):
|
||||
return json.dumps({})
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
@classmethod
|
||||
def to_html(cls):
|
||||
return ""
|
||||
raise Exception("Not implemented, maybe not needed")
|
||||
|
||||
|
||||
|
||||
class Settings(Singleton):
|
||||
SETTINGS_FILENAME = "settings.json"
|
||||
@@ -240,7 +315,7 @@ class Settings(Singleton):
|
||||
# "wallet": {
|
||||
# "network": SeedConstants.MAINNET,
|
||||
# "coordinators": SettingsConstants.ALL_COORDINATORS,
|
||||
# "qr_density": EncodeQRDensity.MEDIUM,
|
||||
# "qr_density": EncodeQR.DENSITY__MEDIUM,
|
||||
# "custom_derivation": "m/"
|
||||
# },
|
||||
# "features": {
|
||||
@@ -254,7 +329,7 @@ class Settings(Singleton):
|
||||
# }
|
||||
# }
|
||||
|
||||
# # Read persistent settings, if it exists
|
||||
# TODO: Read persistent settings, if it exists
|
||||
# if os.path.exists(Settings.SETTINGS_FILENAME):
|
||||
# with open(Settings.SETTINGS_FILENAME) as settings_file:
|
||||
# settings._data.update(json.load(settings_file))
|
||||
@@ -287,43 +362,45 @@ class Settings(Singleton):
|
||||
self._data[attr_name] = value
|
||||
# self.save()
|
||||
|
||||
# TODO: auto-intialize dict keys as getter properties?
|
||||
|
||||
def get_value(self, attr_name: str):
|
||||
if attr_name not in self._data:
|
||||
raise Exception(f"Setting for {attr_name} not found")
|
||||
return self._data[attr_name]
|
||||
|
||||
|
||||
### persistent settings handling
|
||||
def get_value_display_name(self, attr_name: str) -> str:
|
||||
if attr_name not in self._data:
|
||||
raise Exception(f"Setting for {attr_name} not found")
|
||||
settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||
if settings_entry.type in [SettingsConstants.TYPE__FREE_ENTRY, SettingsConstants.TYPE__MULTISELECT]:
|
||||
raise Exception(f"Unsupported SettingsEntry.type: {settings_entry.type}")
|
||||
return settings_entry.get_selection_option_display_name_by_value(value=self._data[attr_name])
|
||||
|
||||
|
||||
def get_multiselect_value_display_names(self, attr_name: str) -> List[str]:
|
||||
if attr_name not in self._data:
|
||||
raise Exception(f"Setting for {attr_name} not found")
|
||||
settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||
if settings_entry.type != SettingsConstants.TYPE__MULTISELECT:
|
||||
raise Exception(f"Unsupported SettingsEntry.type: {settings_entry.type}")
|
||||
|
||||
display_names = []
|
||||
for value in self._data[attr_name]:
|
||||
display_names.append(settings_entry.get_selection_option_display_name_by_value(value))
|
||||
return display_names
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def persistent_settings(self):
|
||||
def persistent_settings(self) -> bool:
|
||||
return self._data[SettingsConstants.SETTING__PERSISTENT_SETTINGS] == SettingsConstants.OPTION__ENABLED
|
||||
|
||||
@property
|
||||
def persistent_display(self):
|
||||
if self.persistent:
|
||||
return "Yes"
|
||||
else:
|
||||
return "No"
|
||||
|
||||
### system
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def language(self):
|
||||
return "en"
|
||||
|
||||
@property
|
||||
def wordlist(self):
|
||||
# TODO: Support BIP-39 wordlists in other languages
|
||||
return bip39.WORDLIST
|
||||
|
||||
### display
|
||||
def debug(self) -> bool:
|
||||
return self._data[SettingsConstants.SETTING__DEBUG] == SettingsConstants.OPTION__ENABLED
|
||||
|
||||
# TODO: Deeper support for GUI-wide color customization
|
||||
@property
|
||||
def text_color(self):
|
||||
from seedsigner.gui.components import GUIConstants
|
||||
@@ -332,55 +409,10 @@ class Settings(Singleton):
|
||||
@property
|
||||
def qr_background_color(self):
|
||||
return self._data["display"]["qr_background_color"]
|
||||
|
||||
@qr_background_color.setter
|
||||
def qr_background_color(self, value):
|
||||
self._data["display"]["qr_background_color"] = value
|
||||
self.__writeConfig()
|
||||
|
||||
@property
|
||||
def camera_rotation(self):
|
||||
return 0
|
||||
|
||||
@camera_rotation.setter
|
||||
def camera_rotation(self, value: int):
|
||||
if value in [0, 90, 180, 270]:
|
||||
self._data["display"]["camera_rotation"] = value
|
||||
self.save()
|
||||
else:
|
||||
raise Exception("Unexpected display.camera_rotation settings.json value")
|
||||
|
||||
### wallet
|
||||
|
||||
@property
|
||||
def network(self):
|
||||
return SeedConstants.MAINNET
|
||||
|
||||
@network.setter
|
||||
def network(self, value):
|
||||
if value in [SeedConstants.MAINNET, SeedConstants.TESTNET]:
|
||||
self._data["wallet"]["network"] = value
|
||||
self.save()
|
||||
else:
|
||||
raise Exception("Unexpected wallet.network settings.json value")
|
||||
|
||||
@property
|
||||
def coordinators(self):
|
||||
return self._data[SettingsConstants.SETTING__COORDINATORS]
|
||||
|
||||
@property
|
||||
def qr_density(self):
|
||||
return self._data["wallet"]["qr_density"]
|
||||
|
||||
@qr_density.setter
|
||||
def qr_density(self, value):
|
||||
if value in (EncodeQRDensity.LOW, EncodeQRDensity.MEDIUM, EncodeQRDensity.HIGH, int(EncodeQRDensity.LOW), int(EncodeQRDensity.MEDIUM), int(EncodeQRDensity.HIGH)):
|
||||
self._data["wallet"]["qr_density"] = int(value)
|
||||
self.save()
|
||||
else:
|
||||
raise Exception("Unexpected wallet.qr_density settings.json value")
|
||||
|
||||
def qr_psbt_type(self, coordinator):
|
||||
@staticmethod
|
||||
def qr_psbt_type(coordinator):
|
||||
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
|
||||
return QRType.PSBTSPECTER
|
||||
else:
|
||||
@@ -388,28 +420,17 @@ class Settings(Singleton):
|
||||
|
||||
@property
|
||||
def qr_xpub_type(self):
|
||||
return Settings.getXPubType(self.coordinators)
|
||||
return Settings.getXPubType(self._data[SettingsConstants.SETTING__COORDINATORS])
|
||||
|
||||
@staticmethod
|
||||
def getXPubType(software):
|
||||
if software == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
|
||||
def getXPubType(coordinators):
|
||||
if coordinators == [SettingsConstants.COORDINATOR__SPECTER_DESKTOP]:
|
||||
return QRType.SPECTERXPUBQR
|
||||
elif software == "Blue Wallet":
|
||||
elif coordinators == [SettingsConstants.COORDINATOR__BLUE_WALLET]:
|
||||
return QRType.XPUBQR
|
||||
else:
|
||||
return QRType.URXPUBQR
|
||||
|
||||
@property
|
||||
def qr_density_name(self):
|
||||
if self.qr_density == EncodeQRDensity.LOW:
|
||||
return "Low"
|
||||
elif self.qr_density == EncodeQRDensity.MEDIUM:
|
||||
return "Medium"
|
||||
elif self.qr_density == EncodeQRDensity.HIGH:
|
||||
return "High"
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
@property
|
||||
def custom_derivation(self):
|
||||
return self._data["wallet"]["custom_derivation"]
|
||||
@@ -419,64 +440,3 @@ class Settings(Singleton):
|
||||
# TODO: parse and validate custom derivation path
|
||||
self._data["wallet"]["custom_derivation"] = value
|
||||
self.save()
|
||||
|
||||
@staticmethod
|
||||
def calc_derivation(network, wallet_type, script_type):
|
||||
# TODO: Move this to Seed?
|
||||
if network == SeedConstants.MAINNET:
|
||||
network_path = "0'"
|
||||
elif network == SeedConstants.TESTNET:
|
||||
network_path = "1'"
|
||||
else:
|
||||
raise Exception("Unexpected network")
|
||||
|
||||
if wallet_type == SeedConstants.SINGLE_SIG:
|
||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||
return f"m/84'/{network_path}/0'"
|
||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||
return f"m/49'/{network_path}/0'"
|
||||
elif script_type == SeedConstants.TAPROOT:
|
||||
return f"m/86'/{network_path}/0'"
|
||||
else:
|
||||
raise Exception("Unexpected script type")
|
||||
|
||||
elif wallet_type == SeedConstants.MULTISIG:
|
||||
if script_type == SeedConstants.NATIVE_SEGWIT:
|
||||
return f"m/48'/{network_path}/0'/2'"
|
||||
elif script_type == SeedConstants.NESTED_SEGWIT:
|
||||
return f"m/48'/{network_path}/0'/1'"
|
||||
elif script_type == SeedConstants.TAPROOT:
|
||||
raise Exception("Taproot multisig/musig not yet supported")
|
||||
else:
|
||||
raise Exception("Unexpected script type")
|
||||
else:
|
||||
raise Exception("Unexpected wallet type")
|
||||
|
||||
# Features
|
||||
@property
|
||||
def xpub_export(self):
|
||||
return self._data["features"].get("xpub_export")
|
||||
|
||||
@property
|
||||
def sig_types(self) -> List[str]:
|
||||
return self._data["features"].get("sig_types")
|
||||
|
||||
@property
|
||||
def script_types(self) -> List[str]:
|
||||
return self._data["features"].get("script_types")
|
||||
|
||||
@property
|
||||
def show_xpub_details(self) -> bool:
|
||||
return self._data["features"].get("show_xpub_details") == SettingsConstants.OPTION__ENABLED
|
||||
|
||||
@property
|
||||
def passphrase(self):
|
||||
return "Enabled"
|
||||
|
||||
@property
|
||||
def show_privacy_warnings(self) -> bool:
|
||||
return self._data["features"].get("show_privacy_warnings") == SettingsConstants.OPTION__ENABLED
|
||||
|
||||
@property
|
||||
def show_dire_warnings(self) -> bool:
|
||||
return self._data["features"].get("show_dire_warnings") == SettingsConstants.OPTION__ENABLED
|
||||
|
||||
@@ -28,8 +28,8 @@ class PSBTSelectSeedView(View):
|
||||
ENTER_WORDS = "Enter 12/24 words"
|
||||
button_data = []
|
||||
for seed in seeds:
|
||||
button_str = seed.get_fingerprint(self.settings.network)
|
||||
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.network):
|
||||
button_str = seed.get_fingerprint(self.settings.get_value_display_name(SettingsConstants.SETTING__NETWORK))
|
||||
if not PSBTParser.has_matching_input_fingerprint(psbt=self.controller.psbt, seed=seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
|
||||
# Doesn't look like this seed can sign the current PSBT
|
||||
button_str += " (?)"
|
||||
|
||||
@@ -40,12 +40,11 @@ class PSBTSelectSeedView(View):
|
||||
button_data.append(SCAN_SEED)
|
||||
button_data.append(ENTER_WORDS)
|
||||
|
||||
screen = ButtonListScreen(
|
||||
selected_menu_num = ButtonListScreen(
|
||||
title="Select Signer",
|
||||
is_button_text_centered=False,
|
||||
button_data=button_data
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||
return Destination(BackStackView)
|
||||
@@ -79,7 +78,7 @@ class PSBTOverviewView(View):
|
||||
self.controller.psbt_parser = PSBTParser(
|
||||
self.controller.psbt,
|
||||
seed=self.controller.psbt_seed,
|
||||
network=self.controller.settings.network
|
||||
network=self.controller.settings.get_value(SettingsConstants.SETTING__NETWORK)
|
||||
)
|
||||
|
||||
|
||||
@@ -112,8 +111,7 @@ class PSBTOverviewView(View):
|
||||
|
||||
class PSBTNoChangeWarningView(View):
|
||||
def run(self):
|
||||
screen = psbt_screens.PSBTNoChangeWarningScreen()
|
||||
selected_menu_num = screen.display()
|
||||
selected_menu_num = psbt_screens.PSBTNoChangeWarningScreen().display()
|
||||
|
||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||
return Destination(BackStackView)
|
||||
@@ -140,15 +138,14 @@ class PSBTAmountDetailsView(View):
|
||||
# Should not be able to get here
|
||||
return Destination(MainMenuView)
|
||||
|
||||
screen = PSBTAmountDetailsScreen(
|
||||
selected_menu_num = PSBTAmountDetailsScreen(
|
||||
input_amount=psbt_parser.input_amount,
|
||||
num_inputs=psbt_parser.num_inputs,
|
||||
spend_amount=psbt_parser.spend_amount,
|
||||
num_recipients=psbt_parser.num_destinations,
|
||||
fee_amount=psbt_parser.fee_amount,
|
||||
change_amount=psbt_parser.change_amount,
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||
return Destination(BackStackView)
|
||||
@@ -185,15 +182,13 @@ class PSBTAddressDetailsView(View):
|
||||
else:
|
||||
button_data.append("Next")
|
||||
|
||||
screen = psbt_screens.PSBTAddressDetailsScreen(
|
||||
selected_menu_num = psbt_screens.PSBTAddressDetailsScreen(
|
||||
title=title,
|
||||
button_data=button_data,
|
||||
address=psbt_parser.destination_addresses[self.address_num],
|
||||
amount=psbt_parser.destination_amounts[self.address_num],
|
||||
is_change=self.is_change,
|
||||
)
|
||||
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == 0:
|
||||
if self.address_num < len(psbt_parser.destination_addresses) - 1:
|
||||
@@ -233,10 +228,10 @@ class PSBTChangeDetailsView(View):
|
||||
|
||||
# Single-sig verification is easy. We expect to find a single fingerprint
|
||||
# and derivation path.
|
||||
print(f"seed fingerprint: {self.controller.psbt_seed.get_fingerprint(self.settings.network)}")
|
||||
print(f"seed fingerprint: {self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))}")
|
||||
print(change_data)
|
||||
|
||||
if self.controller.psbt_seed.get_fingerprint(self.settings.network) != change_data.get("fingerprint")[0]:
|
||||
if self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)) != change_data.get("fingerprint")[0]:
|
||||
# TODO: Something is wrong with this psbt(?). Reroute to warning?
|
||||
return Destination(MainMenuView)
|
||||
|
||||
@@ -254,7 +249,7 @@ class PSBTChangeDetailsView(View):
|
||||
if psbt_parser.num_change_outputs > 1:
|
||||
title += f" (#{self.change_address_num + 1})"
|
||||
|
||||
screen = psbt_screens.PSBTChangeDetailsScreen(
|
||||
selected_menu_num = psbt_screens.PSBTChangeDetailsScreen(
|
||||
title=title,
|
||||
button_data=["Next"],
|
||||
address=change_data.get("address"),
|
||||
@@ -263,9 +258,7 @@ class PSBTChangeDetailsView(View):
|
||||
derivation_path=derivation_path,
|
||||
is_own_change_addr=is_own_change_addr,
|
||||
own_addr_index=own_addr_index,
|
||||
)
|
||||
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == 0:
|
||||
if self.change_address_num < psbt_parser.num_change_outputs - 1:
|
||||
@@ -290,10 +283,9 @@ class PSBTFinalizeView(View):
|
||||
# Should not be able to get here
|
||||
return Destination(MainMenuView)
|
||||
|
||||
screen = psbt_screens.PSBTFinalizeScreen(
|
||||
selected_menu_num = psbt_screens.PSBTFinalizeScreen(
|
||||
button_data=["Approve PSBT"]
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == 0:
|
||||
# Sign PSBT
|
||||
@@ -315,8 +307,8 @@ class PSBTFinalizeView(View):
|
||||
else:
|
||||
self.controller.psbt = trimmed_psbt
|
||||
|
||||
if len(self.settings.coordinators) == 1:
|
||||
return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": self.settings.coordinators[0]})
|
||||
if len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)) == 1:
|
||||
return Destination(PSBTSignedQRDisplayView, view_args={"coordinator": self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0]})
|
||||
else:
|
||||
return Destination(PSBTSelectCoordinatorView)
|
||||
|
||||
@@ -327,11 +319,10 @@ class PSBTFinalizeView(View):
|
||||
|
||||
class PSBTSelectCoordinatorView(View):
|
||||
def run(self):
|
||||
button_data = [c for c in self.settings.coordinators]
|
||||
screen = psbt_screens.PSBTSelectCoordinatorScreen(
|
||||
button_data = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)
|
||||
selected_menu_num = psbt_screens.PSBTSelectCoordinatorScreen(
|
||||
button_data=button_data
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||
return Destination(BackStackView)
|
||||
@@ -347,13 +338,12 @@ class PSBTSignedQRDisplayView(View):
|
||||
|
||||
def run(self):
|
||||
qr_encoder = EncodeQR(
|
||||
psbt=self.controller.psbt,
|
||||
qr_type=self.settings.qr_psbt_type(self.coordinator),
|
||||
qr_density=self.settings.qr_density,
|
||||
psbt=self.controller.psbt,
|
||||
qr_type=self.settings.qr_psbt_type(self.coordinator),
|
||||
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY),
|
||||
wordlist=self.settings.wordlist
|
||||
)
|
||||
screen = QRDisplayScreen(qr_encoder=qr_encoder)
|
||||
ret = screen.display()
|
||||
ret = QRDisplayScreen(qr_encoder=qr_encoder).display()
|
||||
|
||||
# We're done with this PSBT. Remove all related data
|
||||
self.controller.psbt = None
|
||||
@@ -371,14 +361,13 @@ class PSBTSigningErrorView(View):
|
||||
# Should not be able to get here
|
||||
return Destination(MainMenuView)
|
||||
|
||||
screen = WarningScreen(
|
||||
selected_menu_num = WarningScreen(
|
||||
title="Finalize PSBT",
|
||||
warning_icon_name="warning",
|
||||
warning_headline="Signing Failed",
|
||||
warning_text="",
|
||||
button_data=["Select Diff Seed"],
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == 0:
|
||||
return Destination(PSBTSelectSeedView, clear_history=True)
|
||||
|
||||
@@ -111,20 +111,22 @@ class SeedOptionsView(View):
|
||||
button_data = []
|
||||
|
||||
if self.controller.psbt:
|
||||
if not PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.network):
|
||||
if not PSBTParser.has_matching_input_fingerprint(self.controller.psbt, self.seed, network=self.settings.get_value(SettingsConstants.SETTING__NETWORK)):
|
||||
# This seed does not seem to be a signer for this PSBT
|
||||
# TODO: How sure are we? Should disable this entirely if we're 100% sure?
|
||||
SIGN_PSBT += " (?)"
|
||||
button_data.append(SIGN_PSBT)
|
||||
|
||||
button_data.append(VIEW_WORDS)
|
||||
|
||||
if self.settings.xpub_export == SettingsConstants.OPTION__ENABLED:
|
||||
if self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED:
|
||||
button_data.append(EXPORT_XPUB)
|
||||
|
||||
button_data.append(EXPORT_SEEDQR)
|
||||
|
||||
screen = seed_screens.SeedOptionsScreen(
|
||||
button_data=button_data,
|
||||
fingerprint=self.seed.get_fingerprint(self.settings.network),
|
||||
fingerprint=self.seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK)),
|
||||
has_passphrase=self.seed.passphrase is not None
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
@@ -139,7 +141,7 @@ class SeedOptionsView(View):
|
||||
elif button_data[selected_menu_num] == VIEW_WORDS:
|
||||
return Destination(SeedWordsWarningView, view_args={"seed_num": self.seed_num})
|
||||
|
||||
elif button_data[selected_menu_num] == EXPORT_XPUB and self.settings.xpub_export == SettingsConstants.OPTION__ENABLED:
|
||||
elif button_data[selected_menu_num] == EXPORT_XPUB and self.settings.get_value(SettingsConstants.SETTING__XPUB_EXPORT) == SettingsConstants.OPTION__ENABLED:
|
||||
return Destination(SeedExportXpubSigTypeView, view_args={"seed_num": self.seed_num})
|
||||
|
||||
elif button_data[selected_menu_num] == EXPORT_SEEDQR:
|
||||
@@ -474,7 +476,7 @@ class SeedExportXpubQRDisplayView(View):
|
||||
derivation=derivation_path,
|
||||
network=self.settings.network,
|
||||
qr_type=qr_type,
|
||||
qr_density=self.settings.qr_density,
|
||||
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY),
|
||||
wordlist=self.seed.wordlist
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from . import View
|
||||
from seedsigner.helpers import B, QR
|
||||
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
|
||||
from seedsigner.models import EncodeQRDensity
|
||||
from seedsigner.models import EncodeQR
|
||||
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ class SettingsToolsView(View):
|
||||
def display_qr_density_selection(self) -> str:
|
||||
r = self.renderer.display_generic_selection_menu(["... [ Return to Settings ]", "Low", "Medium", "High"], "Which QR Density?")
|
||||
if r == 2:
|
||||
return EncodeQRDensity.LOW
|
||||
return EncodeQR.DENSITY__LOW
|
||||
elif r == 3:
|
||||
return EncodeQRDensity.MEDIUM
|
||||
return EncodeQR.DENSITY__MEDIUM
|
||||
elif r == 4:
|
||||
return EncodeQRDensity.HIGH
|
||||
return EncodeQR.DENSITY__HIGH
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@@ -41,13 +41,12 @@ class SettingsMenuView(View):
|
||||
title = "Dev Options"
|
||||
next = None
|
||||
|
||||
screen = ButtonListScreen(
|
||||
selected_menu_num = ButtonListScreen(
|
||||
title=title,
|
||||
is_button_text_centered=False,
|
||||
button_data=button_data,
|
||||
selected_button=selected_button,
|
||||
)
|
||||
selected_menu_num = screen.display()
|
||||
).display()
|
||||
|
||||
if selected_menu_num == RET_CODE__BACK_BUTTON:
|
||||
if self.visibility == SettingsConstants.VISIBILITY__GENERAL:
|
||||
@@ -61,76 +60,94 @@ class SettingsMenuView(View):
|
||||
return next
|
||||
|
||||
else:
|
||||
return Destination(SettingsEntryUpdateView, view_args={"attr_name": settings_entries[selected_menu_num].attr_name})
|
||||
# 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})
|
||||
|
||||
|
||||
|
||||
class SettingsEntryUpdateView(View):
|
||||
class SettingsEntryUpdateSelectionView(View):
|
||||
"""
|
||||
Handles changes to all selection-type settings (Multiselect, SELECT_1,
|
||||
Enabled/Disabled, etc).
|
||||
"""
|
||||
def __init__(self, attr_name: str):
|
||||
super().__init__()
|
||||
self.settings_entry = SettingsDefinition.get_settings_entry(attr_name)
|
||||
self.selected_button = 0
|
||||
|
||||
|
||||
def run(self):
|
||||
button_data = self.settings_entry.possible_values
|
||||
cur_value = self.settings.get_value(self.settings_entry.attr_name)
|
||||
button_data = []
|
||||
checked_buttons = []
|
||||
for i, value in enumerate(button_data):
|
||||
selected_button = None
|
||||
for i, value in enumerate(self.settings_entry.selection_options):
|
||||
print(value)
|
||||
if type(value) == tuple:
|
||||
value, display_name = value
|
||||
else:
|
||||
display_name = value
|
||||
button_data.append(display_name)
|
||||
if (type(cur_value) == list and value in cur_value) or value == cur_value:
|
||||
checked_buttons.append(i)
|
||||
|
||||
screen = settings_screens.SettingsEntryUpdateScreen(
|
||||
if selected_button is None:
|
||||
# Highlight the selection (for multiselect highlight the first
|
||||
# selected option).
|
||||
selected_button = i
|
||||
print(f"setting selected_button: {i} - {display_name}")
|
||||
|
||||
ret_value = settings_screens.SettingsEntryUpdateSelectionScreen(
|
||||
display_name=self.settings_entry.display_name,
|
||||
help_text=self.settings_entry.help_text,
|
||||
button_data=button_data,
|
||||
selected_button=self.selected_button,
|
||||
selected_button=selected_button,
|
||||
checked_buttons=checked_buttons,
|
||||
settings_entry_type=self.settings_entry.type,
|
||||
).display()
|
||||
|
||||
destination = None
|
||||
settings_menu_view_destination = Destination(
|
||||
SettingsMenuView,
|
||||
view_args={
|
||||
"visibility": self.settings_entry.visibility,
|
||||
"selected_attr": self.settings_entry.attr_name
|
||||
}
|
||||
)
|
||||
ret_value = screen.display()
|
||||
|
||||
if self.settings_entry.type == SettingsConstants.TYPE__MULTISELECT:
|
||||
if ret_value == RET_CODE__BACK_BUTTON:
|
||||
return Destination(
|
||||
SettingsMenuView,
|
||||
view_args={
|
||||
"visibility": self.settings_entry.visibility,
|
||||
"selected_attr": self.settings_entry.attr_name
|
||||
}
|
||||
)
|
||||
if ret_value == RET_CODE__BACK_BUTTON:
|
||||
return settings_menu_view_destination
|
||||
|
||||
value = self.settings_entry.get_selection_option_value(ret_value)
|
||||
|
||||
if self.settings_entry.type == SettingsConstants.TYPE__FREE_ENTRY:
|
||||
updated_value = ret_value
|
||||
destination = settings_menu_view_destination
|
||||
|
||||
elif self.settings_entry.type == SettingsConstants.TYPE__MULTISELECT:
|
||||
updated_value = list(cur_value)
|
||||
if ret_value not in checked_buttons:
|
||||
# This is a new selection to add
|
||||
cur_value.append(self.settings_entry.possible_values[ret_value])
|
||||
updated_value.append(value)
|
||||
else:
|
||||
# This is a de-select to remove
|
||||
cur_value.remove(self.settings_entry.possible_values[ret_value])
|
||||
|
||||
# Save the updated selections
|
||||
self.settings.set_value(
|
||||
attr_name=self.settings_entry.attr_name,
|
||||
value=cur_value
|
||||
)
|
||||
# Multiselects stay in place; re-initialize where in the list we left off
|
||||
# TODO: get Screen.scroll_y to pass in?
|
||||
self.selected_button = ret_value
|
||||
return self.run()
|
||||
|
||||
elif ret_value == RET_CODE__BACK_BUTTON:
|
||||
return Destination(BackStackView)
|
||||
updated_value.remove(value)
|
||||
|
||||
else:
|
||||
self.settings.set_value(
|
||||
attr_name=self.settings_entry.attr_name,
|
||||
value=self.settings_entry.possible_values[ret_value]
|
||||
)
|
||||
# All other types are single selects (e.g. Enabled/Disabled, SELECT_1)
|
||||
updated_value = value
|
||||
|
||||
print(updated_value)
|
||||
|
||||
return Destination(
|
||||
SettingsMenuView,
|
||||
view_args={
|
||||
"visibility": self.settings_entry.visibility,
|
||||
"selected_attr": self.settings_entry.attr_name
|
||||
}
|
||||
)
|
||||
self.settings.set_value(
|
||||
attr_name=self.settings_entry.attr_name,
|
||||
value=updated_value
|
||||
)
|
||||
|
||||
if destination:
|
||||
return destination
|
||||
|
||||
# All selects stay in place; re-initialize where in the list we left off
|
||||
# TODO: get Screen.scroll_y to pass in?
|
||||
self.selected_button = ret_value
|
||||
return self.run()
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ class View:
|
||||
self.canvas_height = self.renderer.canvas_height
|
||||
|
||||
self.buttons = self.controller.buttons
|
||||
self.color = self.controller.color
|
||||
|
||||
|
||||
def run(self, **kwargs):
|
||||
@@ -64,28 +63,6 @@ class View:
|
||||
else:
|
||||
raise Exception("Must implement in the child class")
|
||||
|
||||
###
|
||||
### Reusable components
|
||||
###
|
||||
# TODO: Obviated by seedsigner.gui.components.TopNav
|
||||
def render_previous_button(self, highlight=False):
|
||||
# Set up the "back" arrow in the upper left
|
||||
arrow = "<"
|
||||
word_font = Fonts.get_font("RobotoCondensed-Bold", 26)
|
||||
top_padding = -3
|
||||
bottom_padding = 3
|
||||
side_padding = 3
|
||||
tw, th = word_font.getsize(arrow)
|
||||
self.previous_button_width = tw + 2 * side_padding
|
||||
if highlight:
|
||||
font_color = "black"
|
||||
background_color = self.color
|
||||
else:
|
||||
font_color = self.color
|
||||
background_color = "black"
|
||||
self.renderer.draw.rectangle((0,0, self.previous_button_width, th + top_padding + bottom_padding), fill=background_color)
|
||||
self.renderer.draw.text((side_padding, top_padding), arrow, fill=font_color, font=word_font)
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -78,7 +78,7 @@ def test_specter_xpub_qr():
|
||||
|
||||
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.SPECTERXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQRDensity.LOW, wordlist=bip39.WORDLIST)
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.SPECTERXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__LOW, wordlist=bip39.WORDLIST)
|
||||
|
||||
assert e.nextPart() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN"
|
||||
assert e.nextPart() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo"
|
||||
@@ -89,7 +89,7 @@ def test_ur_xpub_qr():
|
||||
|
||||
mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash"
|
||||
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQRDensity.MEDIUM, wordlist=bip39.WORDLIST)
|
||||
e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.URXPUBQR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__MEDIUM, wordlist=bip39.WORDLIST)
|
||||
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH"
|
||||
assert e.nextPart() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL"
|
||||
|
||||
Reference in New Issue
Block a user