From 7f3b0b021858cfb2fe914f3ba6b30a39e3ae05ff Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Fri, 6 Oct 2023 18:20:42 +0200 Subject: [PATCH] qa: test a PSBT has only the BIP32 derivations for a single spending path --- tests/test_rpc.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/test_rpc.py b/tests/test_rpc.py index 3a2dcc2e..193088fa 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -6,6 +6,7 @@ import time from fixtures import * from test_framework.serializations import ( PSBT, + PSBT_IN_BIP32_DERIVATION, PSBT_IN_PARTIAL_SIG, PSBT_IN_NON_WITNESS_UTXO, ) @@ -385,6 +386,25 @@ def test_create_spend(lianad, bitcoind): with pytest.raises(RpcError, match=".*is from an immature coinbase transaction."): lianad.rpc.createspend(destinations, [imma_coin["outpoint"]], 1) + # Receive a coin and make it immediately available for the recovery path. + txid = bitcoind.rpc.sendtoaddress(lianad.rpc.getnewaddress()["address"], 1) + bitcoind.generate_block(10, wait_for_mempool=txid) + wait_for( + lambda: lianad.rpc.getinfo()["block_height"] == bitcoind.rpc.getblockcount() + ) + + # Create both a spend transaction and recovery transaction spending this coin. + outpoints = [c["outpoint"] for c in lianad.rpc.listcoins(["confirmed"])["coins"]] + res_spend = lianad.rpc.createspend(destinations, outpoints, 1) + res_reco = lianad.rpc.createrecovery(bitcoind.rpc.getnewaddress(), 2) + + # The two PSBTs don't share any BIP32 derivation paths in their input. + res_spend_psbt = PSBT.from_base64(res_spend["psbt"]) + res_reco_psbt = PSBT.from_base64(res_reco["psbt"]) + res_spend_keys = set(res_spend_psbt.i[0].map[PSBT_IN_BIP32_DERIVATION]) + res_reco_keys = set(res_reco_psbt.i[0].map[PSBT_IN_BIP32_DERIVATION]) + assert res_spend_keys.intersection(res_reco_keys) == set() + def test_list_spend(lianad, bitcoind): # Start by creating two conflicting Spend PSBTs. The first one will have a change @@ -841,6 +861,39 @@ def test_create_recovery(lianad, bitcoind): sign_and_broadcast(lianad, bitcoind, reco_psbt, recovery=True) +def test_create_recovery_specific_paths(lianad_multipath, bitcoind): + """Test creating recovery PSBTs for specific recovery paths.""" + # We can't create a recovery for a specific recovery path without specifying the precise + # timelock value of this recovery path. + with pytest.raises( + RpcError, + match="Provided timelock does not correspond to any recovery path: '42424'", + ): + lianad_multipath.rpc.createrecovery(bitcoind.rpc.getnewaddress(), 2, 42424) + + # Receive a coin and make it immediately available for both reco paths. + txid = bitcoind.rpc.sendtoaddress( + lianad_multipath.rpc.getnewaddress()["address"], 1 + ) + bitcoind.generate_block(20, wait_for_mempool=txid) + wait_for( + lambda: lianad_multipath.rpc.getinfo()["block_height"] + == bitcoind.rpc.getblockcount() + ) + + # But we can create one for both existing recovery paths. + res_10 = lianad_multipath.rpc.createrecovery(bitcoind.rpc.getnewaddress(), 2, 10) + res_20 = lianad_multipath.rpc.createrecovery(bitcoind.rpc.getnewaddress(), 2, 20) + + # Both don't have the same BIP32 derivations set in their input, unfortunately. This + # is because we only set them for the keys from a specific spending path. + res_10_psbt = PSBT.from_base64(res_10["psbt"]) + res_20_psbt = PSBT.from_base64(res_20["psbt"]) + res_10_keys = set(res_10_psbt.i[0].map[PSBT_IN_BIP32_DERIVATION]) + res_20_keys = set(res_20_psbt.i[0].map[PSBT_IN_BIP32_DERIVATION]) + assert res_10_keys.intersection(res_20_keys) == set() + + def test_labels(lianad, bitcoind): """Test the creation and updating of labels.""" # We can set a label for an address.