COINBASE_MATURITY = 100
DUST_THRESHOLD = 5430
+# internal ID for imported account
+IMPORTED_ACCOUNT = '/x'
+
# AES encryption
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
self.frozen_addresses = storage.get('frozen_addresses',[])
self.addressbook = storage.get('contacts', [])
- self.imported_keys = storage.get('imported_keys',{})
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
self.fee = int(storage.get('fee_per_kb', 10000))
def load_accounts(self):
self.accounts = {}
+ self.imported_keys = self.storage.get('imported_keys',{})
+ if self.imported_keys:
+ self.accounts['/x'] = ImportedAccount(self.imported_keys)
def synchronize(self):
pass
- def get_pending_accounts(self):
- return {}
-
def can_create_accounts(self):
return False
def check_password(self, password):
- pass
+ raise
def set_up_to_date(self,b):
# store the originally requested keypair into the imported keys table
self.imported_keys[address] = pw_encode(sec, password )
self.storage.put('imported_keys', self.imported_keys, True)
+ self.accounts[IMPORTED_ACCOUNT] = ImportedAccount(self.imported_keys)
+
if self.synchronizer:
self.synchronizer.subscribe_to_addresses([address])
return address
if addr in self.imported_keys:
self.imported_keys.pop(addr)
self.storage.put('imported_keys', self.imported_keys, True)
-
+ if self.imported_keys:
+ self.accounts[IMPORTED_ACCOUNT] = ImportedAccount(self.imported_keys)
+ else:
+ self.accounts.pop(IMPORTED_ACCOUNT)
def set_label(self, name, text = None):
def addresses(self, include_change = True, _next=True):
- o = self.get_account_addresses(-1, include_change)
+ o = []
for a in self.accounts.keys():
o += self.get_account_addresses(a, include_change)
def is_mine(self, address):
- return address in self.addresses(True)
+ return address in self.addresses(True)
def is_change(self, address):
if not self.is_mine(address): return False
- if address in self.imported_keys.keys(): return False
acct, s = self.get_address_index(address)
if s is None: return False
return s[0] == 1
def get_address_index(self, address):
- if address in self.imported_keys.keys():
- return -1, None
for account in self.accounts.keys():
for for_change in [0,1]:
def getpubkeys(self, addr):
assert is_valid(addr) and self.is_mine(addr)
account, sequence = self.get_address_index(addr)
- if account != -1:
+ if account != IMPORTED_ACCOUNT:
a = self.accounts[account]
return a.get_pubkeys( sequence )
if self.is_watching_only():
return []
- # first check the provided password
- seed = self.get_seed(password)
-
out = []
if address in self.imported_keys.keys():
+ self.check_password(password)
out.append( pw_decode( self.imported_keys[address], password ) )
else:
+ seed = self.get_seed(password)
account_id, sequence = self.get_address_index(address)
account = self.accounts[account_id]
xpubs = account.get_master_pubkeys()
for sec in private_keys:
pubkey = public_key_from_private_key(sec)
keypairs[ pubkey ] = sec
+
+ # this is needed because we don't store imported pubkeys
if address in self.imported_keys.keys():
txin['redeemPubkey'] = pubkey
def get_addr_balance(self, address):
- assert self.is_mine(address)
+ #assert self.is_mine(address)
h = self.history.get(address,[])
if h == ['*']: return 0,0
c = u = 0
def get_account_name(self, k):
- 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
-
- 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
+ return self.labels.get(k, self.accounts[k].get_name(k))
def get_account_names(self):
- accounts = {}
- for k, account in self.accounts.items():
- accounts[k] = self.get_account_name(k)
- if self.imported_keys:
- accounts[-1] = 'Imported keys'
- return accounts
+ account_names = {}
+ for k in self.accounts.keys():
+ account_names[k] = self.get_account_name(k)
+ return account_names
def get_account_addresses(self, a, include_change=True):
if a is None:
o = self.addresses(True)
- elif a == -1:
- o = self.imported_keys.keys()
- else:
+ elif a in self.accounts:
ac = self.accounts[a]
o = ac.get_addresses(0)
if include_change: o += ac.get_addresses(1)
return o
- def get_imported_balance(self):
- return self.get_balance(self.imported_keys.keys())
def get_account_balance(self, account):
return self.get_balance(self.get_account_addresses(account))
address = inputs[0].get('address')
account, _ = self.get_address_index(address)
- if not self.use_change or account == -1:
+ if not self.use_change or account == IMPORTED_ACCOUNT:
change_addr = inputs[-1]['address']
else:
change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
def update_password(self, old_password, new_password):
- if new_password == '': new_password = None
- decoded = self.get_seed(old_password)
- self.seed = pw_encode( decoded, new_password)
- self.storage.put('seed', self.seed, True)
- self.use_encryption = (new_password != None)
- self.storage.put('use_encryption', self.use_encryption,True)
+ if new_password == '':
+ new_password = None
+
+ if self.has_seed():
+ decoded = self.get_seed(old_password)
+ self.seed = pw_encode( decoded, new_password)
+ self.storage.put('seed', self.seed, True)
+
for k in self.imported_keys.keys():
a = self.imported_keys[k]
b = pw_decode(a, old_password)
self.master_private_keys[k] = c
self.storage.put('master_private_keys', self.master_private_keys, True)
+ self.use_encryption = (new_password != None)
+ self.storage.put('use_encryption', self.use_encryption,True)
+
def freeze(self,addr):
if self.is_mine(addr) and addr not in self.frozen_addresses:
def restore(self, cb):
pass
+ def get_accounts(self):
+ return self.accounts
class Imported_Wallet(Abstract_Wallet):
def has_seed(self):
return False
+ def is_deterministic(self):
+ return False
+
+ def check_password(self, password):
+ if self.imported_keys:
+ k, v = self.imported_keys.items()[0]
+ sec = pw_decode(v, password)
+ address = address_from_private_key(sec)
+ assert address == k
+
+
class Deterministic_Wallet(Abstract_Wallet):
def has_seed(self):
return self.seed != ''
+ def is_deterministic(self):
+ return True
+
def is_watching_only(self):
return not self.has_seed()
def check_password(self, password):
self.get_seed(password)
+ def add_seed(self, seed, password):
+ if self.seed:
+ raise Exception("a seed exists")
+
+ self.seed_version, self.seed = self.prepare_seed(seed)
+ if password:
+ self.seed = pw_encode( self.seed, password)
+ self.use_encryption = True
+ else:
+ self.use_encryption = False
+
+ self.storage.put('seed', self.seed, True)
+ self.storage.put('seed_version', self.seed_version, True)
+ self.storage.put('use_encryption', self.use_encryption,True)
+ self.create_master_keys(password)
+
def get_seed(self, password):
s = pw_decode(self.seed, password)
seed = mnemonic_to_seed(s,'').encode('hex')
self.check_pending_accounts()
new = []
for account in self.accounts.values():
+ if type(account) in [ImportedAccount, PendingAccount]:
+ continue
new += self.synchronize_account(account)
if new:
self.save_accounts()
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 load_accounts(self):
+ Abstract_Wallet.load_accounts(self)
d = self.storage.get('accounts', {})
- self.accounts = {}
for k, v in d.items():
if k == 0:
v['mpk'] = self.storage.get('master_public_key')
self.accounts[k] = BIP32_Account_2of2(v)
elif v.get('xpub'):
self.accounts[k] = BIP32_Account(v)
+ elif v.get('pending'):
+ self.accounts[k] = PendingAccount(v)
else:
- raise
+ print_error("cannot load account", v)
- self.pending_accounts = self.storage.get('pending_accounts',{})
+ def account_is_pending(self, k):
+ return type(self.accounts.get(k)) == PendingAccount
def delete_pending_account(self, k):
- self.pending_accounts.pop(k)
- self.storage.put('pending_accounts', self.pending_accounts)
-
- def account_is_pending(self, k):
- return k in self.pending_accounts
+ assert self.account_is_pending(k)
+ self.accounts.pop(k)
+ self.save_accounts()
def create_pending_account(self, name, password):
account_id, addr = self.next_account_address(password)
self.set_label(account_id, name)
- self.pending_accounts[account_id] = addr
- self.storage.put('pending_accounts', self.pending_accounts)
+ self.accounts[account_id] = PendingAccount({'pending':addr})
+ self.save_accounts()
- def get_pending_accounts(self):
- return self.pending_accounts.items()
xpriv = pw_decode( k, password)
return xpriv
- def add_seed(self, seed, password):
- if self.seed:
- raise Exception("a seed exists")
-
- self.seed_version, self.seed = self.prepare_seed(seed)
- if password:
- self.seed = pw_encode( self.seed, password)
- self.use_encryption = True
- else:
- self.use_encryption = False
-
- self.storage.put('seed', self.seed, True)
- self.storage.put('seed_version', self.seed_version, True)
- self.storage.put('use_encryption', self.use_encryption,True)
- self.create_master_keys(password)
-
def create_watching_only_wallet(self, xpub):
self.storage.put('seed_version', self.seed_version, True)
keypairs[pubkey] = pk
- def get_account_name(self, k):
- assert k == 0
- return 'Main account'
-
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():
+ self.check_password(password)
out.append( pw_decode( self.imported_keys[address], password ) )
else:
+ seed = self.get_seed(password)
account_id, sequence = self.get_address_index(address)
pk = self.accounts[0].get_private_key(seed, sequence)
out.append(pk)