@ -5,6 +5,7 @@ import sys
import sqlite3
import sqlite3
import binascii
import binascii
from datetime import datetime
from datetime import datetime
from calendar import timegm
from optparse import OptionParser
from optparse import OptionParser
from numbers import Integral
from numbers import Integral
from collections import Counter
from collections import Counter
@ -12,12 +13,13 @@ from itertools import islice
from jmclient import ( get_network , WALLET_IMPLEMENTATIONS , Storage , podle ,
from jmclient import ( get_network , WALLET_IMPLEMENTATIONS , Storage , podle ,
jm_single , BitcoinCoreInterface , WalletError ,
jm_single , BitcoinCoreInterface , WalletError ,
VolatileStorage , StoragePasswordError , is_segwit_mode , SegwitLegacyWallet ,
VolatileStorage , StoragePasswordError , is_segwit_mode , SegwitLegacyWallet ,
LegacyWallet , SegwitWallet , is_native_segwit_mode , load_program_config ,
LegacyWallet , SegwitWallet , FidelityBondMixin , is_native_segwit_mode ,
add_base_options , check_regtest )
load_program_config , add_base_options , check_regtest )
from jmclient . wallet_service import WalletService
from jmclient . wallet_service import WalletService
from jmbase . support import get_password , jmprint , EXIT_FAILURE , EXIT_ARGERROR
from jmbase . support import get_password , jmprint , EXIT_FAILURE , EXIT_ARGERROR
from . cryptoengine import TYPE_P2PKH , TYPE_P2SH_P2WPKH , TYPE_P2WPKH
from . cryptoengine import TYPE_P2PKH , TYPE_P2SH_P2WPKH , TYPE_P2WPKH , \
TYPE_SEGWIT_LEGACY_WALLET_FIDELITY_BONDS
from . output import fmt_utxo
from . output import fmt_utxo
import jmbitcoin as btc
import jmbitcoin as btc
@ -42,8 +44,9 @@ def get_wallettool_parser():
' (dumpprivkey) Export a single private key, specify an hd wallet path \n '
' (dumpprivkey) Export a single private key, specify an hd wallet path \n '
' (signmessage) Sign a message with the private key from an address in \n '
' (signmessage) Sign a message with the private key from an address in \n '
' the wallet. Use with -H and specify an HD wallet path for the address. \n '
' the wallet. Use with -H and specify an HD wallet path for the address. \n '
' (freeze) Freeze or un-freeze a specific utxo. Specify mixdepth with -m. ' )
' (freeze) Freeze or un-freeze a specific utxo. Specify mixdepth with -m. \n '
parser = OptionParser ( usage = ' usage: % prog [options] [wallet file] [method] ' ,
' (gettimelockaddress) Obtain a timelocked address. Argument is locktime value as yyyy-mm. For example `2021-03` ' )
parser = OptionParser ( usage = ' usage: % prog [options] [wallet file] [method] [args..] ' ,
description = description )
description = description )
add_base_options ( parser )
add_base_options ( parser )
parser . add_option ( ' -p ' ,
parser . add_option ( ' -p ' ,
@ -167,7 +170,9 @@ class WalletViewEntry(WalletViewBase):
super ( WalletViewEntry , self ) . __init__ ( wallet_path_repr , serclass = serclass ,
super ( WalletViewEntry , self ) . __init__ ( wallet_path_repr , serclass = serclass ,
custom_separator = custom_separator )
custom_separator = custom_separator )
self . account = account
self . account = account
assert address_type in [ 0 , 1 , - 1 ]
assert address_type in [ SegwitWallet . BIP32_EXT_ID ,
SegwitWallet . BIP32_INT_ID , - 1 , FidelityBondMixin . BIP32_TIMELOCK_ID ,
FidelityBondMixin . BIP32_BURN_ID ]
self . address_type = address_type
self . address_type = address_type
assert isinstance ( aindex , Integral )
assert isinstance ( aindex , Integral )
assert aindex > = 0
assert aindex > = 0
@ -219,7 +224,9 @@ class WalletViewBranch(WalletViewBase):
serclass = serclass ,
serclass = serclass ,
custom_separator = custom_separator )
custom_separator = custom_separator )
self . account = account
self . account = account
assert address_type in [ 0 , 1 , - 1 ]
assert address_type in [ SegwitWallet . BIP32_EXT_ID ,
SegwitWallet . BIP32_INT_ID , - 1 , FidelityBondMixin . BIP32_TIMELOCK_ID ,
FidelityBondMixin . BIP32_BURN_ID ]
self . address_type = address_type
self . address_type = address_type
if xpub :
if xpub :
assert xpub . startswith ( ' xpub ' ) or xpub . startswith ( ' tpub ' )
assert xpub . startswith ( ' xpub ' ) or xpub . startswith ( ' tpub ' )
@ -441,11 +448,42 @@ def wallet_display(wallet_service, showprivkey, displayall=False,
entrylist . append ( WalletViewEntry (
entrylist . append ( WalletViewEntry (
wallet_service . get_path_repr ( path ) , m , address_type , k , addr ,
wallet_service . get_path_repr ( path ) , m , address_type , k , addr ,
[ balance , balance ] , priv = privkey , used = used ) )
[ balance , balance ] , priv = privkey , used = used ) )
wallet_service . set_next_index ( m , address_type , unused_index )
wallet_service . set_next_index ( m , address_type , unused_index )
path = wallet_service . get_path_repr ( wallet_service . get_path ( m , address_type ) )
path = wallet_service . get_path_repr ( wallet_service . get_path ( m , address_type ) )
branchlist . append ( WalletViewBranch ( path , m , address_type , entrylist ,
branchlist . append ( WalletViewBranch ( path , m , address_type , entrylist ,
xpub = xpub_key ) )
xpub = xpub_key ) )
if m == FidelityBondMixin . FIDELITY_BOND_MIXDEPTH and \
isinstance ( wallet_service . wallet , FidelityBondMixin ) :
address_type = FidelityBondMixin . BIP32_TIMELOCK_ID
unused_index = wallet_service . get_next_unused_index ( m , address_type )
timelocked_gaplimit = ( wallet_service . wallet . gap_limit
/ / FidelityBondMixin . TIMELOCK_GAP_LIMIT_REDUCTION_FACTOR )
entrylist = [ ]
for k in range ( unused_index + timelocked_gaplimit ) :
for timenumber in range ( FidelityBondMixin . TIMENUMBERS_PER_PUBKEY ) :
path = wallet_service . get_path ( m , address_type , k , timenumber )
addr = wallet_service . get_address_from_path ( path )
timelock = datetime . utcfromtimestamp ( path [ - 1 ] )
balance = sum ( [ utxodata [ " value " ] for utxo , utxodata in
iteritems ( utxos [ m ] ) if path == utxodata [ " path " ] ] )
status = timelock . strftime ( " % Y- % m- %d " ) + " [ " + (
" LOCKED " if datetime . now ( ) < timelock else " UNLOCKED " ) + " ] "
privkey = " "
if showprivkey :
privkey = wallet_service . get_wif_path ( path )
if displayall or balance > 0 :
entrylist . append ( WalletViewEntry (
wallet_service . get_path_repr ( path ) , m , address_type , k ,
addr , [ balance , balance ] , priv = privkey , used = status ) )
#TODO fidelity bond master pub key is this, although it should include burner too
xpub_key = wallet_service . get_bip32_pub_export ( m , address_type )
path = wallet_service . get_path_repr ( wallet_service . get_path ( m , address_type ) )
branchlist . append ( WalletViewBranch ( path , m , address_type , entrylist ,
xpub = xpub_key ) )
ipb = get_imported_privkey_branch ( wallet_service , m , showprivkey )
ipb = get_imported_privkey_branch ( wallet_service , m , showprivkey )
if ipb :
if ipb :
branchlist . append ( ipb )
branchlist . append ( ipb )
@ -499,11 +537,19 @@ def cli_get_mnemonic_extension():
" info " )
" info " )
return input ( " Enter mnemonic extension: " )
return input ( " Enter mnemonic extension: " )
def cli_do_support_fidelity_bonds ( ) :
uin = input ( " Would you like this wallet to support fidelity bonds? "
" write ' n ' if you don ' t know what this is (y/n): " )
if len ( uin ) == 0 or uin [ 0 ] != ' y ' :
jmprint ( " Not supporting fidelity bonds " , " info " )
return False
else :
return True
def wallet_generate_recover_bip39 ( method , walletspath , default_wallet_name ,
def wallet_generate_recover_bip39 ( method , walletspath , default_wallet_name ,
display_seed_callback , enter_seed_callback , enter_wallet_password_callback ,
display_seed_callback , enter_seed_callback , enter_wallet_password_callback ,
enter_wallet_file_name_callback , enter_if_use_seed_extension ,
enter_wallet_file_name_callback , enter_if_use_seed_extension ,
enter_seed_extension_callback , mixdepth = DEFAULT_MIXDEPTH ) :
enter_seed_extension_callback , enter_do_support_fidelity_bonds , mixdepth = DEFAULT_MIXDEPTH ) :
entropy = None
entropy = None
mnemonic_extension = None
mnemonic_extension = None
if method == " generate " :
if method == " generate " :
@ -537,7 +583,10 @@ def wallet_generate_recover_bip39(method, walletspath, default_wallet_name,
wallet_name = default_wallet_name
wallet_name = default_wallet_name
wallet_path = os . path . join ( walletspath , wallet_name )
wallet_path = os . path . join ( walletspath , wallet_name )
wallet = create_wallet ( wallet_path , password , mixdepth ,
support_fidelity_bonds = enter_do_support_fidelity_bonds ( )
wallet_cls = get_wallet_cls ( get_configured_wallet_type ( support_fidelity_bonds ) )
wallet = create_wallet ( wallet_path , password , mixdepth , wallet_cls ,
entropy = entropy ,
entropy = entropy ,
entropy_extension = mnemonic_extension )
entropy_extension = mnemonic_extension )
mnemonic , mnext = wallet . get_mnemonic_words ( )
mnemonic , mnext = wallet . get_mnemonic_words ( )
@ -555,7 +604,7 @@ def wallet_generate_recover(method, walletspath,
default_wallet_name , cli_display_user_words , cli_user_mnemonic_entry ,
default_wallet_name , cli_display_user_words , cli_user_mnemonic_entry ,
cli_get_wallet_passphrase_check , cli_get_wallet_file_name ,
cli_get_wallet_passphrase_check , cli_get_wallet_file_name ,
cli_do_use_mnemonic_extension , cli_get_mnemonic_extension ,
cli_do_use_mnemonic_extension , cli_get_mnemonic_extension ,
mixdepth = mixdepth )
cli_do_support_fidelity_bonds , mixdepth = mixdepth )
entropy = None
entropy = None
if method == ' recover ' :
if method == ' recover ' :
@ -1035,29 +1084,54 @@ def wallet_freezeutxo(wallet, md, display_callback=None, info_callback=None):
. format ( fmt_utxo ( ( txid , index ) ) ) )
. format ( fmt_utxo ( ( txid , index ) ) ) )
return " Done "
return " Done "
def get_wallet_type ( ) :
def wallet_gettimelockaddress ( wallet_service , locktime_string ) :
if not isinstance ( wallet_service . wallet , FidelityBondMixin ) :
jmprint ( " Error: not a fidelity bond wallet " , " error " )
return " "
m = FidelityBondMixin . FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin . BIP32_TIMELOCK_ID
index = wallet_service . get_next_unused_index ( m , address_type )
lock_datetime = datetime . strptime ( locktime_string , " % Y- % m " )
timenumber = FidelityBondMixin . timestamp_to_time_number ( timegm (
lock_datetime . timetuple ( ) ) )
path = wallet_service . get_path ( m , address_type , index , timenumber )
jmprint ( " path = " + wallet_service . get_path_repr ( path ) , " info " )
jmprint ( " Coins sent to this address will be not be spendable until "
+ lock_datetime . strftime ( " % B % Y " ) + " . Full date: "
+ str ( lock_datetime ) )
addr = wallet_service . get_address_from_path ( path )
return addr
def get_configured_wallet_type ( support_fidelity_bonds ) :
configured_type = TYPE_P2PKH
if is_segwit_mode ( ) :
if is_segwit_mode ( ) :
if is_native_segwit_mode ( ) :
if is_native_segwit_mode ( ) :
return TYPE_P2WPKH
configured_type = TYPE_P2WPKH
return TYPE_P2SH_P2WPKH
else :
return TYPE_P2PKH
configured_type = TYPE_P2SH_P2W PKH
if not support_fidelity_bonds :
return configured_type
def get_wallet_cls ( wtype = None ) :
if configured_type == TYPE_P2SH_P2WPKH :
if wtype is None :
return TYPE_SEGWIT_LEGACY_WALLET_FIDELITY_BONDS
wtype = get_wallet_type ( )
else :
raise ValueError ( " Fidelity bonds not supported with the configured "
" options of segwit and native. Edit joinmarket.cfg " )
def get_wallet_cls ( wtype ) :
cls = WALLET_IMPLEMENTATIONS . get ( wtype )
cls = WALLET_IMPLEMENTATIONS . get ( wtype )
if not cls :
if not cls :
raise WalletError ( " No wallet implementation found for type {} . "
raise WalletError ( " No wallet implementation found for type {} . "
" " . format ( wtype ) )
" " . format ( wtype ) )
return cls
return cls
def create_wallet ( path , password , max_mixdepth , wallet_cls , * * kwargs ) :
def create_wallet ( path , password , max_mixdepth , wallet_cls = None , * * kwargs ) :
storage = Storage ( path , password , create = True )
storage = Storage ( path , password , create = True )
wallet_cls = wallet_cls or get_wallet_cls ( )
wallet_cls . initialize ( storage , get_network ( ) , max_mixdepth = max_mixdepth ,
wallet_cls . initialize ( storage , get_network ( ) , max_mixdepth = max_mixdepth ,
* * kwargs )
* * kwargs )
storage . save ( )
storage . save ( )
@ -1195,11 +1269,12 @@ def wallet_tool_main(wallet_root_path):
wallet_root_path = os . path . join ( jm_single ( ) . datadir , wallet_root_path )
wallet_root_path = os . path . join ( jm_single ( ) . datadir , wallet_root_path )
noseed_methods = [ ' generate ' , ' recover ' ]
noseed_methods = [ ' generate ' , ' recover ' ]
methods = [ ' display ' , ' displayall ' , ' summary ' , ' showseed ' , ' importprivkey ' ,
methods = [ ' display ' , ' displayall ' , ' summary ' , ' showseed ' , ' importprivkey ' ,
' history ' , ' showutxos ' , ' freeze ' ]
' history ' , ' showutxos ' , ' freeze ' , ' gettimelockaddress ' ]
methods . extend ( noseed_methods )
methods . extend ( noseed_methods )
noscan_methods = [ ' showseed ' , ' importprivkey ' , ' dumpprivkey ' , ' signmessage ' ]
noscan_methods = [ ' showseed ' , ' importprivkey ' , ' dumpprivkey ' , ' signmessage ' ]
readonly_methods = [ ' display ' , ' displayall ' , ' summary ' , ' showseed ' ,
readonly_methods = [ ' display ' , ' displayall ' , ' summary ' , ' showseed ' ,
' history ' , ' showutxos ' , ' dumpprivkey ' , ' signmessage ' ]
' history ' , ' showutxos ' , ' dumpprivkey ' , ' signmessage ' ,
' gettimelockaddress ' ]
if len ( args ) < 1 :
if len ( args ) < 1 :
parser . error ( ' Needs a wallet file or method ' )
parser . error ( ' Needs a wallet file or method ' )
@ -1273,9 +1348,17 @@ def wallet_tool_main(wallet_root_path):
map_key_type ( options . key_type ) )
map_key_type ( options . key_type ) )
return " Key import completed. "
return " Key import completed. "
elif method == " signmessage " :
elif method == " signmessage " :
if len ( args ) < 3 :
jmprint ( ' Must provide message to sign ' , " error " )
sys . exit ( EXIT_ARGERROR )
return wallet_signmessage ( wallet_service , options . hd_path , args [ 2 ] )
return wallet_signmessage ( wallet_service , options . hd_path , args [ 2 ] )
elif method == " freeze " :
elif method == " freeze " :
return wallet_freezeutxo ( wallet_service , options . mixdepth )
return wallet_freezeutxo ( wallet_service , options . mixdepth )
elif method == " gettimelockaddress " :
if len ( args ) < 3 :
jmprint ( ' Must have locktime value yyyy-mm. For example 2021-03 ' , " error " )
sys . exit ( EXIT_ARGERROR )
return wallet_gettimelockaddress ( wallet_service , args [ 2 ] )
else :
else :
parser . error ( " Unknown wallet-tool method: " + method )
parser . error ( " Unknown wallet-tool method: " + method )
sys . exit ( EXIT_ARGERROR )
sys . exit ( EXIT_ARGERROR )