def on_auth_received(self, nick, offer, commitment, cr, amount, kphex): """Receives data on proposed transaction offer from daemon, verifies commitment, returns necessary data to send ioauth message (utxos etc) """ #deserialize the commitment revelation cr_dict = PoDLE.deserialize_revelation(cr) #check the validity of the proof of discrete log equivalence tries = jm_single().config.getint("POLICY", "taker_utxo_retries") def reject(msg): jlog.info("Counterparty commitment not accepted, reason: " + msg) return (False, ) if not verify_podle(str(cr_dict['P']), str(cr_dict['P2']), str(cr_dict['sig']), str(cr_dict['e']), str(commitment), index_range=range(tries)): reason = "verify_podle failed" return reject(reason) #finally, check that the proffered utxo is real, old enough, large enough, #and corresponds to the pubkey res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']], includeconf=True) if len(res) != 1 or not res[0]: reason = "authorizing utxo is not valid" return reject(reason) age = jm_single().config.getint("POLICY", "taker_utxo_age") if res[0]['confirms'] < age: reason = "commitment utxo not old enough: " + str( res[0]['confirms']) return reject(reason) reqd_amt = int( amount * jm_single().config.getint("POLICY", "taker_utxo_amtpercent") / 100.0) if res[0]['value'] < reqd_amt: reason = "commitment utxo too small: " + str(res[0]['value']) return reject(reason) if res[0]['address'] != self.wallet.pubkey_to_address(cr_dict['P']): reason = "Invalid podle pubkey: " + str(cr_dict['P']) return reject(reason) # authorisation of taker passed #Find utxos for the transaction now: utxos, cj_addr, change_addr = self.oid_to_order(offer, amount) if not utxos: #could not find funds return (False, ) self.wallet.update_cache_index() # Construct data for auth request back to taker. # Need to choose an input utxo pubkey to sign with # (no longer using the coinjoin pubkey from 0.2.0) # Just choose the first utxo in self.utxos and retrieve key from wallet. auth_address = utxos[utxos.keys()[0]]['address'] auth_key = self.wallet.get_key_from_addr(auth_address) auth_pub = btc.privtopub(auth_key) btc_sig = btc.ecdsa_sign(kphex, auth_key) return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig)
def make_commitment(self): """The Taker default commitment function, which uses PoDLE. Alternative commitment types should use a different commit type byte. This will allow future upgrades to provide different style commitments by subclassing Taker and changing the commit_type_byte; existing makers will simply not accept this new type of commitment. In case of success, return the commitment and its opening. In case of failure returns (None, None) and constructs a detailed log for the user to read and discern the reason. """ def filter_by_coin_age_amt(utxos, age, amt): results = jm_single().bc_interface.query_utxo_set(utxos, includeconf=True) newresults = [] too_old = [] too_small = [] for i, r in enumerate(results): #results return "None" if txo is spent; drop this if not r: continue valid_age = r['confirms'] >= age valid_amt = r['value'] >= amt if not valid_age: too_old.append(utxos[i]) if not valid_amt: too_small.append(utxos[i]) if valid_age and valid_amt: newresults.append(utxos[i]) return newresults, too_old, too_small def priv_utxo_pairs_from_utxos(utxos, age, amt): #returns pairs list of (priv, utxo) for each valid utxo; #also returns lists "too_old" and "too_small" for any #utxos that did not satisfy the criteria for debugging. priv_utxo_pairs = [] new_utxos, too_old, too_small = filter_by_coin_age_amt( utxos.keys(), age, amt) new_utxos_dict = {k: v for k, v in utxos.items() if k in new_utxos} for k, v in new_utxos_dict.iteritems(): addr = v['address'] priv = self.wallet.get_key_from_addr(addr) if priv: #can be null from create-unsigned priv_utxo_pairs.append((priv, k)) return priv_utxo_pairs, too_old, too_small commit_type_byte = "P" podle_data = None tries = jm_single().config.getint("POLICY", "taker_utxo_retries") age = jm_single().config.getint("POLICY", "taker_utxo_age") #Minor rounding errors don't matter here amt = int( self.cjamount * jm_single().config.getint("POLICY", "taker_utxo_amtpercent") / 100.0) priv_utxo_pairs, to, ts = priv_utxo_pairs_from_utxos( self.input_utxos, age, amt) #Note that we ignore the "too old" and "too small" lists in the first #pass through, because the same utxos appear in the whole-wallet check. #For podle data format see: podle.PoDLE.reveal() #In first round try, don't use external commitments podle_data = generate_podle(priv_utxo_pairs, tries) if not podle_data: #We defer to a second round to try *all* utxos in wallet; #this is because it's much cleaner to use the utxos involved #in the transaction, about to be consumed, rather than use #random utxos that will persist after. At this step we also #allow use of external utxos in the json file. if self.wallet.unspent: priv_utxo_pairs, to, ts = priv_utxo_pairs_from_utxos( self.wallet.unspent, age, amt) #Pre-filter the set of external commitments that work for this #transaction according to its size and age. dummy, extdict = get_podle_commitments() if len(extdict.keys()) > 0: ext_valid, ext_to, ext_ts = filter_by_coin_age_amt( extdict.keys(), age, amt) else: ext_valid = None podle_data = generate_podle(priv_utxo_pairs, tries, ext_valid) if podle_data: jlog.debug("Generated PoDLE: " + pprint.pformat(podle_data)) revelation = PoDLE(u=podle_data['utxo'], P=podle_data['P'], 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") else: errmsgheader, errmsg = generate_podle_error_string( priv_utxo_pairs, to, ts, self.wallet.unspent, self.cjamount, jm_single().config.get("POLICY", "taker_utxo_age"), jm_single().config.get("POLICY", "taker_utxo_amtpercent")) with open("commitments_debug.txt", "wb") as f: errmsgfileheader = ("THIS IS A TEMPORARY FILE FOR DEBUGGING; " "IT CAN BE SAFELY DELETED ANY TIME.\n") errmsgfileheader += ("***\n") f.write(errmsgfileheader + errmsg) return (None, (priv_utxo_pairs, to, ts), errmsgheader + errmsg)
def on_auth_received(self, nick, offer, commitment, cr, amount, kphex): """Receives data on proposed transaction offer from daemon, verifies commitment, returns necessary data to send ioauth message (utxos etc) """ # special case due to cjfee passed as string: it can accidentally parse # as hex: if not isinstance(offer["cjfee"], str): offer["cjfee"] = bintohex(offer["cjfee"]) #check the validity of the proof of discrete log equivalence tries = jm_single().config.getint("POLICY", "taker_utxo_retries") def reject(msg): jlog.info("Counterparty commitment not accepted, reason: " + msg) return (False, ) # deserialize the commitment revelation try: cr_dict = PoDLE.deserialize_revelation(cr) except PoDLEError as e: reason = repr(e) return reject(reason) if not verify_podle(cr_dict['P'], cr_dict['P2'], cr_dict['sig'], cr_dict['e'], commitment, index_range=range(tries)): reason = "verify_podle failed" return reject(reason) #finally, check that the proffered utxo is real, old enough, large enough, #and corresponds to the pubkey res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']], includeconf=True) if len(res) != 1 or not res[0]: reason = "authorizing utxo is not valid" return reject(reason) age = jm_single().config.getint("POLICY", "taker_utxo_age") if res[0]['confirms'] < age: reason = "commitment utxo not old enough: " + str( res[0]['confirms']) return reject(reason) reqd_amt = int( amount * jm_single().config.getint("POLICY", "taker_utxo_amtpercent") / 100.0) if res[0]['value'] < reqd_amt: reason = "commitment utxo too small: " + str(res[0]['value']) return reject(reason) try: if not self.wallet_service.pubkey_has_script( cr_dict['P'], res[0]['script']): raise EngineError() except EngineError: reason = "Invalid podle pubkey: " + str(cr_dict['P']) return reject(reason) # authorisation of taker passed # Find utxos for the transaction now: utxos, cj_addr, change_addr = self.oid_to_order(offer, amount) if not utxos: #could not find funds return (False, ) # for index update persistence: self.wallet_service.save_wallet() # Construct data for auth request back to taker. # Need to choose an input utxo pubkey to sign with # (no longer using the coinjoin pubkey from 0.2.0) # Just choose the first utxo in self.utxos and retrieve key from wallet. auth_address = utxos[list(utxos.keys())[0]]['address'] auth_key = self.wallet_service.get_key_from_addr(auth_address) auth_pub = btc.privkey_to_pubkey(auth_key) # kphex was auto-converted by @hexbin but we actually need to sign the # hex version to comply with pre-existing JM protocol: btc_sig = btc.ecdsa_sign(bintohex(kphex), auth_key) return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig)