]> Untitled Git - bdk/commitdiff
Add signature grinding for ECDSA signatures
authorVladimir Fomene <vladimirfomene@gmail.com>
Thu, 6 Oct 2022 10:30:43 +0000 (13:30 +0300)
committerVladimir Fomene <vladimirfomene@gmail.com>
Mon, 17 Oct 2022 09:27:35 +0000 (12:27 +0300)
This PR adds a new field called `allow_grinding`
in the Signer's `SignOptions` struct that is used
to determine whether or not to grind an ECDSA signature
during the signing process.

src/wallet/mod.rs
src/wallet/signer.rs

index 2e3d9fdffbdab0081915082ba72ab5fb06825b8f..d804fd21bfd49a658ced86ac28608d30044ff882 100644 (file)
@@ -5524,6 +5524,7 @@ pub(crate) mod test {
                     SignOptions {
                         remove_partial_sigs: false,
                         try_finalize: false,
+                        allow_grinding: false,
                         ..Default::default()
                     },
                 )
@@ -5538,6 +5539,7 @@ pub(crate) mod test {
                 &mut psbt,
                 SignOptions {
                     remove_partial_sigs: false,
+                    allow_grinding: false,
                     ..Default::default()
                 },
             )
@@ -5546,6 +5548,39 @@ pub(crate) mod test {
         assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
     }
 
+    #[test]
+    fn test_fee_rate_sign_grinding_low_r() {
+        // Our goal is to obtain a transaction with a signature with low-R (70 bytes)
+        // by setting the `allow_grinding` signing option as true.
+        // We then check that our fee rate and fee calculation is alright and that our
+        // signature is 70 bytes.
+        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+        let addr = wallet.get_address(New).unwrap();
+        let fee_rate = FeeRate::from_sat_per_vb(1.0);
+        let mut builder = wallet.build_tx();
+        builder
+            .drain_to(addr.script_pubkey())
+            .drain_wallet()
+            .fee_rate(fee_rate);
+        let (mut psbt, details) = builder.finish().unwrap();
+
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    remove_partial_sigs: false,
+                    allow_grinding: true,
+                    ..Default::default()
+                },
+            )
+            .unwrap();
+
+        let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
+        let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
+        assert_eq!(sig_len, 70);
+        assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate);
+    }
+
     #[cfg(feature = "test-hardware-signer")]
     #[test]
     fn test_create_signer() {
index a20a232143e55cc1655b0667b67e9f17ce384e78..01bf5628ae777f2e418e67b52ff29852e18d3950 100644 (file)
@@ -472,6 +472,7 @@ impl InputSigner for SignerWrapper<PrivateKey> {
             hash,
             hash_ty,
             secp,
+            sign_options.allow_grinding,
         );
 
         Ok(())
@@ -485,9 +486,14 @@ fn sign_psbt_ecdsa(
     hash: bitcoin::Sighash,
     hash_ty: EcdsaSighashType,
     secp: &SecpCtx,
+    allow_grinding: bool,
 ) {
     let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
-    let sig = secp.sign_ecdsa(msg, secret_key);
+    let sig = if allow_grinding {
+        secp.sign_ecdsa_low_r(msg, secret_key)
+    } else {
+        secp.sign_ecdsa(msg, secret_key)
+    };
     secp.verify_ecdsa(msg, &sig, &pubkey.inner)
         .expect("invalid or corrupted ecdsa signature");
 
@@ -718,6 +724,11 @@ pub struct SignOptions {
     ///
     /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
     pub sign_with_tap_internal_key: bool,
+
+    /// Whether we should grind ECDSA signature to ensure signing with low r
+    /// or not.
+    /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
+    pub allow_grinding: bool,
 }
 
 /// Customize which taproot script-path leaves the signer should sign.
@@ -751,6 +762,7 @@ impl Default for SignOptions {
             try_finalize: true,
             tap_leaves_options: TapLeavesOptions::default(),
             sign_with_tap_internal_key: true,
+            allow_grinding: true,
         }
     }
 }