diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 2e3d9fdf..d804fd21 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -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() { diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index a20a2321..01bf5628 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -472,6 +472,7 @@ impl InputSigner for SignerWrapper { 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, } } }