Browse Source

Refactored wallet printout, reusing WalletView

Add extpub keys for branches to wallet view
Make extpub keys copyable via context menu
Fix bug in seedwords display
Make all wallet view columns resizable
hexseed only for regtest
master
Adam Gibson 8 years ago
parent
commit
5018e28e61
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 3
      jmclient/jmclient/__init__.py
  2. 55
      jmclient/jmclient/wallet_utils.py
  3. 182
      scripts/joinmarket-qt.py
  4. 10
      scripts/qtsupport.py

3
jmclient/jmclient/__init__.py

@ -37,7 +37,8 @@ from .commitment_utils import get_utxo_info, validate_utxo_data, quit
from .taker_utils import (tumbler_taker_finished_update, restart_waiter,
restart_wait, get_tumble_log, direct_send,
tumbler_filter_orders_callback)
from .wallet_utils import wallet_tool_main
from .wallet_utils import (wallet_tool_main, wallet_generate_recover_bip39,
wallet_display)
from .maker import Maker
from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain
# Set default logging handler to avoid "No handler found" warnings.

55
jmclient/jmclient/wallet_utils.py

@ -299,9 +299,11 @@ def wallet_showutxos(wallet, showprivkey):
return json.dumps(unsp, indent=4)
def wallet_display(wallet, gaplimit, showprivkey, displayall=False):
def wallet_display(wallet, gaplimit, showprivkey, displayall=False,
serialized=True):
"""build the walletview object,
then return its serialization directly
then return its serialization directly if serialized,
else return the WalletView object.
"""
acctlist = []
rootpath = wallet.get_root_path()
@ -342,9 +344,12 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False):
acctlist.append(WalletViewAccount(rootpath, m, branchlist,
xpub=xpub_account))
walletview = WalletView(rootpath, acctlist)
return walletview.serialize()
if serialized:
return walletview.serialize()
else:
return walletview
def get_password_check():
def cli_password_check():
password = get_password('Enter wallet encryption passphrase: ')
password2 = get_password('Reenter wallet encryption passphrase: ')
if password != password2:
@ -353,13 +358,23 @@ def get_password_check():
password_key = btc.bin_dbl_sha256(password)
return password, password_key
def persist_walletfile(walletspath, default_wallet_name, encrypted_seed):
def cli_get_walletname():
return raw_input('Input wallet file name (default: wallet.json): ')
def cli_user_words(words):
print('Write down this wallet recovery seed\n\n' + words +'\n')
def cli_user_words_entry():
return raw_input("Input 12 word recovery seed: ")
def persist_walletfile(walletspath, default_wallet_name, encrypted_seed,
callbacks=(cli_get_walletname,)):
timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
walletfile = json.dumps({'creator': 'joinmarket project',
'creation_time': timestamp,
'encrypted_seed': encrypted_seed.encode('hex'),
'network': get_network()})
walletname = raw_input('Input wallet file name (default: wallet.json): ')
walletname = callbacks[0]()
if len(walletname) == 0:
walletname = default_wallet_name
walletpath = os.path.join(walletspath, walletname)
@ -374,25 +389,39 @@ def persist_walletfile(walletspath, default_wallet_name, encrypted_seed):
print('saved to ' + walletname)
return True
def wallet_generate_recover_bip39(method, walletspath, default_wallet_name):
def wallet_generate_recover_bip39(method, walletspath, default_wallet_name,
callbacks=(cli_user_words,
cli_user_words_entry,
cli_password_check,
cli_get_walletname)):
"""Optionally provide callbacks:
0 - display seed
1 - enter seed (for recovery)
2 - enter password
3 - enter wallet name
The defaults are for terminal entry.
"""
#using 128 bit entropy, 12 words, mnemonic module
m = Mnemonic("english")
if method == "generate":
words = m.generate()
print('Write down this wallet recovery seed\n\n' + words +'\n')
callbacks[0](words)
elif method == 'recover':
words = raw_input('Input 12 word recovery seed: ')
words = callbacks[1]()
entropy = str(m.to_entropy(words))
password, password_key = get_password_check()
password, password_key = callbacks[2]()
if not password:
return False
encrypted_entropy = encryptData(password_key, entropy)
return persist_walletfile(walletspath, default_wallet_name, encrypted_entropy)
return persist_walletfile(walletspath, default_wallet_name, encrypted_entropy,
callbacks=(callbacks[3],))
def wallet_generate_recover(method, walletspath,
default_wallet_name='wallet.json'):
if jm_single().config.get("POLICY", "segwit") == "true":
return wallet_generate_recover_bip39(method, walletspath, default_wallet_name)
#Here using default callbacks for scripts (not used in Qt)
return wallet_generate_recover_bip39(method, walletspath,
default_wallet_name)
if method == 'generate':
seed = btc.sha256(os.urandom(64))[:32]
words = mn_encode(seed)
@ -406,7 +435,7 @@ def wallet_generate_recover(method, walletspath,
return False
seed = mn_decode(words)
print(seed)
password, password_key = get_password_check()
password, password_key = cli_password_check()
if not password:
return False
encrypted_seed = encryptData(password_key, seed.decode('hex'))

182
scripts/joinmarket-qt.py

@ -44,16 +44,17 @@ donation_address = "1AZgQZWYRteh6UyF87hwuvyWj73NvWKpL"
JM_CORE_VERSION = '0.2.2'
JM_GUI_VERSION = '5'
from jmclient import (load_program_config, get_network, Wallet,
get_p2pk_vbyte, jm_single, validate_address,
from jmclient import (load_program_config, get_network, SegwitWallet,
get_p2sh_vbyte, jm_single, validate_address,
get_log, weighted_order_choose, Taker,
JMTakerClientProtocolFactory, WalletError,
JMClientProtocolFactory, WalletError,
start_reactor, get_schedule, get_tumble_schedule,
schedule_to_text, mn_decode, mn_encode, create_wallet_file,
schedule_to_text, create_wallet_file,
get_blockchain_interface_instance, sync_wallet, direct_send,
RegtestBitcoinCoreInterface, tweak_tumble_schedule,
human_readable_schedule_entry, tumbler_taker_finished_update,
get_tumble_log, restart_wait, tumbler_filter_orders_callback)
get_tumble_log, restart_wait, tumbler_filter_orders_callback,
wallet_generate_recover_bip39, wallet_display)
from qtsupport import (ScheduleWizard, TumbleRestartWizard, warnings, config_tips,
config_types, TaskThread, QtHandler, XStream, Buttons,
@ -678,7 +679,7 @@ class SpendTab(QWidget):
if not self.clientfactory:
#First run means we need to start: create clientfactory
#and start reactor Thread
self.clientfactory = JMTakerClientProtocolFactory(self.taker)
self.clientfactory = JMClientProtocolFactory(self.taker)
thread = TaskThread(self)
daemon = jm_single().config.getint("DAEMON", "no_daemon")
daemon = True if daemon == 1 else False
@ -1128,18 +1129,24 @@ class JMWalletTab(QWidget):
def create_menu(self, position):
item = self.history.currentItem()
address_valid = False
xpub_exists = False
if item:
address = str(item.text(0))
try:
btc.b58check_to_hex(address)
txt = str(item.text(0))
if validate_address(txt)[0]:
address_valid = True
except AssertionError:
log.debug('no btc address found, not creating menu item')
if "EXTERNAL" in txt:
parsed = txt.split()
if len(parsed) > 1:
xpub = parsed[1]
xpub_exists = True
menu = QMenu()
if address_valid:
menu.addAction("Copy address to clipboard",
lambda: app.clipboard().setText(address))
lambda: app.clipboard().setText(txt))
if xpub_exists:
menu.addAction("Copy extended pubkey to clipboard",
lambda: app.clipboard().setText(xpub))
menu.addAction("Resync wallet from blockchain",
lambda: w.resyncWallet())
#TODO add more items to context menu
@ -1150,7 +1157,7 @@ class JMWalletTab(QWidget):
l.clear()
if walletinfo:
self.mainwindow = self.parent().parent().parent()
rows, mbalances, total_bal = walletinfo
rows, mbalances, xpubs, total_bal = walletinfo
if get_network() == 'testnet':
self.wallet_name = self.mainwindow.wallet.seed
else:
@ -1167,9 +1174,10 @@ class JMWalletTab(QWidget):
mdbalance, '', '', '', ''])
l.addChild(m_item)
for forchange in [0, 1]:
heading = 'EXTERNAL' if forchange == 0 else 'INTERNAL'
heading_end = ' addresses m/0/%d/%d/' % (i, forchange)
heading += heading_end
heading = "EXTERNAL" if forchange == 0 else "INTERNAL"
if walletinfo and heading == "EXTERNAL":
heading_end = ' ' + xpubs[i][forchange]
heading += heading_end
seq_item = QTreeWidgetItem([heading, '', '', '', ''])
m_item.addChild(seq_item)
if not forchange:
@ -1313,7 +1321,7 @@ class JMMainWindow(QMainWindow):
priv = self.wallet.get_key_from_addr(addr)
private_keys[addr] = btc.wif_compressed_privkey(
priv,
vbyte=get_p2pk_vbyte())
vbyte=111)
d.emit(QtCore.SIGNAL('computing_privkeys'))
d.emit(QtCore.SIGNAL('show_privkeys'))
@ -1411,7 +1419,7 @@ class JMMainWindow(QMainWindow):
title="Error")
def selectWallet(self, testnet_seed=None):
if get_network() != 'testnet':
if jm_single().config.get("BLOCKCHAIN", "blockchain_source") != "regtest":
current_path = os.path.dirname(os.path.realpath(__file__))
if os.path.isdir(os.path.join(current_path, 'wallets')):
current_path = os.path.join(current_path, 'wallets')
@ -1448,7 +1456,7 @@ class JMMainWindow(QMainWindow):
def loadWalletFromBlockchain(self, firstarg=None, pwd=None):
if (firstarg and pwd) or (firstarg and get_network() == 'testnet'):
try:
self.wallet = Wallet(
self.wallet = SegwitWallet(
str(firstarg),
pwd,
max_mix_depth=jm_single().config.getint(
@ -1489,7 +1497,7 @@ class JMMainWindow(QMainWindow):
def generateWallet(self):
log.debug('generating wallet')
if get_network() == 'testnet':
if jm_single().config.get("BLOCKCHAIN", "blockchain_source") == "regtest":
seed = self.getTestnetSeed()
self.selectWallet(testnet_seed=seed)
else:
@ -1506,24 +1514,7 @@ class JMMainWindow(QMainWindow):
return
return str(text).strip()
def initWallet(self, seed=None):
'''Creates a new mainnet
wallet
'''
if not seed:
seed = btc.sha256(os.urandom(64))[:32]
words = mn_encode(seed)
mb = QMessageBox()
seed_recovery_warning = [
"WRITE DOWN THIS WALLET RECOVERY SEED.",
"If you fail to do this, your funds are",
"at risk. Do NOT ignore this step!!!"
]
mb.setText("\n".join(seed_recovery_warning))
mb.setInformativeText(' '.join(words))
mb.setStandardButtons(QMessageBox.Ok)
ret = mb.exec_()
def getPassword(self):
pd = PasswordDialog()
while True:
pd.exec_()
@ -1534,79 +1525,92 @@ class JMMainWindow(QMainWindow):
title="Error")
continue
break
self.textpassword = str(pd.new_pw.text())
def getPasswordKey(self):
self.getPassword()
password_key = btc.bin_dbl_sha256(self.textpassword)
return (self.textpassword, password_key)
walletfile = create_wallet_file(str(pd.new_pw.text()), seed)
def getWalletName(self):
walletname, ok = QInputDialog.getText(self, 'Choose wallet name',
'Enter wallet file name:',
QLineEdit.Normal, "wallet.json")
if not ok:
JMQtMessageBox(self, "Create wallet aborted", mbtype='warn')
return
#create wallets subdir if it doesn't exist
if not os.path.exists('wallets'):
os.makedirs('wallets')
walletpath = os.path.join('wallets', str(walletname))
# Does a wallet with the same name exist?
if os.path.isfile(walletpath):
JMQtMessageBox(self,
walletpath + ' already exists. Aborting.',
mbtype='warn',
title="Error")
return
else:
fd = open(walletpath, 'w')
fd.write(walletfile)
fd.close()
JMQtMessageBox(self,
'Wallet saved to ' + str(walletname),
return None
self.walletname = str(walletname)
return self.walletname
def displayWords(self, words):
mb = QMessageBox()
seed_recovery_warning = [
"WRITE DOWN THIS WALLET RECOVERY SEED.",
"If you fail to do this, your funds are",
"at risk. Do NOT ignore this step!!!"
]
mb.setText("\n".join(seed_recovery_warning))
mb.setInformativeText(words)
mb.setStandardButtons(QMessageBox.Ok)
ret = mb.exec_()
def initWallet(self, seed=None):
'''Creates a new mainnet
wallet
'''
if not seed:
success = wallet_generate_recover_bip39("generate",
"wallets",
"wallet.json",
callbacks=(self.displayWords,
None,
self.getPasswordKey,
self.getWalletName))
if not success:
JMQtMessageBox(self, "Failed to create new wallet file.",
title="Error", mbtype="warn")
return
JMQtMessageBox(self, 'Wallet saved to ' + self.walletname,
title="Wallet created")
self.loadWalletFromBlockchain(
str(walletname), str(pd.new_pw.text()))
self.loadWalletFromBlockchain(self.walletname, pwd=self.textpassword)
else:
print('no seed to do')
def get_wallet_printout(wallet):
"""Given a joinmarket wallet, retrieve the list of
addresses and corresponding balances to be displayed;
this could/should be a re-used function for both
command line and GUI.
The format of the retrieved data is:
addresses and corresponding balances to be displayed.
We retrieve a WalletView abstraction, and iterate over
sub-objects to arrange the per-mixdepth and per-address lists.
The format of the returned data is:
rows: is of format [[[addr,index,bal,used],[addr,...]]*5,
[[addr, index,..], [addr, index..]]*5]
mbalances: is a simple array of 5 mixdepth balances
total_balance: whole wallet
xpubs: [[xpubext, xpubint], ...]
Bitcoin amounts returned are in btc, not satoshis
TODO add metadata such as xpubs
"""
walletview = wallet_display(wallet, jm_single().config.getint("GUI",
"gaplimit"), False, serialized=False)
rows = []
mbalances = []
total_balance = 0
for m in range(wallet.max_mix_depth):
xpubs = []
for j, acct in enumerate(walletview.children):
mbalances.append(acct.get_fmt_balance())
rows.append([])
balance_depth = 0
for forchange in [0, 1]:
rows[m].append([])
for k in range(wallet.index[m][forchange] + jm_single(
).config.getint("GUI", "gaplimit")):
addr = wallet.get_addr(m, forchange, k)
balance = 0.0
for addrvalue in wallet.unspent.values():
if addr == addrvalue['address']:
balance += addrvalue['value']
balance_depth += balance
used = ('used' if k < wallet.index[m][forchange] else 'new')
if balance > 0.0 or (k >= wallet.index[m][forchange] and
forchange == 0):
rows[m][forchange].append([addr, str(k), "{0:.8f}".format(
balance / 1e8), used])
mbalances.append(balance_depth)
total_balance += balance_depth
return (rows, ["{0:.8f}".format(x / 1e8) for x in mbalances],
"{0:.8f}".format(total_balance / 1e8))
xpubs.append([])
for i, branch in enumerate(acct.children):
xpubs[j].append(branch.xpub)
rows[j].append([])
for entry in branch.children:
rows[-1][i].append([entry.serialize_address(),
entry.serialize_wallet_position(),
entry.serialize_amounts(),
entry.serialize_extra_data()])
return (rows, mbalances, xpubs, walletview.get_fmt_balance())
################################
config_load_error = False
print('Temporarily disabled in version 0.3.0 waiting for update, please use scripts.')
sys.exit(0)
app = QApplication(sys.argv)
try:
load_program_config()

10
scripts/qtsupport.py

@ -25,11 +25,7 @@ from decimal import Decimal
from PyQt4 import QtCore
from PyQt4.QtGui import *
from jmclient import (load_program_config, get_network, Wallet,
get_p2pk_vbyte, jm_single, validate_address,
get_log, weighted_order_choose, Taker,
JMTakerClientProtocolFactory, WalletError,
start_reactor, get_schedule, get_tumble_schedule)
from jmclient import (jm_single, validate_address, get_tumble_schedule)
GREEN_BG = "QWidget {background-color:#80ff80;}"
@ -413,8 +409,8 @@ class MyTreeWidget(QTreeWidget):
self.setHeaderLabels(headers)
self.header().setStretchLastSection(False)
for col in range(len(headers)):
sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
self.header().setResizeMode(col, sm)
#note, a single stretch column is currently not used.
self.header().setResizeMode(col, QHeaderView.Interactive)
def editItem(self, item, column):
if column in self.editable_columns:

Loading…
Cancel
Save