Merge pull request #494 from kdmukai/qr_encoding_refactor

QR Encoder refactor + fountain encoder `restart()`
This commit is contained in:
Nick Klockenga
2024-04-23 17:51:32 -04:00
committed by GitHub
10 changed files with 319 additions and 475 deletions
+3 -2
View File
@@ -10,6 +10,7 @@ from seedsigner.gui.components import (GUIConstants,
from seedsigner.gui.keyboard import Keyboard, TextEntryDisplay
from seedsigner.gui.renderer import Renderer
from seedsigner.hardware.buttons import HardwareButtonsConstants, HardwareButtons
from seedsigner.models.encode_qr import BaseQrEncoder
from seedsigner.models.settings import SettingsConstants
from seedsigner.models.threads import BaseThread, ThreadsafeCounter
@@ -657,10 +658,10 @@ class LargeButtonScreen(BaseTopNavScreen):
@dataclass
class QRDisplayScreen(BaseScreen):
qr_encoder: 'EncodeQR' = None
qr_encoder: BaseQrEncoder = None
class QRDisplayThread(BaseThread):
def __init__(self, qr_encoder: 'EncodeQR', qr_brightness: ThreadsafeCounter, renderer: Renderer,
def __init__(self, qr_encoder: BaseQrEncoder, qr_brightness: ThreadsafeCounter, renderer: Renderer,
tips_start_time: ThreadsafeCounter):
super().__init__()
self.qr_encoder = qr_encoder
+12 -1
View File
@@ -90,6 +90,7 @@ class FountainEncoder:
self.fragment_len = FountainEncoder.find_nominal_fragment_length(self.message_len, min_fragment_len, max_fragment_len)
self.fragments = FountainEncoder.partition_message(message, self.fragment_len)
self.seq_num = first_seq_num
self.current_part: Part = None
@staticmethod
def find_nominal_fragment_length(message_len, min_fragment_len, max_fragment_len):
@@ -143,7 +144,17 @@ class FountainEncoder:
indexes = choose_fragments(self.seq_num, self.seq_len(), self.checksum)
mixed = self.mix(indexes)
data = bytes(mixed)
return Part(self.seq_num, self.seq_len(), self.message_len, self.checksum, data)
self.current_part = Part(self.seq_num, self.seq_len(), self.message_len, self.checksum, data)
return self.current_part
def restart(self):
"""
Restart from the beginning; each cycle's first n frames are full data frames
(not XOR composites).
"""
self.seq_num = 0
def mix(self, indexes):
result = [0] * self.fragment_len
+16 -2
View File
@@ -34,12 +34,26 @@ class UREncoder:
def is_single_part(self):
return self.fountain_encoder.is_single_part()
def next_part(self):
part = self.fountain_encoder.next_part()
def next_part(self) -> str:
if self.is_single_part():
return UREncoder.encode(self.ur)
else:
part = self.fountain_encoder.next_part()
return UREncoder.encode_part(self.ur.type, part)
def current_part(self) -> str:
if self.is_single_part():
return UREncoder.encode(self.ur)
else:
part = self.fountain_encoder.current_part
if not part:
part = self.fountain_encoder.next_part()
return UREncoder.encode_part(self.ur.type, part)
def restart(self):
self.fountain_encoder.restart()
@staticmethod
def encode_part(type, part):
+222 -361
View File
@@ -2,7 +2,7 @@ import math
from embit import bip32
from embit.networks import NETWORKS
from binascii import b2a_base64, hexlify
from binascii import hexlify
from dataclasses import dataclass
from typing import List
from embit import bip32
@@ -11,7 +11,6 @@ from embit.psbt import PSBT
from seedsigner.helpers.ur2.ur_encoder import UREncoder
from seedsigner.helpers.ur2.ur import UR
from seedsigner.helpers.qr import QR
from seedsigner.models.qr_type import QRType
from seedsigner.models.seed import Seed
from seedsigner.models.settings import SettingsConstants
@@ -20,149 +19,62 @@ from urtypes.crypto import Account, HDKey, Output, Keypath, PathComponent, SCRIP
@dataclass
class EncodeQR:
"""
Encode psbt for displaying as qr image
"""
# TODO: Refactor so that this is a base class with implementation classes for each
# QR type. No reason exterior code can't directly instantiate the encoder it needs.
# Dataclass input vars on __init__()
psbt: PSBT = None
seed_phrase: List[str] = None
passphrase: str = None
derivation: str = None
network: str = SettingsConstants.MAINNET
qr_type: str = None
class BaseQrEncoder:
qr_density: str = SettingsConstants.DENSITY__MEDIUM
wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH
bitcoin_address: str = None
signed_message: str = None
def __post_init__(self):
self.qr = QR()
if not self.qr_type:
raise Exception('qr_type is required')
if self.qr_density == None:
self.qr_density = SettingsConstants.DENSITY__MEDIUM
self.encoder: BaseQrEncoder = None
# PSBT formats
if self.qr_type == QRType.PSBT__SPECTER:
self.encoder = SpecterPsbtQrEncoder(psbt=self.psbt, qr_density=self.qr_density)
elif self.qr_type == QRType.PSBT__UR2:
self.encoder = UrPsbtQrEncoder(psbt=self.psbt, qr_density=self.qr_density)
# XPUB formats
elif self.qr_type == QRType.XPUB:
self.encoder = XpubQrEncoder(
seed_phrase=self.seed_phrase,
passphrase=self.passphrase,
derivation=self.derivation,
network=self.network,
wordlist_language_code=self.wordlist_language_code
)
elif self.qr_type == QRType.XPUB__UR:
self.encoder = UrXpubQrEncoder(
qr_density=self.qr_density,
seed_phrase=self.seed_phrase,
passphrase=self.passphrase,
derivation=self.derivation,
network=self.network,
wordlist_language_code=self.wordlist_language_code
)
elif self.qr_type == QRType.XPUB__SPECTER:
self.encoder = SpecterXPubQrEncoder(
qr_density=self.qr_density,
seed_phrase=self.seed_phrase,
passphrase=self.passphrase,
derivation=self.derivation,
network=self.network,
wordlist_language_code=self.wordlist_language_code
)
# SeedQR formats
elif self.qr_type == QRType.SEED__SEEDQR:
self.encoder = SeedQrEncoder(seed_phrase=self.seed_phrase,
wordlist_language_code=self.wordlist_language_code)
elif self.qr_type == QRType.SEED__COMPACTSEEDQR:
self.encoder = CompactSeedQrEncoder(seed_phrase=self.seed_phrase,
wordlist_language_code=self.wordlist_language_code)
# Misc formats
elif self.qr_type == QRType.BITCOIN_ADDRESS:
self.encoder = BitcoinAddressEncoder(address=self.bitcoin_address)
elif self.qr_type == QRType.SIGN_MESSAGE:
self.encoder = SignedMessageEncoder(signed_message=self.signed_message)
else:
raise Exception('QR Type not supported')
def total_parts(self) -> int:
return self.encoder.seq_len()
def next_part(self):
return self.encoder.next_part()
def part_to_image(self, part, width=240, height=240, border=3):
return self.qr.qrimage_io(part, width, height, border)
def next_part_image(self, width=240, height=240, border=3, background_color="bdbdbd"):
part = self.next_part()
if self.qr_type == QRType.SEED__SEEDQR:
return self.qr.qrimage(part, width, height, border)
else:
return self.qr.qrimage_io(part, width, height, border, background_color=background_color)
# TODO: Make these properties?
def is_complete(self):
return self.encoder.is_complete
def get_qr_density(self):
return self.qr_density
def get_qr_type(self):
return self.qr_type
class BaseQrEncoder:
def seq_len(self):
raise Exception("Not implemented in child class")
def next_part(self) -> str:
raise Exception("Not implemented in child class")
@property
def is_complete(self):
raise Exception("Not implemented in child class")
@property
def qr_max_fragment_size(self):
raise Exception("Not implemented in child class")
def seq_len(self):
raise Exception("Not implemented in child class")
def next_part(self) -> str:
raise Exception("Not implemented in child class")
def cur_part(self) -> str:
raise Exception("Not implemented in child class")
def restart(self):
# only used by animated QR encoders
pass
def _create_parts(self):
raise Exception("Not implemented in child class")
def part_to_image(self, part, width, height, border: int = 3, background_color: str = "ffffff"):
return self.qr.qrimage_io(part, width, height, border, background_color=background_color)
def next_part_image(self, width=240, height=240, border=3, background_color="bdbdbd"):
part = self.next_part()
return self.part_to_image(part, width, height, border, background_color=background_color)
"""**************************************************************************************
STATIC QR encoders
**************************************************************************************"""
@dataclass
class BaseStaticQrEncoder(BaseQrEncoder):
def seq_len(self):
return 1
def cur_part(self) -> str:
""" static QRs only have a single part, which `next_part` always returns """
return self.next_part()
@property
@@ -171,86 +83,130 @@ class BaseStaticQrEncoder(BaseQrEncoder):
class BasePsbtQrEncoder(BaseQrEncoder):
def __init__(self, psbt: PSBT):
self.psbt = psbt
@dataclass
class SeedQrEncoder(BaseStaticQrEncoder):
mnemonic: List[str] = None
wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH
def __post_init__(self):
self.wordlist = Seed.get_wordlist(self.wordlist_language_code)
super().__post_init__()
self.data = ""
# Output as Numeric data format
for word in self.mnemonic:
index = self.wordlist.index(word)
self.data += str("%04d" % index)
def next_part(self):
return self.data
class UrPsbtQrEncoder(BasePsbtQrEncoder):
def __init__(self, psbt, qr_density):
super().__init__(psbt)
self.qr_max_fragment_size = 20
@dataclass
class CompactSeedQrEncoder(SeedQrEncoder):
def next_part(self):
# Output as binary data format
binary_str = ""
for word in self.mnemonic:
index = self.wordlist.index(word)
# Convert index to binary, strip out '0b' prefix; zero-pad to 11 bits
binary_str += bin(index).split('b')[1].zfill(11)
# We can exclude the checksum bits at the end
if len(self.mnemonic) == 24:
# 8 checksum bits in a 24-word seed
binary_str = binary_str[:-8]
elif len(self.mnemonic) == 12:
# 4 checksum bits in a 12-word seed
binary_str = binary_str[:-4]
# Now convert to bytes, 8 bits at a time
as_bytes = bytearray()
for i in range(0, math.ceil(len(binary_str) / 8)):
# int conversion reads byte data as a string prefixed with '0b'
as_bytes.append(int('0b' + binary_str[i*8:(i+1)*8], 2))
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
if qr_density == SettingsConstants.DENSITY__LOW:
self.qr_max_fragment_size = 10
elif qr_density == SettingsConstants.DENSITY__MEDIUM:
self.qr_max_fragment_size = 30
elif qr_density == SettingsConstants.DENSITY__HIGH:
self.qr_max_fragment_size = 120
self.ur2_encode = UREncoder(ur=qr_ur_bytes, max_fragment_len=self.qr_max_fragment_size)
# Must return data as `bytes` for `qrcode` to properly recognize it as byte data
return bytes(as_bytes)
def seq_len(self):
return self.ur2_encode.fountain_encoder.seq_len()
@dataclass
class GenericStaticQrEncoder(BaseStaticQrEncoder):
data: str = None
def next_part(self):
return self.data
def next_part(self) -> str:
return self.ur2_encode.next_part().upper()
@dataclass
class BaseXpubQrEncoder(BaseQrEncoder):
"""
Base Xpub QrEncoder for static and animated formats
"""
mnemonic: list = None
passphrase: str = None
derivation: str = None
network: str = SettingsConstants.MAINNET
wordlist_language_code: str = SettingsConstants.WORDLIST_LANGUAGE__ENGLISH
def prep_xpub(self):
self.wordlist = Seed.get_wordlist(self.wordlist_language_code)
if self.wordlist == None:
raise Exception('Wordlist Required')
version = bip32.detect_version(self.derivation, default="xpub", network=NETWORKS[SettingsConstants.map_network_to_embit(self.network)])
self.seed = Seed(mnemonic=self.mnemonic,
passphrase=self.passphrase,
wordlist_language_code=self.wordlist_language_code)
self.root = bip32.HDKey.from_seed(self.seed.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(self.network)]["xprv"])
self.fingerprint = self.root.child(0).fingerprint
self.xprv = self.root.derive(self.derivation)
self.xpub = self.xprv.to_public()
self.xpub_base58 = self.xpub.to_string(version=version)
self.xpubstring = "[{}{}]{}".format(
hexlify(self.fingerprint).decode('utf-8'),
self.derivation[1:],
self.xpub_base58
)
class StaticXpubQrEncoder(BaseXpubQrEncoder, BaseStaticQrEncoder):
def __post_init__(self):
super().__post_init__()
self.prep_xpub()
def next_part(self):
self.prep_xpub()
return self.xpubstring
"""**************************************************************************************
Simple animated QR encoders
**************************************************************************************"""
@dataclass
class BaseSimpleAnimatedQREncoder(BaseQrEncoder):
def __post_init__(self):
super().__post_init__()
self.parts = []
self.part_num_sent = 0
self.sent_complete = False
self._create_parts()
@property
def is_complete(self):
return self.ur2_encode.is_complete()
class SpecterPsbtQrEncoder(BasePsbtQrEncoder):
def __init__(self, psbt, qr_density):
super().__init__(psbt)
self.qr_max_fragement_size = 65
self.parts = []
self.part_num_sent = 0
self.sent_complete = False
if qr_density == SettingsConstants.DENSITY__LOW:
self.qr_max_fragement_size = 40
elif qr_density == SettingsConstants.DENSITY__MEDIUM:
self.qr_max_fragement_size = 65
elif qr_density == SettingsConstants.DENSITY__HIGH:
self.qr_max_fragement_size = 90
self._create_parts()
def _create_parts(self):
base64_psbt = b2a_base64(self.psbt.serialize())
if base64_psbt[-1:] == b"\n":
base64_psbt = base64_psbt[:-1]
base64_psbt = base64_psbt.decode('utf-8')
start = 0
stop = self.qr_max_fragement_size
qr_cnt = ((len(base64_psbt)-1) // self.qr_max_fragement_size) + 1
if qr_cnt == 1:
self.parts.append(base64_psbt[start:stop])
cnt = 0
while cnt < qr_cnt and qr_cnt != 1:
part = "p" + str(cnt+1) + "of" + str(qr_cnt) + " " + base64_psbt[start:stop]
self.parts.append(part)
start = start + self.qr_max_fragement_size
stop = stop + self.qr_max_fragement_size
if stop > len(base64_psbt):
stop = len(base64_psbt)
cnt += 1
return self.sent_complete
def seq_len(self):
@@ -274,158 +230,34 @@ class SpecterPsbtQrEncoder(BasePsbtQrEncoder):
return part
@property
def is_complete(self):
return self.sent_complete
def cur_part(self) -> str:
if self.part_num_sent == 0:
# Rewind all the way back to the end
self.part_num_sent = len(self.parts) - 1
else:
self.part_num_sent -= 1
return self.next_part()
class SeedQrEncoder(BaseStaticQrEncoder):
def __init__(self, seed_phrase: List[str], wordlist_language_code: str):
super().__init__()
self.seed_phrase = seed_phrase
self.wordlist = Seed.get_wordlist(wordlist_language_code)
if self.wordlist == None:
raise Exception('Wordlist Required')
def next_part(self):
data = ""
# Output as Numeric data format
for word in self.seed_phrase:
index = self.wordlist.index(word)
data += str("%04d" % index)
return data
class CompactSeedQrEncoder(SeedQrEncoder):
def next_part(self):
# Output as binary data format
binary_str = ""
for word in self.seed_phrase:
index = self.wordlist.index(word)
# Convert index to binary, strip out '0b' prefix; zero-pad to 11 bits
binary_str += bin(index).split('b')[1].zfill(11)
# We can exclude the checksum bits at the end
if len(self.seed_phrase) == 24:
# 8 checksum bits in a 24-word seed
binary_str = binary_str[:-8]
elif len(self.seed_phrase) == 12:
# 4 checksum bits in a 12-word seed
binary_str = binary_str[:-4]
# Now convert to bytes, 8 bits at a time
as_bytes = bytearray()
for i in range(0, math.ceil(len(binary_str) / 8)):
# int conversion reads byte data as a string prefixed with '0b'
as_bytes.append(int('0b' + binary_str[i*8:(i+1)*8], 2))
# Must return data as `bytes` for `qrcode` to properly recognize it as byte data
return bytes(as_bytes)
class BitcoinAddressEncoder(BaseStaticQrEncoder):
def __init__(self, address: str):
super().__init__()
self.address = address
def next_part(self):
return self.address
class SignedMessageEncoder(BaseStaticQrEncoder):
"""
Assumes that a signed message will fit in a single-frame QR
"""
def __init__(self, signed_message: str):
super().__init__()
self.signed_message = signed_message
def next_part(self):
return self.signed_message
class XpubQrEncoder(BaseQrEncoder):
def __init__(self, seed_phrase, passphrase, derivation, network, wordlist_language_code):
self.seed_phrase = seed_phrase
self.passphrase = passphrase
self.derivation = derivation
self.network = network
self.wordlist = Seed.get_wordlist(wordlist_language_code)
self.parts = []
def restart(self) -> str:
self.part_num_sent = 0
self.sent_complete = False
if self.wordlist == None:
raise Exception('Wordlist Required')
version = bip32.detect_version(self.derivation, default="xpub", network=NETWORKS[SettingsConstants.map_network_to_embit(self.network)])
self.seed = Seed(mnemonic=self.seed_phrase,
passphrase=self.passphrase,
wordlist_language_code=wordlist_language_code)
self.root = bip32.HDKey.from_seed(self.seed.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(self.network)]["xprv"])
self.fingerprint = self.root.child(0).fingerprint
self.xprv = self.root.derive(self.derivation)
self.xpub = self.xprv.to_public()
self.xpub_base58 = self.xpub.to_string(version=version)
self.xpubstring = "[{}{}]{}".format(
hexlify(self.fingerprint).decode('utf-8'),
self.derivation[1:],
self.xpub_base58
)
self._create_parts()
def _create_parts(self):
self.parts = []
self.parts.append(self.xpubstring)
def next_part(self) -> str:
if len(self.parts) > 0:
self.sent_complete = True
return self.parts[0]
def seq_len(self):
return len(self.parts)
@dataclass
class SpecterXPubQrEncoder(BaseSimpleAnimatedQREncoder, BaseXpubQrEncoder):
@property
def is_complete(self):
return self.sent_complete
class SpecterXPubQrEncoder(XpubQrEncoder):
def __init__(self, qr_density, **kwargs):
# Must set up qr_max_fragment_size before calling super().__init__()
self.qr_max_fragment_size = 65
if qr_density == SettingsConstants.DENSITY__LOW:
self.qr_max_fragment_size = 40
elif qr_density == SettingsConstants.DENSITY__MEDIUM:
self.qr_max_fragment_size = 65
elif qr_density == SettingsConstants.DENSITY__HIGH:
self.qr_max_fragment_size = 90
super().__init__(**kwargs)
def qr_max_fragment_size(self):
density_mapping = {
SettingsConstants.DENSITY__LOW: 40,
SettingsConstants.DENSITY__MEDIUM: 65,
SettingsConstants.DENSITY__HIGH: 90,
}
return density_mapping.get(self.qr_density, 65)
def _create_parts(self):
self.parts = []
self.prep_xpub()
start = 0
stop = self.qr_max_fragment_size
qr_cnt = ((len(self.xpubstring)-1) // self.qr_max_fragment_size) + 1
@@ -445,34 +277,60 @@ class SpecterXPubQrEncoder(XpubQrEncoder):
cnt += 1
"""**************************************************************************************
Fountain encoded animated QR encoders
**************************************************************************************"""
@dataclass
class BaseFountainQrEncoder(BaseQrEncoder):
def __post_init__(self):
super().__post_init__()
self.ur2_encode: UREncoder = None
@property
def is_complete(self):
return self.ur2_encode.is_complete()
@property
def qr_max_fragment_size(self):
density_mapping = {
SettingsConstants.DENSITY__LOW: 10,
SettingsConstants.DENSITY__MEDIUM: 30,
SettingsConstants.DENSITY__HIGH: 120,
}
return density_mapping.get(self.qr_density, 30)
def _create_parts(self):
""" parts are dynamically generated by the fountain encoder """
pass
def seq_len(self):
return self.ur2_encode.fountain_encoder.seq_len()
def next_part(self) -> str:
# if part num sent is gt number of parts, start at 0
if self.part_num_sent > (len(self.parts) - 1):
self.part_num_sent = 0
return self.ur2_encode.next_part().upper()
part = self.parts[self.part_num_sent]
# when parts sent eq num of parts in list
if self.part_num_sent == (len(self.parts) - 1):
self.sent_complete = True
def cur_part(self) -> str:
return self.ur2_encode.current_part().upper()
# increment to next part
self.part_num_sent += 1
return part
def restart(self):
self.ur2_encode.fountain_encoder.restart()
class UrXpubQrEncoder(XpubQrEncoder):
def __init__(self, qr_density, **kwargs):
super().__init__(**kwargs)
if qr_density == SettingsConstants.DENSITY__LOW:
self.qr_max_fragment_size = 10
elif qr_density == SettingsConstants.DENSITY__MEDIUM:
self.qr_max_fragment_size = 30
elif qr_density == SettingsConstants.DENSITY__HIGH:
self.qr_max_fragment_size = 120
@dataclass
class UrXpubQrEncoder(BaseFountainQrEncoder, BaseXpubQrEncoder):
def __post_init__(self):
super().__post_init__()
self.prep_xpub()
def derivation_to_keypath(path: str) -> list:
arr = path.split("/")
@@ -530,9 +388,12 @@ class UrXpubQrEncoder(XpubQrEncoder):
self.ur2_encode = UREncoder(ur=qr_ur_bytes, max_fragment_len=self.qr_max_fragment_size)
def seq_len(self):
return self.ur2_encode.fountain_encoder.seq_len()
@dataclass
class UrPsbtQrEncoder(BaseFountainQrEncoder):
psbt: PSBT = None
def next_part(self) -> str:
return self.ur2_encode.next_part().upper()
def __post_init__(self):
super().__post_init__()
qr_ur_bytes = UR("crypto-psbt", UR_PSBT(self.psbt.serialize()).to_cbor())
self.ur2_encode = UREncoder(ur=qr_ur_bytes, max_fragment_len=self.qr_max_fragment_size)
+2 -5
View File
@@ -4,9 +4,8 @@ from embit.networks import NETWORKS
from seedsigner.controller import Controller
from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerIconConstants
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.encode_qr import UrPsbtQrEncoder
from seedsigner.models.psbt_parser import PSBTParser
from seedsigner.models.qr_type import QRType
from seedsigner.models.settings import SettingsConstants
from seedsigner.gui.screens.psbt_screens import PSBTOverviewScreen, PSBTMathScreen, PSBTAddressDetailsScreen, PSBTChangeDetailsScreen, PSBTFinalizeScreen
from seedsigner.gui.screens.screen import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, QRDisplayScreen)
@@ -498,11 +497,9 @@ class PSBTFinalizeView(View):
class PSBTSignedQRDisplayView(View):
def run(self):
qr_encoder = EncodeQR(
qr_encoder = UrPsbtQrEncoder(
psbt=self.controller.psbt,
qr_type=QRType.PSBT__UR2, # All coordinators (as of 2022-08) use this format
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY),
wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE),
)
self.run_screen(QRDisplayScreen, qr_encoder=qr_encoder)
+30 -36
View File
@@ -16,7 +16,7 @@ from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen,
from seedsigner.gui.screens.screen import LargeIconStatusScreen, QRDisplayScreen
from seedsigner.helpers import embit_utils
from seedsigner.models.decode_qr import DecodeQR
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.encode_qr import CompactSeedQrEncoder, GenericStaticQrEncoder, SeedQrEncoder, SpecterXPubQrEncoder, StaticXpubQrEncoder, UrXpubQrEncoder
from seedsigner.models.psbt_parser import PSBTParser
from seedsigner.models.qr_type import QRType
from seedsigner.models.seed import InvalidSeedException, Seed
@@ -863,35 +863,25 @@ class SeedExportXpubQRDisplayView(View):
super().__init__()
self.seed = self.controller.get_seed(seed_num)
qr_density = self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY)
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
qr_type = QRType.XPUB__SPECTER
elif coordinator == SettingsConstants.COORDINATOR__BLUE_WALLET:
qr_type = QRType.XPUB
elif coordinator == SettingsConstants.COORDINATOR__KEEPER:
qr_type = QRType.XPUB
elif coordinator == SettingsConstants.COORDINATOR__NUNCHUK:
qr_type = QRType.XPUB__UR
# As of 2022-03-02 Nunchuk doesn't seem to support animated QRs for Xpub import
qr_density = SettingsConstants.DENSITY__HIGH
else:
qr_type = QRType.XPUB__UR
self.qr_encoder = EncodeQR(
seed_phrase=self.seed.mnemonic_list,
encoder_args = dict(
mnemonic=self.seed.mnemonic_list,
passphrase=self.seed.passphrase,
derivation=derivation_path,
network=self.settings.get_value(SettingsConstants.SETTING__NETWORK),
qr_type=qr_type,
qr_density=qr_density,
wordlist_language_code=self.seed.wordlist_language_code
qr_density=self.settings.get_value(SettingsConstants.SETTING__QR_DENSITY)
)
if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP:
self.qr_encoder = SpecterXPubQrEncoder(**encoder_args)
elif coordinator in [SettingsConstants.COORDINATOR__BLUE_WALLET,
SettingsConstants.COORDINATOR__KEEPER,
SettingsConstants.COORDINATOR__NUNCHUK]:
self.qr_encoder = StaticXpubQrEncoder(**encoder_args)
else:
self.qr_encoder = UrXpubQrEncoder(**encoder_args)
def run(self):
self.run_screen(
@@ -1391,11 +1381,13 @@ class SeedTranscribeSeedQRWholeQRView(View):
def run(self):
e = EncodeQR(
seed_phrase=self.seed.mnemonic_list,
qr_type=self.seedqr_format,
wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)
)
encoder_args = dict(mnemonic=self.seed.mnemonic_list,
wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE))
if self.seedqr_format == QRType.SEED__SEEDQR:
e = SeedQrEncoder(**encoder_args)
elif self.seedqr_format == QRType.SEED__COMPACTSEEDQR:
e = CompactSeedQrEncoder(**encoder_args)
data = e.next_part()
ret = seed_screens.SeedTranscribeSeedQRWholeQRScreen(
@@ -1426,11 +1418,13 @@ class SeedTranscribeSeedQRZoomedInView(View):
def run(self):
e = EncodeQR(
seed_phrase=self.seed.mnemonic_list,
qr_type=self.seedqr_format,
wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)
)
encoder_args = dict(mnemonic=self.seed.mnemonic_list,
wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE))
if self.seedqr_format == QRType.SEED__SEEDQR:
e = SeedQrEncoder(**encoder_args)
elif self.seedqr_format == QRType.SEED__COMPACTSEEDQR:
e = CompactSeedQrEncoder(**encoder_args)
data = e.next_part()
if len(self.seed.mnemonic_list) == 24:
@@ -2071,7 +2065,7 @@ class SeedSignMessageSignedMessageQRView(View):
def run(self):
qr_encoder = EncodeQR(qr_type=QRType.SIGN_MESSAGE, signed_message=self.signed_message)
qr_encoder = GenericStaticQrEncoder(data=self.signed_message)
self.run_screen(
QRDisplayScreen,
+2 -3
View File
@@ -14,8 +14,7 @@ from seedsigner.gui.screens.tools_screens import (ToolsCalcFinalWordDoneScreen,
ToolsCalcFinalWordScreen, ToolsCoinFlipEntryScreen, ToolsDiceEntropyEntryScreen, ToolsImageEntropyFinalImageScreen,
ToolsImageEntropyLivePreviewScreen, ToolsAddressExplorerAddressTypeScreen)
from seedsigner.helpers import embit_utils, mnemonic_generation
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.qr_type import QRType
from seedsigner.models.encode_qr import GenericStaticQrEncoder
from seedsigner.models.seed import Seed
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.views.seed_views import SeedDiscardView, SeedFinalizeView, SeedMnemonicEntryView, SeedOptionsView, SeedWordsWarningView, SeedExportXpubScriptTypeView
@@ -695,7 +694,7 @@ class ToolsAddressExplorerAddressView(View):
def run(self):
from seedsigner.gui.screens.screen import QRDisplayScreen
qr_encoder = EncodeQR(qr_type=QRType.BITCOIN_ADDRESS, bitcoin_address=self.address)
qr_encoder = GenericStaticQrEncoder(data=self.address)
self.run_screen(
QRDisplayScreen,
qr_encoder=qr_encoder,
File diff suppressed because one or more lines are too long
-1
View File
@@ -1,6 +1,5 @@
from base import FlowTest, FlowStep
from seedsigner.controller import Controller
from seedsigner.views.view import MainMenuView
from seedsigner.views import scan_views, seed_views, psbt_views
+9 -11
View File
@@ -1,23 +1,22 @@
import os
import pyzbar
from embit import bip39
from seedsigner.helpers.qr import QR
from seedsigner.helpers.ur2.bytewords import decode
from seedsigner.models.decode_qr import DecodeQR, DecodeQRStatus
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.encode_qr import SeedQrEncoder, CompactSeedQrEncoder
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)
seed_phrase = bip39.mnemonic_from_bytes(entropy).split()
print(seed_phrase)
assert len(seed_phrase) == mnemonic_length
mnemonic = bip39.mnemonic_from_bytes(entropy).split()
assert len(mnemonic) == mnemonic_length
if qr_type == QRType.SEED__SEEDQR:
e = SeedQrEncoder(mnemonic=mnemonic)
elif qr_type == QRType.SEED__COMPACTSEEDQR:
e = CompactSeedQrEncoder(mnemonic=mnemonic)
e = EncodeQR(seed_phrase=seed_phrase, qr_type=qr_type)
data = e.next_part()
print(data)
@@ -34,8 +33,7 @@ def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type):
assert status == DecodeQRStatus.COMPLETE
decoded_seed_phrase = decoder.get_seed_phrase()
print(decoded_seed_phrase)
assert seed_phrase == decoded_seed_phrase
assert mnemonic == decoded_seed_phrase