def donation_address(cjtx): from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852' '9dfe2de921260a5c46036d97b5eacf2a') donation_utxo_data = cjtx.input_utxos.iteritems().next() global donation_utxo donation_utxo = donation_utxo_data[0] privkey = cjtx.wallet.get_key_from_addr(donation_utxo_data[1]['address']) # tx without our inputs and outputs tx = btc.mktx(cjtx.utxo_tx, cjtx.outputs) msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL) # generate unpredictable k global sign_k sign_k = deterministic_generate_k(msghash, privkey) c = btc.sha256(multiply(reusable_donation_pubkey, sign_k)) sender_pubkey = add_pubkeys(reusable_donation_pubkey, multiply(G, c)) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return sender_address
def donation_address(cjtx): from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852' '9dfe2de921260a5c46036d97b5eacf2a') donation_utxo_data = cjtx.input_utxos.iteritems().next() global donation_utxo donation_utxo = donation_utxo_data[0] privkey = cjtx.wallet.get_key_from_addr(donation_utxo_data[1]['address']) # tx without our inputs and outputs tx = btc.mktx(cjtx.utxo_tx, cjtx.outputs) msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL) # generate unpredictable k global sign_k sign_k = deterministic_generate_k(msghash, privkey) c = btc.sha256(multiply(reusable_donation_pubkey, sign_k)) sender_pubkey = add_pubkeys( reusable_donation_pubkey, multiply( G, c)) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return sender_address
def receive_utxos(self, ioauth_data): """Triggered when the daemon returns utxo data from makers who responded; this is the completion of phase 1 of the protocol """ if self.aborted: return (False, "User aborted") rejected_counterparties = [] #Enough data, but need to authorize against the btc pubkey first. for nick, nickdata in ioauth_data.iteritems(): utxo_list, auth_pub, cj_addr, change_addr, btc_sig, maker_pk = nickdata if not self.auth_counterparty(btc_sig, auth_pub, maker_pk): jlog.debug( "Counterparty encryption verification failed, aborting") #This counterparty must be rejected rejected_counterparties.append(nick) for rc in rejected_counterparties: del ioauth_data[rc] self.maker_utxo_data = {} for nick, nickdata in ioauth_data.iteritems(): utxo_list, auth_pub, cj_addr, change_addr, btc_sig, maker_pk = nickdata self.utxos[nick] = utxo_list utxo_data = jm_single().bc_interface.query_utxo_set( self.utxos[nick]) if None in utxo_data: jlog.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit; currently, the timeout thread suffices. continue #Complete maker authorization: #Extract the address fields from the utxos #Construct the Bitcoin address for the auth_pub field #Ensure that at least one address from utxos corresponds. input_addresses = [d['address'] for d in utxo_data] auth_address = btc.pubkey_to_address(auth_pub, get_p2pk_vbyte()) if not auth_address in input_addresses: jlog.warn("ERROR maker's (" + nick + ")" " authorising pubkey is not included " "in the transaction: " + str(auth_address)) #this will not be added to the transaction, so we will have #to recheck if we have enough continue total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.orderbook[nick]['ordertype'], self.orderbook[nick]['cjfee'], self.cjamount) change_amount = (total_input - self.cjamount - self.orderbook[nick]['txfee'] + real_cjfee) # certain malicious and/or incompetent liquidity providers send # inputs totalling less than the coinjoin amount! this leads to # a change output of zero satoshis; this counterparty must be removed. if change_amount < jm_single().DUST_THRESHOLD: fmt = ('ERROR counterparty requires sub-dust change. nick={}' 'totalin={:d} cjamount={:d} change={:d}').format jlog.debug(fmt(nick, total_input, self.cjamount, change_amount)) jlog.warn("Invalid change, too small, nick= " + nick) continue self.outputs.append({ 'address': change_addr, 'value': change_amount }) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format jlog.debug( fmt(nick, total_input, self.cjamount, self.orderbook[nick]['txfee'], real_cjfee)) self.outputs.append({'address': cj_addr, 'value': self.cjamount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.orderbook[nick]['txfee'] self.maker_utxo_data[nick] = utxo_data #Apply business logic of how many counterparties are enough: if len(self.maker_utxo_data.keys()) < jm_single().config.getint( "POLICY", "minimum_makers"): self.taker_info_callback("INFO", "Not enough counterparties, aborting.") return (False, "Not enough counterparties responded to fill, giving up") self.taker_info_callback("INFO", "Got all parts, enough to build a tx") self.nonrespondants = list(self.maker_utxo_data.keys()) my_total_in = sum( [va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])), len(self.outputs) + 2) jlog.info("Based on initial guess: " + str(self.total_txfee) + ", we estimated a miner fee of: " + str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = (my_total_in - self.cjamount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: " + str(self.total_txfee) + " is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single( ).BITCOIN_DUST_THRESHOLD: jlog.info("Dynamically calculated change lower than dust: " + str(my_change_value) + "; dropping.") self.my_change_addr = None my_change_value = 0 jlog.info( 'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable jlog.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({ 'address': self.my_change_addr, 'value': my_change_value }) self.utxo_tx = [ dict([('output', u)]) for u in sum(self.utxos.values(), []) ] self.outputs.append({ 'address': self.coinjoin_address(), 'value': self.cjamount }) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) jlog.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef' self.taker_info_callback("INFO", "Built tx, sending to counterparties.") return (True, self.maker_utxo_data.keys(), tx)
def recv_txio(self, nick, utxo_list, cj_pub, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list log.debug("Trying to retrieve utxos for: " + nick) utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) log.debug("Got this utxo data: " + pprint.pformat(utxo_data)) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit; currently, the timeout thread suffices. return total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'], self.active_orders[nick]['cjfee'], self.cj_amount) change_amount = (total_input - self.cj_amount - self.active_orders[nick]['txfee'] + real_cjfee) # certain malicious and/or incompetent liquidity providers send # inputs totalling less than the coinjoin amount! this leads to # a change output of zero satoshis, so the invalid transaction # fails harmlessly; let's fail earlier, with a clear message. if change_amount < jm_single().DUST_THRESHOLD: fmt = ('ERROR counterparty requires sub-dust change. nick={}' 'totalin={:d} cjamount={:d} change={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, change_amount)) return # timeout marks this maker as nonresponsive self.outputs.append({'address': change_addr, 'value': change_amount}) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, self.active_orders[nick]['txfee'], real_cjfee)) cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte()) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.active_orders[nick]['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum([va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum( self.utxos.values(),[])), len(self.outputs)+2) log.debug("Based on initial guess: "+str( self.total_txfee)+", we estimated a fee of: "+str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = ( my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: "+str( self.total_txfee)+" is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single().DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: "+str( my_change_value)+"; dropping.") self.my_change_addr = None my_change_value = 0 log.debug('fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({'address': self.my_change_addr, 'value': my_change_value}) self.utxo_tx = [dict([('output', u)]) for u in sum(self.utxos.values(), [])] self.outputs.append({'address': self.coinjoin_address(), 'value': self.cj_amount}) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) #Re-calculate a sensible timeout wait based on the throttling #settings and the tx size. #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S #So we're sending N*1.8*S over the wire, and the #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds, #and need to add some leeway for network delays, we just add the #contents of jm_single().maker_timeout_sec (the user configured value) self.maker_timeout_sec = (len(tx) * 1.8 * len( self.active_orders.keys()))/(B_PER_SEC) + jm_single().maker_timeout_sec log.debug("Based on transaction size: " + str( len(tx)) + ", calculated time to wait for replies: " + str( self.maker_timeout_sec)) self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'
def recv_txio(self, nick, utxo_list, cj_pub, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list log.debug("Trying to retrieve utxos for: " + nick) utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) log.debug("Got this utxo data: " + pprint.pformat(utxo_data)) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit; currently, the timeout thread suffices. return total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'], self.active_orders[nick]['cjfee'], self.cj_amount) change_amount = (total_input - self.cj_amount - self.active_orders[nick]['txfee'] + real_cjfee) # certain malicious and/or incompetent liquidity providers send # inputs totalling less than the coinjoin amount! this leads to # a change output of zero satoshis, so the invalid transaction # fails harmlessly; let's fail earlier, with a clear message. if change_amount < jm_single().DUST_THRESHOLD: fmt = ('ERROR counterparty requires sub-dust change. nick={}' 'totalin={:d} cjamount={:d} change={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, change_amount)) return # timeout marks this maker as nonresponsive self.outputs.append({'address': change_addr, 'value': change_amount}) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug( fmt(nick, total_input, self.cj_amount, self.active_orders[nick]['txfee'], real_cjfee)) cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte()) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.active_orders[nick]['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum( [va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])), len(self.outputs) + 2) log.debug("Based on initial guess: " + str(self.total_txfee) + ", we estimated a fee of: " + str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = (my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: " + str(self.total_txfee) + " is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single( ).DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: " + str(my_change_value) + "; dropping.") self.my_change_addr = None my_change_value = 0 log.debug( 'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({ 'address': self.my_change_addr, 'value': my_change_value }) self.utxo_tx = [ dict([('output', u)]) for u in sum(self.utxos.values(), []) ] self.outputs.append({ 'address': self.coinjoin_address(), 'value': self.cj_amount }) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) #Re-calculate a sensible timeout wait based on the throttling #settings and the tx size. #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S #So we're sending N*1.8*S over the wire, and the #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds, #and need to add some leeway for network delays, we just add the #contents of jm_single().maker_timeout_sec (the user configured value) self.maker_timeout_sec = (len(tx) * 1.8 * len(self.active_orders.keys( ))) / (B_PER_SEC) + jm_single().maker_timeout_sec log.debug("Based on transaction size: " + str(len(tx)) + ", calculated time to wait for replies: " + str(self.maker_timeout_sec)) self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'