def test_verify_tx_input(setup_tx_creation): priv = b"\xaa" * 32 + b"\x01" pub = bitcoin.privkey_to_pubkey(priv) script = bitcoin.pubkey_to_p2sh_p2wpkh_script(pub) addr = str(bitcoin.CCoinAddress.from_scriptPubKey(script)) wallet_service = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) insfull = wallet_service.select_utxos(0, 110000000) outs = [{"address": addr, "value": 1000000}] ins = list(insfull.keys()) tx = bitcoin.mktx(ins, outs) scripts = {0: (insfull[ins[0]]["script"], bitcoin.coins_to_satoshi(1))} success, msg = wallet_service.sign_tx(tx, scripts) assert success, msg # testing Joinmarket's ability to verify transaction inputs # of others: pretend we don't have a wallet owning the transaction, # and instead verify an input using the (sig, pub, scriptCode) data # that is sent by counterparties: cScrWit = tx.wit.vtxinwit[0].scriptWitness sig = cScrWit.stack[0] pub = cScrWit.stack[1] scriptSig = tx.vin[0].scriptSig tx2 = bitcoin.mktx(ins, outs) res = bitcoin.verify_tx_input(tx2, 0, scriptSig, bitcoin.pubkey_to_p2wpkh_script(pub), amount=bitcoin.coins_to_satoshi(1), witness=bitcoin.CScriptWitness([sig, pub])) assert res
def on_sig(self, nick, sigb64): """Processes transaction signatures from counterparties. If all signatures received correctly, returns the result 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: return False if nick not in self.nonrespondants: jlog.debug( ('add_signature => nick={} ' 'not in nonrespondants {}').format(nick, self.nonrespondants)) return False sig = base64.b64decode(sigb64) inserted_sig = False # batch retrieval of utxo data utxo = {} ctr = 0 for index, ins in enumerate(self.latest_tx.vin): if self._is_our_input(ins) or ins.scriptSig != b"": continue utxo_for_checking = (ins.prevout.hash[::-1], ins.prevout.n) 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.items(): if utxo_data[i] is None: continue # Check if the sender included the scriptCode in 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. try: sig_deserialized = [a for a in iter(btc.CScript(sig))] except Exception as e: jlog.debug("Failed to parse junk sig message, ignoring.") 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") break # The second case here is kept for backwards compatibility. if len(sig_deserialized) == 2: ver_sig, ver_pub = sig_deserialized elif len(sig_deserialized) == 3: ver_sig, ver_pub, _ = sig_deserialized else: jlog.debug("Invalid signature message - not 2 or 3 items") break scriptPubKey = btc.CScript(utxo_data[i]['script']) is_witness_input = scriptPubKey.is_p2sh( ) or scriptPubKey.is_witness_v0_keyhash() ver_amt = utxo_data[i]['value'] if is_witness_input else None witness = btc.CScriptWitness([ver_sig, ver_pub ]) if is_witness_input else None # don't attempt to parse `pub` as pubkey unless it's valid. if scriptPubKey.is_p2sh(): try: s = btc.pubkey_to_p2wpkh_script(ver_pub) except: jlog.debug( "Junk signature message, invalid pubkey, ignoring.") break if scriptPubKey.is_witness_v0_keyhash(): scriptSig = btc.CScript(b'') elif scriptPubKey.is_p2sh(): scriptSig = btc.CScript([s]) else: scriptSig = btc.CScript([ver_sig, ver_pub]) sig_good = btc.verify_tx_input(self.latest_tx, u[0], scriptSig, scriptPubKey, amount=ver_amt, witness=witness) if sig_good: 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 is_witness_input: self.latest_tx.wit.vtxinwit[u[0]] = btc.CTxInWitness( btc.CScriptWitness(witness)) inserted_sig = True # check if maker has sent everything possible try: self.utxos[nick].remove(u[1]) except ValueError: pass if len(self.utxos[nick]) == 0: jlog.debug(('nick = {} sent all sigs, removing from ' 'nonrespondant list').format(nick)) try: self.nonrespondants.remove(nick) except ValueError: pass 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, witness in zip(self.latest_tx.vin, self.latest_tx.wit.vtxinwit): if ins.scriptSig == b"" \ and not self._is_our_input(ins) \ and witness == btc.CTxInWitness(btc.CScriptWitness([])): 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()