Ejemplo n.º 1
0
def test_podle_constructor(setup_podle):
    """Tests rules about construction of PoDLE object
    are conformed to.
    """
    priv  = "aa"*32
    #pub and priv together not allowed
    with pytest.raises(PoDLEError) as e_info:
        p = PoDLE(priv=priv, P="dummypub")
    #no pub or priv is allowed, i forget if this is useful for something
    p = PoDLE()
    #create from priv
    p = PoDLE(priv=priv+"01", u="dummyutxo")
    pdict = p.generate_podle(2)
    assert all([k in pdict for k in ['used', 'utxo', 'P', 'P2', 'commit', 'sig', 'e']])
    #using the valid data, serialize/deserialize test
    deser = p.deserialize_revelation(p.serialize_revelation())
    assert all([deser[x] == pdict[x] for x in ['utxo', 'P', 'P2', 'sig', 'e']])
    #deserialization must fail for wrong number of items
    with pytest.raises(PoDLEError) as e_info:
        p.deserialize_revelation(':'.join([str(x) for x in range(4)]), separator=':')
    #reveal() must work without pre-generated commitment
    p.commitment = None
    pdict2 = p.reveal()
    assert pdict2 == pdict
    #corrupt P2, cannot commit:
    p.P2 = "blah"
    with pytest.raises(PoDLEError) as e_info:
        p.get_commitment()
    #generation fails without a utxo
    p = PoDLE(priv=priv)
    with pytest.raises(PoDLEError) as e_info:
        p.generate_podle(0)
    #Test construction from pubkey
    pub = bitcoin.privkey_to_pubkey(priv+"01")
    p = PoDLE(P=pub)
    with pytest.raises(PoDLEError) as e_info:
        p.get_commitment()
    with pytest.raises(PoDLEError) as e_info:
        p.verify("dummycommitment", range(3))
Ejemplo n.º 2
0
def test_spend_p2sh_utxos(setup_tx_creation):
    #make a multisig address 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
    ]
    script = bitcoin.mk_multisig_script(pubs, 2)
    msig_addr = bitcoin.p2sh_scriptaddr(script, magicbyte=196)
    #pay into it
    wallet_service = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet']
    wallet_service.sync_wallet(fast=True)
    amount = 350000000
    ins_full = wallet_service.select_utxos(0, amount)
    txid = make_sign_and_push(ins_full,
                              wallet_service,
                              amount,
                              output_addr=msig_addr)
    assert txid
    #wait for mining
    time.sleep(1)
    #spend out; the input can be constructed from the txid of previous
    msig_in = txid + ":0"
    ins = [msig_in]
    #random output address and change addr
    output_addr = wallet_service.get_internal_addr(1)
    amount2 = amount - 50000
    outs = [{'value': amount2, 'address': output_addr}]
    tx = bitcoin.mktx(ins, outs)
    sigs = []
    for priv in privs[:2]:
        sigs.append(
            bitcoin.multisign(tx, 0, script,
                              binascii.hexlify(priv).decode('ascii')))
    tx = bitcoin.apply_multisignatures(tx, 0, script, sigs)
    txid = jm_single().bc_interface.pushtx(tx)
    assert txid
def validate_utxo_data(utxo_datas, retrieve=False, segwit=False):
    """For each txid: N, privkey, first
    convert the privkey and convert to address,
    then use the blockchain instance to look up
    the utxo and check that its address field matches.
    If retrieve is True, return the set of utxos and their values.
    """
    results = []
    for u, priv in utxo_datas:
        jmprint('validating this utxo: ' + str(u), "info")
        hexpriv = btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte())
        if segwit:
            addr = btc.pubkey_to_p2sh_p2wpkh_address(
                btc.privkey_to_pubkey(hexpriv), get_p2sh_vbyte())
        else:
            addr = btc.privkey_to_address(hexpriv, magicbyte=get_p2pk_vbyte())
        jmprint('claimed address: ' + addr, "info")
        res = jm_single().bc_interface.query_utxo_set([u])
        if len(res) != 1 or None in res:
            jmprint("utxo not found on blockchain: " + str(u), "error")
            return False
        if res[0]['address'] != addr:
            jmprint(
                "privkey corresponds to the wrong address for utxo: " + str(u),
                "error")
            jmprint(
                "blockchain returned address: {}".format(res[0]['address']),
                "error")
            jmprint("your privkey gave this address: " + addr, "error")
            return False
        if retrieve:
            results.append((u, res[0]['value']))
    jmprint('all utxos validated OK', "success")
    if retrieve:
        return results
    return True
Ejemplo n.º 4
0
 def privkey_to_pubkey(privkey):
     return btc.privkey_to_pubkey(privkey)
Ejemplo n.º 5
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")
Ejemplo n.º 6
0
    def on_auth_received(self, nick, offer, commitment, cr, amount, kphex):
        """Receives data on proposed transaction offer from daemon, verifies
        commitment, returns necessary data to send ioauth message (utxos etc)
        """
        # special case due to cjfee passed as string: it can accidentally parse
        # as hex:
        if not isinstance(offer["cjfee"], str):
            offer["cjfee"] = bintohex(offer["cjfee"])
        #check the validity of the proof of discrete log equivalence
        tries = jm_single().config.getint("POLICY", "taker_utxo_retries")

        def reject(msg):
            jlog.info("Counterparty commitment not accepted, reason: " + msg)
            return (False, )

        # deserialize the commitment revelation
        try:
            cr_dict = PoDLE.deserialize_revelation(cr)
        except PoDLEError as e:
            reason = repr(e)
            return reject(reason)

        if not verify_podle(cr_dict['P'],
                            cr_dict['P2'],
                            cr_dict['sig'],
                            cr_dict['e'],
                            commitment,
                            index_range=range(tries)):
            reason = "verify_podle failed"
            return reject(reason)
        #finally, check that the proffered utxo is real, old enough, large enough,
        #and corresponds to the pubkey
        res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']],
                                                      includeconf=True)
        if len(res) != 1 or not res[0]:
            reason = "authorizing utxo is not valid"
            return reject(reason)
        age = jm_single().config.getint("POLICY", "taker_utxo_age")
        if res[0]['confirms'] < age:
            reason = "commitment utxo not old enough: " + str(
                res[0]['confirms'])
            return reject(reason)
        reqd_amt = int(
            amount *
            jm_single().config.getint("POLICY", "taker_utxo_amtpercent") /
            100.0)
        if res[0]['value'] < reqd_amt:
            reason = "commitment utxo too small: " + str(res[0]['value'])
            return reject(reason)

        try:
            if not self.wallet_service.pubkey_has_script(
                    cr_dict['P'], res[0]['script']):
                raise EngineError()
        except EngineError:
            reason = "Invalid podle pubkey: " + str(cr_dict['P'])
            return reject(reason)

        # authorisation of taker passed
        # Find utxos for the transaction now:
        utxos, cj_addr, change_addr = self.oid_to_order(offer, amount)
        if not utxos:
            #could not find funds
            return (False, )
        # for index update persistence:
        self.wallet_service.save_wallet()
        # Construct data for auth request back to taker.
        # Need to choose an input utxo pubkey to sign with
        # (no longer using the coinjoin pubkey from 0.2.0)
        # Just choose the first utxo in self.utxos and retrieve key from wallet.
        auth_address = utxos[list(utxos.keys())[0]]['address']
        auth_key = self.wallet_service.get_key_from_addr(auth_address)
        auth_pub = btc.privkey_to_pubkey(auth_key)
        # kphex was auto-converted by @hexbin but we actually need to sign the
        # hex version to comply with pre-existing JM protocol:
        btc_sig = btc.ecdsa_sign(bintohex(kphex), auth_key)
        return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig)
def test_create_psbt_and_sign(setup_psbt_wallet, unowned_utxo, wallet_cls):
    """ Plan of test:
    1. Create a wallet and source 3 destination addresses.
    2. Make, and confirm, transactions that fund the 3 addrs.
    3. Create a new tx spending 2 of those 3 utxos and spending
       another utxo we don't own (extra is optional per `unowned_utxo`).
    4. Create a psbt using the above transaction and corresponding
       `spent_outs` field to fill in the redeem script.
    5. Compare resulting PSBT with expected structure.
    6. Use the wallet's sign_psbt method to sign the whole psbt, which
       means signing each input we own.
    7. Check that each input is finalized as per expected. Check that the whole
       PSBT is or is not finalized as per whether there is an unowned utxo.
    8. In case where whole psbt is finalized, attempt to broadcast the tx.
    """
    # steps 1 and 2:
    wallet_service = make_wallets(1, [[3, 0, 0, 0, 0]],
                                  1,
                                  wallet_cls=wallet_cls)[0]['wallet']
    wallet_service.sync_wallet(fast=True)
    utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(1.5))
    # for legacy wallets, psbt creation requires querying for the spending
    # transaction:
    if wallet_cls == LegacyWallet:
        fulltxs = []
        for utxo, v in utxos.items():
            fulltxs.append(
                jm_single().bc_interface.get_deser_from_gettransaction(
                    jm_single().bc_interface.get_transaction(utxo[0])))

    assert len(utxos) == 2
    u_utxos = {}
    if unowned_utxo:
        # note: tx creation uses the key only; psbt creation uses the value,
        # which can be fake here; we do not intend to attempt to fully
        # finalize a psbt with an unowned input. See
        # https://github.com/Simplexum/python-bitcointx/issues/30
        # the redeem script creation (which is artificial) will be
        # avoided in future.
        priv = b"\xaa" * 32 + b"\x01"
        pub = bitcoin.privkey_to_pubkey(priv)
        script = bitcoin.pubkey_to_p2sh_p2wpkh_script(pub)
        redeem_script = bitcoin.pubkey_to_p2wpkh_script(pub)
        u_utxos[(b"\xaa" * 32, 12)] = {"value": 1000, "script": script}
    utxos.update(u_utxos)
    # outputs aren't interesting for this test (we selected 1.5 but will get 2):
    outs = [{
        "value": bitcoin.coins_to_satoshi(1.999),
        "address": wallet_service.get_addr(0, 0, 0)
    }]
    tx = bitcoin.mktx(list(utxos.keys()), outs)

    if wallet_cls != LegacyWallet:
        spent_outs = wallet_service.witness_utxos_to_psbt_utxos(utxos)
        force_witness_utxo = True
    else:
        spent_outs = fulltxs
        # the extra input is segwit:
        if unowned_utxo:
            spent_outs.extend(
                wallet_service.witness_utxos_to_psbt_utxos(u_utxos))
        force_witness_utxo = False
    newpsbt = wallet_service.create_psbt_from_tx(
        tx, spent_outs, force_witness_utxo=force_witness_utxo)
    # see note above
    if unowned_utxo:
        newpsbt.inputs[-1].redeem_script = redeem_script
    print(bintohex(newpsbt.serialize()))
    print("human readable: ")
    print(wallet_service.human_readable_psbt(newpsbt))
    # we cannot compare with a fixed expected result due to wallet randomization, but we can
    # check psbt structure:
    expected_inputs_length = 3 if unowned_utxo else 2
    assert len(newpsbt.inputs) == expected_inputs_length
    assert len(newpsbt.outputs) == 1
    # note: redeem_script field is a CScript which is a bytes instance,
    # so checking length is best way to check for existence (comparison
    # with None does not work):
    if wallet_cls == SegwitLegacyWallet:
        assert len(newpsbt.inputs[0].redeem_script) != 0
        assert len(newpsbt.inputs[1].redeem_script) != 0
    if unowned_utxo:
        assert newpsbt.inputs[2].redeem_script == redeem_script

    signed_psbt_and_signresult, err = wallet_service.sign_psbt(
        newpsbt.serialize(), with_sign_result=True)
    assert err is None
    signresult, signed_psbt = signed_psbt_and_signresult
    expected_signed_inputs = len(utxos) if not unowned_utxo else len(utxos) - 1
    assert signresult.num_inputs_signed == expected_signed_inputs
    assert signresult.num_inputs_final == expected_signed_inputs

    if not unowned_utxo:
        assert signresult.is_final
        # only in case all signed do we try to broadcast:
        extracted_tx = signed_psbt.extract_transaction().serialize()
        assert jm_single().bc_interface.pushtx(extracted_tx)
    else:
        # transaction extraction must fail for not-fully-signed psbts:
        with pytest.raises(ValueError) as e:
            extracted_tx = signed_psbt.extract_transaction()
def test_payjoin_workflow(setup_psbt_wallet, payment_amt, wallet_cls_sender,
                          wallet_cls_receiver):
    """ Workflow step 1:
    Create a payment from a wallet, and create a finalized PSBT.
    This step is fairly trivial as the functionality is built-in to
    PSBTWalletMixin.
    Note that only Segwit* wallets are supported for PayJoin.

        Workflow step 2:
    Receiver creates a new partially signed PSBT with the same amount
    and at least one more utxo.

        Workflow step 3:
    Given a partially signed PSBT created by a receiver, here the sender
    completes (co-signs) the PSBT they are given. Note this code is a PSBT
    functionality check, and does NOT include the detailed checks that
    the sender should perform before agreeing to sign (see:
    https://github.com/btcpayserver/btcpayserver-doc/blob/eaac676866a4d871eda5fd7752b91b88fdf849ff/Payjoin-spec.md#receiver-side
    ).
    """

    wallet_r = make_wallets(1, [[3, 0, 0, 0, 0]],
                            1,
                            wallet_cls=wallet_cls_receiver)[0]["wallet"]
    wallet_s = make_wallets(1, [[3, 0, 0, 0, 0]],
                            1,
                            wallet_cls=wallet_cls_sender)[0]["wallet"]
    for w in [wallet_r, wallet_s]:
        w.sync_wallet(fast=True)

    # destination address for payment:
    destaddr = str(
        bitcoin.CCoinAddress.from_scriptPubKey(
            bitcoin.pubkey_to_p2wpkh_script(
                bitcoin.privkey_to_pubkey(b"\x01" * 33))))

    payment_amt = bitcoin.coins_to_satoshi(payment_amt)

    # *** STEP 1 ***
    # **************

    # create a normal tx from the sender wallet:
    payment_psbt = direct_send(wallet_s,
                               payment_amt,
                               0,
                               destaddr,
                               accept_callback=dummy_accept_callback,
                               info_callback=dummy_info_callback,
                               with_final_psbt=True)

    print("Initial payment PSBT created:\n{}".format(
        wallet_s.human_readable_psbt(payment_psbt)))
    # ensure that the payemnt amount is what was intended:
    out_amts = [x.nValue for x in payment_psbt.unsigned_tx.vout]
    # NOTE this would have to change for more than 2 outputs:
    assert any([out_amts[i] == payment_amt for i in [0, 1]])

    # ensure that we can actually broadcast the created tx:
    # (note that 'extract_transaction' represents an implicit
    # PSBT finality check).
    extracted_tx = payment_psbt.extract_transaction().serialize()
    # don't want to push the tx right now, because of test structure
    # (in production code this isn't really needed, we will not
    # produce invalid payment transactions).
    res = jm_single().bc_interface.testmempoolaccept(bintohex(extracted_tx))
    assert res[0]["allowed"], "Payment transaction was rejected from mempool."

    # *** STEP 2 ***
    # **************

    # Simple receiver utxo choice heuristic.
    # For more generality we test with two receiver-utxos, not one.
    all_receiver_utxos = wallet_r.get_all_utxos()
    # TODO is there a less verbose way to get any 2 utxos from the dict?
    receiver_utxos_keys = list(all_receiver_utxos.keys())[:2]
    receiver_utxos = {
        k: v
        for k, v in all_receiver_utxos.items() if k in receiver_utxos_keys
    }

    # receiver will do other checks as discussed above, including payment
    # amount; as discussed above, this is out of the scope of this PSBT test.

    # construct unsigned tx for payjoin-psbt:
    payjoin_tx_inputs = [(x.prevout.hash[::-1], x.prevout.n)
                         for x in payment_psbt.unsigned_tx.vin]
    payjoin_tx_inputs.extend(receiver_utxos.keys())
    # find payment output and change output
    pay_out = None
    change_out = None
    for o in payment_psbt.unsigned_tx.vout:
        jm_out_fmt = {
            "value": o.nValue,
            "address":
            str(bitcoin.CCoinAddress.from_scriptPubKey(o.scriptPubKey))
        }
        if o.nValue == payment_amt:
            assert pay_out is None
            pay_out = jm_out_fmt
        else:
            assert change_out is None
            change_out = jm_out_fmt

    # we now know there were two outputs and know which is payment.
    # bump payment output with our input:
    outs = [pay_out, change_out]
    our_inputs_val = sum([v["value"] for _, v in receiver_utxos.items()])
    pay_out["value"] += our_inputs_val
    print("we bumped the payment output value by: ", our_inputs_val)
    print("It is now: ", pay_out["value"])
    unsigned_payjoin_tx = bitcoin.make_shuffled_tx(
        payjoin_tx_inputs,
        outs,
        version=payment_psbt.unsigned_tx.nVersion,
        locktime=payment_psbt.unsigned_tx.nLockTime)
    print("we created this unsigned tx: ")
    print(bitcoin.human_readable_transaction(unsigned_payjoin_tx))
    # to create the PSBT we need the spent_outs for each input,
    # in the right order:
    spent_outs = []
    for i, inp in enumerate(unsigned_payjoin_tx.vin):
        input_found = False
        for j, inp2 in enumerate(payment_psbt.unsigned_tx.vin):
            if inp.prevout == inp2.prevout:
                spent_outs.append(payment_psbt.inputs[j].utxo)
                input_found = True
                break
        if input_found:
            continue
        # if we got here this input is ours, we must find
        # it from our original utxo choice list:
        for ru in receiver_utxos.keys():
            if (inp.prevout.hash[::-1], inp.prevout.n) == ru:
                spent_outs.append(
                    wallet_r.witness_utxos_to_psbt_utxos(
                        {ru: receiver_utxos[ru]})[0])
                input_found = True
                break
        # there should be no other inputs:
        assert input_found

    r_payjoin_psbt = wallet_r.create_psbt_from_tx(unsigned_payjoin_tx,
                                                  spent_outs=spent_outs)
    print("Receiver created payjoin PSBT:\n{}".format(
        wallet_r.human_readable_psbt(r_payjoin_psbt)))

    signresultandpsbt, err = wallet_r.sign_psbt(r_payjoin_psbt.serialize(),
                                                with_sign_result=True)
    assert not err, err
    signresult, receiver_signed_psbt = signresultandpsbt
    assert signresult.num_inputs_final == len(receiver_utxos)
    assert not signresult.is_final

    print("Receiver signing successful. Payjoin PSBT is now:\n{}".format(
        wallet_r.human_readable_psbt(receiver_signed_psbt)))

    # *** STEP 3 ***
    # **************

    # take the half-signed PSBT, validate and co-sign:

    signresultandpsbt, err = wallet_s.sign_psbt(
        receiver_signed_psbt.serialize(), with_sign_result=True)
    assert not err, err
    signresult, sender_signed_psbt = signresultandpsbt
    print("Sender's final signed PSBT is:\n{}".format(
        wallet_s.human_readable_psbt(sender_signed_psbt)))
    assert signresult.is_final

    # broadcast the tx
    extracted_tx = sender_signed_psbt.extract_transaction().serialize()
    assert jm_single().bc_interface.pushtx(extracted_tx)
Ejemplo n.º 9
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")
Ejemplo n.º 10
0
def test_is_snicker_tx(our_input_val, their_input_val, network_fee,
                       script_type, net_transfer):
    our_input = (bytes([1]) * 32, 0)
    their_input = (bytes([2]) * 32, 1)
    assert our_input_val - their_input_val - network_fee > 0
    total_input_amount = our_input_val + their_input_val
    total_output_amount = total_input_amount - network_fee
    receiver_output_amount = their_input_val + net_transfer
    proposer_output_amount = total_output_amount - receiver_output_amount

    # all keys are just made up; only the script type will be checked
    privs = [bytes([i]) * 32 + bytes([1]) for i in range(1, 4)]
    pubs = [btc.privkey_to_pubkey(x) for x in privs]

    if script_type == "p2wpkh":
        spks = [btc.pubkey_to_p2wpkh_script(x) for x in pubs]
    elif script_type == "p2sh-p2wpkh":
        spks = [btc.pubkey_to_p2sh_p2wpkh_script(x) for x in pubs]
    else:
        assert False
    tweaked_addr, our_addr, change_addr = [
        str(btc.CCoinAddress.from_scriptPubKey(x)) for x in spks
    ]
    # now we must construct the three outputs with correct output amounts.
    outputs = [{"address": tweaked_addr, "value": receiver_output_amount}]
    outputs.append({"address": our_addr, "value": receiver_output_amount})
    outputs.append({
        "address": change_addr,
        "value": total_output_amount - 2 * receiver_output_amount
    })
    assert all([x["value"] > 0 for x in outputs])

    # make_shuffled_tx mutates ordering (yuck), work with copies only:
    outputs1 = copy.deepcopy(outputs)
    # version and locktime as currently specified in the BIP
    # for 0/1 version SNICKER. (Note the locktime is partly because
    # of expected delays).
    tx = btc.make_shuffled_tx([our_input, their_input],
                              outputs1,
                              version=2,
                              locktime=0)
    assert btc.is_snicker_tx(tx)

    # construct variants which will be invalid.

    # mixed script types in outputs
    wrong_tweaked_spk = btc.pubkey_to_p2pkh_script(pubs[1])
    wrong_tweaked_addr = str(
        btc.CCoinAddress.from_scriptPubKey(wrong_tweaked_spk))
    outputs2 = copy.deepcopy(outputs)
    outputs2[0] = {
        "address": wrong_tweaked_addr,
        "value": receiver_output_amount
    }
    tx2 = btc.make_shuffled_tx([our_input, their_input],
                               outputs2,
                               version=2,
                               locktime=0)
    assert not btc.is_snicker_tx(tx2)

    # nonequal output amounts
    outputs3 = copy.deepcopy(outputs)
    outputs3[1] = {"address": our_addr, "value": receiver_output_amount - 1}
    tx3 = btc.make_shuffled_tx([our_input, their_input],
                               outputs3,
                               version=2,
                               locktime=0)
    assert not btc.is_snicker_tx(tx3)

    # too few outputs
    outputs4 = copy.deepcopy(outputs)
    outputs4 = outputs4[:2]
    tx4 = btc.make_shuffled_tx([our_input, their_input],
                               outputs4,
                               version=2,
                               locktime=0)
    assert not btc.is_snicker_tx(tx4)

    # too many outputs
    outputs5 = copy.deepcopy(outputs)
    outputs5.append({"address": change_addr, "value": 200000})
    tx5 = btc.make_shuffled_tx([our_input, their_input],
                               outputs5,
                               version=2,
                               locktime=0)
    assert not btc.is_snicker_tx(tx5)

    # wrong nVersion
    tx6 = btc.make_shuffled_tx([our_input, their_input],
                               outputs,
                               version=1,
                               locktime=0)
    assert not btc.is_snicker_tx(tx6)

    # wrong nLockTime
    tx7 = btc.make_shuffled_tx([our_input, their_input],
                               outputs,
                               version=2,
                               locktime=1)
    assert not btc.is_snicker_tx(tx7)
Ejemplo n.º 11
0
def test_make_commitment(setup_taker, mixdepth, cjamt, failquery, external,
                         expected_success, amtpercent, age, mixdepth_extras):
    def clean_up():
        jm_single().config.set("POLICY", "taker_utxo_age", old_taker_utxo_age)
        jm_single().config.set("POLICY", "taker_utxo_amtpercent",
                               old_taker_utxo_amtpercent)
        set_commitment_file(old_commitment_file)
        jm_single().bc_interface.setQUSFail(False)
        jm_single().bc_interface.reset_confs()
        os.remove('dummyext')

    old_commitment_file = get_commitment_file()
    with open('dummyext', 'wb') as f:
        f.write(json.dumps(t_dummy_ext, indent=4).encode('utf-8'))
    if external:
        set_commitment_file('dummyext')

    # define the appropriate podle acceptance parameters in the global config:
    old_taker_utxo_age = jm_single().config.get("POLICY", "taker_utxo_age")
    old_taker_utxo_amtpercent = jm_single().config.get(
        "POLICY", "taker_utxo_amtpercent")
    if expected_success:
        # set to defaults for mainnet
        newtua = "5"
        newtuap = "20"
    else:
        newtua = str(age)
        newtuap = str(amtpercent)
        jm_single().config.set("POLICY", "taker_utxo_age", newtua)
        jm_single().config.set("POLICY", "taker_utxo_amtpercent", newtuap)

    taker = get_taker([(mixdepth, cjamt, 3,
                        "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", NO_ROUNDING)])

    # modify or add any extra utxos for this run:
    for k, v in mixdepth_extras.items():
        if k == "confchange":
            for k2, v2 in v.items():
                # set the utxos in mixdepth k2 to have confs v2:
                cdict = taker.wallet_service.get_utxos_by_mixdepth()[k2]
                jm_single().bc_interface.set_confs(
                    {utxo: v2
                     for utxo in cdict.keys()})
        elif k == "custom-script":
            # note: this is inspired by fidelity bonds, and currently
            # uses scripts of that specific timelock type, but is really
            # only testing the general concept: that commitments must
            # not be made on any non-standard script type.
            for k2, v2 in v.items():
                priv = os.urandom(32) + b"\x01"
                tl = random.randrange(1430454400, 1430494400)
                script_inner = bitcoin.mk_freeze_script(
                    bitcoin.privkey_to_pubkey(priv), tl)
                script_outer = bitcoin.redeem_script_to_p2wsh_script(
                    script_inner)
                taker.wallet_service.wallet._script_map[script_outer] = (
                    "nonstandard_path", )
                taker.wallet_service.add_extra_utxo(os.urandom(32),
                                                    0,
                                                    v2,
                                                    k2,
                                                    script=script_outer)
        else:
            for value in v:
                taker.wallet_service.add_extra_utxo(os.urandom(32), 0, value,
                                                    k)

    taker.cjamount = cjamt
    taker.input_utxos = taker.wallet_service.get_utxos_by_mixdepth()[mixdepth]
    taker.mixdepth = mixdepth
    if failquery:
        jm_single().bc_interface.setQUSFail(True)
    comm, revelation, msg = taker.make_commitment()
    if expected_success and failquery:
        # for manual tests, show the error message:
        print("Failure case due to QUS fail: ")
        print("Erromsg: ", msg)
        assert not comm
    elif expected_success:
        assert comm, "podle was not generated but should have been."
    else:
        # in these cases we have set the podle acceptance
        # parameters such that our in-mixdepth utxos are not good
        # enough.
        # for manual tests, show the errormsg:
        print("Failure case, errormsg: ", msg)
        assert not comm, "podle was generated but should not have been."
    clean_up()
Ejemplo n.º 12
0
def cli_get_pubkey(wallet_name, address):
    print("Checking for address: ", address)
    wallet = cli_get_wallet(wallet_name)
    privkey = wallet.get_key_from_addr(address)
    pubkey = btc.privkey_to_pubkey(privkey)
    print("Pubkey: ", pubkey)
Ejemplo n.º 13
0
def scan_for_coinjoins(privkey, amount, filename):
    """Given a file which contains encrypted coinjoin proposals,
    and a private key for a pubkey with a known utxo existing
    which we can spend, scan the entries in the file, all assumed
    to be ECIES encrypted to a pubkey, for one which is encrypted
    to *this* pubkey, if found, output the retrieved partially signed
    transaction, and destination key, address to a list which is
    returned to the caller.
    Only if the retrieved coinjoin transaction passes basic checks
    on validity in terms of amount paid, is it returned.
    This is an elementary implementation that will obviously fail
    any performance test (i.e. moderately large lists).
    Note that the tweaked output address must be of type p2sh/p2wpkh.
    """
    try:
        with open(filename, "rb") as f:
            msgs = f.readlines()
    except:
        print("Failed to read from file: ", filename)
        return
    valid_coinjoins = []
    for msg in msgs:
        try:
            decrypted_msg = decrypt_message(msg, privkey)
            tweak, tx = deserialize_coinjoin_proposal(decrypted_msg)
        except:
            print("Could not decrypt message, skipping")
            continue
        if not tweak:
            print("Could not decrypt message, reason: " + str(tx))
            continue
        #We analyse the content of the transaction to check if it follows
        #our requirements
        try:
            deserialized_tx = btc.deserialize(tx)
        except:
            print("Proposed transaction is not correctly formatted, skipping.")
            continue
        #construct our receiving address according to the tweak
        pubkey = btc.privkey_to_pubkey(privkey)
        tweak, destnpt, my_destn_addr = create_recipient_address(pubkey,
                                                                 tweak=tweak,
                                                                 segwit=True)
        #add_privkeys requires both inputs to be compressed (or un-) consistently.
        tweak_priv = tweak + "01"
        my_destn_privkey = btc.add_privkeys(tweak_priv, privkey, True)
        my_output_index = -1
        for i, o in enumerate(deserialized_tx['outs']):
            addr = btc.script_to_address(o['script'], get_p2sh_vbyte())
            if addr == my_destn_addr:
                print('found our output address: ', my_destn_addr)
                my_output_index = i
                break
        if my_output_index == -1:
            print("Proposal doesn't contain our output address, rejecting")
            continue
        my_output_amount = deserialized_tx['outs'][i]['value']
        required_amount = amount - 2 * estimate_tx_fee(3, 3, 'p2sh-p2wpkh')
        if my_output_amount < required_amount:
            print("Proposal pays too little, difference is: ",
                  required_amount - my_output_amount)
            continue
        #now we know output is acceptable to us, we should check that the
        #ctrprty input is signed and the other input is ours, but will do this
        #later; if it's not, it just won't work so NBD for now.
        valid_coinjoins.append((my_destn_addr, my_destn_privkey, tx))
    return valid_coinjoins
    def process_proposals(self, proposals):
        """ Each entry in `proposals` is of form:
        encrypted_proposal - base64 string
        key - hex encoded compressed pubkey, or ''
        if the key is not null, we attempt to decrypt and
        process according to that key, else cycles over all keys.

        If all SNICKER validations succeed, the decision to spend is
        entirely dependent on self.acceptance_callback.
        If the callback returns True, we co-sign and broadcast the
        transaction and also update the wallet with the new
        imported key (TODO: future versions will enable searching
        for these keys using history + HD tree; note the jmbitcoin
        snicker.py module DOES insist on ECDH being correctly used,
        so this will always be possible for transactions created here.

        Returned is a list of txids of any transactions which
        were broadcast, unless a critical error occurs, in which case
        False is returned (to minimize this function's trust in other
        parts of the code being executed, if something appears to be
        inconsistent, we trigger immediate halt with this return).
        """

        for kp in proposals:
            try:
                p, k = kp.split(b',')
            except:
                jlog.error("Invalid proposal string, ignoring: " + kp)
            if k is not None:
                # note that this operation will succeed as long as
                # the key is in the wallet._script_map, which will
                # be true if the key is at an HD index lower than
                # the current wallet.index_cache
                k = hextobin(k.decode('utf-8'))
                addr = self.wallet_service.pubkey_to_addr(k)
                if not self.wallet_service.is_known_addr(addr):
                    jlog.debug("Key not recognized as part of our "
                               "wallet, ignoring.")
                    continue
                # TODO: interface/API of SNICKERWalletMixin would better take
                # address as argument here, not privkey:
                priv = self.wallet_service.get_key_from_addr(addr)
                result = self.wallet_service.parse_proposal_to_signed_tx(
                    priv, p, self.acceptance_callback)
                if result[0] is not None:
                    tx, tweak, out_spk = result

                    # We will: rederive the key as a sanity check,
                    # and see if it matches the claimed spk.
                    # Then, we import the key into the wallet
                    # (even though it's re-derivable from history, this
                    # is the easiest for a first implementation).
                    # Finally, we co-sign, then push.
                    # (Again, simplest function: checks already passed,
                    # so do it automatically).
                    # TODO: the more sophisticated actions.
                    tweaked_key = btc.snicker_pubkey_tweak(k, tweak)
                    tweaked_spk = btc.pubkey_to_p2sh_p2wpkh_script(tweaked_key)
                    if not tweaked_spk == out_spk:
                        jlog.error("The spk derived from the pubkey does "
                                   "not match the scriptPubkey returned from "
                                   "the snicker module - code error.")
                        return False
                    # before import, we should derive the tweaked *private* key
                    # from the tweak, also:
                    tweaked_privkey = btc.snicker_privkey_tweak(priv, tweak)
                    if not btc.privkey_to_pubkey(
                            tweaked_privkey) == tweaked_key:
                        jlog.error("Was not able to recover tweaked pubkey "
                                   "from tweaked privkey - code error.")
                        jlog.error("Expected: " + bintohex(tweaked_key))
                        jlog.error(
                            "Got: " +
                            bintohex(btc.privkey_to_pubkey(tweaked_privkey)))
                        return False
                    # the recreated private key matches, so we import to the wallet,
                    # note that type = None here is because we use the same
                    # scriptPubKey type as the wallet, this has been implicitly
                    # checked above by deriving the scriptPubKey.
                    self.wallet_service.import_private_key(
                        self.import_branch,
                        self.wallet_service._ENGINE.privkey_to_wif(
                            tweaked_privkey))

                    # TODO condition on automatic brdcst or not
                    if not jm_single().bc_interface.pushtx(tx.serialize()):
                        jlog.error("Failed to broadcast SNICKER CJ.")
                        return False
                    self.successful_txs.append(tx)
                    return True
                else:
                    jlog.debug('Failed to parse proposal: ' + result[1])
                    continue
            else:
                # Some extra work to implement checking all possible
                # keys.
                raise NotImplementedError()

        # Completed processing all proposals without any logic
        # errors (whether the proposals were valid or accepted
        # or not).
        return True