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
|
assert len(__b43chars) == 43
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDecodeError(BitcoinException): pass
|
||||||
|
|
||||||
|
|
||||||
def base_encode(v: bytes, *, base: int) -> str:
|
def base_encode(v: bytes, *, base: int) -> str:
|
||||||
""" encode v, which is a string of bytes, to base58."""
|
""" encode v, which is a string of bytes, to base58."""
|
||||||
assert_bytes(v)
|
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]:
|
for c in v[::-1]:
|
||||||
digit = chars.find(bytes([c]))
|
digit = chars.find(bytes([c]))
|
||||||
if digit == -1:
|
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)
|
# naive but slow variant: long_value += digit * (base**i)
|
||||||
long_value += digit * power_of_base
|
long_value += digit * power_of_base
|
||||||
power_of_base *= 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)
|
return bytes(result)
|
||||||
|
|
||||||
|
|
||||||
class InvalidChecksum(Exception):
|
class InvalidChecksum(BaseDecodeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -633,18 +636,17 @@ def deserialize_privkey(key: str) -> Tuple[str, bytes, bool]:
|
|||||||
raise BitcoinException('unknown script type: {}'.format(txin_type))
|
raise BitcoinException('unknown script type: {}'.format(txin_type))
|
||||||
try:
|
try:
|
||||||
vch = DecodeBase58Check(key)
|
vch = DecodeBase58Check(key)
|
||||||
except BaseException:
|
except Exception as e:
|
||||||
neutered_privkey = str(key)[:3] + '..' + str(key)[-2:]
|
neutered_privkey = str(key)[:3] + '..' + str(key)[-2:]
|
||||||
raise BitcoinException("cannot deserialize privkey {}"
|
raise BaseDecodeError(f"cannot deserialize privkey {neutered_privkey}") from e
|
||||||
.format(neutered_privkey))
|
|
||||||
|
|
||||||
if txin_type is None:
|
if txin_type is None:
|
||||||
# keys exported in version 3.0.x encoded script type in first byte
|
# keys exported in version 3.0.x encoded script type in first byte
|
||||||
prefix_value = vch[0] - constants.net.WIF_PREFIX
|
prefix_value = vch[0] - constants.net.WIF_PREFIX
|
||||||
try:
|
try:
|
||||||
txin_type = WIF_SCRIPT_TYPES_INV[prefix_value]
|
txin_type = WIF_SCRIPT_TYPES_INV[prefix_value]
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
raise BitcoinException('invalid prefix ({}) for WIF key (1)'.format(vch[0]))
|
raise BitcoinException('invalid prefix ({}) for WIF key (1)'.format(vch[0])) from None
|
||||||
else:
|
else:
|
||||||
# all other keys must have a fixed first byte
|
# all other keys must have a fixed first byte
|
||||||
if vch[0] != constants.net.WIF_PREFIX:
|
if vch[0] != constants.net.WIF_PREFIX:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from functools import lru_cache
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
from . import bitcoin, ecc, constants, bip32
|
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 .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput, TxInput
|
||||||
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
|
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
|
||||||
is_xpub, is_xprv, BIP32Node, normalize_bip32_derivation,
|
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):
|
def get_private_key(self, pubkey: str, password):
|
||||||
sec = pw_decode(self.keypairs[pubkey], password, version=self.pw_hash_version)
|
sec = pw_decode(self.keypairs[pubkey], password, version=self.pw_hash_version)
|
||||||
txin_type, privkey, compressed = deserialize_privkey(sec)
|
try:
|
||||||
# this checks the password
|
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):
|
if pubkey != ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed):
|
||||||
raise InvalidPassword()
|
raise InvalidPassword()
|
||||||
return privkey, compressed
|
return privkey, compressed
|
||||||
@@ -529,7 +531,11 @@ class BIP32_KeyStore(Xpub, Deterministic_KeyStore):
|
|||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
xprv = pw_decode(self.xprv, password, version=self.pw_hash_version)
|
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()
|
raise InvalidPassword()
|
||||||
|
|
||||||
def update_password(self, old_password, new_password):
|
def update_password(self, old_password, new_password):
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ from io import StringIO
|
|||||||
from electrum.storage import WalletStorage
|
from electrum.storage import WalletStorage
|
||||||
from electrum.wallet_db import FINAL_SEED_VERSION
|
from electrum.wallet_db import FINAL_SEED_VERSION
|
||||||
from electrum.wallet import (Abstract_Wallet, Standard_Wallet, create_new_wallet,
|
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.exchange_rate import ExchangeBase, FxThread
|
||||||
from electrum.util import TxMinedInfo
|
from electrum.util import TxMinedInfo, InvalidPassword
|
||||||
from electrum.bitcoin import COIN
|
from electrum.bitcoin import COIN
|
||||||
from electrum.wallet_db import WalletDB
|
from electrum.wallet_db import WalletDB
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
@@ -229,3 +229,37 @@ class TestCreateRestoreWallet(WalletTestCase):
|
|||||||
# also test addr deletion
|
# also test addr deletion
|
||||||
wallet.delete_address('bc1qnp78h78vp92pwdwq5xvh8eprlga5q8gu66960c')
|
wallet.delete_address('bc1qnp78h78vp92pwdwq5xvh8eprlga5q8gu66960c')
|
||||||
self.assertEqual(1, len(wallet.get_receiving_addresses()))
|
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