More tests; minor reformatting

This commit is contained in:
kdmukai
2024-07-14 13:21:23 -05:00
parent fcd4242012
commit 407d1dbf75
5 changed files with 70 additions and 12 deletions
+24 -8
View File
@@ -42,7 +42,7 @@ class Seed:
raise Exception(f"Unrecognized wordlist_language_code {wordlist_language_code}")
def _generate_seed(self) -> bool:
def _generate_seed(self):
try:
self.seed_bytes = bip39.mnemonic_to_seed(self.mnemonic_str, password=self._passphrase, wordlist=self.wordlist)
except Exception as e:
@@ -64,6 +64,7 @@ class Seed:
def wordlist_language_code(self) -> str:
return self._wordlist_language_code
@property
def mnemonic_display_str(self) -> str:
return unicodedata.normalize("NFC", " ".join(self._mnemonic))
@@ -106,29 +107,36 @@ class Seed:
# TODO: Support other BIP-39 wordlist languages!
raise Exception("Not yet implemented!")
@property
def script_override(self) -> list:
return None
def derivation_override(self, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
return None
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
embit_network = NETWORKS[SettingsConstants.map_network_to_embit(network)]
return bip32.detect_version(derivation_path, default="xpub", network=embit_network)
@property
def passphrase_label(self) -> str:
return SettingsConstants.LABEL__BIP39_PASSPHRASE
@property
def seedqr_supported(self) -> bool:
return True
@property
def bip85_supported(self) -> bool:
return True
def get_fingerprint(self, network: str = SettingsConstants.MAINNET) -> str:
root = bip32.HDKey.from_seed(self.seed_bytes, version=NETWORKS[SettingsConstants.map_network_to_embit(network)]["xprv"])
return hexlify(root.child(0).fingerprint).decode('utf-8')
@@ -158,19 +166,20 @@ class Seed:
class ElectrumSeed(Seed):
def _generate_seed(self) -> bool:
def _generate_seed(self):
if len(self._mnemonic) != 12:
return False
raise InvalidSeedException(f"Unsupported Electrum seed length: {len(self._mnemonic)}")
s = hmac.digest(b"Seed version", self.mnemonic_str.encode('utf8'), hashlib.sha512).hex()
prefix = s[0:3]
# only support Electrum Segwit version for now
if SettingsConstants.ELECTRUM_SEED_SEGWIT == prefix:
self.seed_bytes=hashlib.pbkdf2_hmac('sha512', self.mnemonic_str.encode('utf-8'), b'electrum' + self._passphrase.encode('utf-8'), iterations = SettingsConstants.ELECTRUM_PBKDF2_ROUNDS)
return True
else:
raise InvalidSeedException("Unsupported electrum seed input")
return False
raise InvalidSeedException(f"Unsupported Electrum seed format: {prefix}")
def set_passphrase(self, passphrase: str, regenerate_seed: bool = True):
if passphrase:
@@ -184,6 +193,7 @@ class ElectrumSeed(Seed):
# Regenerate the internal seed since passphrase changes the result
self._generate_seed()
@staticmethod
def normalize_electrum_passphrase(passphrase : str) -> str:
passphrase = unicodedata.normalize('NFKD', passphrase)
@@ -193,25 +203,31 @@ class ElectrumSeed(Seed):
passphrase = u' '.join(passphrase.split())
return passphrase
@property
def script_override(self) -> list:
return [SettingsConstants.NATIVE_SEGWIT]
def derivation_override(self, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
return "m/0h" if SettingsConstants.SINGLE_SIG == wallet_type else "m/1h"
def detect_version(self, derivation_path: str, network: str = SettingsConstants.MAINNET, wallet_type: str = SettingsConstants.SINGLE_SIG) -> str:
embit_network = NETWORKS[SettingsConstants.map_network_to_embit(network)]
return embit_network["zpub"] if SettingsConstants.SINGLE_SIG == wallet_type else embit_network["Zpub"]
@property
def passphrase_label(self) -> str:
return SettingsConstants.LABEL__CUSTOM_EXTENSION
@property
def seedqr_supported(self) -> bool:
return False
@property
def bip85_supported(self) -> bool:
return False
@@ -209,6 +209,8 @@ class SettingsConstants:
LABEL__BIP39_PASSPHRASE = "BIP-39 Passphrase"
LABEL__CUSTOM_EXTENSION = "Custom Extension"
@dataclass
class SettingsEntry:
"""
+1
View File
@@ -620,6 +620,7 @@ class SeedBackupView(View):
def run(self):
button_data = [self.VIEW_WORDS]
if self.seed.seedqr_supported:
button_data.append(self.EXPORT_SEEDQR)
+2 -3
View File
@@ -152,6 +152,7 @@ class TestSeedFlows(FlowTest):
]
else:
# Or we bomb out if the mnemonic is invalid for Electrum
sequence.append(FlowStep(seed_views.SeedMnemonicInvalidView, button_data_selection=seed_views.SeedMnemonicInvalidView.DISCARD))
self.run_sequence(sequence)
@@ -164,10 +165,8 @@ class TestSeedFlows(FlowTest):
test_with_mnemonic("morning pretty hobby click extend color wait joke define sausage boost salmon".split())
test_with_mnemonic("basket print toy noodle betray weird filter ticket insect copy force machine".split())
# Most BIP-39 seeds should fail; test seeds generated by bitcoiner.guide
# Most BIP-39 seeds should fail; test seed generated by bitcoiner.guide
test_with_mnemonic("pioneer divide volcano art victory family grow novel mandate bicycle senior adjust".split(), expects_electrum_seed_is_valid=False)
test_with_mnemonic("gentle combine cool hamster ghost harvest gossip lend dismiss slam any toast".split(), expects_electrum_seed_is_valid=False)
test_with_mnemonic("enough board blossom stamp fire buffalo digital solution sadness random number stone".split(), expects_electrum_seed_is_valid=False)
def test_export_xpub_standard_flow(self):
+41 -1
View File
@@ -1,8 +1,10 @@
from seedsigner.models.seed import Seed, ElectrumSeed
import pytest
from seedsigner.models.seed import InvalidSeedException, Seed, ElectrumSeed
from seedsigner.models.settings import SettingsConstants
# TODO: Change TAB indents to SPACE
def test_seed():
seed = Seed(mnemonic="obscure bone gas open exotic abuse virus bunker shuffle nasty ship dash".split())
@@ -37,9 +39,47 @@ def test_seed():
def test_electrum_seed():
"""
ElectrumSeed should correctly parse a modern Electrum mnemonic.
"""
seed = ElectrumSeed(mnemonic="regular reject rare profit once math fringe chase until ketchup century escape".split())
intended_seed = b'\xcan|\xf8\x8a\x8d\xf78=Pq\xc4_\xe6\x02\x91\xfcs\xb2[\xed*\xdc\xc7%\xb6[_-(~D\xe5\x1e\x85%N\x9c\x03\x9dh\xafX}\x16\xb1\x99,\xbe\xc4\x11\xfaW\x0f\xb0\x89yD\xf4\x0f\xd5?\x8eA'
assert seed.seed_bytes == intended_seed
def test_electrum_mnemonic_format():
"""
ElectrumSeed should reject mnemonics that are not 12 words long.
"""
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic=["regular"] * 11)
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic=["regular"] * 13)
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic=["regular"] * 24)
def test_electrum_seed_rejects_most_bip39_mnemonics():
"""
ElectrumSeed should throw an exception for most BIP-39 mnemonics.
There are 1/16 odds that a seed will be valid for both formats.
"""
# Most BIP-39 seeds should fail; test seeds generated by bitcoiner.guide
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic="pioneer divide volcano art victory family grow novel mandate bicycle senior adjust".split())
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic="gentle combine cool hamster ghost harvest gossip lend dismiss slam any toast".split())
with pytest.raises(InvalidSeedException):
ElectrumSeed(mnemonic="enough board blossom stamp fire buffalo digital solution sadness random number stone".split())
# This one is valid for both formats
mnemonic = "only gain spot output unknown craft simple cram absorb suggest ridge famous".split()
Seed(mnemonic)
ElectrumSeed(mnemonic)