]> Untitled Git - bdk/commitdiff
[wallet] Eagerly finalize inputs
authorLLFourn <lloyd.fourn@gmail.com>
Tue, 17 Nov 2020 06:53:06 +0000 (17:53 +1100)
committerLLFourn <lloyd.fourn@gmail.com>
Mon, 23 Nov 2020 05:07:50 +0000 (16:07 +1100)
If we know the final witness/scriptsig for an input we should add it
right away to the PSBT. Before, if we couldn't finalize any of them we
finalized none of them.

src/wallet/mod.rs

index aecef31e66c5d2490af876e907472ae2b543934c..9b4f44482a38cd2c24a24b378e8493f333e8e623 100644 (file)
@@ -858,9 +858,14 @@ where
         mut psbt: PSBT,
         assume_height: Option<u32>,
     ) -> Result<(PSBT, bool), Error> {
-        let mut tx = psbt.global.unsigned_tx.clone();
+        let tx = &psbt.global.unsigned_tx;
+        let mut finished = true;
 
-        for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
+        for (n, input) in tx.input.iter().enumerate() {
+            let psbt_input = &psbt.inputs[n];
+            if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
+                continue;
+            }
             // if the height is None in the database it means it's still unconfirmed, so consider
             // that as a very high value
             let create_height = self
@@ -881,52 +886,53 @@ where
             //   is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
             //   `redeem_script` and `witness_script` to determine the right derivation
             // - If that also fails, it will try it on the internal descriptor, if present
-            let desc = if let Some(desc) = psbt
+            let desc = psbt
                 .get_utxo_for(n)
                 .map(|txout| self.get_descriptor_for_txout(&txout))
                 .transpose()?
                 .flatten()
-            {
-                desc
-            } else if let Some(desc) =
-                self.descriptor
-                    .derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
-            {
-                desc
-            } else if let Some(desc) = self.change_descriptor.as_ref().and_then(|desc| {
-                desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
-            }) {
-                desc
-            } else {
-                debug!("Couldn't find the right derived descriptor for input {}", n);
-                return Ok((psbt, false));
-            };
-
-            let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
-            match desc.satisfy(
-                input,
-                (
-                    PsbtInputSatisfier::new(&psbt, n),
-                    After::new(current_height, false),
-                    Older::new(current_height, create_height, false),
-                ),
-                deriv_ctx,
-            ) {
-                Ok(_) => continue,
-                Err(e) => {
-                    debug!("satisfy error {:?} for input {}", e, n);
-                    return Ok((psbt, false));
+                .or_else(|| {
+                    self.descriptor.derive_from_psbt_input(
+                        psbt_input,
+                        psbt.get_utxo_for(n),
+                        &self.secp,
+                    )
+                })
+                .or_else(|| {
+                    self.change_descriptor.as_ref().and_then(|desc| {
+                        desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
+                    })
+                });
+
+            match desc {
+                Some(desc) => {
+                    let mut tmp_input = bitcoin::TxIn::default();
+                    let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
+                    match desc.satisfy(
+                        &mut tmp_input,
+                        (
+                            PsbtInputSatisfier::new(&psbt, n),
+                            After::new(current_height, false),
+                            Older::new(current_height, create_height, false),
+                        ),
+                        deriv_ctx,
+                    ) {
+                        Ok(_) => {
+                            let psbt_input = &mut psbt.inputs[n];
+                            psbt_input.final_script_sig = Some(tmp_input.script_sig);
+                            psbt_input.final_script_witness = Some(tmp_input.witness);
+                        }
+                        Err(e) => {
+                            debug!("satisfy error {:?} for input {}", e, n);
+                            finished = false
+                        }
+                    }
                 }
+                None => finished = false,
             }
         }
 
-        // consume tx to extract its input's script_sig and witnesses and move them into the psbt
-        for (input, psbt_input) in tx.input.into_iter().zip(psbt.inputs.iter_mut()) {
-            psbt_input.final_script_sig = Some(input.script_sig);
-            psbt_input.final_script_witness = Some(input.witness);
-        }
-
-        Ok((psbt, true))
+        Ok((psbt, finished))
     }
 
     pub fn secp_ctx(&self) -> &SecpCtx {
@@ -3254,4 +3260,38 @@ mod test {
             .iter()
             .any(|output| output.redeem_script.is_some() && output.witness_script.is_some()));
     }
+
+    #[test]
+    fn test_signing_only_one_of_multiple_inputs() {
+        let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
+        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+        let (mut psbt, _) = wallet
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)])
+                    .include_output_redeem_witness_script(),
+            )
+            .unwrap();
+
+        // add another input to the psbt that is at least passable.
+        let mut dud_input = bitcoin::util::psbt::Input::default();
+        dud_input.witness_utxo = Some(TxOut {
+            value: 100_000,
+            script_pubkey: miniscript::Descriptor::<bitcoin::PublicKey>::from_str(
+                "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
+            )
+            .unwrap()
+            .script_pubkey(miniscript::NullCtx),
+        });
+        psbt.inputs.push(dud_input);
+        psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());
+        let (psbt, is_final) = wallet.sign(psbt, None).unwrap();
+        assert!(
+            !is_final,
+            "shouldn't be final since we can't sign one of the inputs"
+        );
+        assert!(
+            psbt.inputs[0].final_script_witness.is_some(),
+            "should finalized input it signed"
+        )
+    }
 }