X-Git-Url: https://git.novaco.in/?p=electrum-nvc.git;a=blobdiff_plain;f=lib%2Faccount.py;h=87fcb54c91453f699ae271e0284daf238a5788e0;hp=b734d14473478cf24e25776268675f66df07e2d8;hb=0ce2c870bb5faf3bc9e627f08188b63cdf8fb944;hpb=db501aa976259385fb0f2fa03bebbb16e7414b09 diff --git a/lib/account.py b/lib/account.py index b734d14..87fcb54 100644 --- a/lib/account.py +++ b/lib/account.py @@ -1,58 +1,161 @@ -""" -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 . + +import bitcoin from bitcoin import * +from i18n import _ +from transaction import Transaction, is_extended_pubkey +from util import print_msg class Account(object): def __init__(self, v): - self.addresses = v.get('0', []) - self.change = v.get('1', []) - self.name = v.get('name', 'unnamed') + self.receiving_pubkeys = v.get('receiving', []) + self.change_pubkeys = v.get('change', []) + # addresses will not be stored on disk + self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys) + self.change_addresses = map(self.pubkeys_to_address, self.change_pubkeys) def dump(self): - return {'0':self.addresses, '1':self.change, 'name':self.name} + return {'receiving':self.receiving_pubkeys, 'change':self.change_pubkeys} + + def get_pubkey(self, for_change, n): + pubkeys_list = self.change_pubkeys if for_change else self.receiving_pubkeys + return pubkeys_list[n] - def get_name(self): - return self.name + def get_address(self, for_change, n): + addr_list = self.change_addresses if for_change else self.receiving_addresses + return addr_list[n] + + def get_pubkeys(self, for_change, n): + return [ self.get_pubkey(for_change, n)] def get_addresses(self, for_change): - return self.change[:] if for_change else self.addresses[:] + addr_list = self.change_addresses if for_change else self.receiving_addresses + return addr_list[:] + + def derive_pubkeys(self, for_change, n): + pass def create_new_address(self, for_change): - addresses = self.change if for_change else self.addresses - n = len(addresses) - address = self.get_new_address( for_change, n) - addresses.append(address) - print address + pubkeys_list = self.change_pubkeys if for_change else self.receiving_pubkeys + addr_list = self.change_addresses if for_change else self.receiving_addresses + n = len(pubkeys_list) + pubkeys = self.derive_pubkeys(for_change, n) + address = self.pubkeys_to_address(pubkeys) + pubkeys_list.append(pubkeys) + addr_list.append(address) + print_msg(address) return address - def get_new_address(self, for_change, n): + def pubkeys_to_address(self, pubkey): + return public_key_to_bc_address(pubkey.decode('hex')) + + def has_change(self): + return True + + def get_name(self, k): + return _('Main account') + + def get_keyID(self, *sequence): pass - + def redeem_script(self, *sequence): + pass + + +class PendingAccount(Account): + def __init__(self, v): + self.pending_address = v['pending'] + + def get_addresses(self, is_change): + return [self.pending_address] + + def has_change(self): + return False + + def dump(self): + return {'pending':self.pending_address } + + def get_name(self, k): + return _('Pending account') + + def get_master_pubkeys(self): + return [] + +class ImportedAccount(Account): + def __init__(self, d): + self.keypairs = d['imported'] + + def get_addresses(self, for_change): + return [] if for_change else sorted(self.keypairs.keys()) + + def get_pubkey(self, *sequence): + for_change, i = sequence + assert for_change == 0 + addr = self.get_addresses(0)[i] + return self.keypairs[addr][0] + + def get_xpubkeys(self, for_change, n): + return self.get_pubkeys(for_change, n) + + def get_private_key(self, sequence, wallet, password): + from wallet import pw_decode + for_change, i = sequence + assert for_change == 0 + address = self.get_addresses(0)[i] + pk = pw_decode(self.keypairs[address][1], password) + # this checks the password + assert address == address_from_private_key(pk) + return [pk] + + def has_change(self): + return False + + def add(self, address, pubkey, privkey, password): + from wallet import pw_encode + self.keypairs[address] = (pubkey, pw_encode(privkey, password )) + + def remove(self, address): + self.keypairs.pop(address) + + def dump(self): + return {'imported':self.keypairs} + + def get_name(self, k): + return _('Imported keys') + + + def update_password(self, old_password, new_password): + for k, v in self.keypairs.items(): + pubkey, a = v + b = pw_decode(a, old_password) + c = pw_encode(b, new_password) + self.keypairs[k] = (pubkey, c) class OldAccount(Account): """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - def __init__(self, mpk, mpk2 = None, mpk3 = None): - self.mpk = mpk - self.mpk2 = mpk2 - self.mpk3 = mpk3 + def __init__(self, v): + Account.__init__(self, v) + self.mpk = v['mpk'].decode('hex') + @classmethod def mpk_from_seed(klass, seed): @@ -69,129 +172,172 @@ class OldAccount(Account): seed = hashlib.sha256(seed + oldseed).digest() return string_to_number( seed ) - def get_sequence(self, sequence, mpk): - for_change, n = sequence - return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk.decode('hex') ) ) - - def get_address(self, sequence): - if not self.mpk2: - pubkey = self.get_pubkey(sequence) - address = public_key_to_bc_address( pubkey.decode('hex') ) - elif not self.mpk3: - pubkey1 = self.get_pubkey(sequence) - pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2) - address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"] - else: - pubkey1 = self.get_pubkey(sequence) - pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2) - pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3) - address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"] + @classmethod + def get_sequence(self, mpk, for_change, n): + return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk ) ) + + def get_address(self, for_change, n): + pubkey = self.get_pubkey(for_change, n) + address = public_key_to_bc_address( pubkey.decode('hex') ) return address - def get_pubkey(self, sequence, mpk=None): + @classmethod + def get_pubkey_from_mpk(self, mpk, for_change, n): curve = SECP256k1 - if mpk is None: mpk = self.mpk - z = self.get_sequence(sequence, mpk) - master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 ) + z = self.get_sequence(mpk, for_change, n) + master_public_key = ecdsa.VerifyingKey.from_string( mpk, 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_from_stretched_exponent(self, sequence, secexp): + def derive_pubkeys(self, for_change, n): + return self.get_pubkey_from_mpk(self.mpk, for_change, n) + + def get_private_key_from_stretched_exponent(self, for_change, n, secexp): order = generator_secp256k1.order() - secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order + secexp = ( secexp + self.get_sequence(self.mpk, for_change, n) ) % order pk = number_to_string( secexp, generator_secp256k1.order() ) compressed = False return SecretToASecret( pk, compressed ) - def get_private_key(self, sequence, seed): - secexp = self.stretch_key(seed) - return self.get_private_key_from_stretched_exponent(sequence, secexp) - def get_private_keys(self, sequence_list, seed): + def get_private_key(self, sequence, wallet, password): + seed = wallet.get_seed(password) + self.check_seed(seed) + for_change, n = sequence secexp = self.stretch_key(seed) - return [ self.get_private_key_from_stretched_exponent( sequence, secexp) for sequence in sequence_list] + pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp) + return [pk] + 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') + master_public_key = master_private_key.get_verifying_key().to_string() if master_public_key != self.mpk: - print_error('invalid password (mpk)') - raise BaseException('Invalid password') + print_error('invalid password (mpk)', self.mpk.encode('hex'), master_public_key.encode('hex')) + raise Exception('Invalid password') return True - def get_input_info(self, sequence): - if not self.mpk2: - pk_addr = self.get_address(sequence) - redeemScript = None - elif not self.mpk3: - pubkey1 = self.get_pubkey(sequence) - pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2) - pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key - redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript'] - else: - pubkey1 = self.get_pubkey(sequence) - pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2) - pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3) - pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key - redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript'] - return pk_addr, redeemScript + def redeem_script(self, sequence): + return None + + def get_master_pubkeys(self): + return [self.mpk.encode('hex')] + + def get_type(self): + return _('Old Electrum format') + def get_xpubkeys(self, sequence): + s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), sequence)) + mpk = self.mpk.encode('hex') + x_pubkey = 'fe' + mpk + s + return [ x_pubkey ] + + @classmethod + def parse_xpubkey(self, x_pubkey): + assert is_extended_pubkey(x_pubkey) + pk = x_pubkey[2:] + mpk = pk[0:128] + dd = pk[128:] + s = [] + while dd: + n = int(bitcoin.rev_hex(dd[0:4]), 16) + dd = dd[4:] + s.append(n) + assert len(s) == 2 + return mpk, s 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_new_address(self, for_change, n): - pubkey = self.get_pubkey(for_change, n) - address = public_key_to_bc_address( pubkey ) - return address - - def get_pubkey(self, for_change, n): - K = self.K - chain = self.c - for i in [for_change, n]: - K, K_compressed, chain = CKD_prime(K, chain, i) - return K_compressed - - def get_address(self, sequence): - for_change, n = sequence - pubkey = self.get_pubkey(for_change, n) - address = public_key_to_bc_address( pubkey ) + def first_address(self): + pubkeys = self.derive_pubkeys(0, 0) + address = self.pubkeys_to_address(pubkeys) return address - def get_private_key(self, sequence, master_k): - chain = self.c - k = master_k - for i in sequence: - k, chain = CKD(k, chain, i) - return SecretToASecret(k, True) + def get_master_pubkeys(self): + return [self.xpub] - def get_private_keys(self, sequence_list, seed): - return [ self.get_private_key( sequence, seed) for sequence in sequence_list] - - def check_seed(self, seed): - master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed) - assert self.mpk == (master_public_key.encode('hex'), master_chain.encode('hex')) + @classmethod + def derive_pubkey_from_xpub(self, xpub, for_change, n): + _, _, _, c, cK = deserialize_xkey(xpub) + for i in [for_change, n]: + cK, c = CKD_pub(cK, c, i) + return cK.encode('hex') + + def get_pubkey_from_xpub(self, xpub, for_change, n): + xpubs = self.get_master_pubkeys() + i = xpubs.index(xpub) + pubkeys = self.get_pubkeys(sequence, n) + return pubkeys[i] + + def derive_pubkeys(self, for_change, n): + return self.derive_pubkey_from_xpub(self.xpub, for_change, n) + + + def get_private_key(self, sequence, wallet, password): + out = [] + xpubs = self.get_master_pubkeys() + roots = [k for k, v in wallet.master_public_keys.iteritems() if v in xpubs] + for root in roots: + xpriv = wallet.get_master_private_key(root, password) + if not xpriv: + continue + _, _, _, c, k = deserialize_xkey(xpriv) + pk = bip32_private_key( sequence, k, c ) + out.append(pk) + return out + + def redeem_script(self, sequence): + return None + + def get_type(self): + return _('Standard 1 of 1') + + def get_xpubkeys(self, for_change, n): + # unsorted + s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change,n))) + xpubs = self.get_master_pubkeys() + return map(lambda xpub: 'ff' + bitcoin.DecodeBase58Check(xpub).encode('hex') + s, xpubs) - def get_input_info(self, sequence): - pk_addr = self.get_address(sequence) - redeemScript = None - return pk_addr, redeemScript + @classmethod + def parse_xpubkey(self, pubkey): + assert is_extended_pubkey(pubkey) + pk = pubkey.decode('hex') + pk = pk[1:] + xkey = bitcoin.EncodeBase58Check(pk[0:78]) + dd = pk[78:] + s = [] + while dd: + n = int( bitcoin.rev_hex(dd[0:2].encode('hex')), 16) + dd = dd[2:] + s.append(n) + assert len(s) == 2 + return xkey, s + + + def get_name(self, k): + name = "Unnamed account" + m = re.match("m/(\d+)'", k) + if m: + num = m.group(1) + if num == '0': + name = "Main account" + else: + name = "Account %s"%num + + return name @@ -199,36 +345,55 @@ 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 - for i in [for_change, n]: - K, K_compressed, chain = CKD_prime(K, chain, i) - return K_compressed + def get_pubkeys(self, for_change, n): + return self.get_pubkey(for_change, n) + + def derive_pubkeys(self, for_change, n): + return map(lambda x: self.derive_pubkey_from_xpub(x, for_change, n), self.get_master_pubkeys()) - def get_new_address(self, for_change, n): - pubkey1 = self.get_pubkey(for_change, n) - pubkey2 = self.get_pubkey2(for_change, n) - address = Transaction.multisig_script([pubkey1.encode('hex'), pubkey2.encode('hex')], 2)["address"] + def redeem_script(self, for_change, n): + pubkeys = self.get_pubkeys(for_change, n) + return Transaction.multisig_script(sorted(pubkeys), 2) + + def pubkeys_to_address(self, pubkeys): + redeem_script = Transaction.multisig_script(sorted(pubkeys), 2) + address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) return address - def get_input_info(self, sequence): - chain, i = sequence - pubkey1 = self.get_pubkey(chain, i) - pubkey2 = self.get_pubkey2(chain, i) - # fixme - pk_addr = None # public_key_to_bc_address( pubkey1 ) # we need to return that address to get the right private key - redeemScript = Transaction.multisig_script([pubkey1.encode('hex'), pubkey2.encode('hex')], 2)['redeemScript'] - return pk_addr, redeemScript + def get_address(self, for_change, n): + return self.pubkeys_to_address(self.get_pubkeys(for_change, n)) + + 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.xpub3 = v['xpub3'] + + def dump(self): + d = BIP32_Account_2of2.dump(self) + d['xpub3'] = self.xpub3 + return d + + def get_master_pubkeys(self): + return [self.xpub, self.xpub2, self.xpub3] + + def get_type(self): + return _('Multisig 2 of 3') + + +