From 27a6c72a7daf412787bd774b841af1eae7d97b5b Mon Sep 17 00:00:00 2001 From: newtonick Date: Tue, 7 Sep 2021 02:26:49 +0000 Subject: [PATCH] work in progress --- setup.py | 2 +- src/seedsigner/controller.py | 18 +++---- src/seedsigner/helpers/mnemonic_generation.py | 6 +-- src/seedsigner/models/__init__.py | 6 +-- src/seedsigner/models/decode_qr.py | 47 ++++++++++++------- src/seedsigner/models/encode_qr.py | 10 ++-- src/seedsigner/models/psbt_parser.py | 2 - src/seedsigner/models/seed.py | 9 ++-- src/seedsigner/views/seed_tools_view.py | 2 +- tests/test_decodepsbtqr.py | 2 +- tests/test_mnemonic_generation.py | 4 +- tests/test_psbt_parser.py | 8 ++++ 12 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 tests/test_psbt_parser.py diff --git a/setup.py b/setup.py index 66fa1c99..bfee1ee7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="seedsigner", - version="0.4.1", + version="0.4.4", author="SeedSigner", author_email="author@example.com", description="Build an offline, airgapped Bitcoin signing device for less than $50!", diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py index 1de1ce16..87d9d870 100644 --- a/src/seedsigner/controller.py +++ b/src/seedsigner/controller.py @@ -174,7 +174,7 @@ class Controller(Singleton): ### Generate Last Word 12 / 24 Menu def show_generate_last_word_tool(self) -> int: - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) ret_val = 0 while True: @@ -215,7 +215,7 @@ class Controller(Singleton): ### Create a Seed w/ Dice Screen def show_create_seed_with_dice_tool(self) -> int: - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) ret_val = True while True: @@ -249,7 +249,7 @@ class Controller(Singleton): ### Create a Seed w/ Image def show_create_seed_with_image_tool(self) -> int: - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) ret_val = True while True: @@ -287,7 +287,7 @@ class Controller(Singleton): ### Store a seed (temp) Menu def show_store_a_seed_tool(self): - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) ret_val = 0 display_saved_seed = False ret_val = self.menu_view.display_saved_seed_menu(self.storage, 1, "... [ Return to Seed Tools ]") @@ -410,7 +410,7 @@ class Controller(Singleton): ### Generate XPUB def show_generate_xpub(self): - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) # If there is a saved seed, ask to use saved seed if self.storage.num_of_saved_seeds() > 0: @@ -487,7 +487,7 @@ class Controller(Singleton): qr_xpub_type = self.settings.qr_xpub_type self.signing_tools_view.draw_modal(["Generating xPub QR ..."]) - e = EncodeQR(seed_phrase=seed.mnemonic_list, passphrase=seed.passphrase, derivation=self.settings.derivation, network=self.settings.network, policy=self.settings.script_policy, qr_type=qr_xpub_type, qr_density=self.settings.qr_density) + e = EncodeQR(seed_phrase=seed.mnemonic_list, passphrase=seed.passphrase, derivation=self.settings.derivation, network=self.settings.network, policy=self.settings.script_policy, qr_type=qr_xpub_type, qr_density=self.settings.qr_density, wordlist=self.settings.wordlist) while e.totalParts() > 1: image = e.nextPartImage(240,240,2) @@ -506,7 +506,7 @@ class Controller(Singleton): ### Sign Transactions def show_sign_transaction(self): - seed = Seed() + seed = Seed(wordlist=self.settings.wordlist) used_saved_seed = False # reusable qr scan function @@ -514,7 +514,7 @@ class Controller(Singleton): # Scan QR using Camera self.menu_view.draw_modal(["Initializing Camera"]) self.camera.start_video_stream_mode(resolution=(480, 480), framerate=12, format="rgb") - decoder = DecodeQR() + decoder = DecodeQR(wordlist=self.settings.wordlist) def live_preview(camera, decoder, scan_text): while True: @@ -714,7 +714,7 @@ class Controller(Singleton): # Display Animated QR Code self.menu_view.draw_modal(["Generating PSBT QR ..."]) - e = EncodeQR(psbt=trimmed_psbt, qr_type=self.settings.qr_psbt_type, qr_density=self.settings.qr_density) + e = EncodeQR(psbt=trimmed_psbt, qr_type=self.settings.qr_psbt_type, qr_density=self.settings.qr_density, wordlist=self.settings.wordlist) while True: image = e.nextPartImage(240,240,1) View.DispShowImage(image) diff --git a/src/seedsigner/helpers/mnemonic_generation.py b/src/seedsigner/helpers/mnemonic_generation.py index 326a1ee9..5b3f8a44 100644 --- a/src/seedsigner/helpers/mnemonic_generation.py +++ b/src/seedsigner/helpers/mnemonic_generation.py @@ -2,11 +2,9 @@ from embit import bip39 from embit.bip39 import mnemonic_to_bytes, mnemonic_from_bytes import unicodedata -from seedsigner.models import Settings - -def calculate_checksum(partial_mnemonic: list): +def calculate_checksum(partial_mnemonic: list, wordlist): # Provide 11- or 23-word mnemonic, returns complete mnemonic w/checksum if len(partial_mnemonic) not in [11, 23]: raise Exception("Pass in a 11- or 23-word mnemonic") @@ -16,7 +14,7 @@ def calculate_checksum(partial_mnemonic: list): mnemonic_copy.append("abandon") # Ignores the final checksum word and recalcs - mnemonic_bytes = bip39.mnemonic_to_bytes(unicodedata.normalize("NFKD", " ".join(mnemonic_copy)), ignore_checksum=True, wordlist=Settings.get_instance().wordlist) + mnemonic_bytes = bip39.mnemonic_to_bytes(unicodedata.normalize("NFKD", " ".join(mnemonic_copy)), ignore_checksum=True, wordlist=wordlist) # Return as a list return bip39.mnemonic_from_bytes(mnemonic_bytes).split() diff --git a/src/seedsigner/models/__init__.py b/src/seedsigner/models/__init__.py index e378e13d..d82d803b 100644 --- a/src/seedsigner/models/__init__.py +++ b/src/seedsigner/models/__init__.py @@ -1,7 +1,7 @@ from .qr_type import * +from .seed import * from .decode_qr import * from .encode_qr import * -from .settings import * from .psbt_parser import * -from .seed import * -from .seed_storage import * \ No newline at end of file +from .seed_storage import * +from .settings import * \ No newline at end of file diff --git a/src/seedsigner/models/decode_qr.py b/src/seedsigner/models/decode_qr.py index 033caea2..ef864844 100644 --- a/src/seedsigner/models/decode_qr.py +++ b/src/seedsigner/models/decode_qr.py @@ -8,7 +8,6 @@ from binascii import a2b_base64, b2a_base64 from seedsigner.helpers.ur2.ur_decoder import URDecoder from seedsigner.helpers.bcur import (cbor_decode, bc32decode) from seedsigner.models.qr_type import QRType -from seedsigner.models.settings import Settings from seedsigner.models.seed import Seed ### @@ -25,11 +24,18 @@ class DecodeQR: 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.seedqr = SeedQR() + self.wordlist = None for key, value in kwargs.items(): if key == "qr_type": self.qr_type = value + if key == "wordlist": + self.wordlist = value + + if self.wordlist == None: + raise Exception('Wordlist Required') + + self.seedqr = SeedQR(wordlist=self.wordlist) def addImage(self, image): qr_str = DecodeQR.QR2Str(image) @@ -42,7 +48,7 @@ class DecodeQR: if qr_str == None: return DecodeQRStatus.FALSE - qr_type = DecodeQR.SegmentType(qr_str) + qr_type = DecodeQR.SegmentType(qr_str, wordlist=self.wordlist) if self.qr_type == None: self.qr_type = qr_type @@ -188,27 +194,29 @@ class DecodeQR: return barcode.data.decode("utf-8") @staticmethod - def SegmentType(s): - - _WORDLIST = Settings.get_instance().wordlist - _4LETTER_WORDLIST = [word[:4].strip() for word in _WORDLIST] + def SegmentType(s, wordlist=None): + # PSBT if re.search("^UR:CRYPTO-PSBT/", s, re.IGNORECASE): return QRType.PSBTUR2 elif re.search(r'^p(\d+)of(\d+) ', s, re.IGNORECASE): return QRType.PSBTSPECTER elif re.search("^UR:BYTES/", s, re.IGNORECASE): return QRType.PSBTURLEGACY - elif re.search(r'\d{48,96}', s): + elif DecodeQR.isBase64PSBT(s): + return QRType.PSBTBASE64 + + _4LETTER_WORDLIST = [word[:4].strip() for word in wordlist] + + # Seed + if re.search(r'\d{48,96}', s): return QRType.SEEDSSQR - elif all(x in _WORDLIST for x in s.strip().split(" ")): + elif all(x in wordlist for x in s.strip().split(" ")): # checks if all words in list are in bip39 word list return QRType.SEEDMNEMONIC 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.isBase64PSBT(s): - return QRType.PSBTBASE64 else: return QRType.INVALID @@ -422,16 +430,19 @@ class Base64DecodeQR: class SeedQR: - def __init__(self): + def __init__(self, wordlist=None): self.total_segments = 1 self.collected_segments = 0 self.complete = False self.seed_phrase = [] + self.wordlist = wordlist + + if self.wordlist == None: + raise Exception('Wordlist Required') def add(self, segment, qr_type=QRType.SEEDSSQR): - _WORDLIST = Settings.get_instance().wordlist - _4LETTER_WORDLIST = [word[:4].strip() for word in _WORDLIST] + _4LETTER_WORDLIST = [word[:4].strip() for word in self.wordlist] if qr_type == QRType.SEEDSSQR: @@ -442,7 +453,7 @@ class SeedQR: num_words = int(len(segment) / 4) for i in range(0, num_words): index = int(segment[i * 4: (i*4) + 4]) - word = _WORDLIST[index] + word = self.wordlist[index] self.seed_phrase.append(word) if len(self.seed_phrase) > 0: if self.is1224Phrase() == False: @@ -459,7 +470,7 @@ class SeedQR: try: # embit mnemonic code to validate - seed = Seed(segment, passphrase="") + seed = Seed(segment, passphrase="", wordlist=self.wordlist) if not seed: # seed is not valid, return invalid return DecodeQRStatus.INVALID @@ -478,10 +489,10 @@ class SeedQR: sl = segment.strip().split(" ") words = [] for s in sl: - words.append(_WORDLIST[_4LETTER_WORDLIST.index(s)]) + words.append(self.wordlist[_4LETTER_WORDLIST.index(s)]) # embit mnemonic code to validate - seed = Seed(words, passphrase="") + seed = Seed(words, passphrase="", wordlist=self.wordlist) if not seed: # seed is not valid, return invalid return DecodeQRStatus.INVALID diff --git a/src/seedsigner/models/encode_qr.py b/src/seedsigner/models/encode_qr.py index 2841202f..a272d7f5 100644 --- a/src/seedsigner/models/encode_qr.py +++ b/src/seedsigner/models/encode_qr.py @@ -8,7 +8,6 @@ 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.qr_type import QRType, EncodeQRDensity -from seedsigner.models.settings import Settings from seedsigner.models.seed import Seed ### @@ -21,7 +20,6 @@ class EncodeQR: WORDLIST = None def __init__(self, **kwargs): - EncodeQR.WORDLIST = Settings.get_instance().wordlist self.psbt = None self.seed_phrase = None self.passphrase = None @@ -31,6 +29,7 @@ class EncodeQR: self.qr_type = None self.qr_density = None self.qr = QR() + self.wordlist = None for key, value in kwargs.items(): if key == "psbt": @@ -49,6 +48,11 @@ class EncodeQR: self.qr_type = value elif key == "qr_density": self.qr_density = value + elif key == "wordlist": + self.wordlist = value + + if self.wordlist == None: + raise Exception('Wordlist Required') if self.qr_type == None: raise Exception('Encoder Type Required') @@ -217,7 +221,7 @@ class XPubQR: self.sent_complete = False self.network = network - self.seed = Seed(self.seed_phrase, self.passphrase) + self.seed = Seed(mnemonic=self.seed_phrase, passphrase=self.passphrase, wordlist=self.wordlist) self.root = bip32.HDKey.from_seed(self.seed.seed, version=NETWORKS[self.network]["xprv"]) self.fingerprint = self.root.child(0).fingerprint self.bip48_xprv = self.root.derive(self.derivation) diff --git a/src/seedsigner/models/psbt_parser.py b/src/seedsigner/models/psbt_parser.py index b567c6a7..7406bc3b 100644 --- a/src/seedsigner/models/psbt_parser.py +++ b/src/seedsigner/models/psbt_parser.py @@ -2,8 +2,6 @@ from embit import psbt, script, ec, bip32, bip39 from embit.networks import NETWORKS from io import BytesIO -from seedsigner.models.seed import Seed - class PSBTParser(): def __init__(self, p, seed: Seed, network="main"): diff --git a/src/seedsigner/models/seed.py b/src/seedsigner/models/seed.py index c3a95ffa..6d1e990e 100644 --- a/src/seedsigner/models/seed.py +++ b/src/seedsigner/models/seed.py @@ -9,11 +9,10 @@ class Seed: self._valid = False self.passphrase = passphrase self.mnemonic = mnemonic - if wordlist == None: - from seedsigner.models.settings import Settings - self._wordlist = Settings.get_instance().wordlist - else: - self._wordlist = wordlist + self.wordlist = wordlist + + if self.wordlist == None: + raise Exception('Wordlist Required') self._valid = self._generate_seed() self._init_complete = True diff --git a/src/seedsigner/views/seed_tools_view.py b/src/seedsigner/views/seed_tools_view.py index 5f6bb8c7..0fb27e8c 100644 --- a/src/seedsigner/views/seed_tools_view.py +++ b/src/seedsigner/views/seed_tools_view.py @@ -665,7 +665,7 @@ class SeedToolsView(View): ### def display_last_word(self, partial_seed_phrase) -> list: - finalseed = mnemonic_generation.calculate_checksum(partial_seed_phrase) + finalseed = mnemonic_generation.calculate_checksum(partial_seed_phrase, wordlist=self.controller.settings.wordlist) last_word = finalseed[-1] self.draw.rectangle((0, 0, View.canvas_width, View.canvas_height), outline=0, fill=0) diff --git a/tests/test_decodepsbtqr.py b/tests/test_decodepsbtqr.py index d8b0781e..d4892fa7 100644 --- a/tests/test_decodepsbtqr.py +++ b/tests/test_decodepsbtqr.py @@ -1,8 +1,8 @@ import pytest from mock import MagicMock -from seedsigner.models.seed import Seed from seedsigner.models.decode_qr import DecodeQR, QRType, DecodeQRStatus from seedsigner.models.psbt_parser import PSBTParser +from seedsigner.models.seed import Seed from embit import psbt, bip39 diff --git a/tests/test_mnemonic_generation.py b/tests/test_mnemonic_generation.py index 65f002a8..67ecaf9a 100644 --- a/tests/test_mnemonic_generation.py +++ b/tests/test_mnemonic_generation.py @@ -23,10 +23,10 @@ def test_calculate_checksum(): """ # Test mnemonics from https://iancoleman.io/bip39/ partial_mnemonic = "crawl focus rescue cable view pledge rather dinner cousin unfair day" - mnemonic = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" ")) + mnemonic = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist=bip39.WORDLIST) assert bip39.mnemonic_is_valid(" ".join(mnemonic)) partial_mnemonic = "bubble father debate ankle injury fence mesh evolve section wet coyote violin pyramid flower rent arrow round clutch myth safe base skin mobile" - mnemonic = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" ")) + mnemonic = mnemonic_generation.calculate_checksum(partial_mnemonic.split(" "), wordlist=bip39.WORDLIST) assert bip39.mnemonic_is_valid(" ".join(mnemonic)) diff --git a/tests/test_psbt_parser.py b/tests/test_psbt_parser.py new file mode 100644 index 00000000..195d07b2 --- /dev/null +++ b/tests/test_psbt_parser.py @@ -0,0 +1,8 @@ +import pytest +from mock import MagicMock +from seedsigner.models.psbt_parser import PSBTParser +from embit import psbt + +def test_sig_count(): + print("test") + \ No newline at end of file