diff --git a/src/lib.rs b/src/lib.rs index d1878a3cc..43a3b1d17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -521,6 +521,8 @@ pub enum Error { InvalidRecoveryId, /// Invalid tweak for add_*_assign or mul_*_assign InvalidTweak, + /// `tweak_add_check` failed on an xonly public key + TweakCheckFailed, /// Didn't pass enough memory to context creation with preallocated memory NotEnoughMemory, } @@ -535,6 +537,7 @@ impl Error { Error::InvalidSecretKey => "secp: malformed or out-of-range secret key", Error::InvalidRecoveryId => "secp: bad recovery id", Error::InvalidTweak => "secp: bad tweak", + Error::TweakCheckFailed => "secp: xonly_pubkey_tewak_add_check failed", Error::NotEnoughMemory => "secp: not enough memory allocated", } } diff --git a/src/schnorrsig.rs b/src/schnorrsig.rs index 5a01a523e..d76f9b2d2 100644 --- a/src/schnorrsig.rs +++ b/src/schnorrsig.rs @@ -169,7 +169,7 @@ impl KeyPair { /// Will return an error if the resulting key would be invalid or if /// the tweak was not a 32-byte length slice. #[inline] - pub fn add_assign( + pub fn tweak_add_assign( &mut self, secp: &Secp256k1, tweak: &[u8], @@ -264,14 +264,17 @@ impl PublicKey { ret } - /// Tweak a schnorrsig PublicKey by adding the generator multiplied with the given tweak to it. - /// Will return an error if the resulting key would be invalid or if - /// the tweak was not a 32-byte length slice. - pub fn add_assign( + /// Tweak an x-only PublicKey by adding the generator multiplied with the given tweak to it. + /// + /// Returns a boolean representing the parity of the tweaked key, which can be provided to + /// `tweak_add_check` which can be used to verify a tweak more efficiently than regenerating + /// it and checking equality. Will return an error if the resulting key would be invalid or + /// if the tweak was not a 32-byte length slice. + pub fn tweak_add_assign( &mut self, secp: &Secp256k1, tweak: &[u8], - ) -> Result<(), Error> { + ) -> Result { if tweak.len() != 32 { return Err(Error::InvalidTweak); } @@ -289,18 +292,57 @@ impl PublicKey { return Err(Error::InvalidTweak); } + let mut parity: ::secp256k1_sys::types::c_int = 0; err = ffi::secp256k1_xonly_pubkey_from_pubkey( secp.ctx, &mut self.0 as *mut _, - ptr::null_mut(), + &mut parity as *mut _, &pubkey, ); - return if err == 0 { + if err == 0 { Err(Error::InvalidPublicKey) } else { + Ok(parity != 0) + } + } + } + + /// Verify that a tweak produced by `tweak_add_assign` was computed correctly + /// + /// Should be called on the original untweaked key. Takes the tweaked key and + /// output parity from `tweak_add_assign` as input. + /// + /// Currently this is not much more efficient than just recomputing the tweak + /// and checking equality. However, in future this API will support batch + /// verification, which is significantly faster, so it is wise to design + /// protocols with this in mind. + pub fn tweak_add_check( + &self, + secp: &Secp256k1, + tweaked_key: &Self, + tweaked_parity: bool, + tweak: &[u8], + ) -> Result<(), Error> { + if tweak.len() != 32 { + return Err(Error::InvalidTweak); + } + + let tweaked_ser = tweaked_key.serialize(); + unsafe { + let err = ffi::secp256k1_xonly_pubkey_tweak_add_check( + secp.ctx, + tweaked_ser.as_c_ptr(), + if tweaked_parity { 1 } else { 0 }, + &self.0 as *const _, + tweak.as_c_ptr(), + ); + + if err == 1 { Ok(()) - }; + } else { + Err(Error::TweakCheckFailed) + } } } } @@ -720,9 +762,11 @@ mod tests { let mut tweak = [0u8; 32]; thread_rng().fill_bytes(&mut tweak); let (mut kp, mut pk) = s.generate_schnorrsig_keypair(&mut thread_rng()); - kp.add_assign(&s, &tweak).expect("Tweak error"); - pk.add_assign(&s, &tweak).expect("Tweak error"); + let orig_pk = pk; + kp.tweak_add_assign(&s, &tweak).expect("Tweak error"); + let parity = pk.tweak_add_assign(&s, &tweak).expect("Tweak error"); assert_eq!(PublicKey::from_keypair(&s, &kp), pk); + orig_pk.tweak_add_check(&s, &pk, parity, &tweak).expect("tweak check"); } }