mirror of
https://github.com/JoinMarket-Org/joinmarket-clientserver.git
synced 2026-08-13 12:33:40 +02:00
change yieldgenerator using new wallet implementation, start porting wallet_utils
This commit is contained in:
parent
995c123eec
commit
6aaabb2f30
2 changed files with 186 additions and 115 deletions
|
|
@ -9,10 +9,10 @@ from datetime import datetime
|
|||
from mnemonic import Mnemonic
|
||||
from optparse import OptionParser
|
||||
import getpass
|
||||
from jmclient import (get_network, get_wallet_cls, Bip39Wallet, podle,
|
||||
encryptData, get_p2sh_vbyte, get_p2pk_vbyte, jm_single,
|
||||
mn_decode, mn_encode, BitcoinCoreInterface,
|
||||
JsonRpcError, sync_wallet, WalletError)
|
||||
from jmclient import (get_network, WALLET_IMPLEMENTATIONS, Storage, podle,
|
||||
encryptData, get_p2sh_vbyte, get_p2pk_vbyte, jm_single, mn_decode,
|
||||
mn_encode, BitcoinCoreInterface, JsonRpcError, sync_wallet, WalletError,
|
||||
BIP49Wallet, ImportWalletMixin, VolatileStorage, StoragePasswordError)
|
||||
from jmbase.support import get_password
|
||||
import jmclient.btc as btc
|
||||
|
||||
|
|
@ -330,25 +330,26 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False,
|
|||
"""
|
||||
acctlist = []
|
||||
rootpath = wallet.get_root_path()
|
||||
for m in range(wallet.max_mix_depth):
|
||||
for m in xrange(wallet.max_mixdepth):
|
||||
branchlist = []
|
||||
for forchange in [0, 1]:
|
||||
entrylist = []
|
||||
# FIXME: why does this if/else exist?
|
||||
if forchange == 0:
|
||||
xpub_key = btc.bip32_privtopub(wallet.keys[m][forchange])
|
||||
xpub_key = wallet.get_bip32_pub_export(m, forchange)
|
||||
else:
|
||||
xpub_key = ""
|
||||
|
||||
for k in range(wallet.index[m][forchange] + gaplimit):
|
||||
addr = wallet.get_addr(m, forchange, k)
|
||||
for k in xrange(wallet.get_next_unused_index(m, forchange) + gaplimit):
|
||||
path = wallet.get_path(m, forchange, k)
|
||||
addr = wallet.get_addr_path(path)
|
||||
balance = 0
|
||||
for addrvalue in wallet.unspent.values():
|
||||
if addr == addrvalue['address']:
|
||||
balance += addrvalue['value']
|
||||
used = 'used' if k < wallet.index[m][forchange] else 'new'
|
||||
for utxodata in wallet.get_utxos_by_mixdepth_()[m].values():
|
||||
if path == utxodata['path']:
|
||||
balance += utxodata['value']
|
||||
used = 'used' if k < wallet.get_next_unused_index(m, forchange) else 'new'
|
||||
if showprivkey:
|
||||
privkey = btc.wif_compressed_privkey(
|
||||
wallet.get_key(m, forchange, k), get_p2pk_vbyte())
|
||||
privkey = wallet.get_wif_path(path)
|
||||
else:
|
||||
privkey = ''
|
||||
if (displayall or balance > 0 or
|
||||
|
|
@ -362,8 +363,7 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False,
|
|||
if ipb:
|
||||
branchlist.append(ipb)
|
||||
#get the xpub key of the whole account
|
||||
xpub_account = btc.bip32_privtopub(
|
||||
wallet.get_mixing_depth_keys(wallet.get_master_key())[m])
|
||||
xpub_account = wallet.get_bip32_pub_export(mixdepth=m)
|
||||
acctlist.append(WalletViewAccount(rootpath, m, branchlist,
|
||||
xpub=xpub_account))
|
||||
walletview = WalletView(rootpath, acctlist)
|
||||
|
|
@ -397,12 +397,14 @@ def cli_user_mnemonic_entry():
|
|||
return (mnemonic_phrase, mnemonic_extension)
|
||||
|
||||
def cli_get_mnemonic_extension():
|
||||
uin = raw_input('Would you like to use a two-factor mnemonic recovery'
|
||||
+ ' phrase? write \'n\' if you don\'t know what this is (y/n): ')
|
||||
uin = raw_input("Would you like to use a two-factor mnemonic recovery "
|
||||
"phrase? write 'n' if you don't know what this is (y/n): ")
|
||||
if len(uin) == 0 or uin[0] != 'y':
|
||||
print('Not using mnemonic extension')
|
||||
print("Not using mnemonic extension")
|
||||
return None #no mnemonic extension
|
||||
return raw_input('Enter mnemonic extension: ')
|
||||
print("Note: This will be stored in a reversible way. Do not reuse!")
|
||||
return raw_input("Enter mnemonic extension: ")
|
||||
|
||||
|
||||
def persist_walletfile(walletspath, default_wallet_name, encrypted_entropy,
|
||||
encrypted_mnemonic_extension=None,
|
||||
|
|
@ -761,71 +763,170 @@ def wallet_fetch_history(wallet, options):
|
|||
|
||||
|
||||
def wallet_showseed(wallet):
|
||||
if isinstance(wallet, Bip39Wallet):
|
||||
if not wallet.entropy:
|
||||
return "Entropy is not initialized."
|
||||
m = Mnemonic("english")
|
||||
text = "Wallet mnemonic recovery phrase:\n\n" + m.to_mnemonic(wallet.entropy) + "\n"
|
||||
if wallet.mnemonic_extension:
|
||||
text += '\nWallet mnemonic extension: ' + wallet.mnemonic_extension + '\n'
|
||||
return text
|
||||
hexseed = wallet.seed
|
||||
print("hexseed = " + hexseed)
|
||||
words = mn_encode(hexseed)
|
||||
return "Wallet mnemonic seed phrase:\n\n" + " ".join(words) + "\n"
|
||||
seed, extension = wallet.get_mnemonic_words()
|
||||
text = "Wallet mnemonic recovery phrase:\n\n{}\n".format(seed)
|
||||
if extension:
|
||||
text += "\nWallet mnemonic extension: {}\n".format(extension)
|
||||
return text
|
||||
|
||||
|
||||
def wallet_importprivkey(wallet, mixdepth):
|
||||
print('WARNING: This imported key will not be recoverable with your 12 ' +
|
||||
'word mnemonic phrase. Make sure you have backups.')
|
||||
print('WARNING: Handling of raw ECDSA bitcoin private keys can lead to '
|
||||
'non-intuitive behaviour and loss of funds.\n Recommended instead '
|
||||
'is to use the \'sweep\' feature of sendpayment.py ')
|
||||
privkeys = raw_input('Enter private key(s) to import: ')
|
||||
print("WARNING: This imported key will not be recoverable with your 12 "
|
||||
"word mnemonic phrase. Make sure you have backups.")
|
||||
print("WARNING: Handling of raw ECDSA bitcoin private keys can lead to "
|
||||
"non-intuitive behaviour and loss of funds.\n Recommended instead "
|
||||
"is to use the \'sweep\' feature of sendpayment.py.")
|
||||
privkeys = raw_input("Enter private key(s) to import: ")
|
||||
privkeys = privkeys.split(',') if ',' in privkeys else privkeys.split()
|
||||
imported_addr = []
|
||||
# TODO read also one key for each line
|
||||
for privkey in privkeys:
|
||||
for wif in privkeys:
|
||||
# TODO is there any point in only accepting wif format? check what
|
||||
# other wallets do
|
||||
privkey_bin = btc.from_wif_privkey(privkey,
|
||||
vbyte=get_p2pk_vbyte()).decode('hex')[:-1]
|
||||
encrypted_privkey = encryptData(wallet.password_key, privkey_bin)
|
||||
if 'imported_keys' not in wallet.walletdata:
|
||||
wallet.walletdata['imported_keys'] = []
|
||||
wallet.walletdata['imported_keys'].append(
|
||||
{'encrypted_privkey': encrypted_privkey.encode('hex'),
|
||||
'mixdepth': mixdepth})
|
||||
if wallet.walletdata['imported_keys']:
|
||||
fd = open(wallet.path, 'w')
|
||||
fd.write(json.dumps(wallet.walletdata))
|
||||
fd.close()
|
||||
print('Private key(s) successfully imported')
|
||||
imported_addr.append(wallet.import_private_key(mixdepth, wif))
|
||||
wallet.save()
|
||||
|
||||
if not imported_addr:
|
||||
print("Warning: No keys imported!")
|
||||
return
|
||||
|
||||
# show addresses to user so they can verify everything went as expected
|
||||
print("Imported keys for addresses:")
|
||||
for addr in imported_addr:
|
||||
print(addr)
|
||||
|
||||
|
||||
def wallet_dumpprivkey(wallet, hdpath):
|
||||
pathlist = bip32pathparse(hdpath)
|
||||
print('got pathlist: ' + str(pathlist))
|
||||
if pathlist and len(pathlist) in [5, 4]:
|
||||
#note here we assume the path conforms to Wallet or SegwitWallet(BIP49) standard
|
||||
m, forchange, k = pathlist[-3:]
|
||||
key = wallet.get_key(m, forchange, k)
|
||||
wifkey = btc.wif_compressed_privkey(key, vbyte=get_p2pk_vbyte())
|
||||
return wifkey
|
||||
else:
|
||||
return hdpath + " is not a valid hd wallet path"
|
||||
path = wallet.path_repr_to_path(hdpath)
|
||||
return wallet.get_wif_path(path) # will raise exception on invalid path
|
||||
|
||||
|
||||
def wallet_signmessage(wallet, hdpath, message):
|
||||
if hdpath.startswith(wallet.get_root_path()):
|
||||
hp = bip32pathparse(hdpath)
|
||||
m, forchange, k = hp[-3:]
|
||||
key = wallet.get_key(m, forchange, k)
|
||||
addr = wallet.pubkey_to_address(btc.privkey_to_pubkey(key))
|
||||
print('Using address: ' + addr)
|
||||
msg = message.encode('utf-8')
|
||||
|
||||
path = wallet.path_repr_to_path(hdpath)
|
||||
sig = wallet.sign_message(msg, path)
|
||||
return ("Signature: {}\n"
|
||||
"To verify this in Bitcoin Core use the RPC command 'verifymessage'"
|
||||
"".format(sig))
|
||||
|
||||
|
||||
def get_wallet_type():
|
||||
if jm_single().config.get('POLICY', 'segwit') == 'true':
|
||||
return 'p2sh-p2wpkh'
|
||||
return 'p2pkh'
|
||||
|
||||
|
||||
def get_wallet_cls(wtype=None):
|
||||
if wtype is None:
|
||||
wtype = get_wallet_type()
|
||||
|
||||
cls = WALLET_IMPLEMENTATIONS.get(wtype)
|
||||
|
||||
if not cls:
|
||||
raise WalletError("No wallet implementation found for type {}."
|
||||
"".format(wtype))
|
||||
return cls
|
||||
|
||||
|
||||
def create_wallet(path, password, max_mixdepth, **kwargs):
|
||||
storage = Storage(path, password, create=True)
|
||||
wallet_cls = get_wallet_cls()
|
||||
wallet_cls.initialize(storage, get_network(), max_mixdepth=max_mixdepth,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def open_test_wallet_maybe(path, seed, max_mixdepth, **kwargs):
|
||||
"""
|
||||
Create a volatile test wallet if path is a hex-encoded string of length 64,
|
||||
otherwise run open_wallet().
|
||||
|
||||
params:
|
||||
path: path to wallet file, ignored for test wallets
|
||||
seed: hex-encoded test seed
|
||||
max_mixdepth: see create_wallet(), ignored when calling open_wallet()
|
||||
kwargs: see open_wallet()
|
||||
|
||||
returns:
|
||||
wallet object
|
||||
"""
|
||||
class SewgitTestWallet(ImportWalletMixin, BIP49Wallet):
|
||||
TYPE = 'p2sh-p2wpkh'
|
||||
|
||||
if len(seed) == SewgitTestWallet.ENTROPY_BYTES * 2:
|
||||
try:
|
||||
seed = binascii.unhexlify(seed)
|
||||
except binascii.Error:
|
||||
pass
|
||||
else:
|
||||
storage = VolatileStorage()
|
||||
SewgitTestWallet.initialize(
|
||||
storage, get_network(), max_mixdepth=max_mixdepth,
|
||||
entropy=seed)
|
||||
assert 'ask_for_password' not in kwargs
|
||||
assert 'read_only' not in kwargs
|
||||
return SewgitTestWallet(storage, **kwargs)
|
||||
|
||||
return open_wallet(path, **kwargs)
|
||||
|
||||
|
||||
def open_wallet(path, ask_for_password=True, read_only=False, **kwargs):
|
||||
"""
|
||||
Open the wallet file at path and return the corresponding wallet object.
|
||||
|
||||
params:
|
||||
path: str, full path to wallet file
|
||||
ask_for_password: bool, if False password is assumed unset and user
|
||||
will not be asked to type it
|
||||
read_only: bool, if True, open wallet in read-only mode
|
||||
kwargs: additional options to pass to wallet's init method
|
||||
|
||||
returns:
|
||||
wallet object
|
||||
"""
|
||||
if ask_for_password:
|
||||
while True:
|
||||
try:
|
||||
# do not try empty password, assume unencrypted on empty password
|
||||
pwd = get_password("Enter wallet decryption passphrase: ") or None
|
||||
storage = Storage(path, password=pwd, read_only=read_only)
|
||||
except StoragePasswordError:
|
||||
print("Wrong password, try again.")
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Failed to load wallet, error message: " + repr(e))
|
||||
raise e
|
||||
break
|
||||
else:
|
||||
print('%s is not a valid hd wallet path' % hdpath)
|
||||
return None
|
||||
sig = btc.ecdsa_sign(message, key, formsg=True)
|
||||
retval = "Signature: " + str(sig) + "\n"
|
||||
retval += "To verify this in Bitcoin Core use the RPC command 'verifymessage'"
|
||||
return retval
|
||||
storage = Storage(path, read_only=read_only)
|
||||
|
||||
wallet_cls = get_wallet_cls(storage)
|
||||
wallet = wallet_cls(storage, **kwargs)
|
||||
wallet_sanity_check(wallet)
|
||||
return wallet
|
||||
|
||||
|
||||
def get_wallet_cls_from_storage(storage):
|
||||
wtype = storage.data.get([b'wallet_type'])
|
||||
|
||||
if not wtype:
|
||||
raise WalletError("File {} is not a valid wallet.".format(storage.path))
|
||||
|
||||
wtype = wtype.decode('ascii')
|
||||
return get_wallet_cls(wtype)
|
||||
|
||||
|
||||
def wallet_sanity_check(wallet):
|
||||
if wallet.network != get_network():
|
||||
raise Exception("Wallet network mismatch: we are on {} but wallet is "
|
||||
"on {}".format(get_network(), wallet.network))
|
||||
|
||||
|
||||
def get_wallet_path(file_name, wallet_dir):
|
||||
# TODO: move default wallet path to ~/.joinmarket
|
||||
wallet_dir = wallet_dir or 'wallets'
|
||||
return os.path.join(wallet_dir, file_name)
|
||||
|
||||
|
||||
def wallet_tool_main(wallet_root_path):
|
||||
"""Main wallet tool script function; returned is a string (output or error)
|
||||
|
|
@ -853,29 +954,12 @@ def wallet_tool_main(wallet_root_path):
|
|||
method = args[0]
|
||||
else:
|
||||
seed = args[0]
|
||||
wallet_path = get_wallet_path(seed, wallet_root_path)
|
||||
method = ('display' if len(args) == 1 else args[1].lower())
|
||||
if not os.path.exists(os.path.join(wallet_root_path, seed)):
|
||||
wallet = get_wallet_cls()(seed, None, options.maxmixdepth,
|
||||
options.gaplimit, extend_mixdepth= not maxmixdepth_configured,
|
||||
storepassword=(method == 'importprivkey'),
|
||||
wallet_dir=wallet_root_path)
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
pwd = get_password("Enter wallet decryption passphrase: ")
|
||||
wallet = get_wallet_cls()(seed, pwd,
|
||||
options.maxmixdepth,
|
||||
options.gaplimit,
|
||||
extend_mixdepth=not maxmixdepth_configured,
|
||||
storepassword=(method == 'importprivkey'),
|
||||
wallet_dir=wallet_root_path)
|
||||
except WalletError:
|
||||
print("Wrong password, try again.")
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Failed to load wallet, error message: " + repr(e))
|
||||
sys.exit(0)
|
||||
break
|
||||
|
||||
wallet = open_test_wallet_maybe(
|
||||
wallet_path, seed, options.maxmixdepth, gap_limit=options.gaplimit)
|
||||
|
||||
if method not in noscan_methods:
|
||||
# if nothing was configured, we override bitcoind's options so that
|
||||
# unconfirmed balance is included in the wallet display by default
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import time
|
|||
import abc
|
||||
from twisted.python.log import startLogging
|
||||
from optparse import OptionParser
|
||||
from jmbase import get_password
|
||||
from jmclient import (Maker, jm_single, get_network, load_program_config, get_log,
|
||||
get_wallet_cls, sync_wallet, JMClientProtocolFactory,
|
||||
start_reactor, calc_cj_fee, WalletError)
|
||||
from .wallet_utils import open_test_wallet_maybe, get_wallet_path
|
||||
|
||||
jlog = get_log()
|
||||
|
||||
|
|
@ -232,23 +232,11 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe
|
|||
nickserv_password = options.password
|
||||
|
||||
load_program_config()
|
||||
if not os.path.exists(os.path.join('wallets', wallet_name)):
|
||||
wallet = get_wallet_cls()(wallet_name, None, max_mix_depth=MAX_MIX_DEPTH,
|
||||
gaplimit=options.gaplimit)
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
pwd = get_password("Enter wallet decryption passphrase: ")
|
||||
wallet = get_wallet_cls()(wallet_name, pwd,
|
||||
max_mix_depth=MAX_MIX_DEPTH,
|
||||
gaplimit=options.gaplimit)
|
||||
except WalletError:
|
||||
print("Wrong password, try again.")
|
||||
continue
|
||||
except Exception as e:
|
||||
print("Failed to load wallet, error message: " + repr(e))
|
||||
sys.exit(0)
|
||||
break
|
||||
|
||||
wallet_path = get_wallet_path(wallet_name, 'wallets')
|
||||
wallet = open_test_wallet_maybe(
|
||||
wallet_path, wallet_name, 4, gap_limit=options.gaplimit)
|
||||
|
||||
if jm_single().config.get("BLOCKCHAIN", "blockchain_source") == "electrum-server":
|
||||
jm_single().bc_interface.synctype = "with-script"
|
||||
sync_wallet(wallet, fast=options.fastsync)
|
||||
|
|
@ -265,4 +253,3 @@ def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffe
|
|||
start_reactor(jm_single().config.get("DAEMON", "daemon_host"),
|
||||
jm_single().config.getint("DAEMON", "daemon_port"),
|
||||
clientfactory, daemon=daemon)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue