]> Untitled Git - bdk/commitdiff
taproot-tests: Add test coverage for tx signing
authorAlekos Filini <alekos.filini@gmail.com>
Wed, 18 May 2022 17:55:21 +0000 (19:55 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Wed, 1 Jun 2022 12:51:32 +0000 (14:51 +0200)
src/wallet/mod.rs

index 872d59192ad4c13603e3dc88717399a6756a2094..13c7fa2abdcdcccef3b611b44aad9f00c7a2b87d 100644 (file)
@@ -1855,7 +1855,7 @@ pub(crate) mod test {
     }
 
     pub(crate) fn get_test_tr_single_sig() -> &'static str {
-        "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
+        "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
     }
 
     pub(crate) fn get_test_tr_with_taptree() -> &'static str {
@@ -1866,6 +1866,14 @@ pub(crate) mod test {
         "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100)),and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(200))})"
     }
 
+    pub(crate) fn get_test_tr_single_sig_xprv() -> &'static str {
+        "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
+    }
+
+    pub(crate) fn get_test_tr_with_taptree_xprv() -> &'static str {
+        "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
+    }
+
     macro_rules! assert_fee_rate {
         ($tx:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
             let mut tx = $tx.clone();
@@ -4185,7 +4193,7 @@ pub(crate) mod test {
 
     #[test]
     fn test_taproot_psbt_populate_tap_key_origins() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
         let addr = wallet.get_address(AddressIndex::New).unwrap();
 
         let mut builder = wallet.build_tx();
@@ -4322,6 +4330,52 @@ pub(crate) mod test {
         );
     }
 
+    #[test]
+    fn test_taproot_sign_missing_witness_utxo() {
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New).unwrap();
+        let mut builder = wallet.build_tx();
+        builder.drain_to(addr.script_pubkey()).drain_wallet();
+        let (mut psbt, _) = builder.finish().unwrap();
+        let witness_utxo = psbt.inputs[0].witness_utxo.take();
+
+        let result = wallet.sign(
+            &mut psbt,
+            SignOptions {
+                allow_all_sighashes: true,
+                ..Default::default()
+            },
+        );
+        assert!(
+            result.is_err(),
+            "Signing should have failed because the witness_utxo is missing"
+        );
+        assert!(
+            matches!(
+                result.unwrap_err(),
+                Error::Signer(SignerError::MissingWitnessUtxo)
+            ),
+            "Signing failed with the wrong error type"
+        );
+
+        // restore the witness_utxo
+        psbt.inputs[0].witness_utxo = witness_utxo;
+
+        let result = wallet.sign(
+            &mut psbt,
+            SignOptions {
+                allow_all_sighashes: true,
+                ..Default::default()
+            },
+        );
+
+        assert!(result.is_ok(), "Signing should have worked");
+        assert!(
+            result.unwrap(),
+            "Should finalize the input since we can produce signatures"
+        );
+    }
+
     #[test]
     fn test_taproot_foreign_utxo() {
         let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
@@ -4362,9 +4416,7 @@ pub(crate) mod test {
         );
     }
 
-    #[test]
-    fn test_taproot_key_spend() {
-        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+    fn test_spend_from_wallet(wallet: Wallet<AnyDatabase>) {
         let addr = wallet.get_address(AddressIndex::New).unwrap();
 
         let mut builder = wallet.build_tx();
@@ -4373,22 +4425,143 @@ pub(crate) mod test {
 
         assert!(
             wallet.sign(&mut psbt, Default::default()).unwrap(),
-            "Unable to finalize taproot key spend"
+            "Unable to finalize tx"
         );
     }
 
+    #[test]
+    fn test_taproot_key_spend() {
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        test_spend_from_wallet(wallet);
+
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+        test_spend_from_wallet(wallet);
+    }
+
     #[test]
     fn test_taproot_script_spend() {
         let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
+        test_spend_from_wallet(wallet);
+
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
+        test_spend_from_wallet(wallet);
+    }
+
+    #[test]
+    fn test_taproot_sign_derive_index_from_psbt() {
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+
         let addr = wallet.get_address(AddressIndex::New).unwrap();
 
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (mut psbt, _) = builder.finish().unwrap();
 
+        // re-create the wallet with an empty db
+        let wallet_empty = Wallet::new(
+            get_test_tr_single_sig_xprv(),
+            None,
+            Network::Regtest,
+            AnyDatabase::Memory(MemoryDatabase::new()),
+        )
+        .unwrap();
+
+        // signing with an empty db means that we will only look at the psbt to infer the
+        // derivation index
         assert!(
-            wallet.sign(&mut psbt, Default::default()).unwrap(),
-            "Unable to finalize taproot script spend"
+            wallet_empty.sign(&mut psbt, Default::default()).unwrap(),
+            "Unable to finalize tx"
+        );
+    }
+
+    #[test]
+    fn test_taproot_sign_explicit_sighash_all() {
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New).unwrap();
+        let mut builder = wallet.build_tx();
+        builder
+            .drain_to(addr.script_pubkey())
+            .sighash(SchnorrSighashType::All.into())
+            .drain_wallet();
+        let (mut psbt, _) = builder.finish().unwrap();
+
+        let result = wallet.sign(&mut psbt, Default::default());
+        assert!(
+            result.is_ok(),
+            "Signing should work because SIGHASH_ALL is safe"
+        )
+    }
+
+    #[test]
+    fn test_taproot_sign_non_default_sighash() {
+        let sighash = SchnorrSighashType::NonePlusAnyoneCanPay;
+
+        let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
+        let addr = wallet.get_address(New).unwrap();
+        let mut builder = wallet.build_tx();
+        builder
+            .drain_to(addr.script_pubkey())
+            .sighash(sighash.into())
+            .drain_wallet();
+        let (mut psbt, _) = builder.finish().unwrap();
+
+        let witness_utxo = psbt.inputs[0].witness_utxo.take();
+
+        let result = wallet.sign(&mut psbt, Default::default());
+        assert!(
+            result.is_err(),
+            "Signing should have failed because the TX uses non-standard sighashes"
+        );
+        assert!(
+            matches!(
+                result.unwrap_err(),
+                Error::Signer(SignerError::NonStandardSighash)
+            ),
+            "Signing failed with the wrong error type"
+        );
+
+        // try again after opting-in
+        let result = wallet.sign(
+            &mut psbt,
+            SignOptions {
+                allow_all_sighashes: true,
+                ..Default::default()
+            },
+        );
+        assert!(
+            result.is_err(),
+            "Signing should have failed because the witness_utxo is missing"
+        );
+        assert!(
+            matches!(
+                result.unwrap_err(),
+                Error::Signer(SignerError::MissingWitnessUtxo)
+            ),
+            "Signing failed with the wrong error type"
+        );
+
+        // restore the witness_utxo
+        psbt.inputs[0].witness_utxo = witness_utxo;
+
+        let result = wallet.sign(
+            &mut psbt,
+            SignOptions {
+                allow_all_sighashes: true,
+                ..Default::default()
+            },
+        );
+
+        assert!(result.is_ok(), "Signing should have worked");
+        assert!(
+            result.unwrap(),
+            "Should finalize the input since we can produce signatures"
+        );
+
+        let extracted = psbt.extract_tx();
+        assert_eq!(
+            *extracted.input[0].witness.to_vec()[0].last().unwrap(),
+            sighash as u8,
+            "The signature should have been made with the right sighash"
         );
     }
 }