update bip32 accounts and wallet

This commit is contained in:
ThomasV 2014-04-01 11:25:12 +02:00
parent ffc72e65df
commit 5a43b633d6
5 changed files with 193 additions and 241 deletions

View File

@ -90,7 +90,7 @@ def arg_parser():
parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
#parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32 (not final)")
parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32 (not final)")
parser.add_option("--mpk", dest="mpk", default=False, help="restore from master public key")
return parser

View File

@ -1477,7 +1477,8 @@ class ElectrumWindow(QMainWindow):
self.tabs.setCurrentIndex(3)
def new_account_dialog(self):
@protected
def new_account_dialog(self, password):
dialog = QDialog(self)
dialog.setModal(1)
@ -1501,7 +1502,7 @@ class ElectrumWindow(QMainWindow):
name = str(e.text())
if not name: return
self.wallet.create_pending_account('1', name)
self.wallet.create_pending_account('1of1', name, password)
self.update_receive_tab()
self.tabs.setCurrentIndex(2)
@ -1545,11 +1546,6 @@ class ElectrumWindow(QMainWindow):
dialog.setModal(1)
dialog.setWindowTitle(_("Master Public Keys"))
chain_text = QTextEdit()
chain_text.setReadOnly(True)
chain_text.setMaximumHeight(170)
chain_qrw = QRCodeWidget()
mpk_text = QTextEdit()
mpk_text.setReadOnly(True)
mpk_text.setMaximumHeight(170)
@ -1561,17 +1557,10 @@ class ElectrumWindow(QMainWindow):
main_layout.addWidget(mpk_text, 1, 1)
main_layout.addWidget(mpk_qrw, 1, 2)
main_layout.addWidget(QLabel(_('Chain')), 2, 0)
main_layout.addWidget(chain_text, 2, 1)
main_layout.addWidget(chain_qrw, 2, 2)
def update(key):
c, K, cK = self.wallet.master_public_keys[str(key)]
chain_text.setText(c)
chain_qrw.set_addr(c)
chain_qrw.update_qr()
mpk_text.setText(K)
mpk_qrw.set_addr(K)
xpub = self.wallet.master_public_keys[str(key)]
mpk_text.setText(xpub)
mpk_qrw.set_addr(xpub)
mpk_qrw.update_qr()
key_selector = QComboBox()
@ -1683,6 +1672,7 @@ class ElectrumWindow(QMainWindow):
try:
pk_list = self.wallet.get_private_key(address, password)
except Exception as e:
traceback.print_exc(file=sys.stdout)
self.show_message(str(e))
return
@ -2269,30 +2259,30 @@ class ElectrumWindow(QMainWindow):
def show_account_details(self, k):
account = self.wallet.accounts[k]
d = QDialog(self)
d.setWindowTitle(_('Account Details'))
d.setModal(1)
vbox = QVBoxLayout(d)
roots = self.wallet.get_roots(k)
name = self.wallet.get_account_name(k)
label = QLabel('Name: ' + name)
vbox.addWidget(label)
acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
vbox.addWidget(QLabel('Type: ' + acctype))
vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
label = QLabel('Derivation: ' + k)
vbox.addWidget(label)
vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
#for root in roots:
# mpk = self.wallet.master_public_keys[root]
# text = QTextEdit()
# text.setReadOnly(True)
# text.setMaximumHeight(120)
# text.setText(repr(mpk))
# vbox.addWidget(text)
vbox.addWidget(QLabel(_('Master Public Key:')))
text = QTextEdit()
text.setReadOnly(True)
text.setMaximumHeight(170)
vbox.addWidget(text)
mpk_text = '\n'.join( account.get_master_pubkeys() )
text.setText(mpk_text)
vbox.addLayout(close_button(d))
d.exec_()

View File

@ -18,6 +18,7 @@
from bitcoin import *
from i18n import _
from transaction import Transaction
class Account(object):
@ -115,20 +116,23 @@ class OldAccount(Account):
def redeem_script(self, sequence):
return None
def get_master_pubkeys(self):
return [self.mpk]
def get_type(self):
return _('Old Electrum format')
class BIP32_Account(Account):
def __init__(self, v):
Account.__init__(self, v)
self.c = v['c'].decode('hex')
self.K = v['K'].decode('hex')
self.cK = v['cK'].decode('hex')
self.xpub = v['xpub']
def dump(self):
d = Account.dump(self)
d['c'] = self.c.encode('hex')
d['K'] = self.K.encode('hex')
d['cK'] = self.cK.encode('hex')
d['xpub'] = self.xpub
return d
def get_address(self, for_change, n):
@ -140,39 +144,41 @@ class BIP32_Account(Account):
return self.get_address(0,0)
def get_pubkey(self, for_change, n):
K = self.K
chain = self.c
_, _, _, c, cK = deserialize_xkey(self.xpub)
for i in [for_change, n]:
K, K_compressed, chain = CKD_prime(K, chain, i)
return K_compressed.encode('hex')
cK, c = CKD_pub(cK, c, i)
return cK.encode('hex')
def redeem_script(self, sequence):
return None
def get_pubkeys(self, sequence):
return [self.get_pubkey(*sequence)]
def get_master_pubkeys(self):
return [self.xpub]
def get_type(self):
return _('Standard 1 of 1')
#acctype = 'multisig 2 of 2' if len(roots) == 2 else 'multisig 2 of 3' if len(roots) == 3 else 'standard 1 of 1'
class BIP32_Account_2of2(BIP32_Account):
def __init__(self, v):
BIP32_Account.__init__(self, v)
self.c2 = v['c2'].decode('hex')
self.K2 = v['K2'].decode('hex')
self.cK2 = v['cK2'].decode('hex')
self.xpub2 = v['xpub2']
def dump(self):
d = BIP32_Account.dump(self)
d['c2'] = self.c2.encode('hex')
d['K2'] = self.K2.encode('hex')
d['cK2'] = self.cK2.encode('hex')
d['xpub2'] = self.xpub2
return d
def get_pubkey2(self, for_change, n):
K = self.K2
chain = self.c2
_, _, _, c, cK = deserialize_xkey(self.xpub2)
for i in [for_change, n]:
K, K_compressed, chain = CKD_prime(K, chain, i)
return K_compressed.encode('hex')
cK, c = CKD_prime(cK, c, i)
return cK.encode('hex')
def redeem_script(self, sequence):
chain, i = sequence
@ -187,27 +193,29 @@ class BIP32_Account_2of2(BIP32_Account):
def get_pubkeys(self, sequence):
return [ self.get_pubkey( *sequence ), self.get_pubkey2( *sequence )]
def get_master_pubkeys(self):
return [self.xpub, self.xpub2]
def get_type(self):
return _('Multisig 2 of 2')
class BIP32_Account_2of3(BIP32_Account_2of2):
def __init__(self, v):
BIP32_Account_2of2.__init__(self, v)
self.c3 = v['c3'].decode('hex')
self.K3 = v['K3'].decode('hex')
self.cK3 = v['cK3'].decode('hex')
self.xpub3 = v['xpub3']
def dump(self):
d = BIP32_Account_2of2.dump(self)
d['c3'] = self.c3.encode('hex')
d['K3'] = self.K3.encode('hex')
d['cK3'] = self.cK3.encode('hex')
d['xpub3'] = self.xpub3
return d
def get_pubkey3(self, for_change, n):
K = self.K3
chain = self.c3
_, _, _, c, cK = deserialize_xkey(self.xpub3)
for i in [for_change, n]:
K, K_compressed, chain = CKD_prime(K, chain, i)
return K_compressed.encode('hex')
cK, c = CKD_prime(cK, c, i)
return cK.encode('hex')
def get_redeem_script(self, sequence):
chain, i = sequence
@ -219,5 +227,11 @@ class BIP32_Account_2of3(BIP32_Account_2of2):
def get_pubkeys(self, sequence):
return [ self.get_pubkey( *sequence ), self.get_pubkey2( *sequence ), self.get_pubkey3( *sequence )]
def get_master_pubkeys(self):
return [self.xpub, self.xpub2, self.xpub3]
def get_type(self):
return _('Multisig 2 of 3')

View File

@ -1,5 +1,5 @@
ELECTRUM_VERSION = "1.9.8" # version of the client package
PROTOCOL_VERSION = '0.9' # protocol version requested
NEW_SEED_VERSION = 6 # bip32 wallets
NEW_SEED_VERSION = 7 # bip32 wallets
OLD_SEED_VERSION = 4 # old electrum deterministic generation
SEED_PREFIX = '01' # the hash of the mnemonic seed must begin with this

View File

@ -321,96 +321,40 @@ class NewWallet:
self.create_accounts(password)
def create_watching_only_wallet(self, K0, c0):
cK0 = "" #FIXME
self.master_public_keys = {
"m/0'/": (c0, K0, cK0),
}
def create_watching_only_wallet(self, xpub):
self.master_public_keys = { "m/": xpub }
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('seed_version', self.seed_version, True)
self.create_account('1of1','Main account')
account = BIP32_Account({'xpub':xpub})
self.add_account("m/", account)
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
# create default account
self.create_master_keys('1of1', password)
self.create_account('1of1','Main account')
self.create_master_keys(password)
self.create_account('Main account', password)
def create_master_keys(self, account_type, password):
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(password))
if account_type == '1of1':
k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
self.master_public_keys["m/0'/"] = (c0, K0, cK0)
self.master_private_keys["m/0'/"] = pw_encode(k0, password)
elif account_type == '2of2':
k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/")
k2, c2, K2, cK2 = bip32_private_derivation(master_k, master_c, "m/", "m/2'/")
self.master_public_keys["m/1'/"] = (c1, K1, cK1)
self.master_public_keys["m/2'/"] = (c2, K2, cK2)
self.master_private_keys["m/1'/"] = pw_encode(k1, password)
self.master_private_keys["m/2'/"] = pw_encode(k2, password)
elif account_type == '2of3':
k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/")
k4, c4, K4, cK4 = bip32_private_derivation(master_k, master_c, "m/", "m/4'/")
k5, c5, K5, cK5 = bip32_private_derivation(master_k, master_c, "m/", "m/5'/")
self.master_public_keys["m/3'/"] = (c3, K3, cK3)
self.master_public_keys["m/4'/"] = (c4, K4, cK4)
self.master_public_keys["m/5'/"] = (c5, K5, cK5)
self.master_private_keys["m/3'/"] = pw_encode(k3, password)
self.master_private_keys["m/4'/"] = pw_encode(k4, password)
self.master_private_keys["m/5'/"] = pw_encode(k5, password)
def create_master_keys(self, password):
xpriv, xpub = bip32_root(self.get_seed(password))
self.master_public_keys["m/"] = xpub
self.master_private_keys["m/"] = pw_encode(xpriv, password)
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('master_private_keys', self.master_private_keys, True)
def has_master_public_keys(self, account_type):
if account_type == '1of1':
return "m/0'/" in self.master_public_keys
elif account_type == '2of2':
return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
elif account_type == '2of3':
return set(["m/3'/", "m/4'/", "m/5'/"]) <= set(self.master_public_keys.keys())
def find_root_by_master_key(self, c, K):
for key, v in self.master_public_keys.items():
def find_root_by_master_key(self, xpub):
for key, xpub2 in self.master_public_keys.items():
if key == "m/":continue
cc, KK, _ = v
if (c == cc) and (K == KK):
if xpub == xpub2:
return key
def deseed_root(self, seed, password):
# for safety, we ask the user to enter their seed
assert seed == self.get_seed(password)
self.seed = ''
self.storage.put('seed', '', True)
def deseed_branch(self, k):
# check that parent has no seed
# assert self.seed == ''
self.master_private_keys.pop(k)
self.storage.put('master_private_keys', self.master_private_keys, True)
def is_watching_only(self):
return (self.seed == '') and (self.master_private_keys == {})
def account_id(self, account_type, i):
if account_type == '1of1':
return "m/0'/%d"%i
elif account_type == '2of2':
return "m/1'/%d & m/2'/%d"%(i,i)
elif account_type == '2of3':
return "m/3'/%d & m/4'/%d & m/5'/%d"%(i,i,i)
else:
raise Exception('unknown account type')
def num_accounts(self, account_type):
def num_accounts(self, account_type = '1of1'):
keys = self.accounts.keys()
i = 0
while True:
@ -420,47 +364,35 @@ class NewWallet:
return i
def new_account_address(self, account_type = '1of1'):
def next_account_address(self, account_type, password):
i = self.num_accounts(account_type)
k = self.account_id(account_type,i)
account_id = self.account_id(account_type, i)
addr = self.next_addresses.get(k)
addr = self.next_addresses.get(account_id)
if not addr:
account_id, account = self.next_account(account_type)
account = self.make_account(account_id, password)
addr = account.first_address()
self.next_addresses[k] = addr
self.storage.put('next_addresses',self.next_addresses)
self.next_addresses[account_id] = addr
self.storage.put('next_addresses', self.next_addresses)
return k, addr
return account_id, addr
def account_id(self, account_type, i):
if account_type == '1of1':
return "m/%d'"%i
else:
raise
def next_account(self, account_type = '1of1'):
i = self.num_accounts(account_type)
account_id = self.account_id(account_type,i)
if account_type is '1of1':
master_c0, master_K0, _ = self.master_public_keys["m/0'/"]
c0, K0, cK0 = bip32_public_derivation(master_c0.decode('hex'), master_K0.decode('hex'), "m/0'/", "m/0'/%d"%i)
account = BIP32_Account({ 'c':c0, 'K':K0, 'cK':cK0 })
elif account_type == '2of2':
master_c1, master_K1, _ = self.master_public_keys["m/1'/"]
c1, K1, cK1 = bip32_public_derivation(master_c1.decode('hex'), master_K1.decode('hex'), "m/1'/", "m/1'/%d"%i)
master_c2, master_K2, _ = self.master_public_keys["m/2'/"]
c2, K2, cK2 = bip32_public_derivation(master_c2.decode('hex'), master_K2.decode('hex'), "m/2'/", "m/2'/%d"%i)
account = BIP32_Account_2of2({ 'c':c1, 'K':K1, 'cK':cK1, 'c2':c2, 'K2':K2, 'cK2':cK2 })
elif account_type == '2of3':
master_c3, master_K3, _ = self.master_public_keys["m/3'/"]
c3, K3, cK3 = bip32_public_derivation(master_c3.decode('hex'), master_K3.decode('hex'), "m/3'/", "m/3'/%d"%i)
master_c4, master_K4, _ = self.master_public_keys["m/4'/"]
c4, K4, cK4 = bip32_public_derivation(master_c4.decode('hex'), master_K4.decode('hex'), "m/4'/", "m/4'/%d"%i)
master_c5, master_K5, _ = self.master_public_keys["m/5'/"]
c5, K5, cK5 = bip32_public_derivation(master_c5.decode('hex'), master_K5.decode('hex'), "m/5'/", "m/5'/%d"%i)
account = BIP32_Account_2of3({ 'c':c3, 'K':K3, 'cK':cK3, 'c2':c4, 'K2':K4, 'cK2':cK4, 'c3':c5, 'K3':K5, 'cK3':cK5 })
return account_id, account
def make_account(self, account_id, password):
"""Creates and saves the master keys, but does not save the account"""
master_xpriv = pw_decode( self.master_private_keys["m/"] , password )
xpriv, xpub = bip32_private_derivation(master_xpriv, "m/", account_id)
self.master_private_keys[account_id] = pw_encode(xpriv, password)
self.master_public_keys[account_id] = xpub
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('master_private_keys', self.master_private_keys, True)
account = BIP32_Account({'xpub':xpub})
return account
def set_label(self, name, text = None):
@ -482,17 +414,24 @@ class NewWallet:
return changed
def create_account(self, account_type = '1of1', name = None):
k, account = self.next_account(account_type)
if k in self.pending_accounts:
self.pending_accounts.pop(k)
self.storage.put('pending_accounts', self.pending_accounts)
self.accounts[k] = account
self.save_accounts()
def create_account(self, name, password):
i = self.num_accounts('1of1')
account_id = self.account_id('1of1', i)
account = self.make_account(account_id, password)
self.add_account(account_id, account)
if name:
self.set_label(k, name)
self.set_label(account_id, name)
# add address of the next account
_, _ = self.next_account_address('1of1', password)
def add_account(self, account_id, account):
self.accounts[account_id] = account
if account_id in self.pending_accounts:
self.pending_accounts.pop(account_id)
self.storage.put('pending_accounts', self.pending_accounts)
self.save_accounts()
def save_accounts(self):
@ -525,10 +464,10 @@ class NewWallet:
def account_is_pending(self, k):
return k in self.pending_accounts
def create_pending_account(self, acct_type, name):
k, addr = self.new_account_address(acct_type)
self.set_label(k, name)
self.pending_accounts[k] = addr
def create_pending_account(self, acct_type, name, password):
account_id, addr = self.next_account_address(acct_type, password)
self.set_label(account_id, name)
self.pending_accounts[account_id] = addr
self.storage.put('pending_accounts', self.pending_accounts)
def get_pending_accounts(self):
@ -559,20 +498,13 @@ class NewWallet:
return s[0] == 1
def get_master_public_key(self):
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
return repr((c, K))
return self.storage.get("master_public_keys")["m/"]
def get_master_private_key(self, account, password):
k = self.master_private_keys.get(account)
if not k: return
master_k = pw_decode( k, 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 Exception:
raise Exception("Invalid password")
return master_k
xpriv = pw_decode( k, password)
return xpriv
def get_address_index(self, address):
@ -605,13 +537,14 @@ class NewWallet:
roots = []
for a in account.split('&'):
s = a.strip()
m = re.match("(m/\d+'/)(\d+)", s)
m = re.match("m/(\d+')", s)
roots.append( m.group(1) )
return roots
def is_seeded(self, account):
if type(account) is int:
return self.seed is not None
return True
for root in self.get_roots(account):
if root not in self.master_private_keys.keys():
@ -619,29 +552,27 @@ class NewWallet:
return True
def rebase_sequence(self, account, sequence):
# account has one or more xpub
# sequence is a sequence of public derivations
c, i = sequence
dd = []
for a in account.split('&'):
s = a.strip()
m = re.match("(m/\d+'/)(\d+)", s)
root = m.group(1)
num = int(m.group(2))
m = re.match("m/(\d+)'", s)
root = "m/"
num = int(m.group(1))
dd.append( (root, [num,c,i] ) )
return dd
def get_keyID(self, account, sequence):
if account == 0:
a, b = sequence
mpk = self.storage.get('master_public_key')
return 'old(%s,%d,%d)'%(mpk,a,b)
def get_keyID(self, account, sequence):
rs = self.rebase_sequence(account, sequence)
dd = []
for root, public_sequence in rs:
c, K, cK = self.master_public_keys[root]
xpub = self.master_public_keys[root]
s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
dd.append( 'bip32(%s,%s,%s)'%(c, cK, s) )
dd.append( 'bip32(%s,%s)'%(xpub, s) )
return '&'.join(dd)
@ -666,20 +597,14 @@ class NewWallet:
if address in self.imported_keys.keys():
out.append( pw_decode( self.imported_keys[address], password ) )
else:
account, sequence = self.get_address_index(address)
if account == 0:
pk = self.accounts[account].get_private_key(seed, sequence)
out.append(pk)
return out
# assert address == self.accounts[account].get_address(*sequence)
rs = self.rebase_sequence( account, sequence)
account_id, sequence = self.get_address_index(address)
#rs = self.rebase_sequence( account, sequence)
rs = [(account_id, sequence)]
for root, public_sequence in rs:
if root not in self.master_private_keys.keys(): continue
master_k = self.get_master_private_key(root, password)
master_c, _, _ = self.master_public_keys[root]
pk = bip32_private_key( public_sequence, master_k.decode('hex'), master_c.decode('hex'))
xpriv = self.get_master_private_key(root, password)
if not xpriv: continue
_, _, _, c, k = deserialize_xkey(xpriv)
pk = bip32_private_key( public_sequence, k, c )
out.append(pk)
return out
@ -849,7 +774,7 @@ class NewWallet:
for tx_hash, tx_height in h:
if tx_height == 0:
tx_age = 0
else:
else:
tx_age = self.network.get_local_height() - tx_height + 1
if tx_age > age:
age = tx_age
@ -877,16 +802,14 @@ class NewWallet:
return new_addresses
def create_pending_accounts(self):
for account_type in ['1of1','2of2','2of3']:
if not self.has_master_public_keys(account_type):
continue
k, a = self.new_account_address(account_type)
if self.address_is_old(a):
print_error( "creating account", a )
self.create_account(account_type)
self.next_addresses.pop(k)
def check_pending_accounts(self):
for account_id, addr in self.next_addresses.items():
if self.address_is_old(addr):
print_error( "creating account", account_id )
xpub = self.master_public_keys[account_id]
account = BIP32_Account({'xpub':xpub})
self.add_account(account_id, account)
self.next_addresses.pop(account_id)
def synchronize_account(self, account):
@ -897,8 +820,7 @@ class NewWallet:
def synchronize(self):
if self.master_public_keys:
self.create_pending_accounts()
self.check_pending_accounts()
new = []
for account in self.accounts.values():
new += self.synchronize_account(account)
@ -1761,10 +1683,10 @@ class OldWallet(NewWallet):
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def create_watching_only_wallet(self, K0):
def create_watching_only_wallet(self, mpk):
self.seed_version = OLD_SEED_VERSION
self.storage.put('seed_version', self.seed_version, True)
self.create_account(K0)
self.create_account(mpk)
def get_seed(self, password):
seed = pw_decode(self.seed, password)
@ -1801,7 +1723,32 @@ class OldWallet(NewWallet):
assert k == 0
return 'Main account'
def is_seeded(self, account):
return self.seed is not None
def get_private_key(self, address, password):
if self.is_watching_only():
return []
# first check the provided password
seed = self.get_seed(password)
out = []
if address in self.imported_keys.keys():
out.append( pw_decode( self.imported_keys[address], password ) )
else:
account_id, sequence = self.get_address_index(address)
pk = self.accounts[0].get_private_key(seed, sequence)
out.append(pk)
return out
def get_keyID(self, account, sequence):
a, b = sequence
mpk = self.storage.get('master_public_key')
return 'old(%s,%d,%d)'%(mpk,a,b)
def check_pending_accounts(self):
pass
# former WalletFactory
@ -1867,19 +1814,20 @@ class Wallet(object):
@classmethod
def from_mpk(self, s, storage):
try:
mpk, chain = s.split(':')
except:
mpk = s
chain = False
def from_mpk(self, mpk, storage):
if chain:
w = NewWallet(storage)
w.create_watching_only_wallet(mpk, chain)
else:
try:
int(mpk, 16)
old = True
except:
old = False
if old:
w = OldWallet(storage)
w.seed = ''
w.create_watching_only_wallet(mpk)
else:
w = NewWallet(storage)
w.create_watching_only_wallet(mpk)
return w