Add signature grinding for ECDSA signatures

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.
This commit is contained in:
Vladimir Fomene 2022-10-06 13:30:43 +03:00
parent 7de8be46c0
commit 68dd6d2031
No known key found for this signature in database
GPG Key ID: 8BBAE8CC0B1530A0
2 changed files with 48 additions and 1 deletions

View File

@ -5524,6 +5524,7 @@ pub(crate) mod test {
SignOptions { SignOptions {
remove_partial_sigs: false, remove_partial_sigs: false,
try_finalize: false, try_finalize: false,
allow_grinding: false,
..Default::default() ..Default::default()
}, },
) )
@ -5538,6 +5539,7 @@ pub(crate) mod test {
&mut psbt, &mut psbt,
SignOptions { SignOptions {
remove_partial_sigs: false, remove_partial_sigs: false,
allow_grinding: false,
..Default::default() ..Default::default()
}, },
) )
@ -5546,6 +5548,39 @@ pub(crate) mod test {
assert_fee_rate!(psbt, details.fee.unwrap_or(0), fee_rate); 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")] #[cfg(feature = "test-hardware-signer")]
#[test] #[test]
fn test_create_signer() { fn test_create_signer() {

View File

@ -472,6 +472,7 @@ impl InputSigner for SignerWrapper<PrivateKey> {
hash, hash,
hash_ty, hash_ty,
secp, secp,
sign_options.allow_grinding,
); );
Ok(()) Ok(())
@ -485,9 +486,14 @@ fn sign_psbt_ecdsa(
hash: bitcoin::Sighash, hash: bitcoin::Sighash,
hash_ty: EcdsaSighashType, hash_ty: EcdsaSighashType,
secp: &SecpCtx, secp: &SecpCtx,
allow_grinding: bool,
) { ) {
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap(); 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) secp.verify_ecdsa(msg, &sig, &pubkey.inner)
.expect("invalid or corrupted ecdsa signature"); .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. /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
pub sign_with_tap_internal_key: bool, 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. /// Customize which taproot script-path leaves the signer should sign.
@ -751,6 +762,7 @@ impl Default for SignOptions {
try_finalize: true, try_finalize: true,
tap_leaves_options: TapLeavesOptions::default(), tap_leaves_options: TapLeavesOptions::default(),
sign_with_tap_internal_key: true, sign_with_tap_internal_key: true,
allow_grinding: true,
} }
} }
} }