@ -5,12 +5,10 @@ import base64
import pprint
import pprint
import random
import random
from twisted . internet import reactor , task
from twisted . internet import reactor , task
from binascii import hexlify , unhexlify
from jmbitcoin import SerializationError , SerializationTruncationError
import jmbitcoin as btc
import jmbitcoin as btc
from jmclient . configure import jm_single , validate_address
from jmclient . configure import jm_single , validate_address
from jmbase . support import get_log
from jmbase import get_log , hextobin , bintohex , hexbin
from jmclient . support import ( calc_cj_fee , weighted_order_choose , choose_orders ,
from jmclient . support import ( calc_cj_fee , weighted_order_choose , choose_orders ,
choose_sweep_orders )
choose_sweep_orders )
from jmclient . wallet import estimate_tx_fee , compute_tx_locktime
from jmclient . wallet import estimate_tx_fee , compute_tx_locktime
@ -238,7 +236,6 @@ class Taker(object):
#Initialization has been successful. We must set the nonrespondants
#Initialization has been successful. We must set the nonrespondants
#now to keep track of what changed when we receive the utxo data
#now to keep track of what changed when we receive the utxo data
self . nonrespondants = list ( self . orderbook . keys ( ) )
self . nonrespondants = list ( self . orderbook . keys ( ) )
return ( True , self . cjamount , commitment , revelation , self . orderbook )
return ( True , self . cjamount , commitment , revelation , self . orderbook )
def filter_orderbook ( self , orderbook , sweep = False ) :
def filter_orderbook ( self , orderbook , sweep = False ) :
@ -344,6 +341,7 @@ class Taker(object):
self . utxos = { None : list ( self . input_utxos . keys ( ) ) }
self . utxos = { None : list ( self . input_utxos . keys ( ) ) }
return True
return True
@hexbin
def receive_utxos ( self , ioauth_data ) :
def receive_utxos ( self , ioauth_data ) :
""" Triggered when the daemon returns utxo data from
""" Triggered when the daemon returns utxo data from
makers who responded ; this is the completion of phase 1
makers who responded ; this is the completion of phase 1
@ -354,6 +352,7 @@ class Taker(object):
#Temporary list used to aggregate all ioauth data that must be removed
#Temporary list used to aggregate all ioauth data that must be removed
rejected_counterparties = [ ]
rejected_counterparties = [ ]
#Need to authorize against the btc pubkey first.
#Need to authorize against the btc pubkey first.
for nick , nickdata in iteritems ( ioauth_data ) :
for nick , nickdata in iteritems ( ioauth_data ) :
utxo_list , auth_pub , cj_addr , change_addr , btc_sig , maker_pk = nickdata
utxo_list , auth_pub , cj_addr , change_addr , btc_sig , maker_pk = nickdata
@ -376,10 +375,9 @@ class Taker(object):
self . maker_utxo_data = { }
self . maker_utxo_data = { }
for nick , nickdata in iteritems ( ioauth_data ) :
for nick , nickdata in iteritems ( ioauth_data ) :
utxo_list , auth_pub , cj_addr , change_addr , btc_sig , maker_pk = nickdata
utxo_list , auth_pub , cj_addr , change_addr , _ , _ = nickdata
utxo_data = jm_single ( ) . bc_interface . query_utxo_set ( utxo_list )
self . utxos [ nick ] = utxo_list
self . utxos [ nick ] = utxo_list
utxo_data = jm_single ( ) . bc_interface . query_utxo_set ( self . utxos [
nick ] )
if None in utxo_data :
if None in utxo_data :
jlog . warn ( ( ' ERROR outputs unconfirmed or already spent. '
jlog . warn ( ( ' ERROR outputs unconfirmed or already spent. '
' utxo_data= {} ' ) . format ( pprint . pformat ( utxo_data ) ) )
' utxo_data= {} ' ) . format ( pprint . pformat ( utxo_data ) ) )
@ -391,13 +389,12 @@ class Taker(object):
#Extract the address fields from the utxos
#Extract the address fields from the utxos
#Construct the Bitcoin address for the auth_pub field
#Construct the Bitcoin address for the auth_pub field
#Ensure that at least one address from utxos corresponds.
#Ensure that at least one address from utxos corresponds.
auth_pub_bin = unhexlify ( auth_pub )
for inp in utxo_data :
for inp in utxo_data :
try :
try :
if self . wallet_service . pubkey_has_script (
if self . wallet_service . pubkey_has_script (
auth_pub_bin , unhexlify ( inp [ ' script ' ] ) ) :
auth_pub , inp [ ' script ' ] ) :
break
break
except EngineError :
except EngineError as e :
pass
pass
else :
else :
jlog . warn ( " ERROR maker ' s ( " + nick + " ) "
jlog . warn ( " ERROR maker ' s ( " + nick + " ) "
@ -500,81 +497,85 @@ class Taker(object):
else :
else :
self . outputs . append ( { ' address ' : self . my_change_addr ,
self . outputs . append ( { ' address ' : self . my_change_addr ,
' value ' : my_change_value } )
' value ' : my_change_value } )
self . utxo_tx = [ dict ( [ ( ' output ' , u ) ] )
self . utxo_tx = [ u for u in sum ( self . utxos . values ( ) , [ ] ) ]
for u in sum ( self . utxos . values ( ) , [ ] ) ]
self . outputs . append ( { ' address ' : self . coinjoin_address ( ) ,
self . outputs . append ( { ' address ' : self . coinjoin_address ( ) ,
' value ' : self . cjamount } )
' value ' : self . cjamount } )
tx = btc . make_shuffled_tx ( self . utxo_tx , self . outputs , False )
self . latest_ tx = btc . make_shuffled_tx ( self . utxo_tx , self . outputs )
jlog . info ( ' obtained tx \n ' + pprint . pformat ( btc . deserialize ( tx ) ) )
jlog . info ( ' obtained tx \n ' + bintohex ( self . latest_tx . serialize ( ) ) )
self . latest_tx = btc . deserialize ( tx )
for index , ins in enumerate ( self . latest_tx . vin ) :
for index , ins in enumerate ( self . latest_tx [ ' ins ' ] ) :
utxo = ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n )
utxo = ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str ( ins [ ' outpoint ' ] [ ' index ' ] )
if utxo not in self . input_utxos . keys ( ) :
if utxo not in self . input_utxos . keys ( ) :
continue
continue
# placeholders required
# placeholders required
ins [ ' script ' ] = ' deadbeef '
ins . scriptSig = btc . CScript . fromhex ( " deadbeef " )
self . taker_info_callback ( " INFO " , " Built tx, sending to counterparties. " )
self . taker_info_callback ( " INFO " , " Built tx, sending to counterparties. " )
return ( True , list ( self . maker_utxo_data . keys ( ) ) , tx )
return ( True , list ( self . maker_utxo_data . keys ( ) ) ,
bintohex ( self . latest_tx . serialize ( ) ) )
@hexbin
def auth_counterparty ( self , btc_sig , auth_pub , maker_pk ) :
def auth_counterparty ( self , btc_sig , auth_pub , maker_pk ) :
""" Validate the counterpartys claim to own the btc
""" Validate the counterpartys claim to own the btc
address / pubkey that will be used for coinjoining
address / pubkey that will be used for coinjoining
with an ecdsa verification .
with an ecdsa verification .
"""
"""
try :
try :
if not btc . ecdsa_verify ( maker_pk , btc_sig , auth_pub ) :
# maker pubkey as message is in hex format:
if not btc . ecdsa_verify ( bintohex ( maker_pk ) , btc_sig , auth_pub ) :
jlog . debug ( ' signature didnt match pubkey and message ' )
jlog . debug ( ' signature didnt match pubkey and message ' )
return False
return False
except Exception as e :
except Exception as e :
jlog . info ( " Failed ecdsa verify for maker pubkey: " + str ( maker_pk ) )
jlog . info ( " Failed ecdsa verify for maker pubkey: " + bintohex ( maker_pk ) )
jlog . info ( " Exception was: " + repr ( e ) )
jlog . info ( " Exception was: " + repr ( e ) )
return False
return False
return True
return True
def on_sig ( self , nick , sigb64 ) :
def on_sig ( self , nick , sigb64 ) :
""" Processes transaction signatures from counterparties.
""" Processes transaction signatures from counterparties.
Returns True if all signatures received correctly , else
If all signatures received correctly , returns the result
returns False
of self . self_sign_and_push ( ) ( i . e . we complete the signing
and broadcast ) ; else returns False ( thus returns False for
all but last signature ) .
"""
"""
if self . aborted :
if self . aborted :
return False
return False
if nick not in self . nonrespondants :
if nick not in self . nonrespondants :
jlog . debug ( ( ' add_signature => nick= {} '
jlog . debug ( ( ' add_signature => nick= {} '
' not in nonrespondants {} ' ) . format ( nick , self . nonrespondants ) )
' not in nonrespondants {} ' ) . format ( nick , self . nonrespondants ) )
return
return False
sig = hexlify ( base64 . b64decode ( sigb64 ) ) . decode ( ' ascii ' )
sig = base64 . b64decode ( sigb64 )
inserted_sig = False
inserted_sig = False
txhex = btc . serialize ( self . latest_tx )
# batch retrieval of utxo data
# batch retrieval of utxo data
utxo = { }
utxo = { }
ctr = 0
ctr = 0
for index , ins in enumerate ( self . latest_tx [ ' ins ' ] ) :
for index , ins in enumerate ( self . latest_tx . vin ) :
utxo_for_checking = ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str ( ins [
utxo_for_checking = ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n )
' outpoint ' ] [ ' index ' ] )
# 'deadbeef' markers mean our own input scripts are not queried
#'deadbeef' markers mean our own input scripts are not ''
if ins . scriptSig != b " " :
if ( ins [ ' script ' ] != ' ' ) :
continue
continue
utxo [ ctr ] = [ index , utxo_for_checking ]
utxo [ ctr ] = [ index , utxo_for_checking ]
ctr + = 1
ctr + = 1
utxo_data = jm_single ( ) . bc_interface . query_utxo_set ( [ x [
utxo_data = jm_single ( ) . bc_interface . query_utxo_set ( [ x [
1 ] for x in utxo . values ( ) ] )
1 ] for x in utxo . values ( ) ] )
# insert signatures
# insert signatures
for i , u in iteritems ( utxo ) :
for i , u in iteritems ( utxo ) :
if utxo_data [ i ] is None :
if utxo_data [ i ] is None :
continue
continue
#Check if the sender serialize_scripted the scriptCode
# Check if the sender included the scriptCode in the sig message;
#item into the sig message; if so, also pick up the amount
# if so, also pick up the amount from the utxo data retrieved
#from the utxo data retrieved from the blockchain to verify
# from the blockchain to verify the segwit-style signature.
#the segwit-style signature. Note that this allows a mixed
# Note that this allows a mixed SW/non-SW transaction as each utxo
#SW/non-SW transaction as each utxo is interpreted separately.
# is interpreted separately.
sig_deserialized = btc . deserialize_script ( sig )
try :
#verify_tx_input will not even parse the script if it has integers or None,
sig_deserialized = [ a for a in iter ( btc . CScript ( sig ) ) ]
#so abort in case we were given a junk sig:
except Exception as e :
if not all ( [ not isinstance ( x , int ) and x for x in sig_deserialized ] ) :
jlog . debug ( " Failed to parse junk sig message, ignoring. " )
jlog . warn ( " Junk signature: " + str ( sig_deserialized ) + \
break
# abort in case we were given a junk sig (note this previously had
# to check to avoid crashes in verify_tx_input, no longer (Feb 2020)):
if not all ( [ x for x in sig_deserialized ] ) :
jlog . debug ( " Junk signature: " + str ( sig_deserialized ) + \
" , not attempting to verify " )
" , not attempting to verify " )
break
break
if len ( sig_deserialized ) == 2 :
if len ( sig_deserialized ) == 2 :
@ -583,44 +584,48 @@ class Taker(object):
elif len ( sig_deserialized ) == 3 :
elif len ( sig_deserialized ) == 3 :
ver_sig , ver_pub , scriptCode = sig_deserialized
ver_sig , ver_pub , scriptCode = sig_deserialized
else :
else :
jlog . debug ( " Invalid signature message - more than 3 items " )
jlog . debug ( " Invalid signature message - not 2 or 3 items " )
break
break
ver_amt = utxo_data [ i ] [ ' value ' ] if scriptCode else None
ver_amt = utxo_data [ i ] [ ' value ' ] if scriptCode else None
sig_good = btc . verify_tx_input ( txhex , u [ 0 ] , utxo_data [ i ] [ ' script ' ] ,
witness = btc . CScriptWitness (
ver_sig , ver_pub , scriptCode = scriptCode , amount = ver_amt )
[ ver_sig , ver_pub ] ) if scriptCode else None
if ver_amt is not None and not sig_good :
# don't attempt to parse `pub` as pubkey unless it's valid.
# Special case to deal with legacy bots 0.5.0 or lower:
if scriptCode :
# the third field in the sigmessage was originally *not* the
try :
# scriptCode, but the contents of tx['ins'][index]['script'],
s = btc . pubkey_to_p2wpkh_script ( ver_pub )
# i.e. the witness program 0014... ; for this we can verify
except :
# implicitly, as verify_tx_input used to, by reconstructing
jlog . debug ( " Junk signature message, invalid pubkey, ignoring. " )
# from the public key. For these cases, we can *assume* that
break
# the input is of type p2sh-p2wpkh; we call the jmbitcoin method
scriptSig = btc . CScript ( [ ver_sig , ver_pub ] ) if not scriptCode else btc . CScript ( [ s ] )
# directly, as we cannot assume that *our* wallet handles this.
scriptCode = hexlify ( btc . pubkey_to_p2pkh_script (
# Pre-Feb 2020, we used the third field scriptCode differently in
ver_pub , True ) ) . decode ( ' ascii ' )
# pre- and post-0.5.0; now the scriptCode is implicit (i.e. calculated
sig_good = btc . verify_tx_input ( txhex , u [ 0 ] , utxo_data [ i ] [ ' script ' ] ,
# by underlying library, so that exceptional case is covered.
ver_sig , ver_pub , scriptCode = scriptCode , amount = ver_amt )
sig_good = btc . verify_tx_input ( self . latest_tx , u [ 0 ] , scriptSig ,
btc . CScript ( utxo_data [ i ] [ ' script ' ] ) , amount = ver_amt , witness = witness )
# verification for the native case is functionally identical but
# adds another flag; so we can allow it here:
if not sig_good :
sig_good = btc . verify_tx_input ( self . latest_tx , u [ 0 ] , scriptSig ,
btc . CScript ( utxo_data [ i ] [ ' script ' ] ) , amount = ver_amt ,
witness = witness , native = True )
# if passes, below code executes, and we should change for native:
scriptSig = btc . CScript ( [ b " " ] )
if sig_good :
if sig_good :
jlog . debug ( ' found good sig at index= %d ' % ( u [ 0 ] ) )
jlog . debug ( ' found good sig at index= %d ' % ( u [ 0 ] ) )
# Note that, due to the complexity of handling multisig or other
# arbitrary script (considering sending multiple signatures OTW),
# there is an assumption of p2sh-p2wpkh or p2wpkh, for the segwit
# case.
self . latest_tx . vin [ u [ 0 ] ] . scriptSig = scriptSig
if ver_amt :
if ver_amt :
# Note that, due to the complexity of handling multisig or other
self . latest_tx . wit . vtxinwit [ u [ 0 ] ] = btc . CTxInWitness (
# arbitrary script (considering sending multiple signatures OTW),
btc . CScriptWitness ( witness ) )
# there is an assumption of p2sh-p2wpkh or p2wpkh, for the segwit
# case.
self . latest_tx [ " ins " ] [ u [ 0 ] ] [ " txinwitness " ] = [ ver_sig , ver_pub ]
if btc . is_segwit_native_script ( utxo_data [ i ] [ ' script ' ] ) :
scriptSig = " "
else :
scriptSig = btc . serialize_script_unit (
btc . pubkey_to_p2wpkh_script ( ver_pub ) )
self . latest_tx [ " ins " ] [ u [ 0 ] ] [ " script " ] = scriptSig
else :
# Non segwit (as per above comments) is limited only to single key,
# p2pkh case.
self . latest_tx [ " ins " ] [ u [ 0 ] ] [ " script " ] = sig
inserted_sig = True
inserted_sig = True
# check if maker has sent everything possible
# check if maker has sent everything possible
@ -643,8 +648,8 @@ class Taker(object):
# other guy sent a failed signature
# other guy sent a failed signature
tx_signed = True
tx_signed = True
for ins in self . latest_tx [ ' ins ' ] :
for ins in self . latest_tx . vin :
if ins [ ' script ' ] == ' ' :
if ins . scriptSig == b " " :
tx_signed = False
tx_signed = False
if not tx_signed :
if not tx_signed :
return False
return False
@ -667,7 +672,7 @@ class Taker(object):
def filter_by_coin_age_amt ( utxos , age , amt ) :
def filter_by_coin_age_amt ( utxos , age , amt ) :
results = jm_single ( ) . bc_interface . query_utxo_set ( utxos ,
results = jm_single ( ) . bc_interface . query_utxo_set ( utxos ,
includeconf = True )
includeconf = True )
newresults = [ ]
newresults = [ ]
too_old = [ ]
too_old = [ ]
too_small = [ ]
too_small = [ ]
@ -683,7 +688,6 @@ class Taker(object):
too_small . append ( utxos [ i ] )
too_small . append ( utxos [ i ] )
if valid_age and valid_amt :
if valid_age and valid_amt :
newresults . append ( utxos [ i ] )
newresults . append ( utxos [ i ] )
return newresults , too_old , too_small
return newresults , too_old , too_small
def priv_utxo_pairs_from_utxos ( utxos , age , amt ) :
def priv_utxo_pairs_from_utxos ( utxos , age , amt ) :
@ -695,7 +699,7 @@ class Taker(object):
age , amt )
age , amt )
new_utxos_dict = { k : v for k , v in utxos . items ( ) if k in new_utxos }
new_utxos_dict = { k : v for k , v in utxos . items ( ) if k in new_utxos }
for k , v in iteritems ( new_utxos_dict ) :
for k , v in iteritems ( new_utxos_dict ) :
addr = v [ ' address ' ]
addr = self . wallet_service . script_to_addr ( v [ " script " ] )
priv = self . wallet_service . get_key_from_addr ( addr )
priv = self . wallet_service . get_key_from_addr ( addr )
if priv : #can be null from create-unsigned
if priv : #can be null from create-unsigned
priv_utxo_pairs . append ( ( priv , k ) )
priv_utxo_pairs . append ( ( priv , k ) )
@ -723,7 +727,7 @@ class Taker(object):
#in the transaction, about to be consumed, rather than use
#in the transaction, about to be consumed, rather than use
#random utxos that will persist after. At this step we also
#random utxos that will persist after. At this step we also
#allow use of external utxos in the json file.
#allow use of external utxos in the json file.
if any ( self . wallet_service . get_utxos_by_mixdepth ( hexfmt = False ) . values ( ) ) :
if any ( self . wallet_service . get_utxos_by_mixdepth ( ) . values ( ) ) :
utxos = { }
utxos = { }
for mdutxo in self . wallet_service . get_utxos_by_mixdepth ( ) . values ( ) :
for mdutxo in self . wallet_service . get_utxos_by_mixdepth ( ) . values ( ) :
utxos . update ( mdutxo )
utxos . update ( mdutxo )
@ -739,13 +743,9 @@ class Taker(object):
ext_valid = None
ext_valid = None
podle_data = generate_podle ( priv_utxo_pairs , tries , ext_valid )
podle_data = generate_podle ( priv_utxo_pairs , tries , ext_valid )
if podle_data :
if podle_data :
jlog . debug ( " Generated PoDLE: " + pprint . pformat ( podle_data ) )
jlog . debug ( " Generated PoDLE: " + repr ( podle_data ) )
revelation = PoDLE ( u = podle_data [ ' utxo ' ] ,
return ( commit_type_byte + bintohex ( podle_data . commitment ) ,
P = podle_data [ ' P ' ] ,
podle_data . serialize_revelation ( ) ,
P2 = podle_data [ ' P2 ' ] ,
s = podle_data [ ' sig ' ] ,
e = podle_data [ ' e ' ] ) . serialize_revelation ( )
return ( commit_type_byte + podle_data [ " commit " ] , revelation ,
" Commitment sourced OK " )
" Commitment sourced OK " )
else :
else :
errmsgheader , errmsg = generate_podle_error_string ( priv_utxo_pairs ,
errmsgheader , errmsg = generate_podle_error_string ( priv_utxo_pairs ,
@ -771,25 +771,26 @@ class Taker(object):
def self_sign ( self ) :
def self_sign ( self ) :
# now sign it ourselves
# now sign it ourselves
our_inputs = { }
our_inputs = { }
for index , ins in enumerate ( self . latest_tx [ ' ins ' ] ) :
for index , ins in enumerate ( self . latest_tx . vin ) :
utxo = ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str ( ins [ ' outpoint ' ] [ ' index ' ] )
utxo = ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n )
if utxo not in self . input_utxos . keys ( ) :
if utxo not in self . input_utxos . keys ( ) :
continue
continue
script = self . wallet_service . addr_to_script ( self . input_utxos [ utxo ] [ ' address ' ] )
script = self . input_utxos [ utxo ] [ " script " ]
amount = self . input_utxos [ utxo ] [ ' value ' ]
amount = self . input_utxos [ utxo ] [ ' value ' ]
our_inputs [ index ] = ( script , amount )
our_inputs [ index ] = ( script , amount )
self . latest_tx = self . wallet_service . sign_tx ( self . latest_tx , our_inputs )
success , msg = self . wallet_service . sign_tx ( self . latest_tx , our_inputs )
if not success :
jlog . error ( " Failed to sign transaction: " + msg )
def push ( self ) :
def push ( self ) :
tx = btc . serialize ( self . latest_tx )
jlog . debug ( ' \n ' + bintohex ( self . latest_tx . serialize ( ) ) )
jlog . debug ( ' \n ' + tx )
self . txid = bintohex ( self . latest_tx . GetTxid ( ) [ : : - 1 ] )
self . txid = btc . txhash ( tx )
jlog . info ( ' txid = ' + self . txid )
jlog . info ( ' txid = ' + self . txid )
#If we are sending to a bech32 address, in case of sweep, will
#If we are sending to a bech32 address, in case of sweep, will
#need to use that bech32 for address import, which requires
#need to use that bech32 for address import, which requires
#converting to script (Core does not allow import of bech32)
#converting to script (Core does not allow import of bech32)
if self . my_cj_addr . lower ( ) [ : 2 ] in [ ' bc ' , ' tb ' ] :
if self . my_cj_addr . lower ( ) [ : 2 ] in [ ' bc ' , ' tb ' ] :
notify_addr = btc . address_to_script ( self . my_cj_addr )
notify_addr = btc . CCoinAddress ( self . my_cj_addr ) . to_scriptPubKey ( )
else :
else :
notify_addr = self . my_cj_addr
notify_addr = self . my_cj_addr
#add the callbacks *before* pushing to ensure triggering;
#add the callbacks *before* pushing to ensure triggering;
@ -810,7 +811,7 @@ class Taker(object):
tx_broadcast = jm_single ( ) . config . get ( ' POLICY ' , ' tx_broadcast ' )
tx_broadcast = jm_single ( ) . config . get ( ' POLICY ' , ' tx_broadcast ' )
nick_to_use = None
nick_to_use = None
if tx_broadcast == ' self ' :
if tx_broadcast == ' self ' :
pushed = jm_single ( ) . bc_interface . pushtx ( tx )
pushed = jm_single ( ) . bc_interface . pushtx ( self . latest_ tx. serialize ( ) )
elif tx_broadcast in [ ' random-peer ' , ' not-self ' ] :
elif tx_broadcast in [ ' random-peer ' , ' not-self ' ] :
n = len ( self . maker_utxo_data )
n = len ( self . maker_utxo_data )
if tx_broadcast == ' random-peer ' :
if tx_broadcast == ' random-peer ' :
@ -818,14 +819,14 @@ class Taker(object):
else :
else :
i = random . randrange ( n )
i = random . randrange ( n )
if i == n :
if i == n :
pushed = jm_single ( ) . bc_interface . pushtx ( tx )
pushed = jm_single ( ) . bc_interface . pushtx ( self . latest_ tx. serialize ( ) )
else :
else :
nick_to_use = list ( self . maker_utxo_data . keys ( ) ) [ i ]
nick_to_use = list ( self . maker_utxo_data . keys ( ) ) [ i ]
pushed = True
pushed = True
else :
else :
jlog . info ( " Only self, random-peer and not-self broadcast "
jlog . info ( " Only self, random-peer and not-self broadcast "
" methods supported. Reverting to self-broadcast. " )
" methods supported. Reverting to self-broadcast. " )
pushed = jm_single ( ) . bc_interface . pushtx ( tx )
pushed = jm_single ( ) . bc_interface . pushtx ( self . latest_ tx. serialize ( ) )
if not pushed :
if not pushed :
self . on_finished_callback ( False , fromtx = True )
self . on_finished_callback ( False , fromtx = True )
else :
else :
@ -841,7 +842,7 @@ class Taker(object):
# Takers process only in series, so this should not occur:
# Takers process only in series, so this should not occur:
assert self . latest_tx is not None
assert self . latest_tx is not None
# check if the transaction matches our created tx:
# check if the transaction matches our created tx:
if txd [ ' outs ' ] != self . latest_tx [ ' outs ' ] :
if txd . vout != self . latest_tx . vout :
return False
return False
return True
return True
@ -928,12 +929,12 @@ class P2EPTaker(Taker):
# For the p2ep taker, the variable 'my_cj_addr' is the destination:
# For the p2ep taker, the variable 'my_cj_addr' is the destination:
self . my_cj_addr = si [ 3 ]
self . my_cj_addr = si [ 3 ]
if isinstance ( self . cjamount , float ) :
if isinstance ( self . cjamount , float ) :
raise JMTakerError ( " P2EP coin join must use amount in satoshis " )
raise JMTakerError ( " Pay join must use amount in satoshis " )
if self . cjamount == 0 :
if self . cjamount == 0 :
# Note that we don't allow sweep, currently, since the coin
# Note that we don't allow sweep, currently, since the coin
# choosing algo would not apply in that case (we'd have to rewrite
# choosing algo would not apply in that case (we'd have to rewrite
# prepare_my_bitcoin_data for that case).
# prepare_my_bitcoin_data for that case).
raise JMTakerError ( " P2EP coin join does not currently support sweep " )
raise JMTakerError ( " Pay join does not currently support sweep " )
# Next we prepare our coins with the inherited method
# Next we prepare our coins with the inherited method
# for this purpose; for this we must set the
# for this purpose; for this we must set the
@ -1004,27 +1005,27 @@ class P2EPTaker(Taker):
if self . my_change_addr is not None :
if self . my_change_addr is not None :
self . outputs . append ( { ' address ' : self . my_change_addr ,
self . outputs . append ( { ' address ' : self . my_change_addr ,
' value ' : my_change_value } )
' value ' : my_change_value } )
# As for JM coinjoins, the `None` key is used for our own inputs
# Oour own inputs to the transaction; this preparatory version
# to the transaction; this preparatory version contains only those.
# contains only those.
tx = btc . make_shuffled_tx ( self . utxos [ None ] , self . outputs ,
tx = btc . make_shuffled_tx ( self . input_utxos , self . outputs ,
False , 2 , compute_tx_locktime ( ) )
version = 2 , locktime = compute_tx_locktime ( ) )
jlog . info ( ' Created proposed fallback tx \n ' + pprint . pformat (
jlog . info ( ' Created proposed fallback tx \n ' + pprint . pformat ( str ( tx ) ) )
btc . deserialize ( tx ) ) )
# We now sign as a courtesy, because if we disappear the recipient
# We now sign as a courtesy, because if we disappear the recipient
# can still claim his coins with this.
# can still claim his coins with this.
# sign our inputs before transfer
# sign our inputs before transfer
our_inputs = { }
our_inputs = { }
dtx = btc . deserializ e( tx )
for index , ins in enumerat e( tx . vin ) :
for index , ins in enumerate ( dtx [ ' ins ' ] ) :
utxo = ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n )
utxo = ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str ( ins [ ' outpoint ' ] [ ' index ' ] )
our_inputs [ index ] = ( self . input_utxos [ utxo ] [ " script " ] ,
script = self . wallet_service . addr_to_script ( self . input_utxos [ utxo ] [ ' address ' ] )
self . input_utxos [ utxo ] [ ' value ' ] )
amount = self . input_utxos [ utxo ] [ ' value ' ]
success , msg = self . wallet_service . sign_tx ( tx , our_inputs )
our_inputs [ index ] = ( script , amount )
if not success :
self . signed_noncj_tx = btc . serialize ( self . wallet_service . sign_tx ( dtx , our_inputs ) )
jlog . error ( " Failed to create backup transaction; error: " + msg )
self . taker_info_callback ( " INFO " , " Built tx proposal, sending to receiver. " )
self . taker_info_callback ( " INFO " , " Built tx proposal, sending to receiver. " )
return ( True , [ self . p2ep_receiver_nick ] , self . signed_noncj_tx )
return ( True , [ self . p2ep_receiver_nick ] , bintohex ( tx . serialize ( ) ) )
def on_tx_received ( self , nick , txhex ) :
@hexbin
def on_tx_received ( self , nick , txser ) :
""" Here the taker (payer) retrieves a version of the
""" Here the taker (payer) retrieves a version of the
transaction from the maker ( receiver ) which should have
transaction from the maker ( receiver ) which should have
the following properties :
the following properties :
@ -1041,12 +1042,14 @@ class P2EPTaker(Taker):
and then broadcast ( TODO broadcast delay or don ' t broadcast).
and then broadcast ( TODO broadcast delay or don ' t broadcast).
"""
"""
try :
try :
tx = btc . deserialize ( txhex )
tx = btc . CMutableTransaction . deserialize ( txser )
except ( IndexError , SerializationError , SerializationTruncationError ) as e :
except Exception as e :
return ( False , " malformed txhex. " + repr ( e ) )
return ( False , " malformed txhex. " + repr ( e ) )
jlog . info ( " Obtained tx from receiver: \n " + pprint . pformat ( tx ) )
jlog . info ( " Obtained tx from receiver: \n " + pprint . pformat ( str ( tx ) ) )
cjaddr_script = btc . address_to_script ( self . my_cj_addr )
cjaddr_script = btc . CCoinAddress (
changeaddr_script = btc . address_to_script ( self . my_change_addr )
self . my_cj_addr ) . to_scriptPubKey ( )
changeaddr_script = btc . CCoinAddress (
self . my_change_addr ) . to_scriptPubKey ( )
# We ensure that the coinjoin address and our expected change
# We ensure that the coinjoin address and our expected change
# address are still in the outputs, once (with the caveat that
# address are still in the outputs, once (with the caveat that
@ -1054,19 +1057,19 @@ class P2EPTaker(Taker):
# of dust change, which we assess after).
# of dust change, which we assess after).
times_seen_cj_addr = 0
times_seen_cj_addr = 0
times_seen_change_addr = 0
times_seen_change_addr = 0
for outs in tx [ ' outs ' ] :
for outs in tx . vout :
if outs [ ' script ' ] == cjaddr_script :
if outs . scriptPubKey == cjaddr_script :
times_seen_cj_addr + = 1
times_seen_cj_addr + = 1
new_cj_amount = outs [ ' value ' ]
new_cj_amount = outs . nValue
if new_cj_amount < self . cjamount :
if new_cj_amount < self . cjamount :
# This is a violation of protocol;
# This is a violation of protocol;
# receiver must be providing extra bitcoin
# receiver must be providing extra bitcoin
# as input, so his receiving amount should have increased.
# as input, so his receiving amount should have increased.
return ( False ,
return ( False ,
' Wrong cj_amount. I expect at least ' + str ( self . cjamount ) )
' Wrong cj_amount. I expect at least ' + str ( self . cjamount ) )
if outs [ ' script ' ] == changeaddr_script :
if outs . scriptPubKey == changeaddr_script :
times_seen_change_addr + = 1
times_seen_change_addr + = 1
new_change_amount = outs [ ' value ' ]
new_change_amount = outs . nValue
if times_seen_cj_addr != 1 :
if times_seen_cj_addr != 1 :
fmt = ( ' cj addr not in tx outputs once, #cjaddr= {} ' ) . format
fmt = ( ' cj addr not in tx outputs once, #cjaddr= {} ' ) . format
return ( False , ( fmt ( times_seen_cj_addr ) ) )
return ( False , ( fmt ( times_seen_cj_addr ) ) )
@ -1077,8 +1080,7 @@ class P2EPTaker(Taker):
new_change_amount = 0
new_change_amount = 0
# Check that our inputs are present.
# Check that our inputs are present.
tx_utxo_set = set ( ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str (
tx_utxo_set = set ( ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n ) for ins in tx . vin )
ins [ ' outpoint ' ] [ ' index ' ] ) for ins in tx [ ' ins ' ] )
if not tx_utxo_set . issuperset ( set ( self . utxos [ None ] ) ) :
if not tx_utxo_set . issuperset ( set ( self . utxos [ None ] ) ) :
return ( False , " my utxos are not contained " )
return ( False , " my utxos are not contained " )
# Check that the sequence numbers of all inputs are unaltered
# Check that the sequence numbers of all inputs are unaltered
@ -1087,10 +1089,10 @@ class P2EPTaker(Taker):
# Note that this is hacky and is most elegantly addressed by
# Note that this is hacky and is most elegantly addressed by
# use of PSBT (although any object encapsulation of tx input
# use of PSBT (although any object encapsulation of tx input
# would serve the same purpose).
# would serve the same purpose).
if tx [ " locktime " ] == 0 :
if tx . nLockTime == 0 :
return ( False , " Invalid PayJoin v0 transaction: locktime 0 " )
return ( False , " Invalid PayJoin v0 transaction: locktime 0 " )
for i in tx [ " ins " ] :
for i in tx . vin :
if i [ " sequence " ] != 0xffffffff - 1 :
if i . nSequence != 0xffffffff - 1 :
return ( False , " Invalid PayJoin v0 transaction: " + \
return ( False , " Invalid PayJoin v0 transaction: " + \
" sequence is not 0xffffffff -1 " )
" sequence is not 0xffffffff -1 " )
@ -1099,7 +1101,7 @@ class P2EPTaker(Taker):
# not) of PayJoin to sweep utxos at no cost.
# not) of PayJoin to sweep utxos at no cost.
# (TODO This is very kludgy, more sophisticated approach
# (TODO This is very kludgy, more sophisticated approach
# should be used in future):
# should be used in future):
if len ( tx [ " ins " ] ) - len ( self . utxos [ None ] ) > 5 :
if len ( tx . vin ) - len ( self . utxos [ None ] ) > 5 :
return ( False ,
return ( False ,
" proposed tx has more than 5 inputs from "
" proposed tx has more than 5 inputs from "
" the recipient, which is too expensive. " )
" the recipient, which is too expensive. " )
@ -1122,9 +1124,8 @@ class P2EPTaker(Taker):
# checking input validity and transaction balance.
# checking input validity and transaction balance.
retrieve_utxos = { }
retrieve_utxos = { }
ctr = 0
ctr = 0
for index , ins in enumerate ( tx [ ' ins ' ] ) :
for index , ins in enumerate ( tx . vin ) :
utxo_for_checking = ins [ ' outpoint ' ] [ ' hash ' ] + ' : ' + str (
utxo_for_checking = ( ins . prevout . hash [ : : - 1 ] , ins . prevout . n )
ins [ ' outpoint ' ] [ ' index ' ] )
if utxo_for_checking in self . utxos [ None ] :
if utxo_for_checking in self . utxos [ None ] :
continue
continue
retrieve_utxos [ ctr ] = [ index , utxo_for_checking ]
retrieve_utxos [ ctr ] = [ index , utxo_for_checking ]
@ -1140,32 +1141,11 @@ class P2EPTaker(Taker):
if utxo_data [ i ] is None :
if utxo_data [ i ] is None :
return ( False , " Proposed transaction contains invalid utxos " )
return ( False , " Proposed transaction contains invalid utxos " )
total_receiver_input + = utxo_data [ i ] [ " value " ]
total_receiver_input + = utxo_data [ i ] [ " value " ]
scriptCode = None
ver_amt = None
idx = retrieve_utxos [ i ] [ 0 ]
idx = retrieve_utxos [ i ] [ 0 ]
if " txinwitness " in tx [ " ins " ] [ idx ] :
if not btc . verify_tx_input ( tx , idx , tx . vin [ idx ] . scriptSig ,
ver_amt = utxo_data [ i ] [ " value " ]
btc . CScript ( utxo_data [ i ] [ ' script ' ] ) ,
try :
amount = utxo_data [ i ] [ " value " ] ,
ver_sig , ver_pub = tx [ " ins " ] [ idx ] [ " txinwitness " ]
witness = tx . wit . vtxinwit [ idx ] . scriptWitness ) :
except Exception as e :
print ( " Segwit error: " , repr ( e ) )
return ( False , " Segwit input not of expected type, "
" either p2sh-p2wpkh or p2wpkh " )
# note that the scriptCode is the same whether nested or not
# also note that the scriptCode has to be inferred if we are
# only given a transaction serialization.
scriptCode = " 76a914 " + btc . hash160 ( unhexlify ( ver_pub ) ) + " 88ac "
else :
scriptSig = btc . deserialize_script ( tx [ " ins " ] [ idx ] [ " script " ] )
if len ( scriptSig ) != 2 :
return ( False ,
" Proposed transaction contains unsupported input type " )
ver_sig , ver_pub = scriptSig
if not btc . verify_tx_input ( txhex , idx ,
utxo_data [ i ] [ ' script ' ] ,
ver_sig , ver_pub ,
scriptCode = scriptCode ,
amount = ver_amt ) :
return ( False ,
return ( False ,
" Proposed transaction is not correctly signed. " )
" Proposed transaction is not correctly signed. " )
payment = new_cj_amount - total_receiver_input
payment = new_cj_amount - total_receiver_input
@ -1185,7 +1165,7 @@ class P2EPTaker(Taker):
# our fee estimator. Its return value will be governed by our own fee settings
# our fee estimator. Its return value will be governed by our own fee settings
# in joinmarket.cfg; allow either (a) automatic agreement for any value within
# in joinmarket.cfg; allow either (a) automatic agreement for any value within
# a range of 0.3 to 3x this figure, or (b) user to agree on prompt.
# a range of 0.3 to 3x this figure, or (b) user to agree on prompt.
fee_est = estimate_tx_fee ( len ( tx [ ' ins ' ] ) , len ( tx [ ' outs ' ] ) ,
fee_est = estimate_tx_fee ( len ( tx . vin ) , len ( tx . vout ) ,
txtype = self . wallet_service . get_txtype ( ) )
txtype = self . wallet_service . get_txtype ( ) )
fee_ok = False
fee_ok = False
if btc_fee > 0.3 * fee_est and btc_fee < 3 * fee_est :
if btc_fee > 0.3 * fee_est and btc_fee < 3 * fee_est :