Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gruve-p committed Jan 23, 2024
2 parents da4ca77 + bd7adc8 commit 6d53ca1
Show file tree
Hide file tree
Showing 13 changed files with 54 additions and 47 deletions.
2 changes: 1 addition & 1 deletion electrum_grs/exchange_rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0,
'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0,
# Cryptocurrencies
'BTC': 8, 'LTC': 8, 'XRP': 6, 'ETH': 18,
'BTC': 8, 'LTC': 6, 'XRP': 4, 'ETH': 8,
}

SPOT_RATE_REFRESH_TARGET = 150 # approx. every 2.5 minutes, try to refresh spot price
Expand Down
4 changes: 3 additions & 1 deletion electrum_grs/gui/qml/qewallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ def isLightning(self):
billingInfoChanged = pyqtSignal()
@pyqtProperty('QVariantMap', notify=billingInfoChanged)
def billingInfo(self):
return {} if self.wallet.wallet_type != '2fa' else self.wallet.billing_info
if self.wallet.wallet_type != '2fa':
return {}
return self.wallet.billing_info if self.wallet.billing_info is not None else {}

@pyqtProperty(bool, notify=dataChanged)
def canHaveLightning(self):
Expand Down
1 change: 1 addition & 0 deletions electrum_grs/gui/qt/paytoedit.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ def line_edit_changed():
self.multiline = False

self._is_paytomany = False
self.line_edit.setFont(QFont(MONOSPACE_FONT))
self.text_edit.setFont(QFont(MONOSPACE_FONT))
self.send_tab = send_tab
self.config = send_tab.config
Expand Down
3 changes: 1 addition & 2 deletions electrum_grs/json_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,7 @@ def __init__(self, s: str, storage=None, encoder=None, upgrader=None):
if self.storage and self.storage.file_exists():
self.write_and_force_consolidation()

def load_data(self, s:str) -> dict:
""" overloaded in wallet_db """
def load_data(self, s: str) -> dict:
if s == '':
return {}
try:
Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ def from_seed(seed, passphrase, is_p2sh=False):
keystore = BIP32_KeyStore({})
keystore.add_seed(seed)
keystore.passphrase = passphrase
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase=passphrase)
if t == 'standard':
der = "m/"
xtype = 'standard'
Expand Down
34 changes: 18 additions & 16 deletions electrum_grs/mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import hashlib
import unicodedata
import string
from typing import Sequence, Dict
from typing import Sequence, Dict, Iterator, Optional
from types import MappingProxyType

from .util import resource_path, bfh, randrange
Expand Down Expand Up @@ -69,10 +69,11 @@
(0xA490, 0xA4CF, 'Yi Radicals'),
]

def is_CJK(c):
def is_CJK(c: str) -> bool:
n = ord(c)
for imin,imax,name in CJK_INTERVALS:
if n>=imin and n<=imax: return True
for imin, imax, name in CJK_INTERVALS:
if imin <= n <= imax:
return True
return False


Expand Down Expand Up @@ -110,13 +111,13 @@ def __init__(self, words: Sequence[str]):
index_from_word = {w: i for i, w in enumerate(words)}
self._index_from_word = MappingProxyType(index_from_word) # no mutation

def index(self, word, start=None, stop=None) -> int:
def index(self, word: str, start=None, stop=None) -> int:
try:
return self._index_from_word[word]
except KeyError as e:
raise ValueError from e

def __contains__(self, word) -> bool:
def __contains__(self, word: str) -> bool:
try:
self.index(word)
except ValueError:
Expand All @@ -125,7 +126,7 @@ def __contains__(self, word) -> bool:
return True

@classmethod
def from_file(cls, filename) -> 'Wordlist':
def from_file(cls, filename: str) -> 'Wordlist':
path = resource_path('wordlist', filename)
if path not in _WORDLIST_CACHE:
with open(path, 'r', encoding='utf-8') as f:
Expand Down Expand Up @@ -157,7 +158,7 @@ class Mnemonic(Logger):
# Seed derivation does not follow BIP39
# Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum

def __init__(self, lang=None):
def __init__(self, lang: str = None):
Logger.__init__(self)
lang = lang or 'en'
self.logger.info(f'language {lang}')
Expand All @@ -166,28 +167,28 @@ def __init__(self, lang=None):
self.logger.info(f"wordlist has {len(self.wordlist)} words")

@classmethod
def mnemonic_to_seed(self, mnemonic, passphrase) -> bytes:
def mnemonic_to_seed(cls, mnemonic: str, *, passphrase: Optional[str]) -> bytes:
PBKDF2_ROUNDS = 2048
mnemonic = normalize_text(mnemonic)
passphrase = passphrase or ''
passphrase = normalize_text(passphrase)
return hashlib.pbkdf2_hmac('sha512', mnemonic.encode('utf-8'), b'electrum' + passphrase.encode('utf-8'), iterations = PBKDF2_ROUNDS)

def mnemonic_encode(self, i):
def mnemonic_encode(self, i: int) -> str:
n = len(self.wordlist)
words = []
while i:
x = i%n
x = i % n
i = i//n
words.append(self.wordlist[x])
return ' '.join(words)

def get_suggestions(self, prefix):
def get_suggestions(self, prefix: str) -> Iterator[str]:
for w in self.wordlist:
if w.startswith(prefix):
yield w

def mnemonic_decode(self, seed):
def mnemonic_decode(self, seed: str) -> int:
n = len(self.wordlist)
words = seed.split()
i = 0
Expand All @@ -197,7 +198,7 @@ def mnemonic_decode(self, seed):
i = i*n + k
return i

def make_seed(self, *, seed_type=None, num_bits=None) -> str:
def make_seed(self, *, seed_type: str = None, num_bits: int = None) -> str:
from .keystore import bip39_is_checksum_valid
if seed_type is None:
seed_type = 'segwit'
Expand All @@ -208,10 +209,11 @@ def make_seed(self, *, seed_type=None, num_bits=None) -> str:
bpw = math.log(len(self.wordlist), 2)
num_bits = int(math.ceil(num_bits/bpw) * bpw)
self.logger.info(f"make_seed. prefix: '{prefix}', entropy: {num_bits} bits")
# generate random
entropy = 1
while entropy < pow(2, num_bits - bpw):
# try again if seed would not contain enough words
while entropy < pow(2, num_bits - bpw): # try again if seed would not contain enough words
entropy = randrange(pow(2, num_bits))
# brute-force seed that has correct "version number"
nonce = 0
while True:
nonce += 1
Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/plugins/trustedcoin/common_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def fetch_task():
t.daemon = True
t.start()

@pyqtSlot(str)
@pyqtSlot()
def createKeystore(self):
email = '[email protected]'

Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/plugins/trustedcoin/qml/ShowConfirmOTP.qml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ WizardComponent {

Component.onCompleted: {
plugin = AppController.plugin('trustedcoin')
plugin.createKeystore(wizard_data['2fa_email'])
plugin.createKeystore()
otp_auth.forceActiveFocus()
}

Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/plugins/trustedcoin/trustedcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def do_clear(self, window):
def get_xkeys(self, seed, t, passphrase, derivation):
assert is_any_2fa_seed_type(t)
xtype = 'standard' if t == '2fa' else 'p2wsh'
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase=passphrase)
rootnode = BIP32Node.from_rootseed(bip32_seed, xtype=xtype)
child_node = rootnode.subkey_at_private_derivation(derivation)
return child_node.to_xprv(), child_node.to_xpub()
Expand Down
10 changes: 8 additions & 2 deletions electrum_grs/tests/test_mnemonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,16 @@ def test_mnemonic_to_seed(self):
def test_random_seeds(self):
iters = 10
m = mnemonic.Mnemonic(lang='en')
pool = set()
for _ in range(iters):
seed = m.make_seed(seed_type="standard")
i = m.mnemonic_decode(seed)
self.assertEqual(m.mnemonic_encode(i), seed)
pool.add(seed)
with self.subTest(seed=seed, msg="decode-encode"):
i = m.mnemonic_decode(seed)
self.assertEqual(m.mnemonic_encode(i), seed)
with self.subTest(seed=seed, msg="num-words"):
self.assertTrue(12 <= len(seed.split()) <= 13)
self.assertEqual(iters, len(pool))


class Test_OldMnemonic(ElectrumTestCase):
Expand Down
19 changes: 7 additions & 12 deletions electrum_grs/tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from electrum_grs.bitcoin import COIN
from electrum_grs.wallet_db import WalletDB, JsonDB
from electrum_grs.simple_config import SimpleConfig
from electrum_grs import util
from electrum_grs.daemon import Daemon

from . import ElectrumTestCase

Expand Down Expand Up @@ -92,25 +92,20 @@ async def test_storage_imported_add_privkeys_persistence_test(self):
d = restore_wallet_from_text(text, path=self.wallet_path, config=self.config)
wallet = d['wallet'] # type: Imported_Wallet
self.assertEqual(2, len(wallet.get_receiving_addresses()))

wallet.save_db()
await wallet.stop()

# open the wallet anew again, and add a privkey. This should add the new data as a json_patch
wallet = None
storage = WalletStorage(self.wallet_path)
db = WalletDB(storage.read(), storage=storage, upgrade=True)
wallet = Wallet(db, config=self.config)
del wallet
wallet = Daemon._load_wallet(self.wallet_path, password=None, config=self.config)

wallet.import_private_keys(['p2wpkh:KzuqaaLp9zYjVuj8vQtCwFdiZFreW3NJNBachgVS8S9XMgj5y78b'], password=None)
self.assertEqual(3, len(wallet.get_receiving_addresses()))
self.assertEqual(3, len(wallet.keystore.keypairs))
wallet.save_db()
await wallet.stop()

# open the wallet anew again, and verify if the privkey was stored
wallet = None
storage = WalletStorage(self.wallet_path)
db = WalletDB(storage.read(), storage=storage, upgrade=True)
wallet = Wallet(db, config=self.config)
del wallet
wallet = Daemon._load_wallet(self.wallet_path, password=None, config=self.config)
self.assertEqual(3, len(wallet.get_receiving_addresses()))
self.assertEqual(3, len(wallet.keystore.keypairs))
self.assertTrue('03bf450797034dc95693096e575e3b3db14e5f074679b349b727f90fc7804ce7ab' in wallet.keystore.keypairs)
Expand Down
4 changes: 0 additions & 4 deletions electrum_grs/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
network: Optional['Network']

def __init__(self, db: WalletDB, *, config: SimpleConfig):

#if not db.is_ready_to_be_used_by_wallet():
# raise Exception("storage not ready to be used by Abstract_Wallet")

self.config = config
assert self.config is not None, "config must not be None"
self.db = db
Expand Down
16 changes: 11 additions & 5 deletions electrum_grs/wallet_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
from .plugin import run_hook, plugin_loaders
from .version import ELECTRUM_VERSION

if TYPE_CHECKING:
from .storage import WalletStorage


class WalletRequiresUpgrade(WalletFileException):
pass
Expand Down Expand Up @@ -1204,7 +1207,7 @@ def _raise_unsupported_version(self, seed_version):
raise WalletFileException(msg)


def upgrade_wallet_db(data: dict, do_upgrade) -> Tuple[dict, bool]:
def upgrade_wallet_db(data: dict, do_upgrade: bool) -> Tuple[dict, bool]:
was_upgraded = False

if len(data) == 0:
Expand Down Expand Up @@ -1232,7 +1235,13 @@ def upgrade_wallet_db(data: dict, do_upgrade) -> Tuple[dict, bool]:

class WalletDB(JsonDB):

def __init__(self, s, *, storage=None, upgrade=False):
def __init__(
self,
s: str,
*,
storage: Optional['WalletStorage'] = None,
upgrade: bool = False,
):
JsonDB.__init__(self, s, storage, encoder=MyEncoder, upgrader=partial(upgrade_wallet_db, do_upgrade=upgrade))
# create pointers
self.load_transactions()
Expand Down Expand Up @@ -1649,9 +1658,6 @@ def _should_convert_to_stored_dict(self, key) -> bool:
return False
return True

def is_ready_to_be_used_by_wallet(self):
return not self._requires_upgrade

@classmethod
def split_accounts(klass, root_path, split_data):
from .storage import WalletStorage
Expand Down

0 comments on commit 6d53ca1

Please sign in to comment.