diff --git a/src/seedsigner/gui/screens/scan_screens.py b/src/seedsigner/gui/screens/scan_screens.py index 767f6b23..823cf003 100644 --- a/src/seedsigner/gui/screens/scan_screens.py +++ b/src/seedsigner/gui/screens/scan_screens.py @@ -59,8 +59,8 @@ class ScanScreen(BaseTopNavScreen): frame = self.camera.read_video_stream(as_image=True) if frame is not None: scan_text = self.instructions_text - if self.decoder.getPercentComplete() > 0 and self.decoder.isPSBT(): - scan_text = str(self.decoder.getPercentComplete()) + "% Complete" + if self.decoder.get_percent_complete() > 0 and self.decoder.is_psbt: + scan_text = str(self.decoder.get_percent_complete()) + "% Complete" # TODO: Render TopNav & instructions_background w/transparency # img = Image.new(mode='RGBA', size=(self.canvas_width, self.canvas_height)) @@ -80,7 +80,7 @@ class ScanScreen(BaseTopNavScreen): while True: frame = self.camera.read_video_stream() if frame is not None: - status = self.decoder.addImage(frame) + status = self.decoder.add_image(frame) if status in (DecodeQRStatus.COMPLETE, DecodeQRStatus.INVALID): self.camera.stop_video_stream_mode() diff --git a/src/seedsigner/models/decode_qr.py b/src/seedsigner/models/decode_qr.py index f9ae4727..2093e78a 100644 --- a/src/seedsigner/models/decode_qr.py +++ b/src/seedsigner/models/decode_qr.py @@ -21,49 +21,80 @@ from .settings import SettingsConstants logger = logging.getLogger(__name__) -### -### DecodeQR Class -### Purpose: used to process images or string data from animated qr codes with psbt data to create binary/base64 psbt -### + +class DecodeQRStatus(IntEnum): + """ + Used in DecodeQR to communicate status of adding qr frame/segment + """ + PART_COMPLETE = 1 + PART_EXISTING = 2 + COMPLETE = 3 + FALSE = 4 + INVALID = 5 + + + class DecodeQR: - def __init__(self, qr_type=None, wordlist_language_code=SeedConstants.WORDLIST_LANGUAGE__ENGLISH): - self.complete = False - self.qr_type = qr_type + """ + Used to process images or string data from animated qr codes. + """ + def __init__(self, wordlist_language_code: str = SeedConstants.WORDLIST_LANGUAGE__ENGLISH): self.wordlist_language_code = wordlist_language_code - - # TODO: We don't need to instantiate these ahead of time - self.ur_decoder = URDecoder() # UR2 decoder - self.specter_qr = SpecterDecodePSBTQR() # Specter Desktop PSBT QR base64 decoder - self.legacy_ur = LegacyURDecodeQR() # UR Legacy decoder - self.base64_qr = Base64DecodeQR() # Single Segments Base64 - self.base43_qr = Base43DecodeQR() # Single Segment Base43 - self.address_qr = BitcoinAddressQR() # Single Segment bitcoin address - self.specter_wallet_qr = SpecterDecodeWalletQR() # Specter Desktop Wallet Export decoder - self.settings_qr = SettingsQR() # Settings config in .ini format - self.seedqr = SeedQR(wordlist_language_code=wordlist_language_code) + self.complete = False + self.qr_type = None + self.decoder = None - def addImage(self, image): + def add_image(self, image): data = DecodeQR.extract_qr_data(image, is_binary=True) if data == None: return DecodeQRStatus.FALSE return self.add_data(data) + def add_data(self, data): if data == None: return DecodeQRStatus.FALSE - qr_type = DecodeQR.SegmentType(data, wordlist_language_code=self.wordlist_language_code) + qr_type = DecodeQR.detect_segment_type(data, wordlist_language_code=self.wordlist_language_code) if self.qr_type == None: self.qr_type = qr_type + + if self.qr_type == QRType.PSBT__UR2: + self.decoder = URDecoder() # UR2 decoder + + elif self.qr_type == QRType.PSBT__SPECTER: + self.decoder = SpecterPsbtQrDecoder() # Specter Desktop PSBT QR base64 decoder + + elif self.qr_type == QRType.PSBT__LEGACY_UR: + self.decoder = LegacyUrPsbtQrDecoder() # UR Legacy decoder + + elif self.qr_type == QRType.PSBT__BASE64: + self.decoder = Base64PsbtQrDecoder() # Single Segments Base64 + + elif self.qr_type == QRType.PSBT__BASE43: + self.decoder = Base43PsbtQrDecoder() # Single Segment Base43 + + elif self.qr_type in [QRType.SEED__SEEDQR, QRType.SEED__COMPACTSEEDQR, QRType.SEED__MNEMONIC, QRType.SEED__FOUR_LETTER_MNEMONIC, QRType.SEED__UR2]: + self.decoder = SeedQrDecoder(wordlist_language_code=self.wordlist_language_code) + + elif self.qr_type == QRType.SETTINGS: + self.decoder = SettingsQrDecoder() # Settings config + + elif self.qr_type == QRType.BITCOIN_ADDRESS: + self.decoder = BitcoinAddressQrDecoder() # Single Segment bitcoin address + + elif self.qr_type == QRType.WALLET__SPECTER: + self.decoder = SpecterWalletQrDecoder() # Specter Desktop Wallet Export decoder + elif self.qr_type != qr_type: - raise Exception('QR Fragement Unexpected Type Change') + raise Exception('QR Fragment Unexpected Type Change') # Process the binary formats first - if self.qr_type == QRType.COMPACTSEEDQR: - rt = self.seedqr.add(data, QRType.COMPACTSEEDQR) + if self.qr_type == QRType.SEED__COMPACTSEEDQR: + rt = self.decoder.add(data, QRType.SEED__COMPACTSEEDQR) if rt == DecodeQRStatus.COMPLETE: self.complete = True return rt @@ -78,80 +109,24 @@ class DecodeQR: # it's already str data qr_str = data - if self.qr_type == QRType.PSBTUR2: - self.ur_decoder.receive_part(qr_str) - if self.ur_decoder.is_complete(): + if self.qr_type == QRType.PSBT__UR2: + self.decoder.receive_part(qr_str) + if self.decoder.is_complete(): self.complete = True return DecodeQRStatus.COMPLETE return DecodeQRStatus.PART_COMPLETE # segment added to ur2 decoder - elif self.qr_type == QRType.PSBTSPECTER: - rt = self.specter_qr.add(qr_str) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.PSBTURLEGACY: - rt = self.legacy_ur.add(qr_str) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.PSBTBASE64: - rt = self.base64_qr.add(qr_str) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.PSBTBASE43: - rt = self.base43_qr.add(qr_str) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.SEEDQR: - rt = self.seedqr.add(qr_str, QRType.SEEDQR) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.SETTINGS: - rt = self.settings_qr.add(qr_str, self.qr_type) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.SEEDMNEMONIC: - rt = self.seedqr.add(qr_str, QRType.SEEDMNEMONIC) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.SEED4LETTERMNEMONIC: - rt = self.seedqr.add(qr_str, QRType.SEED4LETTERMNEMONIC) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.BITCOINADDRESSQR: - rt = self.address_qr.add(qr_str, QRType.BITCOINADDRESSQR) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - - elif self.qr_type == QRType.SPECTERWALLETQR: - rt = self.specter_wallet_qr.add(qr_str) - if rt == DecodeQRStatus.COMPLETE: - self.complete = True - return rt - else: - return DecodeQRStatus.INVALID + # All other formats use the same method signature + rt = self.decoder.add(qr_str, self.qr_type) + if rt == DecodeQRStatus.COMPLETE: + self.complete = True + return rt - def getPSBT(self): + def get_psbt(self): if self.complete: - data = self.getDataPSBT() + data = self.get_data_psbt() if data != None: try: return psbt.PSBT.parse(data) @@ -160,26 +135,22 @@ class DecodeQR: return None - def getDataPSBT(self): + def get_data_psbt(self): if self.complete: - if self.qr_type == QRType.PSBTUR2: - cbor = self.ur_decoder.result_message().cbor + if self.qr_type == QRType.PSBT__UR2: + cbor = self.decoder.result_message().cbor return UR_PSBT.from_cbor(cbor).data - # return cbor_decode(cbor) - elif self.qr_type == QRType.PSBTSPECTER: - return self.specter_qr.getData() - elif self.qr_type == QRType.PSBTURLEGACY: - return self.legacy_ur.getData() - elif self.qr_type == QRType.PSBTBASE64: - return self.base64_qr.getData() - elif self.qr_type == QRType.PSBTBASE43: - return self.base43_qr.getData() + + else: + # All the other psbt decoder types use the same method signature + return self.decoder.get_data() + return None - def getBase64PSBT(self): + def get_base64_psbt(self): if self.complete: - data = self.getDataPSBT() + data = self.get_data_psbt() b64_psbt = b2a_base64(data) if b64_psbt[-1:] == b"\n": @@ -189,76 +160,89 @@ class DecodeQR: return None - def getSeedPhrase(self): - return self.seedqr.getSeedPhrase() + def get_seed_phrase(self): + if self.is_seed: + return self.decoder.get_seed_phrase() def get_settings_data(self): - return self.settings_qr.settings + if self.is_settings: + return self.decoder.settings def get_settings_config_name(self): - return self.settings_qr.config_name + if self.is_settings: + return self.decoder.config_name - def getAddress(self): - return self.address_qr.getAddress() + def get_address(self): + if self.is_address: + return self.decoder.get_address() - def getAddressType(self): - return self.address_qr.getAddressType() + def get_address_type(self): + if self.is_address: + return self.decoder.get_address_type() - def getWalletDescriptor(self): - return self.specter_wallet_qr.getWalletDescriptor() + def get_wallet_descriptor(self): + if self.is_wallet_descriptor: + return self.decoder.get_wallet_descriptor() - def getPercentComplete(self) -> int: - if self.qr_type == QRType.PSBTUR2: - return int(self.ur_decoder.estimated_percent_complete() * 100) - elif self.qr_type == QRType.PSBTSPECTER: - if self.specter_qr.total_segments == None: + def get_percent_complete(self) -> int: + if not self.decoder: + return 0 + + if self.qr_type == QRType.PSBT__UR2: + return int(self.decoder.estimated_percent_complete() * 100) + + elif self.qr_type in [QRType.PSBT__SPECTER, QRType.PSBT__LEGACY_UR]: + if self.decoder.total_segments == None: return 0 - return int((self.specter_qr.collected_segments / self.specter_qr.total_segments) * 100) - elif self.qr_type == QRType.PSBTURLEGACY: - if self.legacy_ur.total_segments == None: - return 0 - return int((self.legacy_ur.collected_segments / self.legacy_ur.total_segments) * 100) - elif self.qr_type == QRType.PSBTBASE64: - if self.base64_qr.complete: - return 100 - else: - return 0 - elif self.qr_type == QRType.PSBTBASE43: - if self.base43_qr.complete: + return int((self.decoder.collected_segments / self.decoder.total_segments) * 100) + + elif self.decoder.total_segments == 1: + # The single frame QR formats are all or nothing + if self.decoder.complete: return 100 else: return 0 + else: return 0 - # TODO: Convert these to properties, python-ize naming convention - def isComplete(self) -> bool: + @property + def is_complete(self) -> bool: return self.complete - def isInvalid(self) -> bool: - if self.qr_type == QRType.INVALID: - return True - return False + @property + def is_invalid(self) -> bool: + return self.qr_type == QRType.INVALID - def isPSBT(self) -> bool: - if self.qr_type in (QRType.PSBTUR2, QRType.PSBTSPECTER, QRType.PSBTURLEGACY, QRType.PSBTBASE64, QRType.PSBTBASE43): - return True - return False + @property + def is_psbt(self) -> bool: + return self.qr_type in [ + QRType.PSBT__UR2, + QRType.PSBT__SPECTER, + QRType.PSBT__LEGACY_UR, + QRType.PSBT__BASE64, + QRType.PSBT__BASE43, + ] - def isSeed(self): - if self.qr_type in (QRType.SEEDQR, QRType.COMPACTSEEDQR, QRType.SEEDUR2, QRType.SEEDMNEMONIC, QRType.SEED4LETTERMNEMONIC): - return True - return False + @property + def is_seed(self): + return self.qr_type in [ + QRType.SEED__SEEDQR, + QRType.SEED__COMPACTSEEDQR, + QRType.SEED__UR2, + QRType.SEED__MNEMONIC, + QRType.SEED__FOUR_LETTER_MNEMONIC, + ] @property @@ -266,28 +250,26 @@ class DecodeQR: return self.qr_type in [QRType.SETTINGS, QRType.JSON] - def isAddress(self): - if self.qr_type == QRType.BITCOINADDRESSQR: - return True - return False + @property + def is_address(self): + return self.qr_type == QRType.BITCOIN_ADDRESS - def isWalletDescriptor(self): - if self.qr_type in (QRType.SPECTERWALLETQR, QRType.URWALLETQR, QRType.BLUEWALLETQR): - return True - return False + @property + def is_wallet_descriptor(self): + return self.qr_type in [QRType.WALLET__SPECTER, QRType.WALLET__UR, QRType.WALLET__BLUEWALLET] + - - def qrType(self): - return self.qr_type + @property + def is_settings(self): + return self.qr_type == QRType.SETTINGS @staticmethod - def extract_qr_data(image, is_binary=False) -> str: + def extract_qr_data(image, is_binary:bool = False) -> str: if image is None: return None - # Are there any cases where defaulting `binary` to True causes problems? barcodes = pyzbar.decode(image, symbols=[ZBarSymbol.QRCODE], binary=is_binary) # if barcodes: @@ -298,9 +280,10 @@ class DecodeQR: # Only pull and return the first barcode return barcode.data + @staticmethod - def SegmentType(s, wordlist_language_code=None): - # logger.debug("-------------- DecodeQR.SegmentType --------------") + def detect_segment_type(s, wordlist_language_code=None): + # logger.debug("-------------- DecodeQR.detect_segment_type --------------") # logger.debug(type(s)) # logger.debug(len(s)) @@ -313,30 +296,34 @@ class DecodeQR: s = s.decode('utf-8') # PSBT if re.search("^UR:CRYPTO-PSBT/", s, re.IGNORECASE): - return QRType.PSBTUR2 + return QRType.PSBT__UR2 + elif re.search(r'^p(\d+)of(\d+) ([A-Za-z0-9+\/=]+$)', s, re.IGNORECASE): #must be base64 characters only in segment - return QRType.PSBTSPECTER + return QRType.PSBT__SPECTER + elif re.search("^UR:BYTES/", s, re.IGNORECASE): - return QRType.PSBTURLEGACY - elif DecodeQR.isBase64PSBT(s): - return QRType.PSBTBASE64 + return QRType.PSBT__LEGACY_UR + + elif DecodeQR.is_base64_psbt(s): + return QRType.PSBT__BASE64 # Wallet Descriptor desc_str = s.replace("\n","").replace(" ","") if re.search(r'^p(\d+)of(\d+) ', s, re.IGNORECASE): # when not a SPECTER Base64 PSBT from above, assume it's json - return QRType.SPECTERWALLETQR + return QRType.WALLET__SPECTER + elif re.search(r'^\{\"label\".*\"descriptor\"\:.*', desc_str, re.IGNORECASE): # if json starting with label and contains descriptor, assume specter wallet json - return QRType.SPECTERWALLETQR + return QRType.WALLET__SPECTER # Seed if re.search(r'\d{48,96}', s): - return QRType.SEEDQR + return QRType.SEED__SEEDQR # Bitcoin Address - elif DecodeQR.isBitcoinAddress(s): - return QRType.BITCOINADDRESSQR + elif DecodeQR.is_bitcoin_address(s): + return QRType.BITCOIN_ADDRESS # config data if "type=settings" in s: @@ -352,12 +339,14 @@ class DecodeQR: if all(x in wordlist for x in s.strip().split(" ")): # checks if all words in list are in bip39 word list - return QRType.SEEDMNEMONIC + return QRType.SEED__MNEMONIC + elif all(x in _4LETTER_WORDLIST for x in s.strip().split(" ")): # checks if all 4 letter words are in list are in 4 letter bip39 word list - return QRType.SEED4LETTERMNEMONIC - elif DecodeQR.isBase43PSBT(s): - return QRType.PSBTBASE43 + return QRType.SEED__FOUR_LETTER_MNEMONIC + + elif DecodeQR.is_base43_psbt(s): + return QRType.PSBT__BASE43 except UnicodeDecodeError: # Probably this isn't meant to be string data; check if it's valid byte data @@ -373,7 +362,7 @@ class DecodeQR: bitstream += bin(b).lstrip('0b').zfill(8) # logger.debug(bitstream) - return QRType.COMPACTSEEDQR + return QRType.SEED__COMPACTSEEDQR except Exception as e: # Couldn't extract byte data; assume it's not a byte format pass @@ -382,7 +371,7 @@ class DecodeQR: @staticmethod - def isBase64(s): + def is_base64(s): try: return base64.b64encode(base64.b64decode(s)) == s.encode('ascii') except Exception: @@ -390,9 +379,9 @@ class DecodeQR: @staticmethod - def isBase64PSBT(s): + def is_base64_psbt(s): try: - if DecodeQR.isBase64(s): + if DecodeQR.is_base64(s): psbt.PSBT.parse(a2b_base64(s)) return True except Exception: @@ -401,16 +390,16 @@ class DecodeQR: @staticmethod - def isBase43PSBT(s): + def is_base43_psbt(s): try: - psbt.PSBT.parse(DecodeQR.base43Decode(s)) + psbt.PSBT.parse(DecodeQR.base43_decode(s)) return True except Exception: return False @staticmethod - def base43Decode(s): + def base43_decode(s): chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:' #base43 chars if isinstance(s, bytes): @@ -447,120 +436,119 @@ class DecodeQR: @staticmethod - def isBitcoinAddress(s): - + def is_bitcoin_address(s): if re.search(r'^bitcoin\:.*', s, re.IGNORECASE): return True elif re.search(r'^((bc1|tb1|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,62})$', s): + # TODO: Handle regtest bcrt? return True else: return False -### -### SpecterDecodePSBTQR Class -### Purpose: used in DecodePSBTQR to decode Specter Desktop Animated QR PSBT encoding -### -class SpecterDecodePSBTQR: +class BaseQrDecoder: def __init__(self): self.total_segments = None self.collected_segments = 0 self.complete = False + + @property + def is_complete(self) -> bool: + return self.complete + + def add(self, segment, qr_type): + raise Exception("Not implemented in child class") + + + +class BaseSingleFrameQrDecoder(BaseQrDecoder): + def __init__(self): + super().__init__() + self.total_segments = 1 + + + +class BaseAnimatedQrDecoder(BaseQrDecoder): + def __init__(self): + super().__init__() self.segments = [] + def current_segment_num(self, segment) -> int: + raise Exception("Not implemented in child class") - def add(self, segment): + def total_segment_nums(self, segment) -> int: + raise Exception("Not implemented in child class") + + def parse_segment(self, segment) -> str: + raise Exception("Not implemented in child class") + + @property + def is_valid(self) -> bool: + return True + + def add(self, segment, qr_type=None): if self.total_segments == None: - self.total_segments = SpecterDecodePSBTQR.totalSegmentNum(segment) + self.total_segments = self.total_segment_nums(segment) self.segments = [None] * self.total_segments - elif self.total_segments != SpecterDecodePSBTQR.totalSegmentNum(segment): - raise Exception('Specter Desktop segment total changed unexpectedly') + elif self.total_segments != self.total_segment_nums(segment): + raise Exception('Segment total changed unexpectedly') - if self.segments[SpecterDecodePSBTQR.currentSegmentNum(segment) - 1] == None: - self.segments[SpecterDecodePSBTQR.currentSegmentNum(segment) - 1] = SpecterDecodePSBTQR.parseSegment(segment) + if self.segments[self.current_segment_num(segment) - 1] == None: + self.segments[self.current_segment_num(segment) - 1] = self.parse_segment(segment) self.collected_segments += 1 if self.total_segments == self.collected_segments: - self.complete = True - return DecodeQRStatus.COMPLETE + if self.is_valid: + self.complete = True + return DecodeQRStatus.COMPLETE + else: + return DecodeQRStatus.INVALID return DecodeQRStatus.PART_COMPLETE # new segment added return DecodeQRStatus.PART_EXISTING # segment not added because it's already been added - def getBase64Data(self) -> str: + +class SpecterPsbtQrDecoder(BaseAnimatedQrDecoder): + """ + Used to decode Specter Desktop Animated QR PSBT encoding. + """ + def get_base64_data(self) -> str: base64 = "".join(self.segments) - if self.complete and DecodeQR.isBase64(base64): + if self.complete and DecodeQR.is_base64(base64): return base64 return None - def getData(self): - base64 = self.getBase64Data() + def get_data(self): + base64 = self.get_base64_data() if base64 != None: return a2b_base64(base64) return None - def is_complete(self) -> bool: - return self.complete + def current_segment_num(self, segment) -> int: + if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: + return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(1)) - @staticmethod - def currentSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.PSBTSPECTER: - if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: - return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(1)) - raise Exception('Unable to parse Specter Desktop segment') + def total_segment_nums(self, segment) -> int: + if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: + return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(2)) - @staticmethod - def totalSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.PSBTSPECTER: - if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: - return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(2)) - raise Exception('Unable to parse Specter Desktop segment') - - - @staticmethod - def parseSegment(segment) -> str: + def parse_segment(self, segment) -> str: return segment.split(" ")[-1].strip() -### -### LegacyURDecodeQR Class -### Purpose: used in DecodeQR to decode Legacy UR animated qr encoding -### -class LegacyURDecodeQR: - def __init__(self): - self.total_segments = None - self.collected_segments = 0 - self.complete = False - self.segments = [] - - - def add(self, segment): - if self.total_segments == None: - self.total_segments = LegacyURDecodeQR.totalSegmentNum(segment) - self.segments = [None] * self.total_segments - elif self.total_segments != LegacyURDecodeQR.totalSegmentNum(segment): - raise Exception('UR Legacy segment total changed unexpectedly') - - if self.segments[LegacyURDecodeQR.currentSegmentNum(segment) - 1] == None: - self.segments[LegacyURDecodeQR.currentSegmentNum(segment) - 1] = LegacyURDecodeQR.parseSegment(segment) - self.collected_segments += 1 - if self.total_segments == self.collected_segments: - self.complete = True - return DecodeQRStatus.COMPLETE - return DecodeQRStatus.PART_COMPLETE # new segment added - - return DecodeQRStatus.PART_EXISTING # segment not added because it's already been added - - - def getBase64Data(self) -> str: +class LegacyUrPsbtQrDecoder(BaseAnimatedQrDecoder): + """ + Decodes Legacy UR animated qr encoding + """ + def get_base64_data(self) -> str: bc32_cbor = "".join(self.segments) raw = cbor_decode(bc32decode(bc32_cbor)) base64 = b2a_base64(raw) @@ -571,7 +559,7 @@ class LegacyURDecodeQR: return None - def getData(self): + def get_data(self): if not self.complete: return None @@ -580,51 +568,32 @@ class LegacyURDecodeQR: return raw - def is_complete(self) -> bool: - return self.complete + def parse_current_segment_num(self, segment) -> int: + if re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE) != None: + return int(re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE).group(1)) + else: + raise Exception('Unexpected Legacy UR Error') - @staticmethod - def currentSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.PSBTURLEGACY: - if re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE) != None: - return int(re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE).group(1)) - else: - raise Exception('Unexpected Legacy UR Error') - raise Exception('Unable to parse Legacy UR segment') + def parse_total_segment_nums(self, segment) -> int: + if re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE) != None: + return int(re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE).group(2)) + else: + return 1 - @staticmethod - def totalSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.PSBTURLEGACY: - if re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE) != None: - return int(re.search(r'^UR:BYTES/(\d+)OF(\d+)', segment, re.IGNORECASE).group(2)) - else: - return 1 - raise Exception('Unable to parse Legacy UR segment') - - - @staticmethod - def parseSegment(segment) -> str: + def parse_segment(self, segment) -> str: return segment.split("/")[-1].strip() -### -### Base64DecodeQR Class -### Purpose: used in DecodeQR to decode single frame base64 encoded qr image -### does not support animated qr because no indicator or segments or their order -### -class Base64DecodeQR: - def __init__(self): - self.total_segments = 1 - self.collected_segments = 0 - self.complete = False - self.data = None - - - def add(self, segment): - if DecodeQR.isBase64(segment): +class Base64PsbtQrDecoder(BaseSingleFrameQrDecoder): + """ + Decodes single frame base64 encoded qr image. + Does not support animated qr because no indicator of segments or their order + """ + def add(self, segment, qr_type=QRType.PSBT__BASE64): + if DecodeQR.is_base64(segment): self.complete = True self.data = segment self.collected_segments = 1 @@ -633,12 +602,12 @@ class Base64DecodeQR: return DecodeQRStatus.INVALID - def getBase64Data(self) -> str: + def get_base64_data(self) -> str: return self.data - def getData(self): - base64 = self.getBase64Data() + def get_data(self): + base64 = self.get_base64_data() if base64 != None: return a2b_base64(base64) @@ -646,55 +615,43 @@ class Base64DecodeQR: -### -### Base43DecodeQR Class -### Purpose: used in DecodeQR to decode single frame base43 encoded qr image -### does not support animated qr because no indicator or segments or their order -### -class Base43DecodeQR: - def __init__(self): - self.total_segments = 1 - self.collected_segments = 0 - self.complete = False - self.data = None - - - def add(self, segment): - if DecodeQR.isBase43PSBT(segment): +class Base43PsbtQrDecoder(BaseSingleFrameQrDecoder): + """ + Decodes single frame base43 encoded qr image. + Does not support animated qr because no indicator of segments or their order + """ + def add(self, segment, qr_type=QRType.PSBT__BASE43): + if DecodeQR.is_base43_psbt(segment): self.complete = True - self.data = DecodeQR.base43Decode(segment) + self.data = DecodeQR.base43_decode(segment) self.collected_segments = 1 return DecodeQRStatus.COMPLETE return DecodeQRStatus.INVALID - def getData(self): + def get_data(self): return self.data -### -### SeedQR Class -### Purpose: used in DecodeQR to decode single frame representing a seed -### SeedSigner numeric representation of a seed as a qr is supported -### Mnemonic seed phrase representation of a seed as a qr is supported -### does not support animated qr because no indicator or segments or their order -### -class SeedQR: +class SeedQrDecoder(BaseSingleFrameQrDecoder): + """ + Decodes single frame representing a seed. + Supports SeedSigner SeedQR numeric (wordlist indices) representation of a seed. + Supports SeedSigner CompactSeedQR entropy byte representation of a seed. + Supports mnemonic seed phrase string data. + """ def __init__(self, wordlist_language_code): - self.total_segments = 1 - self.collected_segments = 0 - self.complete = False + super().__init__() self.seed_phrase = [] self.wordlist_language_code = wordlist_language_code self.wordlist = Seed.get_wordlist(wordlist_language_code) - def add(self, segment, qr_type=QRType.SEEDQR): - # `segment` data will either be bytes or str, depending on the qr_type - logger.debug(qr_type) - if qr_type == QRType.SEEDQR: + def add(self, segment, qr_type=QRType.SEED__SEEDQR): + # `segment` data will either be bytes or str, depending on the qr_type + if qr_type == QRType.SEED__SEEDQR: try: self.seed_phrase = [] @@ -705,7 +662,7 @@ class SeedQR: word = self.wordlist[index] self.seed_phrase.append(word) if len(self.seed_phrase) > 0: - if self.is1224Phrase() == False: + if self.is_12_or_24_word_phrase() == False: return DecodeQRStatus.INVALID self.complete = True self.collected_segments = 1 @@ -715,7 +672,7 @@ class SeedQR: except Exception as e: return DecodeQRStatus.INVALID - if qr_type == QRType.COMPACTSEEDQR: + if qr_type == QRType.SEED__COMPACTSEEDQR: try: self.seed_phrase = bip39.mnemonic_from_bytes(segment).split() self.complete = True @@ -725,7 +682,7 @@ class SeedQR: logger.exception(repr(e)) return DecodeQRStatus.INVALID - elif qr_type == QRType.SEEDMNEMONIC: + elif qr_type == QRType.SEED__MNEMONIC: try: # embit mnemonic code to validate seed = Seed(segment, passphrase="", wordlist_language_code=self.wordlist_language_code) @@ -733,7 +690,7 @@ class SeedQR: # seed is not valid, return invalid return DecodeQRStatus.INVALID self.seed_phrase = segment.strip().split(" ") - if self.is1224Phrase() == False: + if self.is_12_or_24_word_phrase() == False: return DecodeQRStatus.INVALID self.complete = True self.collected_segments = 1 @@ -741,7 +698,7 @@ class SeedQR: except Exception as e: return DecodeQRStatus.INVALID - elif qr_type == QRType.SEED4LETTERMNEMONIC: + elif qr_type == QRType.SEED__FOUR_LETTER_MNEMONIC: try: sl = segment.strip().split(" ") words = [] @@ -756,7 +713,7 @@ class SeedQR: # seed is not valid, return invalid return DecodeQRStatus.INVALID self.seed_phrase = words - if self.is1224Phrase() == False: + if self.is_12_or_24_word_phrase() == False: return DecodeQRStatus.INVALID self.complete = True self.collected_segments = 1 @@ -768,13 +725,13 @@ class SeedQR: return DecodeQRStatus.INVALID - def getSeedPhrase(self): + def get_seed_phrase(self): if self.complete: return self.seed_phrase[:] return [] - def is1224Phrase(self): + def is_12_or_24_word_phrase(self): if len(self.seed_phrase) in (12, 24): return True return False @@ -782,11 +739,9 @@ class SeedQR: # TODO: Refactor this to work with the new SettingsDefinition -class SettingsQR: +class SettingsQrDecoder(BaseSingleFrameQrDecoder): def __init__(self): - self.total_segments = 1 - self.collected_segments = 0 - self.complete = False + super().__init__() self.settings = {} self.config_name = None @@ -885,20 +840,17 @@ class SettingsQR: -### -### BitcoinAddressQR Class -### Purpose: used in DecodeQR to decode single frame representing a bitcoin address -### -class BitcoinAddressQR: +class BitcoinAddressQrDecoder(BaseSingleFrameQrDecoder): + """ + Decodes single frame representing a bitcoin address + """ def __init__(self): - self.total_segments = 1 - self.collected_segments = 0 - self.complete = False + super().__init__() self.address = None self.address_type = None - def add(self, segment, qr_type=QRType.BITCOINADDRESSQR): + def add(self, segment, qr_type=QRType.BITCOIN_ADDRESS): r = re.search(r'((bc1|tb1|[123]|[mn])[a-zA-HJ-NP-Z0-9]{25,62})', segment) if r != None: self.address = r.group(1) @@ -930,13 +882,13 @@ class BitcoinAddressQR: return DecodeQRStatus.INVALID - def getAddress(self): + def get_address(self): if self.address != None: return self.address return None - def getAddressType(self): + def get_address_type(self): if self.address != None: if self.address_type != None: return self.address_type @@ -946,40 +898,11 @@ class BitcoinAddressQR: -### -### SpecterDecodeWalletQR Class -### Purpose: used in DecodeQR to decode animated frames to get a wallet descriptor from Specter Desktop -### -class SpecterDecodeWalletQR: - def __init__(self): - self.total_segments = None - self.collected_segments = 0 - self.complete = False - self.segments = [] - - - def add(self, segment): - if self.total_segments == None: - self.total_segments = SpecterDecodeWalletQR.totalSegmentNum(segment) - self.segments = [None] * self.total_segments - elif self.total_segments != SpecterDecodeWalletQR.totalSegmentNum(segment): - raise Exception('Specter Desktop segment total changed unexpectedly') - - if self.segments[SpecterDecodeWalletQR.currentSegmentNum(segment) - 1] == None: - self.segments[SpecterDecodeWalletQR.currentSegmentNum(segment) - 1] = SpecterDecodeWalletQR.parseSegment(segment) - self.collected_segments += 1 - if self.total_segments == self.collected_segments: - if self.validateWalletDescriptor(): - self.complete = True - return DecodeQRStatus.COMPLETE - else: - return DecodeQRStatus.INVALID - return DecodeQRStatus.PART_COMPLETE # new segment added - - return DecodeQRStatus.PART_EXISTING # segment not added because it's already been added - - - def validateJson(self) -> str: +class SpecterWalletQrDecoder(BaseAnimatedQrDecoder): + """ + Decodes animated frames to get a wallet descriptor from Specter Desktop + """ + def validate_json(self) -> str: try: j = "".join(self.segments) json.loads(j) @@ -988,8 +911,9 @@ class SpecterDecodeWalletQR: return True - def validateWalletDescriptor(self): - if self.validateJson(): + @property + def is_valid(self): + if self.validate_json(): j = "".join(self.segments) data = json.loads(j) if "descriptor" in data: @@ -997,8 +921,8 @@ class SpecterDecodeWalletQR: return False - def getWalletDescriptor(self) -> str: - if self.validateWalletDescriptor(): + def get_wallet_descriptor(self) -> str: + if self.is_valid: j = "".join(self.segments) data = json.loads(j) return data['descriptor'] @@ -1006,46 +930,25 @@ class SpecterDecodeWalletQR: def is_complete(self) -> bool: - if self.complete and self.validateWalletDescriptor(): - return True - return False + return self.complete and self.is_valid() - @staticmethod - def currentSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.SPECTERWALLETQR: - if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: - return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(1)) - else: - return 1 + def current_segment_num(self, segment) -> int: + if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: + return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(1)) + else: + return 1 - @staticmethod - def totalSegmentNum(segment) -> int: - if DecodeQR.SegmentType(segment) == QRType.SPECTERWALLETQR: - if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: - return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(2)) - else: - return 1 + def total_segment_nums(self, segment) -> int: + if re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE) != None: + return int(re.search(r'^p(\d+)of(\d+) ', segment, re.IGNORECASE).group(2)) + else: + return 1 - @staticmethod - def parseSegment(segment) -> str: + def parse_segment(self, segment) -> str: try: return re.search(r'^p(\d+)of(\d+) (.+$)', segment, re.IGNORECASE).group(3) except: return segment - - - -### -### DecodeQRStatus Class IntEum -### Purpose: used in DecodeQR to communicate status of adding qr frame/segment -### -class DecodeQRStatus(IntEnum): - PART_COMPLETE = 1 - PART_EXISTING = 2 - COMPLETE = 3 - FALSE = 4 - INVALID = 5 - diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index b35128ee..8113edcd 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -56,14 +56,14 @@ class EncodeQR: self.encoder: BaseQrEncoder = None # PSBT formats - if self.qr_type == QRType.PSBTSPECTER: + if self.qr_type == QRType.PSBT__SPECTER: self.encoder = SpecterPsbtQrEncoder(psbt=self.psbt, qr_density=self.qr_density) - elif self.qr_type == QRType.PSBTUR2: + 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.XPUBQR: + elif self.qr_type == QRType.XPUB: self.encoder = XpubQrEncoder( seed_phrase=self.seed_phrase, passphrase=self.passphrase, @@ -72,7 +72,7 @@ class EncodeQR: wordlist_language_code=self.wordlist_language_code ) - elif self.qr_type == QRType.URXPUBQR: + elif self.qr_type == QRType.XPUB__UR: self.encoder = UrXpubQrEncoder( qr_density=self.qr_density, seed_phrase=self.seed_phrase, @@ -82,7 +82,7 @@ class EncodeQR: wordlist_language_code=self.wordlist_language_code ) - elif self.qr_type == QRType.SPECTERXPUBQR: + elif self.qr_type == QRType.XPUB__SPECTER: self.encoder = SpecterXPubQrEncoder( qr_density=self.qr_density, seed_phrase=self.seed_phrase, @@ -94,11 +94,11 @@ class EncodeQR: # SeedQR formats - elif self.qr_type == QRType.SEEDQR: + 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.COMPACTSEEDQR: + elif self.qr_type == QRType.SEED__COMPACTSEEDQR: self.encoder = CompactSeedQrEncoder(seed_phrase=self.seed_phrase, wordlist_language_code=self.wordlist_language_code) @@ -120,7 +120,7 @@ class EncodeQR: def next_part_image(self, width=240, height=240, border=3, background_color="bdbdbd"): part = self.next_part() - if self.qr_type == QRType.SEEDQR: + 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) diff --git a/src/seedsigner/models/qr_type.py b/src/seedsigner/models/qr_type.py index cc693852..75aac8f9 100644 --- a/src/seedsigner/models/qr_type.py +++ b/src/seedsigner/models/qr_type.py @@ -1,27 +1,29 @@ -from enum import IntEnum +class QRType: + """ + Used with DecodeQR and EncodeQR to communicate qr encoding type + """ + PSBT__BASE64 = "psbt__base64" + PSBT__SPECTER = "psbt__specter" + PSBT__LEGACY_UR = "psbt__legacy_ur" + PSBT__BASE43 = "psbt__base43" + PSBT__UR2 = "psbt__ur2" -### -### QRType Class IntEum -### Purpose: used with DecodeQR and EncodeQR to communicate qr encoding type -### + SEED__SEEDQR = "seed__seedqr" + SEED__COMPACTSEEDQR = "seed__compactseedqr" + SEED__UR2 = "seed__ur2" + SEED__MNEMONIC = "seed__mnemonic" + SEED__FOUR_LETTER_MNEMONIC = "seed__four_letter_mnemonic" -class QRType(IntEnum): - PSBTBASE64 = 1 - PSBTSPECTER = 2 - PSBTURLEGACY = 3 - PSBTBASE43 = 4 - PSBTUR2 = 5 - SEEDQR = 6 - COMPACTSEEDQR = 10 - SEEDUR2 = 7 - SEEDMNEMONIC = 8 - SEED4LETTERMNEMONIC = 9 - SETTINGS = 23 - XPUBQR = 20 - SPECTERXPUBQR = 21 - URXPUBQR = 22 - BITCOINADDRESSQR = 30 - SPECTERWALLETQR = 40 - URWALLETQR = 41 - BLUEWALLETQR = 42 - INVALID = 100 \ No newline at end of file + SETTINGS = "settings" + + XPUB = "xpub" + XPUB__SPECTER = "xpub__specter" + XPUB__UR = "xpub__ur" + + BITCOIN_ADDRESS = "bitcoin_address" + + WALLET__SPECTER = "wallet__specter" + WALLET__UR = "wallet__ur" + WALLET__BLUEWALLET = "wallet__bluewallet" + + INVALID = "invalid" \ No newline at end of file diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 2506bdda..280a2b79 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -339,9 +339,9 @@ class PSBTSignedQRDisplayView(View): self.coordinator = coordinator def run(self): - qr_psbt_type = QRType.PSBTUR2 + qr_psbt_type = QRType.PSBT__UR2 if self.coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP: - qr_psbt_type = QRType.PSBTSPECTER + qr_psbt_type = QRType.PSBT__SPECTER qr_encoder = EncodeQR( psbt=self.controller.psbt, diff --git a/src/seedsigner/views/scan_views.py b/src/seedsigner/views/scan_views.py index 111eb410..8a72d483 100644 --- a/src/seedsigner/views/scan_views.py +++ b/src/seedsigner/views/scan_views.py @@ -5,7 +5,7 @@ from seedsigner.models.settings import SettingsConstants from seedsigner.views.psbt_views import PSBTSelectSeedView from seedsigner.views.seed_views import SeedAddPassphrasePromptView, SeedAddPassphraseView -from .view import BackStackView, MainMenuView, View, Destination +from .view import BackStackView, MainMenuView, NotYetImplementedView, View, Destination from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON from seedsigner.models import DecodeQR, Seed @@ -24,9 +24,9 @@ class ScanView(View): screen = ScanScreen(decoder=self.decoder) screen.display() - if self.decoder.isComplete(): - if self.decoder.isSeed(): - seed_mnemonic = self.decoder.getSeedPhrase() + if self.decoder.is_complete: + if self.decoder.is_seed: + seed_mnemonic = self.decoder.get_seed_phrase() if not seed_mnemonic: # seed is not valid, Exit if not valid with message raise Exception("Not yet implemented!") @@ -44,13 +44,13 @@ class ScanView(View): else: return Destination(SeedValidView) - elif self.decoder.isPSBT(): - psbt = self.decoder.getPSBT() + elif self.decoder.is_psbt: + psbt = self.decoder.get_psbt() self.controller.psbt = psbt self.controller.psbt_parser = None return Destination(PSBTSelectSeedView) - elif self.decoder.qr_type == QRType.SETTINGS: + elif self.decoder.is_settings: from seedsigner.models.settings import Settings settings = self.decoder.get_settings_data() Settings.get_instance().update(new_settings=settings) @@ -59,10 +59,18 @@ class ScanView(View): return Destination(SettingsUpdatedView, {"config_name": self.decoder.get_settings_config_name()}) - elif self.decoder.qr_type == QRType.BITCOINADDRESSQR: + elif self.decoder.is_wallet_descriptor: + # TODO + print("Implement QR scanning for wallet descriptors!") + return Destination(NotYetImplementedView) + + elif self.decoder.is_address: # TODO: Reserved for Nick! - raise Exception("Not yet implemented!") - + print("Implement QR scanning for Bitcoin addresses!") + return Destination(NotYetImplementedView) + + else: + return Destination(NotYetImplementedView) return Destination(MainMenuView) diff --git a/src/seedsigner/views/seed_tools_view.py b/src/seedsigner/views/seed_tools_view.py index 5e85d42f..f38f991b 100644 --- a/src/seedsigner/views/seed_tools_view.py +++ b/src/seedsigner/views/seed_tools_view.py @@ -739,9 +739,9 @@ class SeedToolsView(View): def seed_phrase_as_qr(self, seed_phrase, is_compact_seedqr=False): if is_compact_seedqr: - e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.COMPACTSEEDQR, wordlist=self.controller.settings.wordlist) + e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEED__COMPACTSEEDQR, wordlist=self.controller.settings.wordlist) else: - e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEEDQR, wordlist=self.controller.settings.wordlist) + e = EncodeQR(seed_phrase=seed_phrase, qr_type=QRType.SEED__SEEDQR, wordlist=self.controller.settings.wordlist) data = e.next_part() qr = QR() image = qr.qrimage( @@ -907,7 +907,7 @@ class SeedToolsView(View): frame = self.controller.camera.read_video_stream(as_image=True) if frame is not None: self.renderer.show_image_with_text(frame.resize((240,240)), "Scan Seed QR", font=Fonts.get_font("Assistant-Medium", 22), text_color=self.color, text_background=(0,0,0,225)) - status = decoder.addImage(frame) + status = decoder.add_image(frame) if status in (DecodeQRStatus.COMPLETE, DecodeQRStatus.INVALID): break @@ -917,9 +917,9 @@ class SeedToolsView(View): self.words = [] return self.words[:] - if decoder.isComplete() and decoder.isSeed(): - self.words = decoder.getSeedPhrase() - elif not decoder.isPSBT(): + if decoder.is_complete and decoder.is_seed: + self.words = decoder.get_seed_phrase() + elif not decoder.is_psbt: self.draw_modal(["Not a valid Seed QR"], "", "Right to Exit") input = self.buttons.wait_for([B.KEY_RIGHT]) else: diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 894749ef..f173716f 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -3,7 +3,7 @@ import embit from binascii import hexlify from seedsigner.gui.components import FontAwesomeIconConstants -from seedsigner.models.decode_qr import SeedQR +from seedsigner.models.decode_qr import SeedQrDecoder from .view import NotYetImplementedView, View, Destination, BackStackView, MainMenuView @@ -458,11 +458,11 @@ class SeedExportXpubQRDisplayView(View): self.seed = self.controller.get_seed(seed_num) if coordinator == SettingsConstants.COORDINATOR__SPECTER_DESKTOP: - qr_type = QRType.SPECTERXPUBQR + qr_type = QRType.XPUB__SPECTER elif coordinator == SettingsConstants.COORDINATOR__BLUE_WALLET: - qr_type = QRType.XPUBQR + qr_type = QRType.XPUB else: - qr_type = QRType.URXPUBQR + qr_type = QRType.XPUB__UR self.qr_encoder = EncodeQR( seed_phrase=self.seed.mnemonic_list, diff --git a/tests/test_decodepsbtqr.py b/tests/test_decodepsbtqr.py index 283a582d..20059a9c 100644 --- a/tests/test_decodepsbtqr.py +++ b/tests/test_decodepsbtqr.py @@ -45,16 +45,16 @@ def test_ur_qr_decode_multisig(): d = DecodeQR() for i in qrcodes: d.add_data(i) - assert d.qrType() == QRType.PSBTUR2 + assert d.qr_type == QRType.PSBT__UR2 #complete should be true - assert d.isComplete() == True + assert d.is_complete base64_psbt = "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" - assert base64_psbt == d.getBase64PSBT() + assert base64_psbt == d.get_base64_psbt() - tx = d.getPSBT() + tx = d.get_psbt() assert str(tx) == base64_psbt @@ -67,16 +67,16 @@ def test_base64_single_frame_singlsig(): d = DecodeQR() for i in qrcodes: d.add_data(i) - assert d.qrType() == QRType.PSBTBASE64 + assert d.qr_type == QRType.PSBT__BASE64 #complete should be true - assert d.isComplete() == True + assert d.is_complete base64_psbt = "cHNidP8BAHICAAAAAQDo5ey+2HIrNUkExsFhsImv1OK1cYA9x/bRjYQD+0UaAQAAAAD9////Apg6AAAAAAAAF6kUVuVZEcdpQ2zgABa9dRUNYHD4VuaHgSYAAAAAAAAWABQaLE4t0JbDRg4pNnmcf+cAWIcyawAAAAAAAQEfqGEAAAAAAAAWABRyuw9od6yuS0yiZljV0X12wG9e5CIGA/ZlEZvQubb6PmcnK+vlnd8aftYnrQ8wHYSxsD8tDp61GIshjoFUAACAAQAAgAAAAIAAAAAAAAAAAAAAAA==" - assert base64_psbt == d.getBase64PSBT() + assert base64_psbt == d.get_base64_psbt() - tx = d.getPSBT() + tx = d.get_psbt() assert str(tx) == base64_psbt @@ -112,18 +112,18 @@ def test_ur2_sparrow_singlesig_to_self(): d = DecodeQR() for i in qrcodes: d.add_data(i) - assert d.qrType() == QRType.PSBTUR2 + assert d.qr_type == QRType.PSBT__UR2 #complete should be true - assert d.isComplete() == True + assert d.is_complete base64_psbt = "cHNidP8BAHECAAAAAQDo5ey+2HIrNUkExsFhsImv1OK1cYA9x/bRjYQD+0UaAQAAAAD9////AttBAAAAAAAAFgAUGixOLdCWw0YOKTZ5nH/nAFiHMmtAHwAAAAAAABYAFNbrl43uCJEN4CVjgbO9UzQYq3lB2XUfAE8BBDWHzwOUjfDCgAAAAIf4/x8q+YwsIsMjQZZKiYxG68KbV4Z0mlcxTbrtu8ApA/iB9364EPRoi0aEmjfRmywFry7wyW6Im2SQIsigPN9KEIshjoFUAACAAQAAgAAAAIAAAQDhAgAAAALAWfdi74cSytbNZS6q6ATuemMciRZLi4R6i5i/DiMi1gEAAAAjIgAgcrZ/i7xlN8a8hjVVz0uQfxgD1HO1qu0TMghmm3IbPYP9////L2V6qwld7RyRTMTgADRAw5x8TOHxFgbaB5Qaj/DeqNIAAAAAIyIAIKW/0WF/IrUNuRJzwe6t2qoxZV+Fs4OFO/F+DeatKRAl/f///wKqrwIAAAAAABepFB3sNOahaifEYp68aLkLTgfCxgNTh6hhAAAAAAAAFgAUcrsPaHesrktMomZY1dF9dsBvXuSOdR8AAQEfqGEAAAAAAAAWABRyuw9od6yuS0yiZljV0X12wG9e5AEDBAEAAAAiBgP2ZRGb0Lm2+j5nJyvr5Z3fGn7WJ60PMB2EsbA/LQ6etRiLIY6BVAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDrP6rQqucOQEIEVa/N98mmnQ50B5IGGLEvCTDXAZJ0coYiyGOgVQAAIABAACAAAAAgAEAAAAAAAAAACICA+PijXYEywnDo/b1PUqGK6TA1kod1Ww5UiMjx8R9/4BDGIshjoFUAACAAQAAgAAAAIAAAAAAAwAAAAA=" - assert base64_psbt == d.getBase64PSBT() + assert base64_psbt == d.get_base64_psbt() - tx = d.getPSBT() + tx = d.get_psbt() - assert str(tx) == d.getBase64PSBT() + assert str(tx) == d.get_base64_psbt() def test_base64_2_input_p2wsh(): @@ -131,9 +131,9 @@ def test_base64_2_input_p2wsh(): d = DecodeQR() d.add_data(base64_psbt) - assert d.qrType() == QRType.PSBTBASE64 + assert d.qr_type == QRType.PSBT__BASE64 - tx = d.getPSBT() + tx = d.get_psbt() assert str(tx) == base64_psbt @@ -158,9 +158,9 @@ def test_base64_1_input_p2sh_p2wsh(): d = DecodeQR() d.add_data(base64_psbt) - assert d.qrType() == QRType.PSBTBASE64 + assert d.qr_type == QRType.PSBT__BASE64 - tx = d.getPSBT() + tx = d.get_psbt() assert str(tx) == base64_psbt @@ -213,12 +213,12 @@ def test_ur_legacy(): for i in qrcodes: if d.add_data(i) == DecodeQRStatus.COMPLETE: break - assert d.qrType() == QRType.PSBTURLEGACY + assert d.qr_type == QRType.PSBT__LEGACY_UR #complete should be true - assert d.isComplete() == True + assert d.is_complete - tx = d.getPSBT() + 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") @@ -247,12 +247,12 @@ def test_specter_multisig_animated_qr(): for i in qrcodes: if d.add_data(i) == DecodeQRStatus.COMPLETE: break - assert d.qrType() == QRType.PSBTSPECTER + assert d.qr_type == QRType.PSBT__SPECTER #complete should be true - assert d.isComplete() == True + assert d.is_complete - tx = d.getPSBT() + 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") @@ -285,12 +285,12 @@ def test_specter_multisig_animated_qr(): for i in qrcodes2: if d2.add_data(i) == DecodeQRStatus.COMPLETE: break - assert d2.qrType() == QRType.PSBTSPECTER + assert d2.qr_type == QRType.PSBT__SPECTER #complete should be true - assert d2.isComplete() == True + assert d2.is_complete - tx2 = d2.getPSBT() + 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") @@ -317,7 +317,7 @@ def test_mnemonic_qr(): d = DecodeQR() assert d.add_data(mnemonic) == DecodeQRStatus.COMPLETE - assert d.getSeedPhrase() == mnemonic.split() + assert d.get_seed_phrase() == mnemonic.split() bad_mnemonic = "height useless trap grow lion found off key clown transfer enroll" d2 = DecodeQR() @@ -333,8 +333,8 @@ def test_short_4_letter_mnemonic_qr(): d = DecodeQR() d.add_data(short_nm) - assert d.isComplete() == True - assert d.getSeedPhrase() == ["height", "demise", "useless", "trap", "grow", "lion", "found", "off", "key", "clown", "transfer", "enroll"] + 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(): @@ -357,55 +357,55 @@ def test_bitcoin_address(): d = DecodeQR() d.add_data(bad1) - assert d.qrType() == QRType.INVALID + assert d.qr_type == QRType.INVALID d = DecodeQR() d.add_data(legacy_address1) - assert d.getAddress() == legacy_address1 - assert d.getAddressType() == "P2PKH-main" + assert d.get_address() == legacy_address1 + assert d.get_address_type() == "P2PKH-main" d = DecodeQR() d.add_data(legacy_address2) - assert d.getAddress() == legacy_address2 - assert d.getAddressType() == "P2PKH-main" + assert d.get_address() == legacy_address2 + assert d.get_address_type() == "P2PKH-main" d = DecodeQR() d.add_data(main_bech32_address) - assert d.getAddress() == main_bech32_address - assert d.getAddressType() == "Bech32-main" + assert d.get_address() == main_bech32_address + assert d.get_address_type() == "Bech32-main" d = DecodeQR() d.add_data(test_bech32_address) - assert d.getAddress() == test_bech32_address - assert d.getAddressType() == "Bech32-test" + assert d.get_address() == test_bech32_address + assert d.get_address_type() == "Bech32-test" d = DecodeQR() d.add_data(main_nested_segwit_address) - assert d.getAddress() == main_nested_segwit_address - assert d.getAddressType() == "P2SH-main" + assert d.get_address() == main_nested_segwit_address + assert d.get_address_type() == "P2SH-main" d = DecodeQR() d.add_data(test_nested_segwit_address) - assert d.getAddress() == test_nested_segwit_address - assert d.getAddressType() == "P2SH-test" + assert d.get_address() == test_nested_segwit_address + assert d.get_address_type() == "P2SH-test" d = DecodeQR() d.add_data(main_bech32_address2) - assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" - assert d.getAddressType() == "Bech32-main" + assert d.get_address() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" + assert d.get_address_type() == "Bech32-main" d = DecodeQR() d.add_data(main_bech32_address3) - assert d.getAddress() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" - assert d.getAddressType() == "Bech32-main" + assert d.get_address() == "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" + assert d.get_address_type() == "Bech32-main" def test_seed_qr(): @@ -414,8 +414,8 @@ def test_seed_qr(): d = DecodeQR() d.add_data(seed) - assert d.qrType() == QRType.SEEDQR - assert d.getSeedPhrase() == "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash".split() + assert d.qr_type == QRType.SEED__SEEDQR + assert d.get_seed_phrase() == "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash".split() def test_specter_wallet_json(): @@ -430,9 +430,9 @@ def test_specter_wallet_json(): d.add_data(parts[2]) d.add_data(parts[0]) - assert d.qrType() == QRType.SPECTERWALLETQR - assert d.isComplete() == True - assert d.getWalletDescriptor() == "wsh(sortedmulti(4,[e0811b6b/48h/0h/0h/2h]xpub6E8v7uy63pCeJvHe5W8ea8zTnCtKMFgMRb5bueWWcUFMw6sWmUwTqxM8cFiKQRWkA2Fxth9HJZufJwjWTTvU1UGZNpTrh9khrswYMgeHiCt/0/*,[852b308f/48h/0h/0h/2h]xpub6ErhgAWfnEqW7xDBm1iLq5JjNyUS65YUFnjHLrRv9zmdDEtuE75bpWQ8o6bSBnpT6AkrrsA8eA5SmEFArZn11KEPaZJzx9mHTXPWZCsxLyh/0/*,[7edf9c59/48h/0h/0h/2h]xpub6DaFfKoe7WpofrbYeNo3Wv2AiLUMeyrPwotXfukFxUHbK4JxaLHTd5394QtH5wnjFzBgr2YnJpHhXv25Zsqv2APmMFvH1DsKHj5LCr3pmXs/0/*,[b433e095/48h/0h/0h/2h]xpub6EF51itHko2YhGTjVeuYbBgJjVbTzzpYzn2a3JwZHpDrMePRVgXGBHMx2Yv1KwgLsUn9i7ExcAo8uqMx4pDjVRY9J7qnceFAwRRj16dd5AS/0/*,[184d07eb/48h/0h/0h/2h]xpub6EEoTpcQu7N4R8D84pJjZ69j3minevnYLDDoo2HBzYBXTQ4rGVf4XGTyCYFwJuZdsF9MyFYJNzYEjg5LGMA1ubTGWuDnjHAZz6ficVRDTSy/0/*,[3e451efe/48h/0h/0h/2h]xpub6ExQPvQxGBMaPxr8Fv7Vq91ztJFFX3VWvtpvex6UPZ1AptTeuAiJGCtKkgwJkrwpMZMagh9ex6rL4sM8axfFcdQbERoFCRUKTJxrBkJh56g/0/*))#c44hel9e" + assert d.qr_type == QRType.WALLET__SPECTER + assert d.is_complete + assert d.get_wallet_descriptor() == "wsh(sortedmulti(4,[e0811b6b/48h/0h/0h/2h]xpub6E8v7uy63pCeJvHe5W8ea8zTnCtKMFgMRb5bueWWcUFMw6sWmUwTqxM8cFiKQRWkA2Fxth9HJZufJwjWTTvU1UGZNpTrh9khrswYMgeHiCt/0/*,[852b308f/48h/0h/0h/2h]xpub6ErhgAWfnEqW7xDBm1iLq5JjNyUS65YUFnjHLrRv9zmdDEtuE75bpWQ8o6bSBnpT6AkrrsA8eA5SmEFArZn11KEPaZJzx9mHTXPWZCsxLyh/0/*,[7edf9c59/48h/0h/0h/2h]xpub6DaFfKoe7WpofrbYeNo3Wv2AiLUMeyrPwotXfukFxUHbK4JxaLHTd5394QtH5wnjFzBgr2YnJpHhXv25Zsqv2APmMFvH1DsKHj5LCr3pmXs/0/*,[b433e095/48h/0h/0h/2h]xpub6EF51itHko2YhGTjVeuYbBgJjVbTzzpYzn2a3JwZHpDrMePRVgXGBHMx2Yv1KwgLsUn9i7ExcAo8uqMx4pDjVRY9J7qnceFAwRRj16dd5AS/0/*,[184d07eb/48h/0h/0h/2h]xpub6EEoTpcQu7N4R8D84pJjZ69j3minevnYLDDoo2HBzYBXTQ4rGVf4XGTyCYFwJuZdsF9MyFYJNzYEjg5LGMA1ubTGWuDnjHAZz6ficVRDTSy/0/*,[3e451efe/48h/0h/0h/2h]xpub6ExQPvQxGBMaPxr8Fv7Vq91ztJFFX3VWvtpvex6UPZ1AptTeuAiJGCtKkgwJkrwpMZMagh9ex6rL4sM8axfFcdQbERoFCRUKTJxrBkJh56g/0/*))#c44hel9e" def test_specter_wallet_json2(): @@ -441,5 +441,5 @@ def test_specter_wallet_json2(): d = DecodeQR() d.add_data(part) - assert d.qrType() == QRType.SPECTERWALLETQR - assert d.isComplete() == True + assert d.qr_type == QRType.WALLET__SPECTER + assert d.is_complete diff --git a/tests/test_encodepsbtqr.py b/tests/test_encodepsbtqr.py index cb9991aa..cf55e9a2 100644 --- a/tests/test_encodepsbtqr.py +++ b/tests/test_encodepsbtqr.py @@ -12,7 +12,7 @@ def test_ur_qr_encode(): tx = psbt.PSBT.parse(a2b_base64(base64_psbt)) - e = EncodeQR(psbt=tx, qr_type=QRType.PSBTUR2) + e = EncodeQR(psbt=tx, qr_type=QRType.PSBT__UR2) cnt = 0 while cnt <= 10: @@ -27,7 +27,7 @@ def test_specter_qr_encode(): tx = psbt.PSBT.parse(a2b_base64(base64_psbt)) - e = EncodeQR(psbt=tx, qr_type=QRType.PSBTSPECTER) + e = EncodeQR(psbt=tx, qr_type=QRType.PSBT__SPECTER) cnt = 0 while cnt <= 10: @@ -62,7 +62,7 @@ def test_seedsigner_qr(): mnemonic = "obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash" - e = EncodeQR(seed_phrase=mnemonic.split(" "), qr_type=QRType.SEEDQR) + e = EncodeQR(seed_phrase=mnemonic.split(" "), qr_type=QRType.SEED__SEEDQR) print(e.next_part()) @@ -72,7 +72,7 @@ def test_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.XPUBQR, network="test", derivation="m/48h/1h/0h/2h") + e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.XPUB, network="test", derivation="m/48h/1h/0h/2h") assert e.next_part() == "[c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDNc5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBoMxfrVggXj5NJmaNEToWpax8GMMucozvAdqf1bW1JsZsfdBzsK3VUC5" @@ -80,7 +80,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=EncodeQR.DENSITY__LOW) + e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.XPUB__SPECTER, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__LOW) assert e.next_part() == "p1of4 [c49122a5/48h/1h/0h/2h]Vpub5mXgECaX5yYDN" assert e.next_part() == "p2of4 c5VnUG4jVNptyEg65qUjuofWchQeuMWWiq8rcPBo" @@ -91,7 +91,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=EncodeQR.DENSITY__MEDIUM) + e = EncodeQR(seed_phrase=mnemonic.split(" "), passphrase="pass", qr_type=QRType.XPUB__UR, network="test", derivation="m/48h/1h/0h/2h", qr_density=EncodeQR.DENSITY__MEDIUM) assert e.next_part() == "UR:CRYPTO-ACCOUNT/1-4/LPADAACSJKCYYNRDRTYAHDCAOEADCYSSMECPONAOLYTAADMETAADDLOXAXHDCLAOKSRLNLKPUEGYATHPMNRTAAOLAH" assert e.next_part() == "UR:CRYPTO-ACCOUNT/2-4/LPAOAACSJKCYYNRDRTYAHDCASNKKGHZMLUZORPVDGUOTECSTTKTOLPCWPTNTLKZTTIZTBEAAHDCXVDTPMYQDLAWNGL" diff --git a/tests/test_seedqr.py b/tests/test_seedqr.py index ba07f0c8..b7668bee 100644 --- a/tests/test_seedqr.py +++ b/tests/test_seedqr.py @@ -6,6 +6,7 @@ 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.qr_type import QRType +from seedsigner.models.settings import SettingsConstants def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type): @@ -27,11 +28,11 @@ def run_encode_decode_test(entropy: bytes, mnemonic_length, qr_type): border=3 ) - decoder = DecodeQR(wordlist=bip39.WORDLIST) - status = decoder.addImage(image) + decoder = DecodeQR(wordlist_language_code=SettingsConstants.LANGUAGE__ENGLISH) + status = decoder.add_image(image) assert status == DecodeQRStatus.COMPLETE - decoded_seed_phrase = decoder.getSeedPhrase() + decoded_seed_phrase = decoder.get_seed_phrase() print(decoded_seed_phrase) assert seed_phrase == decoded_seed_phrase @@ -42,10 +43,10 @@ def test_standard_seedqr_encode_decode_(): them back again to their original mnemonic seed phrase. """ # 24-word seed - run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.SEEDQR) + run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.SEED__SEEDQR) # 12-word seed - run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.SEEDQR) + run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.SEED__SEEDQR) @@ -54,10 +55,10 @@ def test_compact_seedqr_encode_decode(): them back again to their original mnemonic seed phrase. """ # 24-word seed - run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(os.urandom(32), mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed - run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(os.urandom(16), mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR) @@ -67,40 +68,40 @@ def test_compact_seedqr_handles_null_bytes(): """ # 24-word seed, null bytes at the front entropy = b'\x00' + os.urandom(31) - run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 24-word seed, null bytes in the middle entropy = os.urandom(10) + b'\x00' + os.urandom(21) - run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 24-word seed, null bytes at the end entropy = os.urandom(31) + b'\x00' - run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 24-word seed, multiple null bytes entropy = os.urandom(5) + b'\x00' + os.urandom(5) + b'\x00' + os.urandom(20) - run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 24-word seed, multiple null bytes in a row entropy = os.urandom(10) + b'\x00\x00' + os.urandom(20) - run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=24, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed, null bytes at the beginning entropy = b'\x00' + os.urandom(15) - run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed, null bytes in the middle entropy = os.urandom(5) + b'\x00' + os.urandom(10) - run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed, null bytes at the end entropy = os.urandom(15) + b'\x00' - run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed, multiple null bytes entropy = os.urandom(5) + b'\x00' + os.urandom(5) + b'\x00' + os.urandom(4) - run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR) # 12-word seed, multiple null bytes in a row entropy = os.urandom(10) + b'\x00\x00' + os.urandom(4) - run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.COMPACTSEEDQR) + run_encode_decode_test(entropy, mnemonic_length=12, qr_type=QRType.SEED__COMPACTSEEDQR)