From 03e216050365a39dc9c1aeb52ff964c88509ce93 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 11:35:46 +0100 Subject: [PATCH] create separate class for deterministic key generation. add pubkeys to validateaddress --- electrum | 19 +++++-- lib/bitcoin.py | 50 ++++++++++++++++++ lib/gui_qt.py | 4 +- lib/wallet.py | 138 ++++++++++++++++++++++--------------------------- 4 files changed, 127 insertions(+), 84 deletions(-) diff --git a/electrum b/electrum index 46ef786b1..7d5bcce5f 100755 --- a/electrum +++ b/electrum @@ -247,7 +247,7 @@ if __name__ == '__main__': wallet.gap_limit = gap if len(seed) == 128: wallet.seed = '' - wallet.master_public_key = seed + wallet.sequence.master_public_key = seed else: wallet.init_seed(str(seed)) @@ -332,7 +332,7 @@ if __name__ == '__main__': if len(seed) == 128: wallet.seed = None - wallet.master_public_key = seed + wallet.sequence.master_public_key = seed else: wallet.seed = str(seed) wallet.init_mpk( wallet.seed ) @@ -488,12 +488,12 @@ if __name__ == '__main__': except: sys.exit("Error: Error with seed file") - mpk = wallet.master_public_key + mpk = wallet.get_master_public_key() wallet.seed = seed wallet.imported_keys = imported_keys wallet.use_encryption = False wallet.init_mpk(seed) - if mpk == wallet.master_public_key: + if mpk == wallet.get_master_public_key(): wallet.save() print_msg("Done: " + wallet.config.path) else: @@ -501,7 +501,16 @@ if __name__ == '__main__': elif cmd == 'validateaddress': addr = args[1] - print_msg(wallet.is_valid(addr)) + is_valid = wallet.is_valid(addr) + out = { 'isvalid':is_valid } + if is_valid: + is_mine = wallet.is_mine(addr) + out['address'] = addr + out['ismine'] = is_mine + if is_mine: + out['pubkey'] = wallet.get_public_key(addr) + + print_json(out) elif cmd == 'balance': try: diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 5cb240ff4..1fd840d1b 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -398,7 +398,57 @@ def CKD_prime(K, c, n): +class DeterministicSequence: + """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ + def __init__(self, master_public_key): + self.master_public_key = master_public_key + + @classmethod + def from_seed(klass, seed): + curve = SECP256k1 + secexp = klass.stretch_key(seed) + master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') + self = klass(master_public_key) + return self + + @classmethod + def stretch_key(self,seed): + oldseed = seed + for i in range(100000): + seed = hashlib.sha256(seed + oldseed).digest() + return string_to_number( seed ) + + def get_sequence(self,n,for_change): + return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) ) + + def get_pubkey(self, n, for_change): + curve = SECP256k1 + z = self.get_sequence(n, for_change) + master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), curve = SECP256k1 ) + pubkey_point = master_public_key.pubkey.point + z*curve.generator + public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) + return '04' + public_key2.to_string().encode('hex') + + def get_private_key(self, n, for_change, seed): + order = generator_secp256k1.order() + secexp = self.stretch_key(seed) + secexp = ( secexp + self.get_sequence(n,for_change) ) % order + pk = number_to_string( secexp, generator_secp256k1.order() ) + compressed = False + return SecretToASecret( pk, compressed ) + + def check_seed(self, seed): + curve = SECP256k1 + secexp = self.stretch_key(seed) + master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') + if master_public_key != self.master_public_key: + print_error('invalid password (mpk)') + raise BaseException('Invalid password') + + return True ################################## transactions diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 68bcb8669..859564b3c 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1319,10 +1319,10 @@ class ElectrumWindow(QMainWindow): dialog.setWindowTitle(_("Master Public Key")) main_text = QTextEdit() - main_text.setText(self.wallet.master_public_key) + main_text.setText(self.wallet.get_master_public_key()) main_text.setReadOnly(True) main_text.setMaximumHeight(170) - qrw = QRCodeWidget(self.wallet.master_public_key, 6) + qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6) ok_button = QPushButton(_("OK")) ok_button.setDefault(True) diff --git a/lib/wallet.py b/lib/wallet.py index bf26257d3..ebe290a30 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -32,7 +32,7 @@ import Queue import time from ecdsa.util import string_to_number, number_to_string -from util import print_error, user_dir, format_satoshis +from util import print_msg, print_error, user_dir, format_satoshis from bitcoin import * # URL decode @@ -43,6 +43,27 @@ urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) +def pw_encode(s, password): + if password: + secret = Hash(password) + return EncodeAES(secret, s) + else: + return s + +def pw_decode(s, password): + if password is not None: + secret = Hash(password) + try: + d = DecodeAES(secret, s) + except: + raise BaseException('Invalid password') + return d + else: + return s + + + + from version import ELECTRUM_VERSION, SEED_VERSION @@ -60,7 +81,6 @@ class Wallet: self.use_change = config.get('use_change',True) self.fee = int(config.get('fee',100000)) self.num_zeros = int(config.get('num_zeros',0)) - self.master_public_key = config.get('master_public_key','') self.use_encryption = config.get('use_encryption', False) self.addresses = config.get('addresses', []) # receiving addresses visible for user self.change_addresses = config.get('change_addresses', []) # addresses used as change @@ -76,6 +96,9 @@ class Wallet: self.history = config.get('addr_history',{}) # address -> list(txid, height) self.tx_height = config.get('tx_height',{}) + master_public_key = config.get('master_public_key','') + self.sequence = DeterministicSequence(master_public_key) + self.transactions = {} tx = config.get('transactions',{}) try: @@ -122,19 +145,15 @@ class Wallet: while not self.is_up_to_date(): time.sleep(0.1) def import_key(self, sec, password): - # try password - try: - seed = self.decode_seed(password) - except: - raise BaseException("Invalid password") - + # check password + seed = self.decode_seed(password) address = address_from_private_key(sec) if address in self.all_addresses(): raise BaseException('Address already in wallet') # store the originally requested keypair into the imported keys table - self.imported_keys[address] = self.pw_encode(sec, password ) + self.imported_keys[address] = pw_encode(sec, password ) return address @@ -149,11 +168,8 @@ class Wallet: def init_mpk(self,seed): # public key - curve = SECP256k1 - secexp = self.stretch_key(seed) - master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - self.master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') - self.config.set_key('master_public_key', self.master_public_key, True) + self.sequence = DeterministicSequence.from_seed(seed) + self.config.set_key('master_public_key', self.sequence.master_public_key, True) def all_addresses(self): return self.addresses + self.change_addresses + self.imported_keys.keys() @@ -173,23 +189,35 @@ class Wallet: return False return addr == hash_160_to_bc_address(h, addrtype) - def stretch_key(self,seed): - oldseed = seed - for i in range(100000): - seed = hashlib.sha256(seed + oldseed).digest() - return string_to_number( seed ) + def get_master_public_key(self): + return self.sequence.master_public_key + + def get_public_key(self, address): + if address in self.imported_keys.keys(): + raise BaseException("imported key") + + if address in self.addresses: + n = self.addresses.index(address) + for_change = False + elif address in self.change_addresses: + n = self.change_addresses.index(address) + for_change = True + + return self.sequence.get_pubkey(n, for_change) - def get_sequence(self,n,for_change): - return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) ) + def decode_seed(self, password): + seed = pw_decode(self.seed, password) + self.sequence.check_seed(seed) + return seed + def get_private_key(self, address, password): - """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - # decode seed in any case, in order to make test the password + # decode seed in any case, in order to test the password seed = self.decode_seed(password) if address in self.imported_keys.keys(): - return self.pw_decode( self.imported_keys[address], password ) + return pw_decode( self.imported_keys[address], password ) else: if address in self.addresses: n = self.addresses.index(address) @@ -199,13 +227,8 @@ class Wallet: for_change = True else: raise BaseException("unknown address", address) - - order = generator_secp256k1.order() - secexp = self.stretch_key(seed) - secexp = ( secexp + self.get_sequence(n,for_change) ) % order - pk = number_to_string( secexp, generator_secp256k1.order() ) - compressed = False - return SecretToASecret( pk, compressed ) + + return self.sequence.get_private_key(n, for_change, seed) def sign_message(self, address, message, password): @@ -225,16 +248,10 @@ class Wallet: return address def get_new_address(self, n, for_change): - """ Publickey(type,n) = Master_public_key + H(n|S|type)*point """ - curve = SECP256k1 - z = self.get_sequence(n, for_change) - master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), curve = SECP256k1 ) - pubkey_point = master_public_key.pubkey.point + z*curve.generator - public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) - address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() ) - print address + pubkey = self.sequence.get_pubkey(n, for_change) + address = public_key_to_bc_address( pubkey.decode('hex') ) + print_msg( address ) return address - def change_gap_limit(self, value): if value >= self.gap_limit: @@ -303,7 +320,7 @@ class Wallet: def synchronize(self): - if not self.master_public_key: + if not self.sequence.master_public_key: return [] new_addresses = [] new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False) @@ -516,39 +533,6 @@ class Wallet: return outputs - def pw_encode(self, s, password): - if password: - secret = Hash(password) - return EncodeAES(secret, s) - else: - return s - - def pw_decode(self, s, password): - if password is not None: - secret = Hash(password) - try: - d = DecodeAES(secret, s) - except: - raise BaseException('Invalid password') - return d - else: - return s - - def decode_seed(self, password): - seed = self.pw_decode(self.seed, password) - - # check decoded seed with master public key - curve = SECP256k1 - secexp = self.stretch_key(seed) - master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') - if master_public_key != self.master_public_key: - print_error('invalid password (mpk)') - raise BaseException('Invalid password') - - return seed - - def get_history(self, address): with self.lock: return self.history.get(address) @@ -791,12 +775,12 @@ class Wallet: def update_password(self, seed, old_password, new_password): if new_password == '': new_password = None self.use_encryption = (new_password != None) - self.seed = self.pw_encode( seed, new_password) + self.seed = pw_encode( seed, new_password) self.config.set_key('seed', self.seed, True) for k in self.imported_keys.keys(): a = self.imported_keys[k] - b = self.pw_decode(a, old_password) - c = self.pw_encode(b, new_password) + b = pw_decode(a, old_password) + c = pw_encode(b, new_password) self.imported_keys[k] = c self.save()