create separate class for deterministic key generation. add pubkeys to validateaddress
authorthomasv <thomasv@gitorious>
Sat, 23 Feb 2013 10:35:46 +0000 (11:35 +0100)
committerthomasv <thomasv@gitorious>
Sat, 23 Feb 2013 10:35:46 +0000 (11:35 +0100)
electrum
lib/bitcoin.py
lib/gui_qt.py
lib/wallet.py

index 46ef786..7d5bcce 100755 (executable)
--- 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:
index 5cb240f..1fd840d 100644 (file)
@@ -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
 
index 68bcb86..859564b 100644 (file)
@@ -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)
index bf26257..ebe290a 100644 (file)
@@ -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()