encryption of bip32 master private keys
This commit is contained in:
parent
a3de537d9b
commit
58538ba825
|
@ -1,18 +1,21 @@
|
||||||
"""
|
#!/usr/bin/env python
|
||||||
todolist:
|
#
|
||||||
* passwords, private keys storage
|
# Electrum - lightweight Bitcoin client
|
||||||
* multisig service
|
# Copyright (C) 2013 thomasv@gitorious
|
||||||
* compatibility with old addresses for restore
|
#
|
||||||
* gui
|
# 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/>.
|
||||||
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
from bitcoin import *
|
from bitcoin import *
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ class Wallet:
|
||||||
self.create_new_account('Main account')
|
self.create_new_account('Main account')
|
||||||
|
|
||||||
|
|
||||||
def create_new_account(self, name):
|
def create_new_account(self, name, password):
|
||||||
keys = self.accounts.keys()
|
keys = self.accounts.keys()
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
|
@ -205,9 +205,9 @@ class Wallet:
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
start = "m/0'/"
|
start = "m/0'/"
|
||||||
|
master_k = self.get_master_private_key(start, password )
|
||||||
master_c, master_K, master_cK = self.master_public_keys[start]
|
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)
|
||||||
k, c, K, cK = bip32_private_derivation(master_k, master_c, start, derivation) # this is a type 1 derivation
|
|
||||||
|
|
||||||
self.accounts[derivation] = BIP32_Account({ 'name':name, 'c':c, 'K':K, 'cK':cK })
|
self.accounts[derivation] = BIP32_Account({ 'name':name, 'c':c, 'K':K, 'cK':cK })
|
||||||
self.save_accounts()
|
self.save_accounts()
|
||||||
|
@ -269,6 +269,17 @@ class Wallet:
|
||||||
raise
|
raise
|
||||||
return self.config.get("master_public_key")
|
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):
|
def get_address_index(self, address):
|
||||||
if address in self.imported_keys.keys():
|
if address in self.imported_keys.keys():
|
||||||
return -1, None
|
return -1, None
|
||||||
|
@ -291,8 +302,29 @@ class Wallet:
|
||||||
#todo: #self.sequences[0].check_seed(seed)
|
#todo: #self.sequences[0].check_seed(seed)
|
||||||
return seed
|
return seed
|
||||||
|
|
||||||
|
|
||||||
def get_private_key(self, address, password):
|
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):
|
def get_private_keys(self, addresses, password):
|
||||||
if not self.seed: return {}
|
if not self.seed: return {}
|
||||||
|
@ -300,29 +332,8 @@ class Wallet:
|
||||||
seed = self.decode_seed(password)
|
seed = self.decode_seed(password)
|
||||||
out = {}
|
out = {}
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
if address in self.imported_keys.keys():
|
pk = self.get_private_key(address, password)
|
||||||
out[address] = pw_decode( self.imported_keys[address], password )
|
if pk: out[address] = pk
|
||||||
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
|
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
@ -724,7 +735,7 @@ class Wallet:
|
||||||
if not self.use_change or account == -1:
|
if not self.use_change or account == -1:
|
||||||
change_addr = inputs[-1]['address']
|
change_addr = inputs[-1]['address']
|
||||||
else:
|
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
|
# Insert the change output at a random position in the outputs
|
||||||
posn = random.randint(0, len(outputs))
|
posn = random.randint(0, len(outputs))
|
||||||
|
@ -949,6 +960,12 @@ class Wallet:
|
||||||
self.imported_keys[k] = c
|
self.imported_keys[k] = c
|
||||||
self.config.set_key('imported_keys', self.imported_keys, True)
|
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):
|
def freeze(self,addr):
|
||||||
if self.is_mine(addr) and addr not in self.frozen_addresses:
|
if self.is_mine(addr) and addr not in self.frozen_addresses:
|
||||||
|
|
Loading…
Reference in New Issue