Merge rust-bitcoin/rust-secp256k1#527: Add constant time `SecretKey::eq`

b9eefea092 Add documentation on side channel attacks to SecretKey (Tobin C. Harding)
7cf3c6c8a4 Implement constant time comparison for SecretKey (Tobin C. Harding)
19039d9281 Remove `Ord`/`Hash` traits from SecretKey (Tobin C. Harding)
4a0c7fca6a Do not use impl_array_newtype for SecretKey (Tobin C. Harding)

Pull request description:

  Add constant time comparison implementation to `SecretKey`.

  This PR does as suggested [here](https://github.com/rust-bitcoin/rust-secp256k1/issues/471#issuecomment-1179783309) at the end of the issue discussion thread.

  Fix: #471

ACKs for top commit:
  apoelstra:
    ACK b9eefea092

Tree-SHA512: 217ed101b967cc048954547bcc0b3ab09e5ccf7c58e5dcb488370caf3f5567871152505a3bfb4558e59eea4849addaf1f11e1881b6744b0c90c633fa0157a5ae
This commit is contained in:
Andrew Poelstra 2022-11-22 15:03:28 +00:00
commit 17c8751d23
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 61 additions and 3 deletions

View File

@ -17,7 +17,7 @@
//! //!
use core::convert::TryFrom; use core::convert::TryFrom;
use core::ops::BitXor; use core::ops::{self, BitXor};
use core::{fmt, ptr, str}; use core::{fmt, ptr, str};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
@ -28,7 +28,7 @@ use crate::ffi::{self, CPtr};
#[cfg(all(feature = "global-context", feature = "rand-std"))] #[cfg(all(feature = "global-context", feature = "rand-std"))]
use crate::schnorr; use crate::schnorr;
use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey}; use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey};
use crate::{constants, from_hex, impl_array_newtype, Scalar, Secp256k1, Signing, Verification}; use crate::{constants, from_hex, Scalar, Secp256k1, Signing, Verification};
#[cfg(feature = "global-context")] #[cfg(feature = "global-context")]
use crate::{ecdsa, Message, SECP256K1}; use crate::{ecdsa, Message, SECP256K1};
#[cfg(feature = "bitcoin-hashes")] #[cfg(feature = "bitcoin-hashes")]
@ -36,6 +36,13 @@ use crate::{hashes, ThirtyTwoByteHash};
/// Secret 256-bit key used as `x` in an ECDSA signature. /// Secret 256-bit key used as `x` in an ECDSA signature.
/// ///
/// # Side channel attacks
///
/// We have attempted to reduce the side channel attack surface by implementing a constant time `eq`
/// method. For similar reasons we explicitly do not implement `PartialOrd`, `Ord`, or `Hash` on
/// `SecretKey`. If you really want to order secrets keys then you can use `AsRef` to get at the
/// underlying bytes and compare them - however this is almost certainly a bad idea.
///
/// # Serde support /// # Serde support
/// ///
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple /// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
@ -58,9 +65,60 @@ use crate::{hashes, ThirtyTwoByteHash};
/// [`cbor`]: https://docs.rs/cbor /// [`cbor`]: https://docs.rs/cbor
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]); pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE);
impl_display_secret!(SecretKey); impl_display_secret!(SecretKey);
impl PartialEq for SecretKey {
/// This implementation is designed to be constant time to help prevent side channel attacks.
#[inline]
fn eq(&self, other: &Self) -> bool {
let accum = self.0.iter().zip(&other.0)
.fold(0, |accum, (a, b)| accum | a ^ b);
unsafe { core::ptr::read_volatile(&accum) == 0 }
}
}
impl Eq for SecretKey {}
impl AsRef<[u8; constants::SECRET_KEY_SIZE]> for SecretKey {
/// Gets a reference to the underlying array.
///
/// # Side channel attacks
///
/// Using ordering functions (`PartialOrd`/`Ord`) on a reference to secret keys leaks data
/// because the implementations are not constant time. Doing so will make your code vulnerable
/// to side channel attacks. [`SecretKey::eq`] is implemented using a constant time algorithm,
/// please consider using it to do comparisons of secret keys.
#[inline]
fn as_ref(&self) -> &[u8; constants::SECRET_KEY_SIZE] {
let &SecretKey(ref dat) = self;
dat
}
}
impl<I> ops::Index<I> for SecretKey
where
[u8]: ops::Index<I>,
{
type Output = <[u8] as ops::Index<I>>::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output { &self.0[index] }
}
impl ffi::CPtr for SecretKey {
type Target = u8;
fn as_c_ptr(&self) -> *const Self::Target {
let &SecretKey(ref dat) = self;
dat.as_ptr()
}
fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
let &mut SecretKey(ref mut dat) = self;
dat.as_mut_ptr()
}
}
impl str::FromStr for SecretKey { impl str::FromStr for SecretKey {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<SecretKey, Error> { fn from_str(s: &str) -> Result<SecretKey, Error> {