def push(self): tx = btc.serialize(self.latest_tx) log.debug('\n' + tx) self.txid = btc.txhash(tx) log.debug('txid = ' + self.txid) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.active_orders) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: self.msgchan.push_tx(self.active_orders.keys()[i], tx) pushed = True elif tx_broadcast == 'random-maker': crow = self.db.execute( 'SELECT DISTINCT counterparty FROM orderbook ORDER BY ' + 'RANDOM() LIMIT 1;' ).fetchone() counterparty = crow['counterparty'] log.debug('pushing tx to ' + counterparty) self.msgchan.push_tx(counterparty, tx) pushed = True if not pushed: log.debug('unable to pushtx') return pushed
def self_sign(self): # now sign it ourselves our_inputs = {} 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 script = self.wallet.addr_to_script(self.input_utxos[utxo]['address']) amount = self.input_utxos[utxo]['value'] our_inputs[index] = (script, amount) # FIXME: ugly hack tx_bin = btc.deserialize(unhexlify(btc.serialize(self.latest_tx))) self.wallet.sign_tx(tx_bin, our_inputs) self.latest_tx = btc.deserialize(hexlify(btc.serialize(tx_bin)))
def push(self): tx = btc.serialize(self.latest_tx) jlog.debug('\n' + tx) self.txid = btc.txhash(tx) jlog.debug('txid = ' + self.txid) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') nick_to_use = None if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.maker_utxo_data) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: nick_to_use = self.maker_utxo_data.keys()[i] pushed = True else: jlog.info("Only self, random-peer and not-self broadcast " "methods supported. Reverting to self-broadcast.") pushed = jm_single().bc_interface.pushtx(tx) if not pushed: self.on_finished_callback(False, fromtx=True) else: jm_single().bc_interface.add_tx_notify(self.latest_tx, self.unconfirm_callback, self.confirm_callback, self.my_cj_addr) if nick_to_use: return (nick_to_use, tx)
def push(self): tx = btc.serialize(self.latest_tx) log.debug('\n' + tx) self.txid = btc.txhash(tx) log.debug('txid = ' + self.txid) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.active_orders) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: self.msgchan.push_tx(self.active_orders.keys()[i], tx) pushed = True elif tx_broadcast == 'random-maker': crow = self.db.execute( 'SELECT DISTINCT counterparty FROM orderbook ORDER BY ' + 'RANDOM() LIMIT 1;').fetchone() counterparty = crow['counterparty'] log.debug('pushing tx to ' + counterparty) self.msgchan.push_tx(counterparty, tx) pushed = True if not pushed: log.debug('unable to pushtx') return pushed
def on_sig(self, nick, sigb64): """Processes transaction signatures from counterparties. Returns True if all signatures received correctly, else returns False """ if self.aborted: return False sig = base64.b64decode(sigb64).encode('hex') inserted_sig = False txhex = btc.serialize(self.latest_tx) # batch retrieval of utxo data utxo = {} ctr = 0 for index, ins in enumerate(self.latest_tx['ins']): utxo_for_checking = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) #'deadbeef' markers mean our own input scripts are not '' if (ins['script'] != ''): continue utxo[ctr] = [index, utxo_for_checking] ctr += 1 utxo_data = jm_single().bc_interface.query_utxo_set( [x[1] for x in utxo.values()]) # insert signatures for i, u in utxo.iteritems(): if utxo_data[i] is None: continue sig_good = btc.verify_tx_input(txhex, u[0], utxo_data[i]['script'], *btc.deserialize_script(sig)) if sig_good: jlog.debug('found good sig at index=%d' % (u[0])) self.latest_tx['ins'][u[0]]['script'] = sig inserted_sig = True # check if maker has sent everything possible self.utxos[nick].remove(u[1]) if len(self.utxos[nick]) == 0: jlog.debug(('nick = {} sent all sigs, removing from ' 'nonrespondant list').format(nick)) self.nonrespondants.remove(nick) break if not inserted_sig: jlog.debug('signature did not match anything in the tx') # TODO what if the signature doesnt match anything # nothing really to do except drop it, carry on and wonder why the # other guy sent a failed signature tx_signed = True for ins in self.latest_tx['ins']: if ins['script'] == '': tx_signed = False if not tx_signed: return False assert not len(self.nonrespondants) jlog.debug('all makers have sent their signatures') self.taker_info_callback("INFO", "Transaction is valid, signing..") jlog.debug("schedule item was: " + str(self.schedule[self.schedule_index])) return self.self_sign_and_push()
def sign_donation_tx(tx, i, priv): from bitcoin.main import fast_multiply, decode_privkey, G, inv, N from bitcoin.transaction import der_encode_sig k = sign_k hashcode = btc.SIGHASH_ALL i = int(i) if len(priv) <= 33: priv = btc.safe_hexlify(priv) pub = btc.privkey_to_pubkey(priv) address = btc.pubkey_to_address(pub) signing_tx = btc.signature_form(tx, i, btc.mk_pubkey_script(address), hashcode) msghash = btc.bin_txhash(signing_tx, hashcode) z = btc.hash_to_int(msghash) # k = deterministic_generate_k(msghash, priv) r, y = fast_multiply(G, k) s = inv(k, N) * (z + r * decode_privkey(priv)) % N rawsig = 27 + (y % 2), r, s sig = der_encode_sig(*rawsig) + btc.encode(hashcode, 16, 2) # sig = ecdsa_tx_sign(signing_tx, priv, hashcode) txobj = btc.deserialize(tx) txobj["ins"][i]["script"] = btc.serialize_script([sig, pub]) return btc.serialize(txobj)
def self_sign(self): # now sign it ourselves tx = btc.serialize(self.latest_tx) if self.sign_method == "wallet": #Currently passes addresses of to-be-signed inputs #to backend wallet; this is correct for Electrum, may need #different info for other backends. addrs = {} 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 addrs[index] = self.input_utxos[utxo]['address'] tx = self.wallet.sign_tx(tx, addrs) else: 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 addr = self.input_utxos[utxo]['address'] tx = self.sign_tx(tx, index, self.wallet.get_key_from_addr(addr)) self.latest_tx = btc.deserialize(tx)
def sign_donation_tx(tx, i, priv): from bitcoin.main import fast_multiply, decode_privkey, G, inv, N from bitcoin.transaction import der_encode_sig k = sign_k hashcode = btc.SIGHASH_ALL i = int(i) if len(priv) <= 33: priv = btc.safe_hexlify(priv) pub = btc.privkey_to_pubkey(priv) address = btc.pubkey_to_address(pub) signing_tx = btc.signature_form( tx, i, btc.mk_pubkey_script(address), hashcode) msghash = btc.bin_txhash(signing_tx, hashcode) z = btc.hash_to_int(msghash) # k = deterministic_generate_k(msghash, priv) r, y = fast_multiply(G, k) s = inv(k, N) * (z + r * decode_privkey(priv)) % N rawsig = 27 + (y % 2), r, s sig = der_encode_sig(*rawsig) + btc.encode(hashcode, 16, 2) # sig = ecdsa_tx_sign(signing_tx, priv, hashcode) txobj = btc.deserialize(tx) txobj["ins"][i]["script"] = btc.serialize_script([sig, pub]) return btc.serialize(txobj)
def outputs_watcher(self, wallet_name, notifyaddr, tx_output_set, unconfirmfun, confirmfun, timeoutfun): """Given a key for the watcher loop (notifyaddr), a wallet name (account), a set of outputs, and unconfirm, confirm and timeout callbacks, check to see if a transaction matching that output set has appeared in the wallet. Call the callbacks and update the watcher loop state. End the loop when the confirmation has been seen (no spent monitoring here). """ wl = self.tx_watcher_loops[notifyaddr] account_name = wallet_name if wallet_name else "*" txlist = self.rpc("listtransactions", [wallet_name, 100, 0, True]) for tx in txlist[::-1]: #changed syntax in 0.14.0; allow both syntaxes try: res = self.rpc("gettransaction", [tx["txid"], True]) except: try: res = self.rpc("gettransaction", [tx["txid"], 1]) except JsonRpcError as e: #This should never happen (gettransaction is a wallet rpc). log.info("Failed any gettransaction call") res = None except Exception as e: log.info(str(e)) if not res: continue if "confirmations" not in res: log.debug("Malformed gettx result: " + str(res)) return txd = self.get_deser_from_gettransaction(res) if txd is None: continue txos = set([(sv['script'], sv['value']) for sv in txd['outs']]) if not txos == tx_output_set: continue #Here we have found a matching transaction in the wallet. real_txid = btc.txhash(btc.serialize(txd)) if not wl[1] and res["confirmations"] == 0: log.debug("Tx: " + str(real_txid) + " seen on network.") unconfirmfun(txd, real_txid) wl[1] = True return if not wl[2] and res["confirmations"] > 0: log.debug("Tx: " + str(real_txid) + " has " + str(res["confirmations"]) + " confirmations.") confirmfun(txd, real_txid, res["confirmations"]) wl[2] = True wl[0].stop() return if res["confirmations"] < 0: log.debug("Tx: " + str(real_txid) + " has a conflict. Abandoning.") wl[0].stop() return
def push(self): tx = btc.serialize(self.latest_tx) jlog.debug('\n' + tx) self.txid = btc.txhash(tx) jlog.debug('txid = ' + self.txid) pushed = jm_single().bc_interface.pushtx(tx) if not pushed: self.on_finished_callback(False, fromtx=True) else: jm_single().bc_interface.add_tx_notify(self.latest_tx, self.unconfirm_callback, self.confirm_callback, self.my_cj_addr)
def tx_watcher(self, txd, unconfirmfun, confirmfun, spentfun, c, n): """Called at a polling interval, checks if the given deserialized transaction (which must be fully signed) is (a) broadcast, (b) confirmed and (c) spent from. (c, n ignored in electrum version, just supports registering first confirmation). TODO: There is no handling of conflicts here. """ txid = btc.txhash(btc.serialize(txd)) wl = self.tx_watcher_loops[txid] #first check if in mempool (unconfirmed) #choose an output address for the query. Filter out #p2pkh addresses, assume p2sh (thus would fail to find tx on #some nonstandard script type) addr = None for i in range(len(txd['outs'])): if not btc.is_p2pkh_script(txd['outs'][i]['script']): addr = btc.script_to_address(txd['outs'][i]['script'], get_p2sh_vbyte()) break if not addr: log.error("Failed to find any p2sh output, cannot be a standard " "joinmarket transaction, fatal error!") reactor.stop() return unconftxs_res = self.get_from_electrum( 'blockchain.address.get_mempool', addr, blocking=True).get('result') unconftxs = [str(t['tx_hash']) for t in unconftxs_res] if not wl[1] and txid in unconftxs: print("Tx: " + str(txid) + " seen on network.") unconfirmfun(txd, txid) wl[1] = True return conftx = self.get_from_electrum('blockchain.address.listunspent', addr, blocking=True).get('result') conftxs = [str(t['tx_hash']) for t in conftx] if not wl[2] and len(conftxs) and txid in conftxs: print("Tx: " + str(txid) + " is confirmed.") confirmfun(txd, txid, 1) wl[2] = True #Note we do not stop the monitoring loop when #confirmations occur, since we are also monitoring for spending. return if not spentfun or wl[3]: return
def self_sign(self): # now sign it ourselves tx = btc.serialize(self.latest_tx) if isinstance(jm_single().bc_interface, ElectrumWalletInterface): #can defer signing to our wallet; it will handle #all relevant inputs tx = self.wallet.sign_tx(tx) log.debug("Back in self_sign in cjtx, tx is: " + str(tx)) else: 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 addr = self.input_utxos[utxo]['address'] tx = self.sign_tx(tx, index, self.wallet.get_key_from_addr(addr)) self.latest_tx = btc.deserialize(tx)
def push(self): tx = btc.serialize(self.latest_tx) jlog.debug('\n' + tx) self.txid = btc.txhash(tx) jlog.info('txid = ' + self.txid) #If we are sending to a bech32 address, in case of sweep, will #need to use that bech32 for address import, which requires #converting to script (Core does not allow import of bech32) if self.my_cj_addr.lower()[:2] in ['bc', 'tb']: notify_addr = btc.address_to_script(self.my_cj_addr) else: notify_addr = self.my_cj_addr #add the txnotify callbacks *before* pushing in case the #walletnotify is triggered before the notify callbacks are set up; #this does leave a dangling notify callback if the push fails, but #that doesn't cause problems. jm_single().bc_interface.add_tx_notify(self.latest_tx, self.unconfirm_callback, self.confirm_callback, notify_addr, vb=get_p2sh_vbyte()) tx_broadcast = jm_single().config.get('POLICY', 'tx_broadcast') nick_to_use = None if tx_broadcast == 'self': pushed = jm_single().bc_interface.pushtx(tx) elif tx_broadcast in ['random-peer', 'not-self']: n = len(self.maker_utxo_data) if tx_broadcast == 'random-peer': i = random.randrange(n + 1) else: i = random.randrange(n) if i == n: pushed = jm_single().bc_interface.pushtx(tx) else: nick_to_use = self.maker_utxo_data.keys()[i] pushed = True else: jlog.info("Only self, random-peer and not-self broadcast " "methods supported. Reverting to self-broadcast.") pushed = jm_single().bc_interface.pushtx(tx) if not pushed: self.on_finished_callback(False, fromtx=True) else: if nick_to_use: return (nick_to_use, tx)
def tx_watcher(self, txd, unconfirmfun, confirmfun, spentfun, c, n): """Called at a polling interval, checks if the given deserialized transaction (which must be fully signed) is (a) broadcast, (b) confirmed and (c) spent from at index n, and notifies confirmation if number of confs = c. TODO: Deal with conflicts correctly. Here just abandons monitoring. """ txid = btc.txhash(btc.serialize(txd)) wl = self.tx_watcher_loops[txid] try: res = self.rpc('gettransaction', [txid, True]) except JsonRpcError as e: return if not res: return if "confirmations" not in res: log.debug("Malformed gettx result: " + str(res)) return if not wl[1] and res["confirmations"] == 0: log.debug("Tx: " + str(txid) + " seen on network.") unconfirmfun(txd, txid) wl[1] = True return if not wl[2] and res["confirmations"] > 0: log.debug("Tx: " + str(txid) + " has " + str(res["confirmations"]) + " confirmations.") confirmfun(txd, txid, res["confirmations"]) if c <= res["confirmations"]: wl[2] = True #Note we do not stop the monitoring loop when #confirmations occur, since we are also monitoring for spending. return if res["confirmations"] < 0: log.debug("Tx: " + str(txid) + " has a conflict. Abandoning.") wl[0].stop() return if not spentfun or wl[3]: return #To trigger the spent callback, we check if this utxo outpoint appears in #listunspent output with 0 or more confirmations. Note that this requires #we have added the destination address to the watch-only wallet, otherwise #that outpoint will not be returned by listunspent. res2 = self.rpc('listunspent', [0, 999999]) if not res2: return txunspent = False for r in res2: if "txid" not in r: continue if txid == r["txid"] and n == r["vout"]: txunspent = True break if not txunspent: #We need to find the transaction which spent this one; #assuming the address was added to the wallet, then this #transaction must be in the recent list retrieved via listunspent. #For each one, use gettransaction to check its inputs. #This is a bit expensive, but should only occur once. txlist = self.rpc("listtransactions", ["*", 1000, 0, True]) for tx in txlist[::-1]: #changed syntax in 0.14.0; allow both syntaxes try: res = self.rpc("gettransaction", [tx["txid"], True]) except: try: res = self.rpc("gettransaction", [tx["txid"], 1]) except: #This should never happen (gettransaction is a wallet rpc). log.info("Failed any gettransaction call") res = None if not res: continue deser = self.get_deser_from_gettransaction(res) if deser is None: continue for vin in deser["ins"]: if not "outpoint" in vin: #coinbases continue if vin["outpoint"]["hash"] == txid and vin["outpoint"][ "index"] == n: #recover the deserialized form of the spending transaction. log.info("We found a spending transaction: " + \ btc.txhash(binascii.unhexlify(res["hex"]))) res2 = self.rpc("gettransaction", [tx["txid"], True]) spending_deser = self.get_deser_from_gettransaction( res2) if not spending_deser: log.info( "ERROR: could not deserialize spending tx.") #Should never happen, it's a parsing bug. #No point continuing to monitor, we just hope we #can extract the secret by scanning blocks. wl[3] = True return spentfun(spending_deser, vin["outpoint"]["hash"]) wl[3] = True return
def add_tx_notify(self, txd, unconfirmfun, confirmfun, notifyaddr, wallet_name=None, timeoutfun=None, spentfun=None, txid_flag=True, n=0, c=1, vb=None): """Given a deserialized transaction txd, callback functions for broadcast and confirmation of the transaction, an address to import, and a callback function for timeout, set up a polling loop to check for events on the transaction. Also optionally set to trigger "confirmed" callback on number of confirmations c. Also checks for spending (if spentfun is not None) of the outpoint n. If txid_flag is True, we create a watcher loop on the txid (hence only really usable in a segwit context, and only on fully formed transactions), else we create a watcher loop on the output set of the transaction (taken from the outs field of the txd). """ if not vb: vb = get_p2pk_vbyte() if isinstance(self, BitcoinCoreInterface) or isinstance( self, RegtestBitcoinCoreInterface): #This code ensures that a walletnotify is triggered, by #ensuring that at least one of the output addresses is #imported into the wallet (note the sweep special case, where #none of the output addresses belong to me). one_addr_imported = False for outs in txd['outs']: addr = btc.script_to_address(outs['script'], vb) if self.rpc('getaccount', [addr]) != '': one_addr_imported = True break if not one_addr_imported: self.rpc('importaddress', [notifyaddr, 'joinmarket-notify', False]) #Warning! In case of txid_flag false, this is *not* a valid txid, #but only a hash of an incomplete transaction serialization. txid = btc.txhash(btc.serialize(txd)) if not txid_flag: tx_output_set = set([(sv['script'], sv['value']) for sv in txd['outs']]) loop = task.LoopingCall(self.outputs_watcher, wallet_name, notifyaddr, tx_output_set, unconfirmfun, confirmfun, timeoutfun) log.debug("Created watcher loop for address: " + notifyaddr) loopkey = notifyaddr else: loop = task.LoopingCall(self.tx_watcher, txd, unconfirmfun, confirmfun, spentfun, c, n) log.debug("Created watcher loop for txid: " + txid) loopkey = txid self.tx_watcher_loops[loopkey] = [loop, False, False, False] #Hardcoded polling interval, but in any case it can be very short. loop.start(5.0) #Give up on un-broadcast transactions and broadcast but not confirmed #transactions as per settings in the config. reactor.callLater( float(jm_single().config.get("TIMEOUT", "unconfirm_timeout_sec")), self.tx_network_timeout, loopkey) confirm_timeout_sec = int(jm_single().config.get( "TIMEOUT", "confirm_timeout_hours")) * 3600 reactor.callLater(confirm_timeout_sec, self.tx_timeout, txd, loopkey, timeoutfun)
def add_signature(self, nick, sigb64): if nick not in self.nonrespondants: log.debug( ('add_signature => nick={} ' 'not in nonrespondants {}').format(nick, self.nonrespondants)) return sig = base64.b64decode(sigb64).encode('hex') inserted_sig = False txhex = btc.serialize(self.latest_tx) # batch retrieval of utxo data utxo = {} ctr = 0 for index, ins in enumerate(self.latest_tx['ins']): utxo_for_checking = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if (ins['script'] != '' or utxo_for_checking in self.input_utxos.keys()): continue utxo[ctr] = [index, utxo_for_checking] ctr += 1 utxo_data = jm_single().bc_interface.query_utxo_set( [x[1] for x in utxo.values()]) # insert signatures for i, u in utxo.iteritems(): if utxo_data[i] is None: continue sig_good = btc.verify_tx_input(txhex, u[0], utxo_data[i]['script'], *btc.deserialize_script(sig)) if sig_good: log.debug('found good sig at index=%d' % (u[0])) self.latest_tx['ins'][u[0]]['script'] = sig inserted_sig = True # check if maker has sent everything possible self.utxos[nick].remove(u[1]) if len(self.utxos[nick]) == 0: log.debug(('nick = {} sent all sigs, removing from ' 'nonrespondant list').format(nick)) self.nonrespondants.remove(nick) break if not inserted_sig: log.debug('signature did not match anything in the tx') # TODO what if the signature doesnt match anything # nothing really to do except drop it, carry on and wonder why the # other guy sent a failed signature tx_signed = True for ins in self.latest_tx['ins']: if ins['script'] == '': tx_signed = False if not tx_signed: return self.end_timeout_thread = True self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() log.debug('all makers have sent their signatures') for index, ins in enumerate(self.latest_tx['ins']): # remove placeholders if ins['script'] == 'deadbeef': ins['script'] = '' if self.finishcallback is not None: log.debug("About to call finishcallback") self.finishcallback(self)
def add_signature(self, nick, sigb64): if nick not in self.nonrespondants: log.debug(('add_signature => nick={} ' 'not in nonrespondants {}').format( nick, self.nonrespondants)) return sig = base64.b64decode(sigb64).encode('hex') inserted_sig = False txhex = btc.serialize(self.latest_tx) # batch retrieval of utxo data utxo = {} ctr = 0 for index, ins in enumerate(self.latest_tx['ins']): utxo_for_checking = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if (ins['script'] != '' or utxo_for_checking in self.input_utxos.keys()): continue utxo[ctr] = [index, utxo_for_checking] ctr += 1 utxo_data = jm_single().bc_interface.query_utxo_set( [x[1] for x in utxo.values()]) # insert signatures for i, u in utxo.iteritems(): if utxo_data[i] is None: continue sig_good = btc.verify_tx_input( txhex, u[0], utxo_data[i]['script'], *btc.deserialize_script(sig)) if sig_good: log.debug('found good sig at index=%d' % (u[0])) self.latest_tx['ins'][u[0]]['script'] = sig inserted_sig = True # check if maker has sent everything possible self.utxos[nick].remove(u[1]) if len(self.utxos[nick]) == 0: log.debug(('nick = {} sent all sigs, removing from ' 'nonrespondant list').format(nick)) self.nonrespondants.remove(nick) break if not inserted_sig: log.debug('signature did not match anything in the tx') # TODO what if the signature doesnt match anything # nothing really to do except drop it, carry on and wonder why the # other guy sent a failed signature tx_signed = True for ins in self.latest_tx['ins']: if ins['script'] == '': tx_signed = False if not tx_signed: return self.end_timeout_thread = True self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() log.debug('all makers have sent their signatures') for index, ins in enumerate(self.latest_tx['ins']): # remove placeholders if ins['script'] == 'deadbeef': ins['script'] = '' if self.finishcallback is not None: log.debug("About to call finishcallback") self.finishcallback(self)
def on_sig(self, nick, sigb64): """Processes transaction signatures from counterparties. Returns True if all signatures received correctly, else returns False """ if self.aborted: return False sig = base64.b64decode(sigb64).encode('hex') inserted_sig = False txhex = btc.serialize(self.latest_tx) # batch retrieval of utxo data utxo = {} ctr = 0 for index, ins in enumerate(self.latest_tx['ins']): utxo_for_checking = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) #'deadbeef' markers mean our own input scripts are not '' if (ins['script'] != ''): continue utxo[ctr] = [index, utxo_for_checking] ctr += 1 utxo_data = jm_single().bc_interface.query_utxo_set( [x[1] for x in utxo.values()]) # insert signatures for i, u in utxo.iteritems(): if utxo_data[i] is None: continue #Check if the sender serialize_scripted the witness #item into the sig message; if so, also pick up the amount #from the utxo data retrieved from the blockchain to verify #the segwit-style signature. Note that this allows a mixed #SW/non-SW transaction as each utxo is interpreted separately. sig_deserialized = btc.deserialize_script(sig) if len(sig_deserialized) == 2: ver_sig, ver_pub = sig_deserialized wit = None elif len(sig_deserialized) == 3: ver_sig, ver_pub, wit = sig_deserialized else: jlog.debug("Invalid signature message - more than 3 items") break ver_amt = utxo_data[i]['value'] if wit else None sig_good = btc.verify_tx_input(txhex, u[0], utxo_data[i]['script'], ver_sig, ver_pub, witness=wit, amount=ver_amt) if sig_good: jlog.debug('found good sig at index=%d' % (u[0])) if wit: self.latest_tx["ins"][u[0]]["txinwitness"] = [ ver_sig, ver_pub ] self.latest_tx["ins"][u[0]]["script"] = "16" + wit else: self.latest_tx["ins"][u[0]]["script"] = sig inserted_sig = True # check if maker has sent everything possible self.utxos[nick].remove(u[1]) if len(self.utxos[nick]) == 0: jlog.debug(('nick = {} sent all sigs, removing from ' 'nonrespondant list').format(nick)) self.nonrespondants.remove(nick) break if not inserted_sig: jlog.debug('signature did not match anything in the tx') # TODO what if the signature doesnt match anything # nothing really to do except drop it, carry on and wonder why the # other guy sent a failed signature tx_signed = True for ins in self.latest_tx['ins']: if ins['script'] == '': tx_signed = False if not tx_signed: return False assert not len(self.nonrespondants) jlog.info('all makers have sent their signatures') self.taker_info_callback("INFO", "Transaction is valid, signing..") jlog.debug("schedule item was: " + str(self.schedule[self.schedule_index])) return self.self_sign_and_push()