Browse Source

mv "electrum seed" stuff from bitcoin.py to mnemonic.py

sqlite_db
SomberNight 6 years ago
parent
commit
b39c51adf7
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 9
      electrum/base_wizard.py
  2. 50
      electrum/bitcoin.py
  3. 3
      electrum/gui/qt/seed_dialog.py
  4. 4
      electrum/keystore.py
  5. 50
      electrum/mnemonic.py
  6. 6
      electrum/old_mnemonic.py
  7. 4
      electrum/plugins/trustedcoin/trustedcoin.py
  8. 50
      electrum/tests/test_bitcoin.py
  9. 49
      electrum/tests/test_mnemonic.py
  10. 17
      electrum/tests/test_wallet_vertical.py

9
electrum/base_wizard.py

@ -31,6 +31,7 @@ from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any
from . import bitcoin
from . import keystore
from . import mnemonic
from .bip32 import is_bip32_derivation, xpub_type
from .keystore import bip44_derivation, purpose48_derivation
from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet,
@ -429,12 +430,12 @@ class BaseWizard(object):
def restore_from_seed(self):
self.opt_bip39 = True
self.opt_ext = True
is_cosigning_seed = lambda x: bitcoin.seed_type(x) in ['standard', 'segwit']
test = bitcoin.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
is_cosigning_seed = lambda x: mnemonic.seed_type(x) in ['standard', 'segwit']
test = mnemonic.is_seed if self.wallet_type == 'standard' else is_cosigning_seed
self.restore_seed_dialog(run_next=self.on_restore_seed, test=test)
def on_restore_seed(self, seed, is_bip39, is_ext):
self.seed_type = 'bip39' if is_bip39 else bitcoin.seed_type(seed)
self.seed_type = 'bip39' if is_bip39 else mnemonic.seed_type(seed)
if self.seed_type == 'bip39':
f = lambda passphrase: self.on_restore_bip39(seed, passphrase)
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
@ -443,7 +444,7 @@ class BaseWizard(object):
self.passphrase_dialog(run_next=f, is_restoring=True) if is_ext else f('')
elif self.seed_type == 'old':
self.run('create_keystore', seed, '')
elif bitcoin.is_any_2fa_seed_type(self.seed_type):
elif mnemonic.is_any_2fa_seed_type(self.seed_type):
self.load_2fa()
self.run('on_restore_seed', seed, is_ext)
else:

50
electrum/bitcoin.py

@ -310,56 +310,6 @@ def hash_decode(x: str) -> bytes:
return bfh(x)[::-1]
################################## electrum seeds
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
from . import mnemonic
x = mnemonic.normalize_text(x)
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
return s.startswith(prefix)
def is_old_seed(seed: str) -> bool:
from . import old_mnemonic, mnemonic
seed = mnemonic.normalize_text(seed)
words = seed.split()
try:
# checks here are deliberately left weak for legacy reasons, see #3149
old_mnemonic.mn_decode(words)
uses_electrum_words = True
except Exception:
uses_electrum_words = False
try:
seed = bfh(seed)
is_hex = (len(seed) == 16 or len(seed) == 32)
except Exception:
is_hex = False
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
def seed_type(x: str) -> str:
if is_old_seed(x):
return 'old'
elif is_new_seed(x):
return 'standard'
elif is_new_seed(x, version.SEED_PREFIX_SW):
return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa'
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
return '2fa_segwit'
return ''
def is_seed(x: str) -> bool:
return bool(seed_type(x))
def is_any_2fa_seed_type(seed_type):
return seed_type in ['2fa', '2fa_segwit']
############ functions from pywallet #####################
def hash160_to_b58_address(h160: bytes, addrtype: int) -> str:

3
electrum/gui/qt/seed_dialog.py

@ -29,7 +29,7 @@ from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
QLabel, QCompleter, QDialog)
from electrum.i18n import _
from electrum.mnemonic import Mnemonic
from electrum.mnemonic import Mnemonic, seed_type
import electrum.old_mnemonic
from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
@ -161,7 +161,6 @@ class SeedLayout(QVBoxLayout):
return ' '.join(text.split())
def on_edit(self):
from electrum.bitcoin import seed_type
s = self.get_seed()
b = self.is_seed(s)
if not self.is_bip39:

4
electrum/keystore.py

@ -30,7 +30,7 @@ from typing import Tuple
from . import bitcoin, ecc, constants, bip32
from .bitcoin import (deserialize_privkey, serialize_privkey,
public_key_to_p2pkh, seed_type, is_seed)
public_key_to_p2pkh)
from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub,
bip32_root, deserialize_xprv, bip32_private_derivation,
bip32_private_key, bip32_derivation, BIP32_PRIME,
@ -40,7 +40,7 @@ from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATE
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
from .util import (PrintError, InvalidPassword, WalletFileException,
BitcoinException, bh2u, bfh, print_error, inv_dict)
from .mnemonic import Mnemonic, load_wordlist
from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed
from .plugin import run_hook

50
electrum/mnemonic.py

@ -30,8 +30,8 @@ import string
import ecdsa
from .util import print_error, resource_path
from .bitcoin import is_old_seed, is_new_seed
from .util import print_error, resource_path, bfh, bh2u
from .crypto import hmac_oneshot
from . import version
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html
@ -181,3 +181,49 @@ class Mnemonic(object):
break
print_error('%d words'%len(seed.split()))
return seed
def is_new_seed(x: str, prefix=version.SEED_PREFIX) -> bool:
x = normalize_text(x)
s = bh2u(hmac_oneshot(b"Seed version", x.encode('utf8'), hashlib.sha512))
return s.startswith(prefix)
def is_old_seed(seed: str) -> bool:
from . import old_mnemonic
seed = normalize_text(seed)
words = seed.split()
try:
# checks here are deliberately left weak for legacy reasons, see #3149
old_mnemonic.mn_decode(words)
uses_electrum_words = True
except Exception:
uses_electrum_words = False
try:
seed = bfh(seed)
is_hex = (len(seed) == 16 or len(seed) == 32)
except Exception:
is_hex = False
return is_hex or (uses_electrum_words and (len(words) == 12 or len(words) == 24))
def seed_type(x: str) -> str:
if is_old_seed(x):
return 'old'
elif is_new_seed(x):
return 'standard'
elif is_new_seed(x, version.SEED_PREFIX_SW):
return 'segwit'
elif is_new_seed(x, version.SEED_PREFIX_2FA):
return '2fa'
elif is_new_seed(x, version.SEED_PREFIX_2FA_SW):
return '2fa_segwit'
return ''
def is_seed(x: str) -> bool:
return bool(seed_type(x))
def is_any_2fa_seed_type(seed_type: str) -> bool:
return seed_type in ['2fa', '2fa_segwit']

6
electrum/old_mnemonic.py

@ -1652,13 +1652,13 @@ words = [
"total",
"unseen",
"weapon",
"weary"
"weary",
]
n = len(words)
assert n == 1626
n = 1626
# Note about US patent no 5892470: Here each word does not represent a given digit.
# Instead, the digit represented by a word is variable, it depends on the previous word.

4
electrum/plugins/trustedcoin/trustedcoin.py

@ -36,12 +36,12 @@ from urllib.parse import quote
from aiohttp import ClientResponse
from electrum import ecc, constants, keystore, version, bip32, bitcoin
from electrum.bitcoin import TYPE_ADDRESS, is_new_seed, seed_type, is_any_2fa_seed_type
from electrum.bitcoin import TYPE_ADDRESS
from electrum.bip32 import (deserialize_xpub, deserialize_xprv, bip32_private_key, CKD_pub,
serialize_xpub, bip32_root, bip32_private_derivation, xpub_type)
from electrum.crypto import sha256
from electrum.transaction import TxOutput
from electrum.mnemonic import Mnemonic
from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type
from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
from electrum.i18n import _
from electrum.plugin import BasePlugin, hook

50
electrum/tests/test_bitcoin.py

@ -2,11 +2,11 @@ import base64
import sys
from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
is_address, is_private_key, is_new_seed, is_old_seed,
is_address, is_private_key,
var_int, _op_push, address_to_script,
deserialize_privkey, serialize_privkey, is_segwit_address,
is_b58_address, address_to_scripthash, is_minikey,
is_compressed_privkey, seed_type, EncodeBase58Check,
is_compressed_privkey, EncodeBase58Check,
script_num_to_hex, push_script, add_number_to_script, int_to_hex,
opcodes)
from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation,
@ -719,49 +719,3 @@ class Test_keyImport(SequentialTestCase):
for priv_details in self.priv_pub_addr:
self.assertEqual(priv_details['compressed'],
is_compressed_privkey(priv_details['priv']))
class Test_seeds(SequentialTestCase):
""" Test old and new seeds. """
mnemonics = {
('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),
('cell dumb heartbeat north boom tease ' * 4, 'old'),
('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),
('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),
(' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'),
# below seed is actually 'invalid old' as it maps to 33 hex chars
('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),
('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),
('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),
(' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'),
('x8', 'standard'),
('science dawn member doll dutch real can brick knife deny drive list', '2fa'),
('science dawn member doll dutch real ca brick knife deny drive list', ''),
(' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'),
('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),
(' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),
('9dk', 'segwit'),
}
def test_new_seed(self):
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
self.assertTrue(is_new_seed(seed))
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
self.assertFalse(is_new_seed(seed))
def test_old_seed(self):
self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
self.assertFalse(is_old_seed("not a seed"))
self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
def test_seed_type(self):
for seed_words, _type in self.mnemonics:
self.assertEqual(_type, seed_type(seed_words), msg=seed_words)

49
electrum/tests/test_mnemonic.py

@ -4,7 +4,7 @@ from electrum import keystore
from electrum import mnemonic
from electrum import old_mnemonic
from electrum.util import bh2u, bfh
from electrum.bitcoin import is_new_seed
from electrum.mnemonic import is_new_seed, is_old_seed, seed_type
from electrum.version import SEED_PREFIX_SW, SEED_PREFIX
from . import SequentialTestCase
@ -134,6 +134,7 @@ class Test_OldMnemonic(SequentialTestCase):
self.assertEqual(result, words.split())
self.assertEqual(old_mnemonic.mn_decode(result), seed)
class Test_BIP39Checksum(SequentialTestCase):
def test(self):
@ -141,3 +142,49 @@ class Test_BIP39Checksum(SequentialTestCase):
is_checksum_valid, is_wordlist_valid = keystore.bip39_is_checksum_valid(mnemonic)
self.assertTrue(is_wordlist_valid)
self.assertTrue(is_checksum_valid)
class Test_seeds(SequentialTestCase):
""" Test old and new seeds. """
mnemonics = {
('cell dumb heartbeat north boom tease ship baby bright kingdom rare squeeze', 'old'),
('cell dumb heartbeat north boom tease ' * 4, 'old'),
('cell dumb heartbeat north boom tease ship baby bright kingdom rare badword', ''),
('cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE', 'old'),
(' cElL DuMb hEaRtBeAt nOrTh bOoM TeAsE ShIp bAbY BrIgHt kInGdOm rArE SqUeEzE ', 'old'),
# below seed is actually 'invalid old' as it maps to 33 hex chars
('hurry idiot prefer sunset mention mist jaw inhale impossible kingdom rare squeeze', 'old'),
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform able', 'standard'),
('cram swing cover prefer miss modify ritual silly deliver chunk behind inform', ''),
('ostrich security deer aunt climb inner alpha arm mutual marble solid task', 'standard'),
('OSTRICH SECURITY DEER AUNT CLIMB INNER ALPHA ARM MUTUAL MARBLE SOLID TASK', 'standard'),
(' oStRiCh sEcUrItY DeEr aUnT ClImB InNeR AlPhA ArM MuTuAl mArBlE SoLiD TaSk ', 'standard'),
('x8', 'standard'),
('science dawn member doll dutch real can brick knife deny drive list', '2fa'),
('science dawn member doll dutch real ca brick knife deny drive list', ''),
(' sCience dawn member doll Dutch rEAl can brick knife deny drive lisT', '2fa'),
('frost pig brisk excite novel report camera enlist axis nation novel desert', 'segwit'),
(' fRoSt pig brisk excIte novel rePort CamEra enlist axis nation nOVeL dEsert ', 'segwit'),
('9dk', 'segwit'),
}
def test_new_seed(self):
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform able"
self.assertTrue(is_new_seed(seed))
seed = "cram swing cover prefer miss modify ritual silly deliver chunk behind inform"
self.assertFalse(is_new_seed(seed))
def test_old_seed(self):
self.assertTrue(is_old_seed(" ".join(["like"] * 12)))
self.assertFalse(is_old_seed(" ".join(["like"] * 18)))
self.assertTrue(is_old_seed(" ".join(["like"] * 24)))
self.assertFalse(is_old_seed("not a seed"))
self.assertTrue(is_old_seed("0123456789ABCDEF" * 2))
self.assertTrue(is_old_seed("0123456789ABCDEF" * 4))
def test_seed_type(self):
for seed_words, _type in self.mnemonics:
self.assertEqual(_type, seed_type(seed_words), msg=seed_words)

17
electrum/tests/test_wallet_vertical.py

@ -11,6 +11,7 @@ from electrum.address_synchronizer import TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCON
from electrum.wallet import sweep, Multisig_Wallet, Standard_Wallet, Imported_Wallet
from electrum.util import bfh, bh2u
from electrum.transaction import TxOutput
from electrum.mnemonic import seed_type
from electrum.plugins.trustedcoin import trustedcoin
@ -80,7 +81,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_standard(self, mock_write):
seed_words = 'cycle rocket west magnet parrot shuffle foot correct salt library feed song'
self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
self.assertEqual(seed_type(seed_words), 'standard')
ks = keystore.from_seed(seed_words, '', False)
@ -100,7 +101,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_segwit(self, mock_write):
seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
self.assertEqual(seed_type(seed_words), 'segwit')
ks = keystore.from_seed(seed_words, '', False)
@ -120,7 +121,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_segwit_passphrase(self, mock_write):
seed_words = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver'
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
self.assertEqual(seed_type(seed_words), 'segwit')
ks = keystore.from_seed(seed_words, UNICODE_HORROR, False)
@ -140,7 +141,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_old(self, mock_write):
seed_words = 'powerful random nobody notice nothing important anyway look away hidden message over'
self.assertEqual(bitcoin.seed_type(seed_words), 'old')
self.assertEqual(seed_type(seed_words), 'old')
ks = keystore.from_seed(seed_words, '', False)
@ -159,7 +160,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_2fa_legacy(self, mock_write):
seed_words = 'kiss live scene rude gate step hip quarter bunker oxygen motor glove'
self.assertEqual(bitcoin.seed_type(seed_words), '2fa')
self.assertEqual(seed_type(seed_words), '2fa')
xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')
@ -194,7 +195,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_seed_2fa_segwit(self, mock_write):
seed_words = 'universe topic remind silver february ranch shine worth innocent cattle enhance wise'
self.assertEqual(bitcoin.seed_type(seed_words), '2fa_segwit')
self.assertEqual(seed_type(seed_words), '2fa_segwit')
xprv1, xpub1, xprv2, xpub2 = trustedcoin.TrustedCoinPlugin.xkeys_from_seed(seed_words, '')
@ -306,7 +307,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_multisig_seed_standard(self, mock_write):
seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'
self.assertEqual(bitcoin.seed_type(seed_words), 'standard')
self.assertEqual(seed_type(seed_words), 'standard')
ks1 = keystore.from_seed(seed_words, '', True)
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)
@ -329,7 +330,7 @@ class TestWalletKeystoreAddressIntegrityForMainnet(SequentialTestCase):
@mock.patch.object(storage.WalletStorage, '_write')
def test_electrum_multisig_seed_segwit(self, mock_write):
seed_words = 'snow nest raise royal more walk demise rotate smooth spirit canyon gun'
self.assertEqual(bitcoin.seed_type(seed_words), 'segwit')
self.assertEqual(seed_type(seed_words), 'segwit')
ks1 = keystore.from_seed(seed_words, '', True)
WalletIntegrityHelper.check_seeded_keystore_sanity(self, ks1)

Loading…
Cancel
Save