2016-07-01 23:58:56 -07:00
|
|
|
#!/usr/bin/env python2
|
|
|
|
# -*- mode: python -*-
|
|
|
|
#
|
|
|
|
# Electrum - lightweight Bitcoin client
|
|
|
|
# Copyright (C) 2016 The Electrum developers
|
|
|
|
#
|
|
|
|
# Permission is hereby granted, free of charge, to any person
|
|
|
|
# obtaining a copy of this software and associated documentation files
|
|
|
|
# (the "Software"), to deal in the Software without restriction,
|
|
|
|
# including without limitation the rights to use, copy, modify, merge,
|
|
|
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
|
|
|
# and to permit persons to whom the Software is furnished to do so,
|
|
|
|
# subject to the following conditions:
|
|
|
|
#
|
|
|
|
# The above copyright notice and this permission notice shall be
|
|
|
|
# included in all copies or substantial portions of the Software.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
# SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
from unicodedata import normalize
|
|
|
|
|
|
|
|
from version import *
|
|
|
|
import bitcoin
|
|
|
|
from bitcoin import pw_encode, pw_decode, bip32_root, bip32_private_derivation, bip32_public_derivation, bip32_private_key, deserialize_xkey
|
|
|
|
from bitcoin import public_key_from_private_key, public_key_to_bc_address
|
|
|
|
from bitcoin import *
|
|
|
|
|
2016-09-27 21:30:00 -07:00
|
|
|
from bitcoin import is_old_seed, is_new_seed, is_seed
|
2016-07-01 23:58:56 -07:00
|
|
|
from util import PrintError, InvalidPassword
|
|
|
|
from mnemonic import Mnemonic
|
|
|
|
|
|
|
|
|
|
|
|
class KeyStore(PrintError):
|
|
|
|
|
|
|
|
def has_seed(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_watching_only(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_import(self):
|
|
|
|
return False
|
|
|
|
|
2016-09-01 04:52:49 -07:00
|
|
|
def get_tx_derivations(self, tx):
|
|
|
|
keypairs = {}
|
|
|
|
for txin in tx.inputs():
|
|
|
|
num_sig = txin.get('num_sig')
|
|
|
|
if num_sig is None:
|
|
|
|
continue
|
|
|
|
x_signatures = txin['signatures']
|
|
|
|
signatures = filter(None, x_signatures)
|
|
|
|
if len(signatures) == num_sig:
|
|
|
|
# input is complete
|
|
|
|
continue
|
|
|
|
for k, x_pubkey in enumerate(txin['x_pubkeys']):
|
|
|
|
if x_signatures[k] is not None:
|
|
|
|
# this pubkey already signed
|
|
|
|
continue
|
|
|
|
derivation = self.get_pubkey_derivation(x_pubkey)
|
|
|
|
if not derivation:
|
|
|
|
continue
|
|
|
|
keypairs[x_pubkey] = derivation
|
|
|
|
return keypairs
|
|
|
|
|
|
|
|
def can_sign(self, tx):
|
|
|
|
if self.is_watching_only():
|
|
|
|
return False
|
|
|
|
return bool(self.get_tx_derivations(tx))
|
|
|
|
|
2016-08-26 02:45:12 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
class Software_KeyStore(KeyStore):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
KeyStore.__init__(self)
|
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def may_have_password(self):
|
|
|
|
return not self.is_watching_only()
|
|
|
|
|
2016-08-14 02:30:38 -07:00
|
|
|
def sign_message(self, sequence, message, password):
|
|
|
|
sec = self.get_private_key(sequence, password)
|
|
|
|
key = regenerate_key(sec)
|
|
|
|
compressed = is_compressed(sec)
|
|
|
|
return key.sign_message(message, compressed)
|
|
|
|
|
|
|
|
def decrypt_message(self, sequence, message, password):
|
|
|
|
sec = self.get_private_key(sequence, password)
|
|
|
|
ec = regenerate_key(sec)
|
|
|
|
decrypted = ec.decrypt_message(message)
|
|
|
|
return decrypted
|
|
|
|
|
2016-08-28 13:14:37 -07:00
|
|
|
def sign_transaction(self, tx, password):
|
2016-09-01 04:52:49 -07:00
|
|
|
if self.is_watching_only():
|
|
|
|
return
|
2016-08-28 13:14:37 -07:00
|
|
|
# Raise if password is not correct.
|
|
|
|
self.check_password(password)
|
|
|
|
# Add private keys
|
2016-09-01 04:52:49 -07:00
|
|
|
keypairs = self.get_tx_derivations(tx)
|
|
|
|
for k, v in keypairs.items():
|
|
|
|
keypairs[k] = self.get_private_key(v, password)
|
2016-08-28 13:14:37 -07:00
|
|
|
# Sign
|
|
|
|
if keypairs:
|
|
|
|
tx.sign(keypairs)
|
|
|
|
|
2016-08-14 02:30:38 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
class Imported_KeyStore(Software_KeyStore):
|
|
|
|
# keystore for imported private keys
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def __init__(self, d):
|
2016-07-01 23:58:56 -07:00
|
|
|
Software_KeyStore.__init__(self)
|
2016-08-21 02:58:15 -07:00
|
|
|
self.keypairs = d.get('keypairs', {})
|
|
|
|
self.receiving_pubkeys = self.keypairs.keys()
|
|
|
|
self.change_pubkeys = []
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def is_deterministic(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_change_password(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def get_master_public_key(self):
|
|
|
|
return None
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def dump(self):
|
|
|
|
return {
|
|
|
|
'type': 'imported',
|
|
|
|
'keypairs': self.keypairs,
|
|
|
|
}
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def can_import(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def check_password(self, password):
|
|
|
|
self.get_private_key((0,0), password)
|
|
|
|
|
|
|
|
def import_key(self, sec, password):
|
|
|
|
if not self.can_import():
|
|
|
|
raise BaseException('This wallet cannot import private keys')
|
|
|
|
try:
|
|
|
|
pubkey = public_key_from_private_key(sec)
|
|
|
|
except Exception:
|
2016-08-17 00:49:58 -07:00
|
|
|
raise BaseException('Invalid private key')
|
|
|
|
if pubkey in self.keypairs:
|
|
|
|
raise BaseException('Private key already in keystore')
|
2016-07-01 23:58:56 -07:00
|
|
|
self.keypairs[pubkey] = sec
|
|
|
|
return pubkey
|
|
|
|
|
|
|
|
def delete_imported_key(self, key):
|
|
|
|
self.keypairs.pop(key)
|
|
|
|
|
2016-08-14 02:30:38 -07:00
|
|
|
def get_public_key(self, sequence):
|
|
|
|
for_change, i = sequence
|
|
|
|
pubkey = (self.change_pubkeys if for_change else self.receiving_pubkeys)[i]
|
|
|
|
return pubkey
|
|
|
|
|
2016-08-28 22:39:27 -07:00
|
|
|
def get_xpubkey(self, c, i):
|
2016-09-21 07:44:21 -07:00
|
|
|
return self.get_public_key((c,i))
|
2016-08-28 22:39:27 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
def get_private_key(self, sequence, password):
|
|
|
|
for_change, i = sequence
|
|
|
|
assert for_change == 0
|
2016-09-01 04:52:49 -07:00
|
|
|
pubkey = self.receiving_pubkeys[i]
|
2016-07-01 23:58:56 -07:00
|
|
|
pk = pw_decode(self.keypairs[pubkey], password)
|
|
|
|
# this checks the password
|
|
|
|
if pubkey != public_key_from_private_key(pk):
|
|
|
|
raise InvalidPassword()
|
|
|
|
return pk
|
|
|
|
|
2016-09-21 07:23:24 -07:00
|
|
|
def get_pubkey_derivation(self, x_pubkey):
|
2016-09-21 07:44:21 -07:00
|
|
|
if x_pubkey[0:2] in ['02', '03', '04']:
|
|
|
|
if x_pubkey in self.receiving_pubkeys:
|
|
|
|
i = self.receiving_pubkeys.index(x_pubkey)
|
2016-09-21 07:23:24 -07:00
|
|
|
return (False, i)
|
2016-09-21 07:44:21 -07:00
|
|
|
elif x_pubkey[0:2] == 'fd':
|
|
|
|
# fixme: this assumes p2pkh
|
|
|
|
_, addr = xpubkey_to_address(x_pubkey)
|
|
|
|
for i, pubkey in enumerate(self.receiving_pubkeys):
|
|
|
|
if public_key_to_bc_address(pubkey.decode('hex')) == addr:
|
|
|
|
return (False, i)
|
2016-09-01 04:52:49 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
def update_password(self, old_password, new_password):
|
2016-10-06 15:20:46 -07:00
|
|
|
self.check_password(old_password)
|
2016-07-01 23:58:56 -07:00
|
|
|
if new_password == '':
|
|
|
|
new_password = None
|
|
|
|
for k, v in self.keypairs.items():
|
|
|
|
b = pw_decode(v, old_password)
|
|
|
|
c = pw_encode(b, new_password)
|
2016-08-21 12:09:00 -07:00
|
|
|
self.keypairs[k] = c
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
class Deterministic_KeyStore(Software_KeyStore):
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def __init__(self, d):
|
2016-07-01 23:58:56 -07:00
|
|
|
Software_KeyStore.__init__(self)
|
2016-08-21 02:58:15 -07:00
|
|
|
self.seed = d.get('seed', '')
|
2016-08-26 07:21:36 -07:00
|
|
|
self.passphrase = d.get('passphrase', '')
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def is_deterministic(self):
|
|
|
|
return True
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def dump(self):
|
2016-08-26 07:21:36 -07:00
|
|
|
d = {}
|
|
|
|
if self.seed:
|
|
|
|
d['seed'] = self.seed
|
|
|
|
if self.passphrase:
|
|
|
|
d['passphrase'] = self.passphrase
|
|
|
|
return d
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def has_seed(self):
|
2016-10-07 23:34:24 -07:00
|
|
|
return bool(self.seed)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def can_change_password(self):
|
|
|
|
return not self.is_watching_only()
|
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def add_seed(self, seed):
|
2016-07-01 23:58:56 -07:00
|
|
|
if self.seed:
|
|
|
|
raise Exception("a seed exists")
|
2016-08-28 13:10:46 -07:00
|
|
|
self.seed = self.format_seed(seed)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def get_seed(self, password):
|
|
|
|
return pw_decode(self.seed, password).encode('utf8')
|
|
|
|
|
2016-08-26 07:21:36 -07:00
|
|
|
def get_passphrase(self, password):
|
2016-08-29 06:33:16 -07:00
|
|
|
return pw_decode(self.passphrase, password).encode('utf8') if self.passphrase else ''
|
2016-08-26 07:21:36 -07:00
|
|
|
|
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
class Xpub:
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.xpub = None
|
|
|
|
self.xpub_receive = None
|
|
|
|
self.xpub_change = None
|
|
|
|
|
|
|
|
def get_master_public_key(self):
|
|
|
|
return self.xpub
|
|
|
|
|
|
|
|
def derive_pubkey(self, for_change, n):
|
|
|
|
xpub = self.xpub_change if for_change else self.xpub_receive
|
|
|
|
if xpub is None:
|
|
|
|
xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
|
|
|
|
if for_change:
|
|
|
|
self.xpub_change = xpub
|
|
|
|
else:
|
|
|
|
self.xpub_receive = xpub
|
2016-09-06 01:09:08 -07:00
|
|
|
return self.get_pubkey_from_xpub(xpub, (n,))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_pubkey_from_xpub(self, xpub, sequence):
|
2016-07-01 23:58:56 -07:00
|
|
|
_, _, _, c, cK = deserialize_xkey(xpub)
|
2016-09-06 01:09:08 -07:00
|
|
|
for i in sequence:
|
|
|
|
cK, c = CKD_pub(cK, c, i)
|
|
|
|
return cK.encode('hex')
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def get_xpubkey(self, c, i):
|
|
|
|
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
|
|
|
|
return 'ff' + bitcoin.DecodeBase58Check(self.xpub).encode('hex') + s
|
|
|
|
|
2016-08-17 05:28:37 -07:00
|
|
|
@classmethod
|
|
|
|
def parse_xpubkey(self, pubkey):
|
|
|
|
assert pubkey[0:2] == 'ff'
|
|
|
|
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
|
|
|
|
|
2016-09-01 04:52:49 -07:00
|
|
|
def get_pubkey_derivation(self, x_pubkey):
|
|
|
|
if x_pubkey[0:2] != 'ff':
|
|
|
|
return
|
|
|
|
xpub, derivation = self.parse_xpubkey(x_pubkey)
|
|
|
|
if self.xpub != xpub:
|
|
|
|
return
|
|
|
|
return derivation
|
|
|
|
|
2016-08-17 05:28:37 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def __init__(self, d):
|
2016-07-01 23:58:56 -07:00
|
|
|
Xpub.__init__(self)
|
2016-08-21 02:58:15 -07:00
|
|
|
Deterministic_KeyStore.__init__(self, d)
|
|
|
|
self.xpub = d.get('xpub')
|
|
|
|
self.xprv = d.get('xprv')
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def format_seed(self, seed):
|
2016-08-28 13:10:46 -07:00
|
|
|
return ' '.join(seed.split())
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def dump(self):
|
|
|
|
d = Deterministic_KeyStore.dump(self)
|
|
|
|
d['type'] = 'bip32'
|
|
|
|
d['xpub'] = self.xpub
|
|
|
|
d['xprv'] = self.xprv
|
|
|
|
return d
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def get_master_private_key(self, password):
|
|
|
|
return pw_decode(self.xprv, password)
|
|
|
|
|
|
|
|
def check_password(self, password):
|
|
|
|
xprv = pw_decode(self.xprv, password)
|
|
|
|
if deserialize_xkey(xprv)[3] != deserialize_xkey(self.xpub)[3]:
|
|
|
|
raise InvalidPassword()
|
|
|
|
|
|
|
|
def update_password(self, old_password, new_password):
|
2016-10-06 15:20:46 -07:00
|
|
|
self.check_password(old_password)
|
2016-07-01 23:58:56 -07:00
|
|
|
if new_password == '':
|
|
|
|
new_password = None
|
|
|
|
if self.has_seed():
|
|
|
|
decoded = self.get_seed(old_password)
|
2016-08-26 07:21:36 -07:00
|
|
|
self.seed = pw_encode(decoded, new_password)
|
|
|
|
if self.passphrase:
|
|
|
|
decoded = self.get_passphrase(old_password)
|
|
|
|
self.passphrase = pw_encode(decoded, new_password)
|
2016-07-01 23:58:56 -07:00
|
|
|
if self.xprv is not None:
|
|
|
|
b = pw_decode(self.xprv, old_password)
|
|
|
|
self.xprv = pw_encode(b, new_password)
|
|
|
|
|
|
|
|
def is_watching_only(self):
|
|
|
|
return self.xprv is None
|
|
|
|
|
|
|
|
def get_mnemonic(self, password):
|
|
|
|
return self.get_seed(password)
|
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def add_xprv(self, xprv):
|
|
|
|
self.xprv = xprv
|
|
|
|
self.xpub = bitcoin.xpub_from_xprv(xprv)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def add_xprv_from_seed(self, bip32_seed, derivation):
|
2016-08-15 02:48:33 -07:00
|
|
|
xprv, xpub = bip32_root(bip32_seed)
|
|
|
|
xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
|
2016-08-25 03:18:51 -07:00
|
|
|
self.add_xprv(xprv)
|
2016-08-15 02:48:33 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
def get_private_key(self, sequence, password):
|
|
|
|
xprv = self.get_master_private_key(password)
|
|
|
|
_, _, _, c, k = deserialize_xkey(xprv)
|
|
|
|
pk = bip32_private_key(sequence, k, c)
|
|
|
|
return pk
|
|
|
|
|
|
|
|
|
2016-09-01 04:52:49 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
class Old_KeyStore(Deterministic_KeyStore):
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def __init__(self, d):
|
|
|
|
Deterministic_KeyStore.__init__(self, d)
|
2016-08-28 13:10:46 -07:00
|
|
|
self.mpk = d.get('mpk')
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def dump(self):
|
|
|
|
d = Deterministic_KeyStore.dump(self)
|
2016-08-28 13:10:46 -07:00
|
|
|
d['mpk'] = self.mpk
|
|
|
|
d['type'] = 'old'
|
2016-08-21 02:58:15 -07:00
|
|
|
return d
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-28 13:10:46 -07:00
|
|
|
def add_seed(self, seedphrase):
|
|
|
|
Deterministic_KeyStore.add_seed(self, seedphrase)
|
|
|
|
self.mpk = self.mpk_from_seed(self.seed)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def add_master_public_key(self, mpk):
|
2016-08-28 13:10:46 -07:00
|
|
|
self.mpk = mpk
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def format_seed(self, seed):
|
|
|
|
import old_mnemonic
|
|
|
|
# see if seed was entered as hex
|
|
|
|
seed = seed.strip()
|
|
|
|
if seed:
|
|
|
|
try:
|
|
|
|
seed.decode('hex')
|
2016-10-07 09:33:49 -07:00
|
|
|
return str(seed)
|
2016-07-01 23:58:56 -07:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
words = seed.split()
|
|
|
|
seed = old_mnemonic.mn_decode(words)
|
|
|
|
if not seed:
|
|
|
|
raise Exception("Invalid seed")
|
2016-08-28 13:10:46 -07:00
|
|
|
return seed
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def get_mnemonic(self, password):
|
|
|
|
import old_mnemonic
|
|
|
|
s = self.get_seed(password)
|
|
|
|
return ' '.join(old_mnemonic.mn_encode(s))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def mpk_from_seed(klass, seed):
|
|
|
|
secexp = klass.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()
|
2016-08-28 13:10:46 -07:00
|
|
|
return master_public_key.encode('hex')
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def stretch_key(self, seed):
|
|
|
|
x = seed
|
|
|
|
for i in range(100000):
|
|
|
|
x = hashlib.sha256(x + seed).digest()
|
|
|
|
return string_to_number(x)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_sequence(self, mpk, for_change, n):
|
2016-08-28 13:10:46 -07:00
|
|
|
return string_to_number(Hash("%d:%d:"%(n, for_change) + mpk.decode('hex')))
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_pubkey_from_mpk(self, mpk, for_change, n):
|
|
|
|
z = self.get_sequence(mpk, for_change, n)
|
2016-08-28 13:10:46 -07:00
|
|
|
master_public_key = ecdsa.VerifyingKey.from_string(mpk.decode('hex'), curve = SECP256k1)
|
2016-07-01 23:58:56 -07:00
|
|
|
pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator
|
|
|
|
public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1)
|
|
|
|
return '04' + public_key2.to_string().encode('hex')
|
|
|
|
|
|
|
|
def derive_pubkey(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(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, password):
|
|
|
|
seed = self.get_seed(password)
|
|
|
|
self.check_seed(seed)
|
|
|
|
for_change, n = sequence
|
|
|
|
secexp = self.stretch_key(seed)
|
|
|
|
pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
|
|
|
|
return pk
|
|
|
|
|
|
|
|
def check_seed(self, seed):
|
|
|
|
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()
|
2016-08-28 13:10:46 -07:00
|
|
|
if master_public_key != self.mpk.decode('hex'):
|
|
|
|
print_error('invalid password (mpk)', self.mpk, master_public_key.encode('hex'))
|
2016-07-01 23:58:56 -07:00
|
|
|
raise InvalidPassword()
|
|
|
|
|
|
|
|
def check_password(self, password):
|
|
|
|
seed = self.get_seed(password)
|
|
|
|
self.check_seed(seed)
|
|
|
|
|
|
|
|
def get_master_public_key(self):
|
2016-08-28 13:10:46 -07:00
|
|
|
return self.mpk
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-17 05:28:37 -07:00
|
|
|
def get_xpubkey(self, for_change, n):
|
2016-07-01 23:58:56 -07:00
|
|
|
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
|
2016-09-01 04:52:49 -07:00
|
|
|
return 'fe' + self.mpk + s
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def parse_xpubkey(self, x_pubkey):
|
2016-08-17 05:28:37 -07:00
|
|
|
assert x_pubkey[0:2] == 'fe'
|
2016-07-01 23:58:56 -07:00
|
|
|
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
|
|
|
|
|
2016-09-01 04:52:49 -07:00
|
|
|
def get_pubkey_derivation(self, x_pubkey):
|
|
|
|
if x_pubkey[0:2] != 'fe':
|
|
|
|
return
|
|
|
|
mpk, derivation = self.parse_xpubkey(x_pubkey)
|
|
|
|
if self.mpk != mpk:
|
|
|
|
return
|
|
|
|
return derivation
|
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
def update_password(self, old_password, new_password):
|
2016-10-06 15:20:46 -07:00
|
|
|
self.check_password(old_password)
|
2016-07-01 23:58:56 -07:00
|
|
|
if new_password == '':
|
|
|
|
new_password = None
|
|
|
|
if self.has_seed():
|
|
|
|
decoded = self.get_seed(old_password)
|
|
|
|
self.seed = pw_encode(decoded, new_password)
|
|
|
|
|
|
|
|
|
|
|
|
class Hardware_KeyStore(KeyStore, Xpub):
|
|
|
|
# Derived classes must set:
|
|
|
|
# - device
|
|
|
|
# - DEVICE_IDS
|
|
|
|
# - wallet_type
|
|
|
|
|
|
|
|
#restore_wallet_class = BIP32_RD_Wallet
|
|
|
|
max_change_outputs = 1
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def __init__(self, d):
|
2016-07-01 23:58:56 -07:00
|
|
|
Xpub.__init__(self)
|
|
|
|
KeyStore.__init__(self)
|
|
|
|
# Errors and other user interaction is done through the wallet's
|
|
|
|
# handler. The handler is per-window and preserved across
|
|
|
|
# device reconnects
|
2016-08-21 02:58:15 -07:00
|
|
|
self.xpub = d.get('xpub')
|
2016-08-26 02:45:12 -07:00
|
|
|
self.label = d.get('label')
|
2016-08-21 02:58:15 -07:00
|
|
|
self.derivation = d.get('derivation')
|
2016-07-01 23:58:56 -07:00
|
|
|
self.handler = None
|
|
|
|
|
2016-08-26 02:45:12 -07:00
|
|
|
def set_label(self, label):
|
|
|
|
self.label = label
|
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def may_have_password(self):
|
|
|
|
return False
|
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
def is_deterministic(self):
|
|
|
|
return True
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
def dump(self):
|
|
|
|
return {
|
|
|
|
'type': 'hardware',
|
|
|
|
'hw_type': self.hw_type,
|
|
|
|
'xpub': self.xpub,
|
|
|
|
'derivation':self.derivation,
|
2016-08-26 02:45:12 -07:00
|
|
|
'label':self.label,
|
2016-08-21 02:58:15 -07:00
|
|
|
}
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def unpaired(self):
|
|
|
|
'''A device paired with the wallet was diconnected. This can be
|
|
|
|
called in any thread context.'''
|
|
|
|
self.print_error("unpaired")
|
|
|
|
|
|
|
|
def paired(self):
|
|
|
|
'''A device paired with the wallet was (re-)connected. This can be
|
|
|
|
called in any thread context.'''
|
|
|
|
self.print_error("paired")
|
|
|
|
|
|
|
|
def can_export(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_watching_only(self):
|
|
|
|
'''The wallet is not watching-only; the user will be prompted for
|
|
|
|
pin and passphrase as appropriate when needed.'''
|
|
|
|
assert not self.has_seed()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_change_password(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-15 02:48:33 -07:00
|
|
|
def bip39_normalize_passphrase(passphrase):
|
|
|
|
return normalize('NFKD', unicode(passphrase or ''))
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-15 02:48:33 -07:00
|
|
|
def bip39_to_seed(mnemonic, passphrase):
|
|
|
|
import pbkdf2, hashlib, hmac
|
|
|
|
PBKDF2_ROUNDS = 2048
|
|
|
|
mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))
|
|
|
|
passphrase = bip39_normalize_passphrase(passphrase)
|
|
|
|
return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase,
|
|
|
|
iterations = PBKDF2_ROUNDS, macmodule = hmac,
|
|
|
|
digestmodule = hashlib.sha512).read(64)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-17 05:28:37 -07:00
|
|
|
# extended pubkeys
|
|
|
|
|
|
|
|
def is_xpubkey(x_pubkey):
|
|
|
|
return x_pubkey[0:2] == 'ff'
|
|
|
|
|
|
|
|
def parse_xpubkey(x_pubkey):
|
|
|
|
assert x_pubkey[0:2] == 'ff'
|
|
|
|
return BIP32_KeyStore.parse_xpubkey(x_pubkey)
|
|
|
|
|
|
|
|
def xpubkey_to_address(x_pubkey):
|
|
|
|
if x_pubkey[0:2] in ['02','03','04']:
|
|
|
|
pubkey = x_pubkey
|
|
|
|
elif x_pubkey[0:2] == 'ff':
|
|
|
|
xpub, s = BIP32_KeyStore.parse_xpubkey(x_pubkey)
|
2016-09-06 01:09:08 -07:00
|
|
|
pubkey = BIP32_KeyStore.get_pubkey_from_xpub(xpub, s)
|
2016-08-17 05:28:37 -07:00
|
|
|
elif x_pubkey[0:2] == 'fe':
|
|
|
|
mpk, s = Old_KeyStore.parse_xpubkey(x_pubkey)
|
2016-09-01 04:52:49 -07:00
|
|
|
pubkey = Old_KeyStore.get_pubkey_from_mpk(mpk, s[0], s[1])
|
2016-08-17 05:28:37 -07:00
|
|
|
elif x_pubkey[0:2] == 'fd':
|
|
|
|
addrtype = ord(x_pubkey[2:4].decode('hex'))
|
|
|
|
hash160 = x_pubkey[4:].decode('hex')
|
|
|
|
pubkey = None
|
|
|
|
address = hash_160_to_bc_address(hash160, addrtype)
|
|
|
|
else:
|
|
|
|
raise BaseException("Cannnot parse pubkey")
|
|
|
|
if pubkey:
|
|
|
|
address = public_key_to_bc_address(pubkey.decode('hex'))
|
|
|
|
return pubkey, address
|
|
|
|
|
|
|
|
|
2016-08-21 02:58:15 -07:00
|
|
|
hw_keystores = {}
|
|
|
|
|
|
|
|
def register_keystore(hw_type, constructor):
|
|
|
|
hw_keystores[hw_type] = constructor
|
|
|
|
|
2016-08-23 23:52:21 -07:00
|
|
|
def hardware_keystore(d):
|
|
|
|
hw_type = d['hw_type']
|
2016-08-21 02:58:15 -07:00
|
|
|
if hw_type in hw_keystores:
|
|
|
|
constructor = hw_keystores[hw_type]
|
|
|
|
return constructor(d)
|
|
|
|
raise BaseException('unknown hardware type', hw_type)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
|
|
|
def load_keystore(storage, name):
|
2016-08-21 02:58:15 -07:00
|
|
|
w = storage.get('wallet_type', 'standard')
|
|
|
|
d = storage.get(name, {})
|
|
|
|
t = d.get('type')
|
|
|
|
if not t:
|
|
|
|
raise BaseException('wallet format requires update')
|
|
|
|
if t == 'old':
|
|
|
|
k = Old_KeyStore(d)
|
2016-07-01 23:58:56 -07:00
|
|
|
elif t == 'imported':
|
2016-08-21 02:58:15 -07:00
|
|
|
k = Imported_KeyStore(d)
|
|
|
|
elif t == 'bip32':
|
|
|
|
k = BIP32_KeyStore(d)
|
2016-07-01 23:58:56 -07:00
|
|
|
elif t == 'hardware':
|
2016-08-23 23:52:21 -07:00
|
|
|
k = hardware_keystore(d)
|
2016-07-01 23:58:56 -07:00
|
|
|
else:
|
|
|
|
raise BaseException('unknown wallet type', t)
|
|
|
|
return k
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_old_mpk(mpk):
|
|
|
|
try:
|
|
|
|
int(mpk, 16)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
return len(mpk) == 128
|
|
|
|
|
|
|
|
def is_xpub(text):
|
|
|
|
if text[0:4] != 'xpub':
|
|
|
|
return False
|
|
|
|
try:
|
|
|
|
deserialize_xkey(text)
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_xprv(text):
|
|
|
|
if text[0:4] != 'xprv':
|
|
|
|
return False
|
|
|
|
try:
|
|
|
|
deserialize_xkey(text)
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_address_list(text):
|
|
|
|
parts = text.split()
|
|
|
|
return bool(parts) and all(bitcoin.is_address(x) for x in parts)
|
|
|
|
|
|
|
|
def is_private_key_list(text):
|
|
|
|
parts = text.split()
|
|
|
|
return bool(parts) and all(bitcoin.is_private_key(x) for x in parts)
|
|
|
|
|
2016-09-27 21:30:00 -07:00
|
|
|
|
2016-07-01 23:58:56 -07:00
|
|
|
is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
|
|
|
|
is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
|
|
|
|
is_any_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x) or is_address_list(x) or is_private_key_list(x)
|
|
|
|
is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
|
|
|
|
is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
|
|
|
|
|
2016-08-31 00:35:27 -07:00
|
|
|
def bip44_derivation(account_id):
|
|
|
|
return "m/44'/0'/%d'"% int(account_id)
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def from_seed(seed, passphrase):
|
2016-07-01 23:58:56 -07:00
|
|
|
if is_old_seed(seed):
|
2016-08-21 02:58:15 -07:00
|
|
|
keystore = Old_KeyStore({})
|
2016-08-25 03:18:51 -07:00
|
|
|
keystore.add_seed(seed)
|
2016-07-01 23:58:56 -07:00
|
|
|
elif is_new_seed(seed):
|
2016-08-21 02:58:15 -07:00
|
|
|
keystore = BIP32_KeyStore({})
|
2016-08-25 03:18:51 -07:00
|
|
|
keystore.add_seed(seed)
|
2016-08-26 07:21:36 -07:00
|
|
|
keystore.passphrase = passphrase
|
2016-08-25 00:48:11 -07:00
|
|
|
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
|
2016-08-25 03:18:51 -07:00
|
|
|
keystore.add_xprv_from_seed(bip32_seed, "m/")
|
2016-07-01 23:58:56 -07:00
|
|
|
return keystore
|
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def from_private_key_list(text):
|
2016-08-21 02:58:15 -07:00
|
|
|
keystore = Imported_KeyStore({})
|
2016-07-01 23:58:56 -07:00
|
|
|
for x in text.split():
|
|
|
|
keystore.import_key(x, None)
|
|
|
|
return keystore
|
|
|
|
|
|
|
|
def from_old_mpk(mpk):
|
2016-08-21 02:58:15 -07:00
|
|
|
keystore = Old_KeyStore({})
|
2016-07-01 23:58:56 -07:00
|
|
|
keystore.add_master_public_key(mpk)
|
|
|
|
return keystore
|
|
|
|
|
|
|
|
def from_xpub(xpub):
|
2016-08-25 03:18:51 -07:00
|
|
|
k = BIP32_KeyStore({})
|
|
|
|
k.xpub = xpub
|
|
|
|
return k
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def from_xprv(xprv):
|
2016-07-01 23:58:56 -07:00
|
|
|
xpub = bitcoin.xpub_from_xprv(xprv)
|
2016-08-25 03:18:51 -07:00
|
|
|
k = BIP32_KeyStore({})
|
|
|
|
k.xprv = xprv
|
|
|
|
k.xpub = xpub
|
|
|
|
return k
|
2016-07-01 23:58:56 -07:00
|
|
|
|
2016-08-25 03:18:51 -07:00
|
|
|
def from_keys(text):
|
2016-07-01 23:58:56 -07:00
|
|
|
if is_xprv(text):
|
2016-08-25 03:18:51 -07:00
|
|
|
k = from_xprv(text)
|
2016-07-01 23:58:56 -07:00
|
|
|
elif is_old_mpk(text):
|
|
|
|
k = from_old_mpk(text)
|
|
|
|
elif is_xpub(text):
|
|
|
|
k = from_xpub(text)
|
|
|
|
elif is_private_key_list(text):
|
2016-08-25 03:18:51 -07:00
|
|
|
k = from_private_key_list(text)
|
2016-07-01 23:58:56 -07:00
|
|
|
else:
|
2016-08-25 00:48:11 -07:00
|
|
|
raise BaseException('Invalid key')
|
2016-07-01 23:58:56 -07:00
|
|
|
return k
|