def test_verify_tx_input(setup_tx_creation): priv = b"\xaa" * 32 + b"\x01" pub = bitcoin.privkey_to_pubkey(priv) script = bitcoin.pubkey_to_p2sh_p2wpkh_script(pub) addr = str(bitcoin.CCoinAddress.from_scriptPubKey(script)) wallet_service = make_wallets(1, [[2, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) insfull = wallet_service.select_utxos(0, 110000000) outs = [{"address": addr, "value": 1000000}] ins = list(insfull.keys()) tx = bitcoin.mktx(ins, outs) scripts = {0: (insfull[ins[0]]["script"], bitcoin.coins_to_satoshi(1))} success, msg = wallet_service.sign_tx(tx, scripts) assert success, msg # testing Joinmarket's ability to verify transaction inputs # of others: pretend we don't have a wallet owning the transaction, # and instead verify an input using the (sig, pub, scriptCode) data # that is sent by counterparties: cScrWit = tx.wit.vtxinwit[0].scriptWitness sig = cScrWit.stack[0] pub = cScrWit.stack[1] scriptSig = tx.vin[0].scriptSig tx2 = bitcoin.mktx(ins, outs) res = bitcoin.verify_tx_input(tx2, 0, scriptSig, bitcoin.pubkey_to_p2wpkh_script(pub), amount=bitcoin.coins_to_satoshi(1), witness=bitcoin.CScriptWitness([sig, pub])) assert res
def test_create_and_sign_psbt_with_legacy(setup_psbt_wallet): """ The purpose of this test is to check that we can create and then partially sign a PSBT where we own one input and the other input is of legacy p2pkh type. """ wallet_service = make_wallets(1, [[1, 0, 0, 0, 0]], 1)[0]['wallet'] wallet_service.sync_wallet(fast=True) utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(utxos) == 1 # create a legacy address and make a payment into it legacy_addr = bitcoin.CCoinAddress.from_scriptPubKey( bitcoin.pubkey_to_p2pkh_script(bitcoin.privkey_to_pubkey(b"\x01" * 33))) tx = direct_send(wallet_service, bitcoin.coins_to_satoshi(0.3), 0, str(legacy_addr), accept_callback=dummy_accept_callback, info_callback=dummy_info_callback, return_transaction=True) assert tx # this time we will have one utxo worth <~ 0.7 my_utxos = wallet_service.select_utxos(0, bitcoin.coins_to_satoshi(0.5)) assert len(my_utxos) == 1 # find the outpoint for the legacy address we're spending n = -1 for i, t in enumerate(tx.vout): if bitcoin.CCoinAddress.from_scriptPubKey( t.scriptPubKey) == legacy_addr: n = i assert n > -1 utxos = copy.deepcopy(my_utxos) utxos[(tx.GetTxid()[::-1], n)] = { "script": legacy_addr.to_scriptPubKey(), "value": bitcoin.coins_to_satoshi(0.3) } outs = [{ "value": bitcoin.coins_to_satoshi(0.998), "address": wallet_service.get_addr(0, 0, 0) }] tx2 = bitcoin.mktx(list(utxos.keys()), outs) spent_outs = wallet_service.witness_utxos_to_psbt_utxos(my_utxos) spent_outs.append(tx) new_psbt = wallet_service.create_psbt_from_tx(tx2, spent_outs, force_witness_utxo=False) signed_psbt_and_signresult, err = wallet_service.sign_psbt( new_psbt.serialize(), with_sign_result=True) assert err is None signresult, signed_psbt = signed_psbt_and_signresult assert signresult.num_inputs_signed == 1 assert signresult.num_inputs_final == 1 assert not signresult.is_final
def test_sign_standard_txs(addrtype): # liberally copied from python-bitcoinlib tests, # in particular see: # https://github.com/petertodd/python-bitcoinlib/pull/227 # Create the (in)famous correct brainwallet secret key. priv = hashlib.sha256(b'correct horse battery staple').digest() + b"\x01" pub = btc.privkey_to_pubkey(priv) # Create an address from that private key. # (note that the input utxo is fake so we are really only creating # a destination here). scriptPubKey = btc.CScript([btc.OP_0, btc.Hash160(pub)]) address = btc.P2WPKHCoinAddress.from_scriptPubKey(scriptPubKey) # Create a dummy outpoint; use same 32 bytes for convenience txid = priv[:32] vout = 2 amount = btc.coins_to_satoshi(float('0.12345')) # Calculate an amount for the upcoming new UTXO. Set a high fee to bypass # bitcoind minfee setting. amount_less_fee = int(amount - btc.coins_to_satoshi(0.01)) # Create a destination to send the coins. destination_address = address target_scriptPubKey = scriptPubKey # Create the unsigned transaction. txin = btc.CTxIn(btc.COutPoint(txid[::-1], vout)) txout = btc.CTxOut(amount_less_fee, target_scriptPubKey) tx = btc.CMutableTransaction([txin], [txout]) # Calculate the signature hash for the transaction. This is then signed by the # private key that controls the UTXO being spent here at this txin_index. if addrtype == "p2wpkh": sig, msg = btc.sign(tx, 0, priv, amount=amount, native="p2wpkh") elif addrtype == "p2sh-p2wpkh": sig, msg = btc.sign(tx, 0, priv, amount=amount, native=False) elif addrtype == "p2pkh": sig, msg = btc.sign(tx, 0, priv) else: assert False if not sig: print(msg) raise print("created signature: ", bintohex(sig)) print("serialized transaction: {}".format(bintohex(tx.serialize()))) print("deserialized transaction: {}\n".format( btc.human_readable_transaction(tx)))
def test_mk_shuffled_tx(): # prepare two addresses for the outputs pub = btc.privkey_to_pubkey(btc.Hash(b"priv") + b"\x01") scriptPubKey = btc.CScript([btc.OP_0, btc.Hash160(pub)]) addr1 = btc.P2WPKHCoinAddress.from_scriptPubKey(scriptPubKey) scriptPubKey_p2sh = scriptPubKey.to_p2sh_scriptPubKey() addr2 = btc.CCoinAddress.from_scriptPubKey(scriptPubKey_p2sh) ins = [(btc.Hash(b"blah"), 7), (btc.Hash(b"foo"), 15)] # note the casts str() ; most calls to mktx will have addresses fed # as strings, so this is enforced for simplicity. outs = [{"address": str(addr1), "value": btc.coins_to_satoshi(float("0.1"))}, {"address": str(addr2), "value": btc.coins_to_satoshi(float("45981.23331234"))}] tx = btc.make_shuffled_tx(ins, outs, version=2, locktime=500000)
def test_bip143_tv(): # p2sh-p2wpkh case: rawtx_hex = "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000" inp_spk_hex = "a9144733f37cf4db86fbc2efed2500b4f4e49f31202387" value = 10 redeemScript = "001479091972186c449eb1ded22b78e40d009bdf0089" privkey_hex = "eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf01" pubkey_hex = "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873" tx = btc.CMutableTransaction.deserialize(btc.x(rawtx_hex)) btc.sign(tx, 0, btc.x(privkey_hex), amount=btc.coins_to_satoshi(10), native=False) expectedsignedtx = "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000" assert btc.b2x(tx.serialize()) == expectedsignedtx
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)
def test_snicker_e2e(setup_snicker, nw, wallet_structures, mean_amt, sdev_amt, amt, net_transfer): """ Test strategy: 1. create two wallets. 2. with wallet 1 (Receiver), create a single transaction tx1, from mixdepth 0 to 1. 3. with wallet 2 (Proposer), take pubkey of all inputs from tx1, and use them to create snicker proposals to the non-change out of tx1, in base64 and place in proposals.txt. 4. Receiver polls for proposals in the file manually (instead of twisted LoopingCall) and processes them. 5. Check for valid final transaction with broadcast. """ # TODO: Make this test work with native segwit wallets wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt, wallet_cls=SegwitLegacyWallet) for w in wallets.values(): w['wallet'].sync_wallet(fast=True) print(wallets) wallet_r = wallets[0]['wallet'] wallet_p = wallets[1]['wallet'] # next, create a tx from the receiver wallet our_destn_script = wallet_r.get_new_script( 1, BaseWallet.ADDRESS_TYPE_INTERNAL) tx = direct_send(wallet_r, btc.coins_to_satoshi(0.3), 0, wallet_r.script_to_addr(our_destn_script), accept_callback=dummy_accept_callback, info_callback=dummy_info_callback, return_transaction=True) assert tx, "Failed to spend from receiver wallet" print("Parent transaction OK. It was: ") print(btc.human_readable_transaction(tx)) wallet_r.process_new_tx(tx) # we must identify the receiver's output we're going to use; # it can be destination or change, that's up to the proposer # to guess successfully; here we'll just choose index 0. txid1 = tx.GetTxid()[::-1] txid1_index = 0 receiver_start_bal = sum( [x['value'] for x in wallet_r.get_all_utxos().values()]) # Now create a proposal for every input index in tx1 # (version 1 proposals mean we source keys from the/an # ancestor transaction) propose_keys = [] for i in range(len(tx.vin)): # todo check access to pubkey sig, pub = [a for a in iter(tx.wit.vtxinwit[i].scriptWitness)] propose_keys.append(pub) # the proposer wallet needs to choose a single # utxo that is bigger than the output amount of tx1 prop_m_utxos = wallet_p.get_utxos_by_mixdepth()[0] prop_utxo = prop_m_utxos[list(prop_m_utxos)[0]] # get the private key for that utxo priv = wallet_p.get_key_from_addr( wallet_p.script_to_addr(prop_utxo['script'])) prop_input_amt = prop_utxo['value'] # construct the arguments for the snicker proposal: our_input = list(prop_m_utxos)[0] # should be (txid, index) their_input = (txid1, txid1_index) our_input_utxo = btc.CMutableTxOut(prop_utxo['value'], prop_utxo['script']) fee_est = estimate_tx_fee(len(tx.vin), 2) change_spk = wallet_p.get_new_script(0, BaseWallet.ADDRESS_TYPE_INTERNAL) encrypted_proposals = [] for p in propose_keys: # TODO: this can be a loop over all outputs, # not just one guessed output, if desired. encrypted_proposals.append( wallet_p.create_snicker_proposal(our_input, their_input, our_input_utxo, tx.vout[txid1_index], net_transfer, fee_est, priv, p, prop_utxo['script'], change_spk, version_byte=1) + b"," + bintohex(p).encode('utf-8')) with open(TEST_PROPOSALS_FILE, "wb") as f: f.write(b"\n".join(encrypted_proposals)) sR = SNICKERReceiver(wallet_r) sR.proposals_source = TEST_PROPOSALS_FILE # avoid clashing with mainnet sR.poll_for_proposals() assert len(sR.successful_txs) == 1 wallet_r.process_new_tx(sR.successful_txs[0]) end_utxos = wallet_r.get_all_utxos() print("At end the receiver has these utxos: ", end_utxos) receiver_end_bal = sum([x['value'] for x in end_utxos.values()]) assert receiver_end_bal == receiver_start_bal + net_transfer