1
0

Merge branch '2.0'

This commit is contained in:
ThomasV
2014-02-27 10:41:22 +01:00
14 changed files with 730 additions and 311 deletions

View File

@@ -73,6 +73,7 @@ class WalletStorage:
def __init__(self, config):
self.lock = threading.Lock()
self.config = config
self.data = {}
self.file_exists = False
self.path = self.init_path(config)
@@ -151,7 +152,10 @@ class WalletStorage:
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
class Wallet:
class NewWallet:
def __init__(self, storage):
@@ -160,7 +164,7 @@ class Wallet:
self.gap_limit_for_change = 3 # constant
# saved fields
self.seed_version = storage.get('seed_version', SEED_VERSION)
self.seed_version = storage.get('seed_version', NEW_SEED_VERSION)
self.gap_limit = storage.get('gap_limit', 5)
self.use_change = storage.get('use_change',True)
@@ -180,12 +184,6 @@ class Wallet:
self.next_addresses = storage.get('next_addresses',{})
if self.seed_version not in [4, 6]:
msg = "This wallet seed is not supported."
if self.seed_version in [5]:
msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%self.seed_version
print msg
sys.exit(1)
# This attribute is set when wallet.start_threads is called.
self.synchronizer = None
@@ -290,42 +288,27 @@ class Wallet:
# we keep only 13 words, that's approximately 139 bits of entropy
words = mnemonic.mn_encode(s)[0:13]
seed = ' '.join(words)
if mnemonic_hash(seed).startswith(SEED_PREFIX):
break # this removes 12 bits of entropy
if is_seed(seed):
break # this will remove 8 bits of entropy
nonce += 1
return seed
def init_seed(self, seed):
import mnemonic
import mnemonic, unicodedata
if self.seed:
raise Exception("a seed exists")
self.seed_version = NEW_SEED_VERSION
if not seed:
self.seed = random_seed(128)
self.seed_version = 4
self.seed = self.make_seed()
return
# check if seed is hexadecimal
seed = seed.strip()
try:
assert seed
seed.decode('hex')
self.seed_version = 4
self.seed = str(seed)
return
except Exception:
pass
self.seed = unicodedata.normalize('NFC', unicode(seed.strip()))
words = seed.split()
self.seed_version = 4
self.seed = mnemonic.mn_decode(words)
if not self.seed:
raise Exception("Invalid seed")
def save_seed(self, password):
@@ -338,41 +321,26 @@ class Wallet:
self.create_accounts(password)
def create_watching_only_wallet(self, params):
K0, c0 = params
if not K0:
return
if not c0:
self.seed_version = 4
self.storage.put('seed_version', self.seed_version, True)
self.create_old_account(K0)
return
cK0 = ""
def create_watching_only_wallet(self, K0, c0):
cK0 = "" #FIXME
self.master_public_keys = {
"m/0'/": (c0, K0, cK0),
}
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('seed_version', self.seed_version, True)
self.create_account('1','Main account')
self.create_account('1of1','Main account')
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
if self.seed_version == 4:
mpk = OldAccount.mpk_from_seed(seed)
self.create_old_account(mpk)
else:
# create default account
self.create_master_keys('1', password)
self.create_account('1','Main account')
# create default account
self.create_master_keys('1of1', password)
self.create_account('1of1','Main account')
def create_master_keys(self, account_type, password):
master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
if account_type == '1':
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)
@@ -398,7 +366,7 @@ class Wallet:
self.storage.put('master_private_keys', self.master_private_keys, True)
def has_master_public_keys(self, account_type):
if account_type == '1':
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())
@@ -421,17 +389,18 @@ class Wallet:
def deseed_branch(self, k):
# check that parent has no seed
assert self.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 == '1':
if account_type == '1of1':
return "m/0'/%d"%i
elif account_type == '2of2':
return "m/1'/%d & m/2'/%d"%(i,i)
@@ -451,7 +420,7 @@ class Wallet:
return i
def new_account_address(self, account_type = '1'):
def new_account_address(self, account_type = '1of1'):
i = self.num_accounts(account_type)
k = self.account_id(account_type,i)
@@ -465,12 +434,12 @@ class Wallet:
return k, addr
def next_account(self, account_type = '1'):
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 '1':
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 })
@@ -514,7 +483,7 @@ class Wallet:
def create_account(self, account_type = '1', name = None):
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)
@@ -526,12 +495,6 @@ class Wallet:
self.set_label(k, name)
def create_old_account(self, mpk):
self.storage.put('master_public_key', mpk, True)
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def save_accounts(self):
d = {}
for k, v in self.accounts.items():
@@ -596,14 +559,13 @@ class Wallet:
return s[0] == 1
def get_master_public_key(self):
if self.seed_version == 4:
return self.storage.get("master_public_key")
else:
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
return repr((c, K))
c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
return repr((c, K))
def get_master_private_key(self, account, password):
master_k = pw_decode( self.master_private_keys[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'))
@@ -675,25 +637,14 @@ class Wallet:
return '&'.join(dd)
def get_seed(self, password):
s = pw_decode(self.seed, password)
if self.seed_version == 4:
seed = s
self.accounts[0].check_seed(seed)
else:
seed = mnemonic_hash(s)
seed = mnemonic_to_seed(s,'').encode('hex')
return seed
def get_mnemonic(self, password):
import mnemonic
s = pw_decode(self.seed, password)
if self.seed_version == 4:
return ' '.join(mnemonic.mn_encode(s))
else:
return s
return pw_decode(self.seed, password)
def get_private_key(self, address, password):
@@ -746,23 +697,6 @@ class Wallet:
for txin in tx.inputs:
keyid = txin.get('KeyID')
if keyid:
if self.seed_version == 4:
m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
if not m: continue
mpk = m.group(1)
if mpk != self.storage.get('master_public_key'): continue
for_change = int(m.group(2))
num = int(m.group(3))
account = self.accounts[0]
addr = account.get_address(for_change, num)
txin['address'] = addr # fixme: side effect
pk = account.get_private_key(seed, (for_change, num))
pubkey = public_key_from_private_key(pk)
keypairs[pubkey] = pk
continue
roots = []
for s in keyid.split('&'):
m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
@@ -921,7 +855,7 @@ class Wallet:
def create_pending_accounts(self):
for account_type in ['1','2of2','2of3']:
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)
@@ -1050,29 +984,23 @@ class Wallet:
def get_account_name(self, k):
if k == 0:
if self.seed_version == 4:
name = 'Main account'
default = "Unnamed account"
m = re.match("m/0'/(\d+)", k)
if m:
num = m.group(1)
if num == '0':
default = "Main account"
else:
name = 'Old account'
else:
default = "Unnamed account"
m = re.match("m/0'/(\d+)", k)
if m:
num = m.group(1)
if num == '0':
default = "Main account"
else:
default = "Account %s"%num
default = "Account %s"%num
m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
if m:
num = m.group(1)
default = "2of2 account %s"%num
name = self.labels.get(k, default)
m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
if m:
num = m.group(1)
default = "2of2 account %s"%num
name = self.labels.get(k, default)
return name
def get_account_names(self):
accounts = {}
for k, account in self.accounts.items():
@@ -1081,6 +1009,7 @@ class Wallet:
accounts[-1] = 'Imported keys'
return accounts
def get_account_addresses(self, a, include_change=True):
if a is None:
o = self.addresses(True)
@@ -1537,7 +1466,7 @@ class Wallet:
def start_threads(self, network):
from verifier import TxVerifier
self.network = network
if self.network:
if self.network is not None:
self.verifier = TxVerifier(self.network, self.storage)
self.verifier.start()
self.set_verifier(self.verifier)
@@ -1622,12 +1551,12 @@ class WalletSynchronizer(threading.Thread):
if not self.network.is_connected():
self.network.wait_until_connected()
self.run_interface(self.network.interface)
self.run_interface()
def run_interface(self, interface):
def run_interface(self):
print_error("synchronizer: connected to", interface.server)
print_error("synchronizer: connected to", self.network.main_server())
requested_tx = []
missing_tx = []
@@ -1657,12 +1586,12 @@ class WalletSynchronizer(threading.Thread):
# request missing transactions
for tx_hash, tx_height in missing_tx:
if (tx_hash, tx_height) not in requested_tx:
interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
self.network.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
requested_tx.append( (tx_hash, tx_height) )
missing_tx = []
# detect if situation has changed
if interface.is_up_to_date() and self.queue.empty():
if self.network.is_up_to_date() and self.queue.empty():
if not self.wallet.is_up_to_date():
self.wallet.set_up_to_date(True)
self.was_updated = True
@@ -1672,7 +1601,7 @@ class WalletSynchronizer(threading.Thread):
self.was_updated = True
if self.was_updated:
self.wallet.network.trigger_callback('updated')
self.network.trigger_callback('updated')
self.was_updated = False
# 2. get a response
@@ -1681,8 +1610,9 @@ class WalletSynchronizer(threading.Thread):
except Queue.Empty:
continue
if interface != self.network.interface:
break
# see if it changed
#if interface != self.network.interface:
# break
if not r:
continue
@@ -1700,7 +1630,7 @@ class WalletSynchronizer(threading.Thread):
addr = params[0]
if self.wallet.get_status(self.wallet.get_history(addr)) != result:
if requested_histories.get(addr) is None:
interface.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
self.network.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
requested_histories[addr] = result
elif method == 'blockchain.address.get_history':
@@ -1750,7 +1680,163 @@ class WalletSynchronizer(threading.Thread):
print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
if self.was_updated and not requested_tx:
self.wallet.network.trigger_callback('updated')
self.wallet.network.trigger_callback("new_transaction") # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
self.network.trigger_callback('updated')
# Updated gets called too many times from other places as well; if we use that signal we get the notification three times
self.network.trigger_callback("new_transaction")
self.was_updated = False
class OldWallet(NewWallet):
def init_seed(self, seed):
import mnemonic
if self.seed:
raise Exception("a seed exists")
if not seed:
seed = random_seed(128)
self.seed_version = OLD_SEED_VERSION
# see if seed was entered as hex
seed = seed.strip()
try:
assert seed
seed.decode('hex')
self.seed = str(seed)
return
except Exception:
pass
words = seed.split()
try:
mnemonic.mn_decode(words)
except Exception:
raise
self.seed = mnemonic.mn_decode(words)
if not self.seed:
raise Exception("Invalid seed")
def get_master_public_key(self):
return self.storage.get("master_public_key")
def create_accounts(self, password):
seed = pw_decode(self.seed, password)
mpk = OldAccount.mpk_from_seed(seed)
self.create_account(mpk)
def create_account(self, mpk):
self.storage.put('master_public_key', mpk, True)
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
self.save_accounts()
def create_watching_only_wallet(self, K0):
self.seed_version = OLD_SEED_VERSION
self.storage.put('seed_version', self.seed_version, True)
self.create_account(K0)
def get_seed(self, password):
seed = pw_decode(self.seed, password)
self.accounts[0].check_seed(seed)
return seed
def get_mnemonic(self, password):
import mnemonic
s = pw_decode(self.seed, password)
return ' '.join(mnemonic.mn_encode(s))
def add_keypairs_from_KeyID(self, tx, keypairs, password):
# first check the provided password
seed = self.get_seed(password)
for txin in tx.inputs:
keyid = txin.get('KeyID')
if keyid:
m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
if not m: continue
mpk = m.group(1)
if mpk != self.storage.get('master_public_key'): continue
for_change = int(m.group(2))
num = int(m.group(3))
account = self.accounts[0]
addr = account.get_address(for_change, num)
txin['address'] = addr # fixme: side effect
pk = account.get_private_key(seed, (for_change, num))
pubkey = public_key_from_private_key(pk)
keypairs[pubkey] = pk
def get_account_name(self, k):
assert k == 0
return 'Main account'
# former WalletFactory
class Wallet(object):
def __new__(self, storage):
config = storage.config
if config.get('bitkey', False):
# if user requested support for Bitkey device,
# import Bitkey driver
from wallet_bitkey import WalletBitkey
return WalletBitkey(config)
if not storage.file_exists:
seed_version = NEW_SEED_VERSION if config.get('bip32') is True else OLD_SEED_VERSION
else:
seed_version = storage.get('seed_version')
if seed_version == OLD_SEED_VERSION:
return OldWallet(storage)
elif seed_version == NEW_SEED_VERSION:
return NewWallet(storage)
else:
msg = "This wallet seed is not supported."
if seed_version in [5]:
msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
print msg
sys.exit(1)
@classmethod
def from_seed(self, seed, storage):
import mnemonic
if not seed:
return
words = seed.strip().split()
try:
mnemonic.mn_decode(words)
uses_electrum_words = True
except Exception:
uses_electrum_words = False
try:
seed.decode('hex')
is_hex = True
except Exception:
is_hex = False
if is_hex or (uses_electrum_words and len(words) != 13):
print "old style wallet", len(words), words
w = OldWallet(storage)
w.init_seed(seed) #hex
else:
#assert is_seed(seed)
w = Wallet(storage)
w.init_seed(seed)
return w