X-Git-Url: https://git.novaco.in/?p=electrum-nvc.git;a=blobdiff_plain;f=lib%2Faccount.py;h=87fcb54c91453f699ae271e0284daf238a5788e0;hp=37ddc60f93d23422a74c0d8474ae4c14e4eeb6e2;hb=0ce2c870bb5faf3bc9e627f08188b63cdf8fb944;hpb=66e1e2707d4a82ecbe3f2bf7d532036d0a9651a8 diff --git a/lib/account.py b/lib/account.py index 37ddc60..87fcb54 100644 --- a/lib/account.py +++ b/lib/account.py @@ -16,48 +16,146 @@ # 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 +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.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} + 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_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_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_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 get_pubkeys(self, sequence): - return [ self.get_pubkey( *sequence )] + 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, v): - self.addresses = v.get(0, []) - self.change = v.get(1, []) + Account.__init__(self, v) self.mpk = v['mpk'].decode('hex') - def dump(self): - return {0:self.addresses, 1:self.change} @classmethod def mpk_from_seed(klass, seed): @@ -74,34 +172,43 @@ class OldAccount(Account): seed = hashlib.sha256(seed + oldseed).digest() return string_to_number( seed ) - def get_sequence(self, for_change, n): - return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) ) + @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, for_change, n): + @classmethod + def get_pubkey_from_mpk(self, mpk, for_change, n): curve = SECP256k1 - mpk = self.mpk - z = self.get_sequence(for_change, n) + 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 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(for_change, n) ) % 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, seed, sequence): + + 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(for_change, n, secexp) + pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp) + return [pk] + def check_seed(self, seed): curve = SECP256k1 @@ -117,15 +224,30 @@ class OldAccount(Account): return None def get_master_pubkeys(self): - return [self.mpk] + return [self.mpk.encode('hex')] def get_type(self): return _('Old Electrum format') - def get_keyID(self, sequence): - a, b = sequence - return 'old(%s,%d,%d)'%(self.mpk,a,b) + 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): @@ -139,28 +261,43 @@ class BIP32_Account(Account): d['xpub'] = self.xpub return d - 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 first_address(self): - return self.get_address(0,0) + pubkeys = self.derive_pubkeys(0, 0) + address = self.pubkeys_to_address(pubkeys) + return address def get_master_pubkeys(self): return [self.xpub] - def get_pubkey_from_x(self, xpub, for_change, n): + @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_pubkeys(self, sequence): - return sorted(map(lambda x: self.get_pubkey_from_x(x, *sequence), self.get_master_pubkeys())) - - def get_pubkey(self, for_change, n): - return self.get_pubkeys((for_change, n))[0] + 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 @@ -168,9 +305,40 @@ class BIP32_Account(Account): def get_type(self): return _('Standard 1 of 1') - def get_keyID(self, sequence): - s = '/' + '/'.join( map(lambda x:str(x), sequence) ) - return '&'.join( map(lambda x: 'bip32(%s,%s)'%(x, s), self.get_master_pubkeys() ) ) + 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) + + @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 + class BIP32_Account_2of2(BIP32_Account): @@ -184,14 +352,24 @@ class BIP32_Account_2of2(BIP32_Account): d['xpub2'] = self.xpub2 return d - def redeem_script(self, sequence): - pubkeys = self.get_pubkeys(sequence) - return Transaction.multisig_script(pubkeys, 2) + def get_pubkeys(self, for_change, n): + return self.get_pubkey(for_change, n) - def get_address(self, for_change, n): - address = hash_160_to_bc_address(hash_160(self.redeem_script((for_change, n)).decode('hex')), 5) + 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 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_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]