Skip to content

Commit

Permalink
mv "electrum seed" stuff from bitcoin.py to mnemonic.py
Browse files Browse the repository at this point in the history
  • Loading branch information
SomberNight committed Feb 22, 2019
1 parent e7f3846 commit b39c51a
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 122 deletions.
9 changes: 5 additions & 4 deletions electrum/base_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

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,
Expand Down Expand Up @@ -429,12 +430,12 @@ def passphrase_dialog(self, run_next, is_restoring=False):
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('')
Expand All @@ -443,7 +444,7 @@ def on_restore_seed(self, seed, is_bip39, is_ext):
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:
Expand Down
50 changes: 0 additions & 50 deletions electrum/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 1 addition & 2 deletions electrum/gui/qt/seed_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
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,
Expand Down Expand Up @@ -161,7 +161,6 @@ def get_seed(self):
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:
Expand Down
4 changes: 2 additions & 2 deletions electrum/keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

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,
Expand All @@ -40,7 +40,7 @@
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


Expand Down
50 changes: 48 additions & 2 deletions electrum/mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@

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
Expand Down Expand Up @@ -181,3 +181,49 @@ def make_seed(self, seed_type='standard', num_bits=132):
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 changes: 3 additions & 3 deletions electrum/old_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1652,13 +1652,13 @@
"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.

Expand Down
4 changes: 2 additions & 2 deletions electrum/plugins/trustedcoin/trustedcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
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
Expand Down
50 changes: 2 additions & 48 deletions electrum/tests/test_bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
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,
Expand Down Expand Up @@ -719,49 +719,3 @@ def test_is_compressed_privkey(self):
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 changes: 48 additions & 1 deletion electrum/tests/test_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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
Expand Down Expand Up @@ -134,10 +134,57 @@ def test(self):
self.assertEqual(result, words.split())
self.assertEqual(old_mnemonic.mn_decode(result), seed)


class Test_BIP39Checksum(SequentialTestCase):

def test(self):
mnemonic = u'gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog'
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)
Loading

0 comments on commit b39c51a

Please sign in to comment.