encryption of bip32 master private keys
authorThomasV <thomasv@gitorious>
Mon, 5 Aug 2013 11:53:50 +0000 (13:53 +0200)
committerThomasV <thomasv@gitorious>
Wed, 7 Aug 2013 20:59:07 +0000 (22:59 +0200)
lib/account.py
lib/wallet.py

index 85a3471..63c5a29 100644 (file)
@@ -1,18 +1,21 @@
-"""
-todolist:
- * passwords, private keys storage
- * multisig service
- * compatibility with old addresses for restore
- * gui
-        an account may use one or several MPKs.
-        due to the type 1 derivations, we need to pass the mpk to this function
-        None : all accounts
-        -1 : imported
-        0,1... : seeded sequences
-
-        each account has a public and private master key
-"""
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2013 thomasv@gitorious
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
 
 from bitcoin import *
 
index 45c8d9d..4189c0b 100644 (file)
@@ -195,7 +195,7 @@ class Wallet:
         self.create_new_account('Main account')
 
 
-    def create_new_account(self, name):
+    def create_new_account(self, name, password):
         keys = self.accounts.keys()
         i = 0
 
@@ -205,9 +205,9 @@ class Wallet:
             i += 1
 
         start = "m/0'/"
+        master_k = self.get_master_private_key(start, password )
         master_c, master_K, master_cK = self.master_public_keys[start]
-        master_k = self.master_private_keys[start] # needs decryption
-        k, c, K, cK = bip32_private_derivation(master_k, master_c, start, derivation) # this is a type 1 derivation
+        k, c, K, cK = bip32_private_derivation(master_k, master_c, start, derivation)
         
         self.accounts[derivation] = BIP32_Account({ 'name':name, 'c':c, 'K':K, 'cK':cK })
         self.save_accounts()
@@ -269,6 +269,17 @@ class Wallet:
         raise
         return self.config.get("master_public_key")
 
+    def get_master_private_key(self, account, password):
+        master_k = pw_decode( self.master_private_keys[account], password)
+        master_c, master_K, master_Kc = self.master_public_keys[account]
+        try:
+            K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
+            assert K.encode('hex') == master_K
+        except:
+            raise BaseException("Invalid password")
+        return master_k
+
+
     def get_address_index(self, address):
         if address in self.imported_keys.keys():
             return -1, None
@@ -291,8 +302,29 @@ class Wallet:
         #todo:  #self.sequences[0].check_seed(seed)
         return seed
         
+
     def get_private_key(self, address, password):
-        return self.get_private_keys([address], password).get(address)
+        if address in self.imported_keys.keys():
+            return pw_decode( self.imported_keys[address], password )
+        else:
+            account, sequence = self.get_address_index(address)
+            m = re.match("m/0'/(\d+)'", account)
+            if m:
+                num = int(m.group(1))
+                master_k = self.get_master_private_key("m/0'/", password)
+                master_c, _, _ = self.master_public_keys["m/0'/"]
+                master_k, master_c = CKD(master_k, master_c, num + BIP32_PRIME)
+                return self.accounts[account].get_private_key(sequence, master_k)
+                
+            m2 = re.match("m/1'/(\d+) & m/2'/(\d+)", account)
+            if m2:
+                num = int(m2.group(1))
+                master_k = self.get_master_private_key("m/1'/", password)
+                master_c, master_K, _ = self.master_public_keys["m/1'/"]
+                master_k, master_c = CKD(master_k.decode('hex'), master_c.decode('hex'), num)
+                return self.accounts[account].get_private_key(sequence, master_k)
+        return
+
 
     def get_private_keys(self, addresses, password):
         if not self.seed: return {}
@@ -300,29 +332,8 @@ class Wallet:
         seed = self.decode_seed(password)
         out = {}
         for address in addresses:
-            if address in self.imported_keys.keys():
-                out[address] = pw_decode( self.imported_keys[address], password )
-            else:
-                account, sequence = self.get_address_index(address)
-                print_error( "found index", address, account, sequence)
-
-                m = re.match("m/0'/(\d+)'", account)
-                if m:
-                    num = int(m.group(1))
-                    master_k = self.master_private_keys["m/0'/"]
-                    master_c, _, _ = self.master_public_keys["m/0'/"]
-                    master_k, master_c = CKD(master_k, master_c, num + BIP32_PRIME)
-                    pk = self.accounts[account].get_private_key(sequence, master_k)
-                    out[address] = pk
-
-                m2 = re.match("m/1'/(\d+) & m/2'/(\d+)", account)
-                if m2:
-                    num = int(m2.group(1))
-                    master_k = self.master_private_keys["m/1'/"]
-                    master_c, master_K, _ = self.master_public_keys["m/1'/"]
-                    master_k, master_c = CKD(master_k.decode('hex'), master_c.decode('hex'), num)
-                    pk = self.accounts[account].get_private_key(sequence, master_k)
-                    out[address] = pk
+            pk = self.get_private_key(address, password)
+            if pk: out[address] = pk
 
         return out
 
@@ -724,7 +735,7 @@ class Wallet:
                 if not self.use_change or account == -1:
                     change_addr = inputs[-1]['address']
                 else:
-                    change_addr = self.accounts[account][1][-self.gap_limit_for_change]
+                    change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
 
             # Insert the change output at a random position in the outputs
             posn = random.randint(0, len(outputs))
@@ -949,6 +960,12 @@ class Wallet:
             self.imported_keys[k] = c
         self.config.set_key('imported_keys', self.imported_keys, True)
 
+        for k, v in self.master_private_keys.items():
+            b = pw_decode(v, old_password)
+            c = pw_encode(b, new_password)
+            self.master_private_keys[k] = c
+        self.config.set_key('master_private_keys', self.master_private_keys, True)
+
 
     def freeze(self,addr):
         if self.is_mine(addr) and addr not in self.frozen_addresses: