From 58538ba825c199b4f3286cc3d7f3b3d2a33473e3 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 5 Aug 2013 13:53:50 +0200 Subject: [PATCH] encryption of bip32 master private keys --- lib/account.py | 33 ++++++++++++----------- lib/wallet.py | 73 +++++++++++++++++++++++++++++++------------------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/lib/account.py b/lib/account.py index 85a3471f3..63c5a2981 100644 --- a/lib/account.py +++ b/lib/account.py @@ -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 . + from bitcoin import * diff --git a/lib/wallet.py b/lib/wallet.py index 45c8d9d8d..4189c0b20 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -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: