Skip to content

Commit 17c8751

Browse files
committed
Merge #527: Add constant time SecretKey::eq
b9eefea Add documentation on side channel attacks to SecretKey (Tobin C. Harding) 7cf3c6c Implement constant time comparison for SecretKey (Tobin C. Harding) 19039d9 Remove `Ord`/`Hash` traits from SecretKey (Tobin C. Harding) 4a0c7fc 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](#471 (comment)) at the end of the issue discussion thread. Fix: #471 ACKs for top commit: apoelstra: ACK b9eefea Tree-SHA512: 217ed101b967cc048954547bcc0b3ab09e5ccf7c58e5dcb488370caf3f5567871152505a3bfb4558e59eea4849addaf1f11e1881b6744b0c90c633fa0157a5ae
2 parents ba47a25 + b9eefea commit 17c8751

File tree

1 file changed

+61
-3
lines changed

1 file changed

+61
-3
lines changed

src/key.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//!
1818
1919
use core::convert::TryFrom;
20-
use core::ops::BitXor;
20+
use core::ops::{self, BitXor};
2121
use core::{fmt, ptr, str};
2222

2323
#[cfg(feature = "serde")]
@@ -28,14 +28,21 @@ use crate::ffi::{self, CPtr};
2828
#[cfg(all(feature = "global-context", feature = "rand-std"))]
2929
use crate::schnorr;
3030
use crate::Error::{self, InvalidPublicKey, InvalidPublicKeySum, InvalidSecretKey};
31-
use crate::{constants, from_hex, impl_array_newtype, Scalar, Secp256k1, Signing, Verification};
31+
use crate::{constants, from_hex, Scalar, Secp256k1, Signing, Verification};
3232
#[cfg(feature = "global-context")]
3333
use crate::{ecdsa, Message, SECP256K1};
3434
#[cfg(feature = "bitcoin-hashes")]
3535
use crate::{hashes, ThirtyTwoByteHash};
3636

3737
/// Secret 256-bit key used as `x` in an ECDSA signature.
3838
///
39+
/// # Side channel attacks
40+
///
41+
/// We have attempted to reduce the side channel attack surface by implementing a constant time `eq`
42+
/// method. For similar reasons we explicitly do not implement `PartialOrd`, `Ord`, or `Hash` on
43+
/// `SecretKey`. If you really want to order secrets keys then you can use `AsRef` to get at the
44+
/// underlying bytes and compare them - however this is almost certainly a bad idea.
45+
///
3946
/// # Serde support
4047
///
4148
/// Implements de/serialization with the `serde` feature enabled. We treat the byte value as a tuple
@@ -58,9 +65,60 @@ use crate::{hashes, ThirtyTwoByteHash};
5865
/// [`cbor`]: https://docs.rs/cbor
5966
#[derive(Copy, Clone)]
6067
pub struct SecretKey([u8; constants::SECRET_KEY_SIZE]);
61-
impl_array_newtype!(SecretKey, u8, constants::SECRET_KEY_SIZE);
6268
impl_display_secret!(SecretKey);
6369

70+
impl PartialEq for SecretKey {
71+
/// This implementation is designed to be constant time to help prevent side channel attacks.
72+
#[inline]
73+
fn eq(&self, other: &Self) -> bool {
74+
let accum = self.0.iter().zip(&other.0)
75+
.fold(0, |accum, (a, b)| accum | a ^ b);
76+
unsafe { core::ptr::read_volatile(&accum) == 0 }
77+
}
78+
}
79+
80+
impl Eq for SecretKey {}
81+
82+
impl AsRef<[u8; constants::SECRET_KEY_SIZE]> for SecretKey {
83+
/// Gets a reference to the underlying array.
84+
///
85+
/// # Side channel attacks
86+
///
87+
/// Using ordering functions (`PartialOrd`/`Ord`) on a reference to secret keys leaks data
88+
/// because the implementations are not constant time. Doing so will make your code vulnerable
89+
/// to side channel attacks. [`SecretKey::eq`] is implemented using a constant time algorithm,
90+
/// please consider using it to do comparisons of secret keys.
91+
#[inline]
92+
fn as_ref(&self) -> &[u8; constants::SECRET_KEY_SIZE] {
93+
let &SecretKey(ref dat) = self;
94+
dat
95+
}
96+
}
97+
98+
impl<I> ops::Index<I> for SecretKey
99+
where
100+
[u8]: ops::Index<I>,
101+
{
102+
type Output = <[u8] as ops::Index<I>>::Output;
103+
104+
#[inline]
105+
fn index(&self, index: I) -> &Self::Output { &self.0[index] }
106+
}
107+
108+
impl ffi::CPtr for SecretKey {
109+
type Target = u8;
110+
111+
fn as_c_ptr(&self) -> *const Self::Target {
112+
let &SecretKey(ref dat) = self;
113+
dat.as_ptr()
114+
}
115+
116+
fn as_mut_c_ptr(&mut self) -> *mut Self::Target {
117+
let &mut SecretKey(ref mut dat) = self;
118+
dat.as_mut_ptr()
119+
}
120+
}
121+
64122
impl str::FromStr for SecretKey {
65123
type Err = Error;
66124
fn from_str(s: &str) -> Result<SecretKey, Error> {

0 commit comments

Comments
 (0)