keystore: improve check_password.
and add tests that exercise it maybe fixes #4128
This commit is contained in:
@@ -497,6 +497,9 @@ __b43chars = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$*+-./:'
|
||||
assert len(__b43chars) == 43
|
||||
|
||||
|
||||
class BaseDecodeError(BitcoinException): pass
|
||||
|
||||
|
||||
def base_encode(v: bytes, *, base: int) -> str:
|
||||
""" encode v, which is a string of bytes, to base58."""
|
||||
assert_bytes(v)
|
||||
@@ -544,7 +547,7 @@ def base_decode(v: Union[bytes, str], *, base: int, length: int = None) -> Optio
|
||||
for c in v[::-1]:
|
||||
digit = chars.find(bytes([c]))
|
||||
if digit == -1:
|
||||
raise ValueError('Forbidden character {} for base {}'.format(c, base))
|
||||
raise BaseDecodeError('Forbidden character {} for base {}'.format(c, base))
|
||||
# naive but slow variant: long_value += digit * (base**i)
|
||||
long_value += digit * power_of_base
|
||||
power_of_base *= base
|
||||
@@ -567,7 +570,7 @@ def base_decode(v: Union[bytes, str], *, base: int, length: int = None) -> Optio
|
||||
return bytes(result)
|
||||
|
||||
|
||||
class InvalidChecksum(Exception):
|
||||
class InvalidChecksum(BaseDecodeError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -633,18 +636,17 @@ def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]:
|
||||
raise BitcoinException('unknown script type: {}'.format(txin_type))
|
||||
try:
|
||||
vch = DecodeBase58Check(key)
|
||||
except BaseException:
|
||||
except Exception as e:
|
||||
neutered_privkey = str(key)[:3] + '..' + str(key)[-2:]
|
||||
raise BitcoinException("cannot deserialize privkey {}"
|
||||
.format(neutered_privkey))
|
||||
raise BaseDecodeError(f"cannot deserialize privkey {neutered_privkey}") from e
|
||||
|
||||
if txin_type is None:
|
||||
# keys exported in version 3.0.x encoded script type in first byte
|
||||
prefix_value = vch[0] - constants.net.WIF_PREFIX
|
||||
try:
|
||||
txin_type = WIF_SCRIPT_TYPES_INV[prefix_value]
|
||||
except KeyError:
|
||||
raise BitcoinException('invalid prefix ({}) for WIF key (1)'.format(vch[0]))
|
||||
except KeyError as e:
|
||||
raise BitcoinException('invalid prefix ({}) for WIF key (1)'.format(vch[0])) from None
|
||||
else:
|
||||
# all other keys must have a fixed first byte
|
||||
if vch[0] != constants.net.WIF_PREFIX:
|
||||
|
||||
@@ -32,7 +32,7 @@ from functools import lru_cache
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from . import bitcoin, ecc, constants, bip32
|
||||
from .bitcoin import deserialize_privkey, serialize_privkey
|
||||
from .bitcoin import deserialize_privkey, serialize_privkey, BaseDecodeError
|
||||
from .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, TxInput
|
||||
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
|
||||
is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation,
|
||||
@@ -251,8 +251,10 @@ class Imported_KeyStore(Software_KeyStore):
|
||||
|
||||
def get_private_key(self, pubkey: str, password):
|
||||
sec = pw_decode(self.keypairs[pubkey], password, version=self.pw_hash_version)
|
||||
txin_type, privkey, compressed = deserialize_privkey(sec)
|
||||
# this checks the password
|
||||
try:
|
||||
txin_type, privkey, compressed = deserialize_privkey(sec)
|
||||
except BaseDecodeError as e:
|
||||
raise InvalidPassword() from e
|
||||
if pubkey != ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed):
|
||||
raise InvalidPassword()
|
||||
return privkey, compressed
|
||||
@@ -529,7 +531,11 @@ class BIP32_KeyStore(Xpub, Deterministic_KeyStore):
|
||||
|
||||
def check_password(self, password):
|
||||
xprv = pw_decode(self.xprv, password, version=self.pw_hash_version)
|
||||
if BIP32Node.from_xkey(xprv).chaincode != self.get_bip32_node_for_xpub().chaincode:
|
||||
try:
|
||||
bip32node = BIP32Node.from_xkey(xprv)
|
||||
except BaseDecodeError as e:
|
||||
raise InvalidPassword() from e
|
||||
if bip32node.chaincode != self.get_bip32_node_for_xpub().chaincode:
|
||||
raise InvalidPassword()
|
||||
|
||||
def update_password(self, old_password, new_password):
|
||||
|
||||
@@ -10,9 +10,9 @@ from io import StringIO
|
||||
from electrum.storage import WalletStorage
|
||||
from electrum.wallet_db import FINAL_SEED_VERSION
|
||||
from electrum.wallet import (Abstract_Wallet, Standard_Wallet, create_new_wallet,
|
||||
restore_wallet_from_text, Imported_Wallet)
|
||||
restore_wallet_from_text, Imported_Wallet, Wallet)
|
||||
from electrum.exchange_rate import ExchangeBase, FxThread
|
||||
from electrum.util import TxMinedInfo
|
||||
from electrum.util import TxMinedInfo, InvalidPassword
|
||||
from electrum.bitcoin import COIN
|
||||
from electrum.wallet_db import WalletDB
|
||||
from electrum.simple_config import SimpleConfig
|
||||
@@ -229,3 +229,37 @@ class TestCreateRestoreWallet(WalletTestCase):
|
||||
# also test addr deletion
|
||||
wallet.delete_address('bc1qnp78h78vp92pwdwq5xvh8eprlga5q8gu66960c')
|
||||
self.assertEqual(1, len(wallet.get_receiving_addresses()))
|
||||
|
||||
|
||||
class TestWalletPassword(WalletTestCase):
|
||||
|
||||
def test_update_password_of_imported_wallet(self):
|
||||
wallet_str = '{"addr_history":{"1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr":[],"15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA":[],"1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6":[]},"addresses":{"change":[],"receiving":["1364Js2VG66BwRdkaoxAaFtdPb1eQgn8Dr","1Exet2BhHsFxKTwhnfdsBMkPYLGvobxuW6","15CyDgLffJsJgQrhcyooFH4gnVDG82pUrA"]},"keystore":{"keypairs":{"0344b1588589958b0bcab03435061539e9bcf54677c104904044e4f8901f4ebdf5":"L2sED74axVXC4H8szBJ4rQJrkfem7UMc6usLCPUoEWxDCFGUaGUM","0389508c13999d08ffae0f434a085f4185922d64765c0bff2f66e36ad7f745cc5f":"L3Gi6EQLvYw8gEEUckmqawkevfj9s8hxoQDFveQJGZHTfyWnbk1U","04575f52b82f159fa649d2a4c353eb7435f30206f0a6cb9674fbd659f45082c37d559ffd19bea9c0d3b7dcc07a7b79f4cffb76026d5d4dff35341efe99056e22d2":"5JyVyXU1LiRXATvRTQvR9Kp8Rx1X84j2x49iGkjSsXipydtByUq"},"type":"imported"},"pruned_txo":{},"seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[100,100,840,405]}'
|
||||
db = WalletDB(wallet_str, manual_upgrades=False)
|
||||
storage = WalletStorage(self.wallet_path)
|
||||
wallet = Wallet(db, storage, config=self.config)
|
||||
|
||||
wallet.check_password(None)
|
||||
|
||||
wallet.update_password(None, "1234")
|
||||
|
||||
with self.assertRaises(InvalidPassword):
|
||||
wallet.check_password(None)
|
||||
with self.assertRaises(InvalidPassword):
|
||||
wallet.check_password("wrong password")
|
||||
wallet.check_password("1234")
|
||||
|
||||
def test_update_password_of_standard_wallet(self):
|
||||
wallet_str = '''{"addr_history":{"12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes":[],"12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1":[],"13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB":[],"13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c":[],"14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz":[],"14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA":[],"15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV":[],"17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z":[],"18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv":[],"18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B":[],"19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz":[],"19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G":[],"1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq":[],"1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d":[],"1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs":[],"1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado":[],"1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z":[],"1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52":[],"1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP":[],"1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv":[],"1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb":[],"1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ":[],"1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G":[],"1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN":[],"1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J":[],"1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt":[]},"addresses":{"change":["1GhmpwqSF5cqNgdr9oJMZx8dKxPRo4pYPP","1GTDSjkVc9vaaBBBGNVqTANHJBcoT5VW9z","15FGuHvRssu1r8fCw98vrbpfc3M4xs5FAV","1A3XSmvLQvePmvm7yctsGkBMX9ZKKXLrVq","19z3j2ELqbg2pR87byCCt3BCyKR7rc3q8G","1JWySzjzJhsETUUcqVZHuvQLA7pfFfmesb"],"receiving":["14gmBxYV97mzYwWdJSJ3MTLbTHVegaKrcA","13HT1pfWctsSXVFzF76uYuVdQvcAQ2MAgB","19a98ZfEezDNbCwidVigV5PAJwrR2kw4Jz","1J5TTUQKhwehEACw6Jjte1E22FVrbeDmpv","1Pm8JBhzUJDqeQQKrmnop1Frr4phe1jbTt","13kG9WH9JqS7hyCcVL1ssLdNv4aXocQY9c","1KQHxcy3QUHAWMHKUtJjqD9cMKXcY2RTwZ","12ECgkzK6gHouKAZ7QiooYBuk1CgJLJxes","12iR43FPb5M7sw4Mcrr5y1nHKepg9EtZP1","14Tf3qiiHJXStSU4KmienAhHfHq7FHpBpz","1KqVEPXdpbYvEbwsZcEKkrA4A2jsgj9hYN","17oJzweA2gn6SDjsKgA9vUD5ocT1sSnr2Z","1E4ygSNJpWL2uPXZHBptmU2LqwZTqb1Ado","18hNcSjZzRcRP6J2bfFRxp9UfpMoC4hGTv","1KoxZfc2KsgovjGDxwqanbFEA76uxgYH4G","18n9PFxBjmKCGhd4PCDEEqYsi2CsnEfn2B","1CmhFe2BN1h9jheFpJf4v39XNPj8F9U6d","1DuphhHUayKzbkdvjVjf5dtjn2ACkz4zEs","1GWqgpThAuSq3tDg6uCoLQxPXQNnU8jZ52","1N16yDSYe76c5A3CoVoWAKxHeAUc8Jhf9J"]},"keystore":{"seed":"cereal wise two govern top pet frog nut rule sketch bundle logic","type":"bip32","xprv":"xprv9s21ZrQH143K29XjRjUs6MnDB9wXjXbJP2kG1fnRk8zjdDYWqVkQYUqaDtgZp5zPSrH5PZQJs8sU25HrUgT1WdgsPU8GbifKurtMYg37d4v","xpub":"xpub661MyMwAqRbcEdcCXm1sTViwjBn28zK9kFfrp4C3JUXiW1sfP34f6HA45B9yr7EH5XGzWuTfMTdqpt9XPrVQVUdgiYb5NW9m8ij1FSZgGBF"},"pruned_txo":{},"seed_type":"standard","seed_version":13,"stored_height":-1,"transactions":{},"tx_fees":{},"txi":{},"txo":{},"use_encryption":false,"verified_tx3":{},"wallet_type":"standard","winpos-qt":[619,310,840,405]}'''
|
||||
db = WalletDB(wallet_str, manual_upgrades=False)
|
||||
storage = WalletStorage(self.wallet_path)
|
||||
wallet = Wallet(db, storage, config=self.config)
|
||||
|
||||
wallet.check_password(None)
|
||||
|
||||
wallet.update_password(None, "1234")
|
||||
with self.assertRaises(InvalidPassword):
|
||||
wallet.check_password(None)
|
||||
with self.assertRaises(InvalidPassword):
|
||||
wallet.check_password("wrong password")
|
||||
wallet.check_password("1234")
|
||||
|
||||
Reference in New Issue
Block a user