示例#1
0
def create_tx_and_offerlist(cj_addr,
                            cj_change_addr,
                            other_output_scripts,
                            cj_script=None,
                            cj_change_script=None,
                            offertype='swreloffer'):
    assert len(other_output_scripts) % 2 == 0, "bug in test"

    cj_value = 100000000
    maker_total_value = cj_value * 3

    if cj_script is None:
        cj_script = btc.address_to_script(cj_addr)
    if cj_change_script is None:
        cj_change_script = btc.address_to_script(cj_change_addr)

    inputs = create_tx_inputs(3)
    outputs = create_tx_outputs(
        (cj_script, cj_value),
        (cj_change_script, maker_total_value - cj_value),  # cjfee=0, txfee=0
        *((script, cj_value + (i%2)*(50000000+i)) \
            for i, script in enumerate(other_output_scripts))
    )

    maker_utxos = [inputs[0]]

    tx = btc.deserialize(btc.mktx(inputs, outputs))
    offerlist = construct_tx_offerlist(cj_addr, cj_change_addr, maker_utxos,
                                       maker_total_value, cj_value, offertype)

    return tx, offerlist
示例#2
0
def test_mktx(setup_tx_creation):
    """Testing exceptional conditions; not guaranteed
    to create valid tx objects"""
    #outpoint structure must be {"outpoint":{"hash":hash, "index": num}}
    ins = [{
        'outpoint': {
            "hash": x * 32,
            "index": 0
        },
        "script": "",
        "sequence": 4294967295
    } for x in ["a", "b", "c"]]
    pub = vpubs[0]
    addr = bitcoin.pubkey_to_address(pub, magicbyte=get_p2pk_vbyte())
    script = bitcoin.address_to_script(addr)
    outs = [
        script + ":1000", addr + ":2000", {
            "script": script,
            "value": 3000
        }
    ]
    tx = bitcoin.mktx(ins, outs)
    print(tx)
    #rewrite with invalid output
    outs.append({"foo": "bar"})
    with pytest.raises(Exception) as e_info:
        tx = bitcoin.mktx(ins, outs)
示例#3
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
 def query_utxo_set(self, txouts,includeconf=False):
     if self.qusfail:
         #simulate failure to find the utxo
         return [None]
     if self.fake_query_results:
         result = []
         for x in self.fake_query_results:
             for y in txouts:
                 if y == x['utxo']:
                     result.append(x)
         return result
     result = []
     #external maker utxos
     known_outs = {"03243f4a659e278a1333f8308f6aaf32db4692ee7df0340202750fd6c09150f6:1": "03a2d1cbe977b1feaf8d0d5cc28c686859563d1520b28018be0c2661cf1ebe4857",
                   "498faa8b22534f3b443c6b0ce202f31e12f21668b4f0c7a005146808f250d4c3:0": "02b4b749d54e96b04066b0803e372a43d6ffa16e75a001ae0ed4b235674ab286be",
                   "3f3ea820d706e08ad8dc1d2c392c98facb1b067ae4c671043ae9461057bd2a3c:1": "023bcbafb4f68455e0d1d117c178b0e82a84e66414f0987453d78da034b299c3a9"}
     #our wallet utxos, faked, for podle tests: utxos are doctored (leading 'f'),
     #and the lists are (amt, age)
     wallet_outs = {'f34b635ed8891f16c4ec5b8236ae86164783903e8e8bb47fa9ef2ca31f3c2d7a:0': [10000000, 2],
                    'f780d6e5e381bff01a3519997bb4fcba002493103a198fde334fd264f9835d75:1': [20000000, 6],
                    'fe574db96a4d43a99786b3ea653cda9e4388f377848f489332577e018380cff1:0': [50000000, 3],
                    'fd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': [50000000, 6]}
     
     if includeconf and set(txouts).issubset(set(wallet_outs)):
         #includeconf used as a trigger for a podle check;
         #here we simulate a variety of amount/age returns
         results = []
         for to in txouts:
             results.append({'value': wallet_outs[to][0],
                             'confirms': wallet_outs[to][1]})
         return results
     if txouts[0] in known_outs:
         addr = btc.pubkey_to_p2sh_p2wpkh_address(
                     known_outs[txouts[0]], get_p2sh_vbyte())
         return [{'value': 200000000,
                  'address': addr,
                  'script': btc.address_to_script(addr),
                  'confirms': 20}]
     for t in txouts:
         result_dict = {'value': 10000000000,
                        'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ",
                        'script': '76a91479b000887626b294a914501a4cd226b58b23598388ac'}
         if includeconf:
             result_dict['confirms'] = 20
         result.append(result_dict)        
     return result
    def query_utxo_set(self, txout, includeconf=False):
        """Behaves as for Core; TODO make it faster if possible.
        Note in particular a failed connection should result in
        a result list containing at least one "None" which the
        caller can use as a flag for failure.
	"""
        self.current_height = self.wallet.network.blockchain.local_height
        if not isinstance(txout, list):
            txout = [txout]
        utxos = [[t[:64], int(t[65:])] for t in txout]
        result = []
        for ut in utxos:
            address = self.wallet.network.synchronous_get(
                ('blockchain.utxo.get_address', ut))
            try:
                utxo_info = self.wallet.network.synchronous_get(
                    ("blockchain.address.listunspent", [address]))
            except Exception as e:
                log.debug("Got exception calling listunspent: " + repr(e))
                raise
            utxo = None
            for u in utxo_info:
                if u['tx_hash'] == ut[0] and u['tx_pos'] == ut[1]:
                    utxo = u
            if utxo is None:
                result.append(None)
                continue
            r = {
                'value': u['value'],
                'address': address,
                'script': btc.address_to_script(address)
            }
            if includeconf:
                if int(u['height']) in [0, -1]:
                    #-1 means unconfirmed inputs
                    r['confirms'] = 0
                else:
                    #+1 because if current height = tx height, that's 1 conf
                    r['confirms'] = int(self.current_height) - int(
                        u['height']) + 1
            result.append(r)
        return result
 def query_utxo_set(self, txout, includeconf=False):
     self.current_height = self.get_from_electrum(
         "blockchain.numblocks.subscribe", blocking=True)['result']
     if not isinstance(txout, list):
         txout = [txout]
     utxos = [[t[:64], int(t[65:])] for t in txout]
     result = []
     for ut in utxos:
         address = self.get_from_electrum("blockchain.utxo.get_address",
                                          ut,
                                          blocking=True)['result']
         utxo_info = self.get_from_electrum(
             "blockchain.address.listunspent", address,
             blocking=True)['result']
         utxo = None
         for u in utxo_info:
             if u['tx_hash'] == ut[0] and u['tx_pos'] == ut[1]:
                 utxo = u
         if utxo is None:
             result.append(None)
         else:
             r = {
                 'value': utxo['value'],
                 'address': address,
                 'script': btc.address_to_script(address)
             }
             if includeconf:
                 if int(utxo['height']) in [0, -1]:
                     #-1 means unconfirmed inputs
                     r['confirms'] = 0
                 else:
                     #+1 because if current height = tx height, that's 1 conf
                     r['confirms'] = int(self.current_height) - int(
                         utxo['height']) + 1
             result.append(r)
     return result
示例#7
0
    def send_tx1id_tx2_sig_tx3_sig(self):
        our_tx2_sig = self.tx2.signatures[0][1]

        #**CONSTRUCT TX1**
        #This call can throw insufficient funds; handled by backout.
        #But, this should be avoided (see handshake). At least, any
        #throw here will not cause fees for client.
        print('wallet used coins is: ', self.wallet.used_coins)
        self.initial_utxo_inputs = self.wallet.select_utxos(0,
                                    self.coinswap_parameters.tx1_amount,
                                    utxo_filter=self.wallet.used_coins)
        #Lock these coins; only unlock if there is a pre-funding backout.
        self.wallet.used_coins.extend(self.initial_utxo_inputs.keys())
        total_in = sum([x['value'] for x in self.initial_utxo_inputs.values()])
        self.signing_privkeys = []
        for i, v in enumerate(self.initial_utxo_inputs.values()):
            privkey = self.wallet.get_key_from_addr(v['address'])
            if not privkey:
                raise CoinSwapException("Failed to get key to sign TX1")
            self.signing_privkeys.append(privkey)
        signing_pubkeys = [[btc.privkey_to_pubkey(x)] for x in self.signing_privkeys]
        signing_redeemscripts = [btc.address_to_script(
            x['address']) for x in self.initial_utxo_inputs.values()]
        change_amount = total_in - self.coinswap_parameters.tx1_amount - \
            self.coinswap_parameters.bitcoin_fee
        cslog.debug("got tx1 change amount: " + str(change_amount))
        #get a change address in same mixdepth
        change_address = self.wallet.get_internal_addr(0)
        self.tx1 = CoinSwapTX01.from_params(
            self.coinswap_parameters.pubkeys["key_2_2_CB_0"],
                                self.coinswap_parameters.pubkeys["key_2_2_CB_1"],
                                utxo_ins=self.initial_utxo_inputs.keys(),
                                signing_pubkeys=signing_pubkeys,
                                signing_redeem_scripts=signing_redeemscripts,
                                output_amount=self.coinswap_parameters.tx1_amount,
                                change_address=change_address,
                                change_amount=change_amount)
        #sign and hold signature, recover txid
        self.tx1.signall(self.signing_privkeys)
        self.tx1.attach_signatures()
        self.tx1.set_txid()
        cslog.info("Carol created and signed TX1:")
        cslog.info(self.tx1)
        #**CONSTRUCT TX3**
        utxo_in = self.tx1.txid + ":"+str(self.tx1.pay_out_index)
        self.tx3 = CoinSwapTX23.from_params(
            self.coinswap_parameters.pubkeys["key_2_2_CB_0"],
                self.coinswap_parameters.pubkeys["key_2_2_CB_1"],
                self.coinswap_parameters.pubkeys["key_TX3_secret"],
                utxo_in=utxo_in,
                recipient_amount=self.coinswap_parameters.tx3_amounts["script"],
                hashed_secret=self.hashed_secret,
                absolutelocktime=self.coinswap_parameters.timeouts["LOCK1"],
                refund_pubkey=self.coinswap_parameters.pubkeys["key_TX3_lock"],
        carol_only_address=self.coinswap_parameters.output_addresses["tx3_carol_address"],
        carol_only_amount=self.coinswap_parameters.tx3_amounts["carol"])
        #create our signature on TX3
        self.tx3.sign_at_index(self.keyset["key_2_2_CB_0"][0], 0)
        our_tx3_sig = self.tx3.signatures[0][0]
        cslog.info("Carol now has partially signed TX3:")
        cslog.info(self.tx3)
        return ([self.tx1.txid + ":" + str(self.tx1.pay_out_index),
                our_tx2_sig, our_tx3_sig], "OK")
示例#8
0
 def send_tx0id_hx_tx2sig(self):
     """Create coinswap secret, create TX0 paying into 2 of 2 AC,
     use the utxo/txid:n of it to create TX2, sign it, and send the hash,
     the tx2 sig and the utxo to Carol.
     """
     self.secret, self.hashed_secret = get_coinswap_secret()
     #**CONSTRUCT TX0**
     #precompute the entirely signed transaction, so as to pass the txid
     self.initial_utxo_inputs = self.wallet.select_utxos(
         0, self.coinswap_parameters.tx0_amount)
     total_in = sum([x['value'] for x in self.initial_utxo_inputs.values()])
     self.signing_privkeys = []
     for i, v in enumerate(self.initial_utxo_inputs.values()):
         privkey = self.wallet.get_key_from_addr(v['address'])
         if not privkey:
             raise CoinSwapException("Failed to get key to sign TX0")
         self.signing_privkeys.append(privkey)
     signing_pubkeys = [[btc.privkey_to_pubkey(x)]
                        for x in self.signing_privkeys]
     signing_redeemscripts = [
         btc.address_to_script(x['address'])
         for x in self.initial_utxo_inputs.values()
     ]
     change_amount = total_in - self.coinswap_parameters.tx0_amount - \
         self.coinswap_parameters.bitcoin_fee
     cslog.debug("got tx0 change amount: " + str(change_amount))
     #get a change address in same mixdepth
     change_address = self.wallet.get_internal_addr(0)
     self.tx0 = CoinSwapTX01.from_params(
         self.coinswap_parameters.pubkeys["key_2_2_AC_0"],
         self.coinswap_parameters.pubkeys["key_2_2_AC_1"],
         utxo_ins=self.initial_utxo_inputs,
         signing_pubkeys=signing_pubkeys,
         signing_redeem_scripts=signing_redeemscripts,
         output_amount=self.coinswap_parameters.tx0_amount,
         change_address=change_address,
         change_amount=change_amount,
         segwit=True)
     #sign and hold signature, recover txid
     self.tx0.signall(self.signing_privkeys)
     self.tx0.attach_signatures()
     self.tx0.set_txid()
     cslog.info("Alice created and signed TX0:")
     cslog.info(self.tx0)
     #**CONSTRUCT TX2**
     #Input is outpoint from TX0
     utxo_in = self.tx0.txid + ":" + str(self.tx0.pay_out_index)
     self.tx2 = CoinSwapTX23.from_params(
         self.coinswap_parameters.pubkeys["key_2_2_AC_0"],
         self.coinswap_parameters.pubkeys["key_2_2_AC_1"],
         self.coinswap_parameters.pubkeys["key_TX2_secret"],
         utxo_in=utxo_in,
         recipient_amount=self.coinswap_parameters.tx2_amounts["script"],
         hashed_secret=self.hashed_secret,
         absolutelocktime=self.coinswap_parameters.timeouts["LOCK0"],
         refund_pubkey=self.coinswap_parameters.pubkeys["key_TX2_lock"],
         carol_only_address=self.coinswap_parameters.
         output_addresses["tx2_carol_address"],
         carol_only_amount=self.coinswap_parameters.tx2_amounts["carol"])
     #Create our own signature for TX2
     self.tx2.sign_at_index(self.keyset["key_2_2_AC_0"][0], 0)
     sigtx2 = self.tx2.signatures[0][0]
     self.send(self.tx0.txid + ":" + str(self.tx0.pay_out_index),
               self.hashed_secret, sigtx2)
     return (True, "TX0id, H(X), TX2 sig sent OK")
示例#9
0
 def address_to_script(addr):
     return unhexlify(btc.address_to_script(addr))
示例#10
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))
示例#11
0
    def verify_unsigned_tx(self, txd, offerinfo):
        """This code is security-critical.
        Before signing the transaction the Maker must ensure
        that all details are as expected, and most importantly
        that it receives the exact number of coins to expected
        in total. The data is taken from the offerinfo dict and
        compared with the serialized txhex.
        """
        tx_utxo_set = set(ins['outpoint']['hash'] + ':' +
                          str(ins['outpoint']['index']) for ins in txd['ins'])

        utxos = offerinfo["utxos"]
        cjaddr = offerinfo["cjaddr"]
        cjaddr_script = btc.address_to_script(cjaddr)
        changeaddr = offerinfo["changeaddr"]
        changeaddr_script = btc.address_to_script(changeaddr)
        #Note: this value is under the control of the Taker,
        #see comment below.
        amount = offerinfo["amount"]
        cjfee = offerinfo["offer"]["cjfee"]
        txfee = offerinfo["offer"]["txfee"]
        ordertype = offerinfo["offer"]["ordertype"]
        my_utxo_set = set(utxos.keys())
        if not tx_utxo_set.issuperset(my_utxo_set):
            return (False, 'my utxos are not contained')

        #The three lines below ensure that the Maker receives
        #back what he puts in, minus his bitcointxfee contribution,
        #plus his expected fee. These values are fully under
        #Maker control so no combination of messages from the Taker
        #can change them.
        #(mathematically: amount + expected_change_value is independent
        #of amount); there is not a (known) way for an attacker to
        #alter the amount (note: !fill resubmissions *overwrite*
        #the active_orders[dict] entry in daemon), but this is an
        #extra layer of safety.
        my_total_in = sum([va['value'] for va in utxos.values()])
        real_cjfee = calc_cj_fee(ordertype, cjfee, amount)
        expected_change_value = (my_total_in - amount - txfee + real_cjfee)
        jlog.info('potentially earned = {}'.format(real_cjfee - txfee))
        jlog.info('mycjaddr, mychange = {}, {}'.format(cjaddr, changeaddr))

        #The remaining checks are needed to ensure
        #that the coinjoin and change addresses occur
        #exactly once with the required amts, in the output.
        times_seen_cj_addr = 0
        times_seen_change_addr = 0
        for outs in txd['outs']:
            if outs['script'] == cjaddr_script:
                times_seen_cj_addr += 1
                if outs['value'] != amount:
                    return (False, 'Wrong cj_amount. I expect ' + str(amount))
            if outs['script'] == changeaddr_script:
                times_seen_change_addr += 1
                if outs['value'] != expected_change_value:
                    return (False, 'wrong change, i expect ' +
                            str(expected_change_value))
        if times_seen_cj_addr != 1 or times_seen_change_addr != 1:
            fmt = ('cj or change addr not in tx '
                   'outputs once, #cjaddr={}, #chaddr={}').format
            return (False, (fmt(times_seen_cj_addr, times_seen_change_addr)))
        return (True, None)