Browse Source

create separate class for deterministic key generation. add pubkeys to validateaddress

283
thomasv 12 years ago
parent
commit
03e2160503
  1. 19
      electrum
  2. 50
      lib/bitcoin.py
  3. 4
      lib/gui_qt.py
  4. 138
      lib/wallet.py

19
electrum

@ -247,7 +247,7 @@ if __name__ == '__main__':
wallet.gap_limit = gap wallet.gap_limit = gap
if len(seed) == 128: if len(seed) == 128:
wallet.seed = '' wallet.seed = ''
wallet.master_public_key = seed wallet.sequence.master_public_key = seed
else: else:
wallet.init_seed(str(seed)) wallet.init_seed(str(seed))
@ -332,7 +332,7 @@ if __name__ == '__main__':
if len(seed) == 128: if len(seed) == 128:
wallet.seed = None wallet.seed = None
wallet.master_public_key = seed wallet.sequence.master_public_key = seed
else: else:
wallet.seed = str(seed) wallet.seed = str(seed)
wallet.init_mpk( wallet.seed ) wallet.init_mpk( wallet.seed )
@ -488,12 +488,12 @@ if __name__ == '__main__':
except: except:
sys.exit("Error: Error with seed file") sys.exit("Error: Error with seed file")
mpk = wallet.master_public_key mpk = wallet.get_master_public_key()
wallet.seed = seed wallet.seed = seed
wallet.imported_keys = imported_keys wallet.imported_keys = imported_keys
wallet.use_encryption = False wallet.use_encryption = False
wallet.init_mpk(seed) wallet.init_mpk(seed)
if mpk == wallet.master_public_key: if mpk == wallet.get_master_public_key():
wallet.save() wallet.save()
print_msg("Done: " + wallet.config.path) print_msg("Done: " + wallet.config.path)
else: else:
@ -501,7 +501,16 @@ if __name__ == '__main__':
elif cmd == 'validateaddress': elif cmd == 'validateaddress':
addr = args[1] 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': elif cmd == 'balance':
try: try:

50
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 ################################## transactions

4
lib/gui_qt.py

@ -1319,10 +1319,10 @@ class ElectrumWindow(QMainWindow):
dialog.setWindowTitle(_("Master Public Key")) dialog.setWindowTitle(_("Master Public Key"))
main_text = QTextEdit() 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.setReadOnly(True)
main_text.setMaximumHeight(170) 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 = QPushButton(_("OK"))
ok_button.setDefault(True) ok_button.setDefault(True)

138
lib/wallet.py

@ -32,7 +32,7 @@ import Queue
import time import time
from ecdsa.util import string_to_number, number_to_string 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 * from bitcoin import *
# URL decode # 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)) EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) 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 from version import ELECTRUM_VERSION, SEED_VERSION
@ -60,7 +81,6 @@ class Wallet:
self.use_change = config.get('use_change',True) self.use_change = config.get('use_change',True)
self.fee = int(config.get('fee',100000)) self.fee = int(config.get('fee',100000))
self.num_zeros = int(config.get('num_zeros',0)) 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.use_encryption = config.get('use_encryption', False)
self.addresses = config.get('addresses', []) # receiving addresses visible for user self.addresses = config.get('addresses', []) # receiving addresses visible for user
self.change_addresses = config.get('change_addresses', []) # addresses used as change 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.history = config.get('addr_history',{}) # address -> list(txid, height)
self.tx_height = config.get('tx_height',{}) self.tx_height = config.get('tx_height',{})
master_public_key = config.get('master_public_key','')
self.sequence = DeterministicSequence(master_public_key)
self.transactions = {} self.transactions = {}
tx = config.get('transactions',{}) tx = config.get('transactions',{})
try: try:
@ -122,19 +145,15 @@ class Wallet:
while not self.is_up_to_date(): time.sleep(0.1) while not self.is_up_to_date(): time.sleep(0.1)
def import_key(self, sec, password): def import_key(self, sec, password):
# try password # check password
try: seed = self.decode_seed(password)
seed = self.decode_seed(password)
except:
raise BaseException("Invalid password")
address = address_from_private_key(sec) address = address_from_private_key(sec)
if address in self.all_addresses(): if address in self.all_addresses():
raise BaseException('Address already in wallet') raise BaseException('Address already in wallet')
# store the originally requested keypair into the imported keys table # 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 return address
@ -149,11 +168,8 @@ class Wallet:
def init_mpk(self,seed): def init_mpk(self,seed):
# public key # public key
curve = SECP256k1 self.sequence = DeterministicSequence.from_seed(seed)
secexp = self.stretch_key(seed) self.config.set_key('master_public_key', self.sequence.master_public_key, True)
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)
def all_addresses(self): def all_addresses(self):
return self.addresses + self.change_addresses + self.imported_keys.keys() return self.addresses + self.change_addresses + self.imported_keys.keys()
@ -173,23 +189,35 @@ class Wallet:
return False return False
return addr == hash_160_to_bc_address(h, addrtype) return addr == hash_160_to_bc_address(h, addrtype)
def stretch_key(self,seed): def get_master_public_key(self):
oldseed = seed return self.sequence.master_public_key
for i in range(100000):
seed = hashlib.sha256(seed + oldseed).digest() def get_public_key(self, address):
return string_to_number( seed ) 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): 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) seed = self.decode_seed(password)
if address in self.imported_keys.keys(): 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: else:
if address in self.addresses: if address in self.addresses:
n = self.addresses.index(address) n = self.addresses.index(address)
@ -199,13 +227,8 @@ class Wallet:
for_change = True for_change = True
else: else:
raise BaseException("unknown address", address) raise BaseException("unknown address", address)
order = generator_secp256k1.order() return self.sequence.get_private_key(n, for_change, seed)
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 sign_message(self, address, message, password): def sign_message(self, address, message, password):
@ -225,16 +248,10 @@ class Wallet:
return address return address
def get_new_address(self, n, for_change): def get_new_address(self, n, for_change):
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """ pubkey = self.sequence.get_pubkey(n, for_change)
curve = SECP256k1 address = public_key_to_bc_address( pubkey.decode('hex') )
z = self.get_sequence(n, for_change) print_msg( address )
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
return address return address
def change_gap_limit(self, value): def change_gap_limit(self, value):
if value >= self.gap_limit: if value >= self.gap_limit:
@ -303,7 +320,7 @@ class Wallet:
def synchronize(self): def synchronize(self):
if not self.master_public_key: if not self.sequence.master_public_key:
return [] return []
new_addresses = [] new_addresses = []
new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False) new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
@ -516,39 +533,6 @@ class Wallet:
return outputs 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): def get_history(self, address):
with self.lock: with self.lock:
return self.history.get(address) return self.history.get(address)
@ -791,12 +775,12 @@ class Wallet:
def update_password(self, seed, old_password, new_password): def update_password(self, seed, old_password, new_password):
if new_password == '': new_password = None if new_password == '': new_password = None
self.use_encryption = (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) self.config.set_key('seed', self.seed, True)
for k in self.imported_keys.keys(): for k in self.imported_keys.keys():
a = self.imported_keys[k] a = self.imported_keys[k]
b = self.pw_decode(a, old_password) b = pw_decode(a, old_password)
c = self.pw_encode(b, new_password) c = pw_encode(b, new_password)
self.imported_keys[k] = c self.imported_keys[k] = c
self.save() self.save()

Loading…
Cancel
Save