示例#1
0
 def include_signature(self, in_index, cp, sig):
     """For receiving counterparty signatures, either
     on promise inputs or NN multisigs. If valid,
     mark that index as completed if appropriate,
     and return True. If invalid, return False.
     """
     tp = self.template.ins[in_index].spk_type
     pub = self.keys["ins"][in_index][cp]
     if tp == "NN":
         if len(self.signatures[in_index]) == 0:
             self.signatures[in_index] = [None] * self.n_counterparties
         sigform = self.signature_form(in_index)
         if not btc.verify_tx_input(self.base_form,
                                    in_index,
                                    self.signing_redeem_scripts[in_index],
                                    sig,
                                    self.keys["ins"][in_index][cp],
                                    witness="deadbeef",
                                    amount=self.ins[in_index][1]):
             cjxtlog.info(
                 "Error in include_signature: signature invalid: " + sig)
             return False
         else:
             self.signatures[in_index][cp] = sig
             if all([
                     self.signatures[in_index][x]
                     for x in range(self.n_counterparties)
             ]):
                 self.completed[in_index] = True
             return True
     elif tp == "p2sh-p2wpkh":
         #counterparty's promise signature
         #verification check
         scriptCode = "76a914" + btc.hash160(
             binascii.unhexlify(pub)) + "88ac"
         if not btc.verify_tx_input(self.base_form,
                                    in_index,
                                    scriptCode,
                                    sig,
                                    pub,
                                    witness="deadbeef",
                                    amount=self.ins[in_index][1]):
             cjxtlog.info(
                 "Error in include_signature: signature invalid: " + sig)
             return False
         else:
             self.signatures[in_index] = [sig]
             self.completed[in_index] = True
             return True
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
示例#3
0
def test_spend_p2wsh(setup_tx_creation):
    #make 2 x 2 of 2multisig outputs; will need 4 privs
    privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 5)]
    privs = [binascii.hexlify(priv).decode('ascii') for priv in privs]
    pubs = [bitcoin.privkey_to_pubkey(priv) for priv in privs]
    redeemScripts = [
        bitcoin.mk_multisig_script(pubs[i:i + 2], 2) for i in [0, 2]
    ]
    scriptPubKeys = [
        bitcoin.pubkeys_to_p2wsh_script(pubs[i:i + 2]) for i in [0, 2]
    ]
    addresses = [
        bitcoin.pubkeys_to_p2wsh_address(pubs[i:i + 2]) for i in [0, 2]
    ]
    #pay into it
    wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet']
    wallet_service.sync_wallet(fast=True)
    amount = 35000000
    p2wsh_ins = []
    for addr in addresses:
        ins_full = wallet_service.select_utxos(0, amount)
        txid = make_sign_and_push(ins_full,
                                  wallet_service,
                                  amount,
                                  output_addr=addr)
        assert txid
        p2wsh_ins.append(txid + ":0")
        #wait for mining
        time.sleep(1)
    #random output address and change addr
    output_addr = wallet_service.get_internal_addr(1)
    amount2 = amount * 2 - 50000
    outs = [{'value': amount2, 'address': output_addr}]
    tx = bitcoin.mktx(p2wsh_ins, outs)
    sigs = []
    for i in range(2):
        sigs = []
        for priv in privs[i * 2:i * 2 + 2]:
            # sign input j with each of 2 keys
            sig = bitcoin.multisign(tx,
                                    i,
                                    redeemScripts[i],
                                    priv,
                                    amount=amount)
            sigs.append(sig)
            # check that verify_tx_input correctly validates;
            assert bitcoin.verify_tx_input(tx,
                                           i,
                                           scriptPubKeys[i],
                                           sig,
                                           bitcoin.privkey_to_pubkey(priv),
                                           scriptCode=redeemScripts[i],
                                           amount=amount)
        tx = bitcoin.apply_p2wsh_multisignatures(tx, i, redeemScripts[i], sigs)
    txid = jm_single().bc_interface.pushtx(tx)
    assert txid
示例#4
0
def test_spend_p2wpkh(setup_tx_creation):
    #make 3 p2wpkh outputs from 3 privs
    privs = [struct.pack(b'B', x) * 32 + b'\x01' for x in range(1, 4)]
    pubs = [
        bitcoin.privkey_to_pubkey(binascii.hexlify(priv).decode('ascii'))
        for priv in privs
    ]
    scriptPubKeys = [bitcoin.pubkey_to_p2wpkh_script(pub) for pub in pubs]
    addresses = [bitcoin.pubkey_to_p2wpkh_address(pub) for pub in pubs]
    #pay into it
    wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet']
    wallet_service.sync_wallet(fast=True)
    amount = 35000000
    p2wpkh_ins = []
    for addr in addresses:
        ins_full = wallet_service.select_utxos(0, amount)
        txid = make_sign_and_push(ins_full,
                                  wallet_service,
                                  amount,
                                  output_addr=addr)
        assert txid
        p2wpkh_ins.append(txid + ":0")
        #wait for mining
        time.sleep(1)
    #random output address
    output_addr = wallet_service.get_internal_addr(1)
    amount2 = amount * 3 - 50000
    outs = [{'value': amount2, 'address': output_addr}]
    tx = bitcoin.mktx(p2wpkh_ins, outs)
    sigs = []
    for i, priv in enumerate(privs):
        # sign each of 3 inputs
        tx = bitcoin.p2wpkh_sign(tx,
                                 i,
                                 binascii.hexlify(priv),
                                 amount,
                                 native=True)
        # check that verify_tx_input correctly validates;
        # to do this, we need to extract the signature and get the scriptCode
        # of this pubkey
        scriptCode = bitcoin.pubkey_to_p2pkh_script(pubs[i])
        witness = bitcoin.deserialize(tx)['ins'][i]['txinwitness']
        assert len(witness) == 2
        assert witness[1] == pubs[i]
        sig = witness[0]
        assert bitcoin.verify_tx_input(tx,
                                       i,
                                       scriptPubKeys[i],
                                       sig,
                                       pubs[i],
                                       scriptCode=scriptCode,
                                       amount=amount)
    txid = jm_single().bc_interface.pushtx(tx)
    assert txid
示例#5
0
def test_verify_tx_input(setup_tx_creation, signall, mktxlist):
    priv = "aa" * 32 + "01"
    addr = bitcoin.privkey_to_address(priv, magicbyte=get_p2pk_vbyte())
    wallet = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet']
    sync_wallet(wallet)
    insfull = wallet.select_utxos(0, 110000000)
    print(insfull)
    if not mktxlist:
        outs = [{"address": addr, "value": 1000000}]
        ins = insfull.keys()
        tx = bitcoin.mktx(ins, outs)
    else:
        out1 = addr + ":1000000"
        ins0, ins1 = insfull.keys()
        print("INS0 is: " + str(ins0))
        print("INS1 is: " + str(ins1))
        tx = bitcoin.mktx(ins0, ins1, out1)
    desertx = bitcoin.deserialize(tx)
    print(desertx)
    if signall:
        privdict = {}
        for index, ins in enumerate(desertx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            ad = insfull[utxo]['address']
            priv = wallet.get_key_from_addr(ad)
            privdict[utxo] = priv
        tx = bitcoin.signall(tx, privdict)
    else:
        for index, ins in enumerate(desertx['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            ad = insfull[utxo]['address']
            priv = wallet.get_key_from_addr(ad)
            if index % 2:
                tx = binascii.unhexlify(tx)
            tx = bitcoin.sign(tx, index, priv)
            if index % 2:
                tx = binascii.hexlify(tx)
    desertx2 = bitcoin.deserialize(tx)
    print(desertx2)
    sig, pub = bitcoin.deserialize_script(desertx2['ins'][0]['script'])
    print(sig, pub)
    pubscript = bitcoin.address_to_script(
        bitcoin.pubkey_to_address(pub, magicbyte=get_p2pk_vbyte()))
    sig = binascii.unhexlify(sig)
    pub = binascii.unhexlify(pub)
    sig_good = bitcoin.verify_tx_input(tx, 0, pubscript, sig, pub)
    assert sig_good
示例#6
0
def test_spend_freeze_script(setup_tx_creation):
    ensure_bip65_activated()

    wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]], 3)[0]['wallet']
    wallet_service.sync_wallet(fast=True)

    mediantime = jm_single().bc_interface.rpc("getblockchaininfo", [])["mediantime"]

    timeoffset_success_tests = [(2, False), (-60*60*24*30, True), (60*60*24*30, False)]

    for timeoffset, required_success in timeoffset_success_tests:
        #generate keypair
        priv = "aa"*32 + "01"
        pub = unhexlify(bitcoin.privkey_to_pubkey(priv))
        addr_locktime = mediantime + timeoffset
        redeem_script = bitcoin.mk_freeze_script(pub, addr_locktime)
        script_pub_key = bitcoin.redeem_script_to_p2wsh_script(redeem_script)
        regtest_vbyte = 100
        addr = bitcoin.script_to_address(script_pub_key, vbyte=regtest_vbyte)

        #fund frozen funds address
        amount = 100000000
        funding_ins_full = wallet_service.select_utxos(0, amount)
        funding_txid = make_sign_and_push(funding_ins_full, wallet_service, amount, output_addr=addr)
        assert funding_txid

        #spend frozen funds
        frozen_in = funding_txid + ":0"
        output_addr = wallet_service.get_internal_addr(1)
        miner_fee = 5000
        outs = [{'value': amount - miner_fee, 'address': output_addr}]
        tx = bitcoin.mktx([frozen_in], outs, locktime=addr_locktime+1)
        i = 0
        sig = bitcoin.get_p2sh_signature(tx, i, redeem_script, priv, amount)

        assert bitcoin.verify_tx_input(tx, i, script_pub_key, sig, pub,
            scriptCode=redeem_script, amount=amount)
        tx = bitcoin.apply_freeze_signature(tx, i, redeem_script, sig)
        push_success = jm_single().bc_interface.pushtx(tx)

        assert push_success == required_success
示例#7
0
    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()
示例#8
0
    def on_tx_received(self, nick, txhex):
        """ Called when the sender-counterparty has sent a transaction proposal.
        1. First we check for the expected destination and amount (this is
           sufficient to identify our cp, as this info was presumably passed
           out of band, as for any normal payment).
        2. Then we verify the validity of the proposed non-coinjoin
           transaction; if not, reject, otherwise store this as a
           fallback transaction in case the protocol doesn't complete.
        3. Next, we select utxos from our wallet, to add into the
           payment transaction as input. Try to select so as to not
           trigger the UIH2 condition, but continue (and inform user)
           even if we can't (if we can't select any coins, broadcast the
           non-coinjoin payment, if the user agrees).
           Proceeding with payjoin:
        4. We update the output amount at the destination address.
        5. We modify the change amount in the original proposal (which
           will be the only other output other than the destination),
           reducing it to account for the increased transaction fee
           caused by our additional proposed input(s).
        6. Finally we sign our own input utxo(s) and re-serialize the
           tx, allowing it to be sent back to the counterparty.
        7. If the transaction is not fully signed and broadcast within
           the time unconfirm_timeout_sec as specified in the joinmarket.cfg,
           we broadcast the non-coinjoin fallback tx instead.
        """
        try:
            tx = btc.deserialize(txhex)
        except (IndexError, SerializationError,
                SerializationTruncationError) as e:
            return (False, 'malformed txhex. ' + repr(e))
        self.user_info('obtained proposed fallback (non-coinjoin) ' +\
                       'transaction from sender:\n' + pprint.pformat(tx))

        if len(tx["outs"]) != 2:
            return (False,
                    "Transaction has more than 2 outputs; not supported.")
        dest_found = False
        destination_index = -1
        change_index = -1
        proposed_change_value = 0
        for index, out in enumerate(tx["outs"]):
            if out["script"] == btc.address_to_script(self.destination_addr):
                # we found the expected destination; is the amount correct?
                if not out["value"] == self.receiving_amount:
                    return (False,
                            "Wrong payout value in proposal from sender.")
                dest_found = True
                destination_index = index
            else:
                change_found = True
                proposed_change_out = out["script"]
                proposed_change_value = out["value"]
                change_index = index

        if not dest_found:
            return (False, "Our expected destination address was not found.")

        # Verify valid input utxos provided and check their value.
        # batch retrieval of utxo data
        utxo = {}
        ctr = 0
        for index, ins in enumerate(tx['ins']):
            utxo_for_checking = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            utxo[ctr] = [index, utxo_for_checking]
            ctr += 1

        utxo_data = jm_single().bc_interface.query_utxo_set(
            [x[1] for x in utxo.values()])

        total_sender_input = 0
        for i, u in iteritems(utxo):
            if utxo_data[i] is None:
                return (False, "Proposed transaction contains invalid utxos")
            total_sender_input += utxo_data[i]["value"]

        # Check that the transaction *as proposed* balances; check that the
        # included fee is within 0.3-3x our own current estimates, if not user
        # must decide.
        btc_fee = total_sender_input - self.receiving_amount - proposed_change_value
        self.user_info("Network transaction fee of fallback tx is: " +
                       str(btc_fee) + " satoshis.")
        fee_est = estimate_tx_fee(len(tx['ins']),
                                  len(tx['outs']),
                                  txtype=self.wallet.get_txtype())
        fee_ok = False
        if btc_fee > 0.3 * fee_est and btc_fee < 3 * fee_est:
            fee_ok = True
        else:
            if self.user_check("Is this transaction fee acceptable? (y/n):"):
                fee_ok = True
        if not fee_ok:
            return (False,
                    "Proposed transaction fee not accepted due to tx fee: " +
                    str(btc_fee))

        # This direct rpc call currently assumes Core 0.17, so not using now.
        # It has the advantage of (a) being simpler and (b) allowing for any
        # non standard coins.
        #
        #res = jm_single().bc_interface.rpc('testmempoolaccept', [txhex])
        #print("Got this result from rpc call: ", res)
        #if not res["accepted"]:
        #    return (False, "Proposed transaction was rejected from mempool.")

        # Manual verification of the transaction signatures. Passing this
        # test does imply that the transaction is valid (unless there is
        # a double spend during the process), but is restricted to standard
        # types: p2pkh, p2wpkh, p2sh-p2wpkh only. Double spend is not counted
        # as a risk as this is a payment.
        for i, u in iteritems(utxo):
            if "txinwitness" in tx["ins"][u[0]]:
                ver_amt = utxo_data[i]["value"]
                try:
                    ver_sig, ver_pub = tx["ins"][u[0]]["txinwitness"]
                except Exception as e:
                    self.user_info("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:
                scriptCode = None
                ver_amt = None
                scriptSig = btc.deserialize_script(tx["ins"][u[0]]["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,
                                       u[0],
                                       utxo_data[i]['script'],
                                       ver_sig,
                                       ver_pub,
                                       scriptCode=scriptCode,
                                       amount=ver_amt):
                return (False, "Proposed transaction is not correctly signed.")

        # At this point we are satisfied with the proposal. Record the fallback
        # in case the sender disappears and the payjoin tx doesn't happen:
        self.user_info(
            "We'll use this serialized transaction to broadcast if your"
            " counterparty fails to broadcast the payjoin version:")
        self.user_info(txhex)
        # Keep a local copy for broadcast fallback:
        self.fallback_tx = txhex

        # Now we add our own inputs:
        # See the gist comment here:
        # https://gist.github.com/AdamISZ/4551b947789d3216bacfcb7af25e029e#gistcomment-2799709
        # which sets out the decision Bob must make.
        # In cases where Bob can add any amount, he selects one utxo
        # to keep it simple.
        # In cases where he must choose at least X, he selects one utxo
        # which provides X if possible, otherwise defaults to a normal
        # selection algorithm.
        # In those cases where he must choose X but X is unavailable,
        # he selects all coins, and proceeds anyway with payjoin, since
        # it has other advantages (CIOH and utxo defrag).
        my_utxos = {}
        largest_out = max(self.receiving_amount, proposed_change_value)
        max_sender_amt = max([u['value'] for u in utxo_data])
        not_uih2 = False
        if max_sender_amt < largest_out:
            # just select one coin.
            # have some reasonable lower limit but otherwise choose
            # randomly; note that this is actually a great way of
            # sweeping dust ...
            self.user_info("Choosing one coin at random")
            try:
                my_utxos = self.wallet.select_utxos(self.mixdepth,
                                                    jm_single().DUST_THRESHOLD,
                                                    select_fn=select_one_utxo)
            except:
                return self.no_coins_fallback()
            not_uih2 = True
        else:
            # get an approximate required amount assuming 4 inputs, which is
            # fairly conservative (but guess by necessity).
            fee_for_select = estimate_tx_fee(len(tx['ins']) + 4,
                                             2,
                                             txtype=self.wallet.get_txtype())
            approx_sum = max_sender_amt - self.receiving_amount + fee_for_select
            try:
                my_utxos = self.wallet.select_utxos(self.mixdepth, approx_sum)
                not_uih2 = True
            except Exception:
                # TODO probably not logical to always sweep here.
                self.user_info("Sweeping all coins in this mixdepth.")
                my_utxos = self.wallet.get_utxos_by_mixdepth()[self.mixdepth]
                if my_utxos == {}:
                    return self.no_coins_fallback()
        if not_uih2:
            self.user_info("The proposed tx does not trigger UIH2, which "
                           "means it is indistinguishable from a normal "
                           "payment. This is the ideal case. Continuing..")
        else:
            self.user_info("The proposed tx does trigger UIH2, which it makes "
                           "it somewhat distinguishable from a normal payment,"
                           " but proceeding with payjoin..")

        my_total_in = sum([va['value'] for va in my_utxos.values()])
        self.user_info("We selected inputs worth: " + str(my_total_in))
        # adjust the output amount at the destination based on our contribution
        new_destination_amount = self.receiving_amount + my_total_in
        # estimate the required fee for the new version of the transaction
        total_ins = len(tx["ins"]) + len(my_utxos.keys())
        est_fee = estimate_tx_fee(total_ins,
                                  2,
                                  txtype=self.wallet.get_txtype())
        self.user_info("We estimated a fee of: " + str(est_fee))
        new_change_amount = total_sender_input + my_total_in - \
            new_destination_amount - est_fee
        self.user_info("We calculated a new change amount of: " +
                       str(new_change_amount))
        self.user_info("We calculated a new destination amount of: " +
                       str(new_destination_amount))
        # now reconstruct the transaction with the new inputs and the
        # amount-changed outputs
        new_outs = [{
            "address": self.destination_addr,
            "value": new_destination_amount
        }]
        if new_change_amount >= jm_single().BITCOIN_DUST_THRESHOLD:
            new_outs.append({
                "script": proposed_change_out,
                "value": new_change_amount
            })
        new_ins = [x[1] for x in utxo.values()]
        new_ins.extend(my_utxos.keys())
        # set locktime for best anonset (Core, Electrum) - most recent block.
        # this call should never fail so no catch here.
        currentblock = jm_single().bc_interface.rpc("getblockchaininfo",
                                                    [])["blocks"]
        new_tx = make_shuffled_tx(new_ins, new_outs, False, 2, currentblock)
        new_tx_deser = btc.deserialize(new_tx)

        # sign our inputs before transfer
        our_inputs = {}
        for index, ins in enumerate(new_tx_deser['ins']):
            utxo = ins['outpoint']['hash'] + ':' + str(
                ins['outpoint']['index'])
            if utxo not in my_utxos:
                continue
            script = self.wallet.addr_to_script(my_utxos[utxo]['address'])
            amount = my_utxos[utxo]['value']
            our_inputs[index] = (script, amount)

        txs = self.wallet.sign_tx(btc.deserialize(new_tx), our_inputs)
        jm_single().bc_interface.add_tx_notify(
            txs,
            self.on_tx_unconfirmed,
            self.on_tx_confirmed,
            self.destination_addr,
            wallet_name=jm_single().bc_interface.get_wallet_name(self.wallet),
            txid_flag=False,
            vb=self.wallet._ENGINE.VBYTE)
        # The blockchain interface just abandons monitoring if the transaction
        # is not broadcast before the configured timeout; we want to take
        # action in this case, so we add an additional callback to the reactor:
        reactor.callLater(
            jm_single().config.getint("TIMEOUT", "unconfirm_timeout_sec"),
            self.broadcast_fallback)
        return (True, nick, btc.serialize(txs))
示例#9
0
 def sign_at_index(self, in_index):
     """Signs with our one key corresponding to one input;
     either sole-owned (promise) or NN.
     Segwit assumed; uses only p2sh-p2wpkh for sole-owned,
     p2wsh for co-owned.
     """
     #the pubkey we're signing against:
     pub = self.keys["ins"][in_index][self.n]
     #the wallet holds the keys for p2sh-p2wpkh addresses directly.
     #for p2wsh addresses, we must use the pubkey to construct
     #the corresponding p2sh-p2wpkh address in the wallet to extract
     #the key. This is obviously stupid for a real world design TODO
     addr = self.wallet.pubkey_to_address(pub)
     privkey = self.wallet.get_key_from_addr(addr)
     #check whether we are multi-signing or single-signing:
     tp = self.template.ins[in_index].spk_type
     if tp == "p2sh-p2wpkh":
         #the main (non-multisig) signing algo(s) return a signed
         #tx, not a signature; extract from the temporary tx
         txwithsig = btc.deserialize(
             self.wallet.sign(self.base_form, in_index, privkey,
                              self.ins[in_index][1]))
         #txinwitness field is [sig, pub]
         sig = txwithsig["ins"][in_index]["txinwitness"][0]
         #verification check
         scriptCode = "76a914" + btc.hash160(
             binascii.unhexlify(pub)) + "88ac"
         assert btc.verify_tx_input(self.base_form,
                                    in_index,
                                    scriptCode,
                                    sig,
                                    pub,
                                    witness="deadbeef",
                                    amount=self.ins[in_index][1])
         self.signatures[in_index] = [sig]
         self.completed[in_index] = True
     elif tp == "NN":
         if len(self.signatures[in_index]) == 0:
             self.signatures[in_index] = [None] * self.n_counterparties
         sig = btc.p2wsh_multisign(self.base_form,
                                   in_index,
                                   self.signing_redeem_scripts[in_index],
                                   privkey,
                                   amount=self.ins[in_index][1])
         assert btc.verify_tx_input(self.base_form,
                                    in_index,
                                    self.signing_redeem_scripts[in_index],
                                    sig,
                                    pub,
                                    witness="deadbeef",
                                    amount=self.ins[in_index][1])
         #Note that it's OK to use self.n as the explicit list index
         #here, as we *always* do N of N multisig.
         self.signatures[in_index][self.n] = sig
         if all([
                 self.signatures[in_index][x]
                 for x in range(self.n_counterparties)
         ]):
             self.completed[in_index] = True
     #in some cases, the sig is used by the caller (to send to counterparty)
     return sig