Skip to content

Commit 74364ee

Browse files
authored
x448: make secret an ephemeralsecret (#1378)
This rewrites `Secret` to an opaque object to make sure they are not reused.
1 parent 2d8e025 commit 74364ee

File tree

1 file changed

+50
-86
lines changed

1 file changed

+50
-86
lines changed

x448/src/lib.rs

Lines changed: 50 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#![no_std]
22

3-
use core::array::TryFromSliceError;
43
use ed448_goldilocks::{
54
MontgomeryPoint,
65
elliptic_curve::{
@@ -14,21 +13,10 @@ use zeroize::Zeroize;
1413

1514
type MontgomeryScalar = ed448_goldilocks::Scalar<ed448_goldilocks::Ed448>;
1615

17-
/// Computes a Scalar according to RFC7748
18-
/// given a byte array of length 56
19-
impl From<[u8; 56]> for Secret {
20-
fn from(arr: [u8; 56]) -> Secret {
21-
let mut secret = Secret(arr.into());
22-
secret.clamp();
23-
secret
24-
}
25-
}
26-
27-
/// Given a Secret Key, compute the corresponding public key
16+
/// Given an [`EphemeralSecret`] Key, compute the corresponding public key
2817
/// using the generator specified in RFC7748
29-
/// XXX: Waiting for upstream PR to use pre-computation
30-
impl From<&Secret> for PublicKey {
31-
fn from(secret: &Secret) -> PublicKey {
18+
impl From<&EphemeralSecret> for PublicKey {
19+
fn from(secret: &EphemeralSecret) -> PublicKey {
3220
let secret = secret.as_scalar();
3321
let point = &MontgomeryPoint::GENERATOR * &secret;
3422
PublicKey(point)
@@ -39,10 +27,10 @@ impl From<&Secret> for PublicKey {
3927
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
4028
pub struct PublicKey(MontgomeryPoint);
4129

42-
/// A Secret is a Scalar on Curve448.
30+
/// An [`EphemeralSecret`] is a Scalar on Curve448.
4331
#[derive(Clone, Zeroize)]
4432
#[zeroize(drop)]
45-
pub struct Secret(Array<u8, U56>);
33+
pub struct EphemeralSecret(Array<u8, U56>);
4634

4735
/// A SharedSecret is a point on Curve448.
4836
/// This point is the result of a Diffie-Hellman key exchange.
@@ -62,6 +50,7 @@ impl PublicKey {
6250
}
6351
Some(public_key)
6452
}
53+
6554
/// Converts a bytes slice into a Public key
6655
/// Returns None if:
6756
/// - The length of the slice is not 56
@@ -77,11 +66,6 @@ impl PublicKey {
7766

7867
Some(PublicKey(point))
7968
}
80-
81-
/// Converts a public key into a byte slice
82-
pub fn as_bytes(&self) -> &[u8; 56] {
83-
self.0.as_bytes()
84-
}
8569
}
8670

8771
impl SharedSecret {
@@ -91,18 +75,24 @@ impl SharedSecret {
9175
}
9276
}
9377

94-
impl Secret {
78+
impl EphemeralSecret {
79+
fn new(value: Array<u8, U56>) -> Self {
80+
let mut out = Self(value);
81+
out.clamp();
82+
out
83+
}
84+
9585
/// Generate a x448 `Secret` key.
9686
// Taken from dalek-x25519
97-
pub fn new<T>(csprng: &mut T) -> Self
87+
pub fn random_from_rng<T>(csprng: &mut T) -> Self
9888
where
9989
T: RngCore + CryptoRng + ?Sized,
10090
{
101-
let mut bytes = [0u8; 56];
91+
let mut bytes = Array::default();
10292

103-
csprng.fill_bytes(&mut bytes);
93+
csprng.fill_bytes(bytes.as_mut_slice());
10494

105-
Secret::from(bytes)
95+
Self::new(bytes)
10696
}
10797

10898
/// Clamps the secret key according to RFC7748
@@ -111,20 +101,18 @@ impl Secret {
111101
self.0[55] |= 128;
112102
}
113103

114-
/// Views a Secret as a Scalar
104+
/// Views an Secret as a Scalar
115105
fn as_scalar(&self) -> MontgomeryScalar {
116106
let secret = U448::from_le_slice(&self.0);
117107
MontgomeryScalar::from_uint_unchecked(secret)
118108
}
119109

120110
/// Performs a Diffie-hellman key exchange between the secret key and an external public key
121-
pub fn as_diffie_hellman(&self, public_key: &PublicKey) -> Option<SharedSecret> {
122-
// Check if the point is one of the low order points
123-
if public_key.0.is_low_order() {
124-
return None;
125-
}
111+
pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret {
112+
// NOTE(security): it is assumed PublicKey is not a low_order. It should be checked when
113+
// created.
126114
let shared_key = &public_key.0 * &self.as_scalar();
127-
Some(SharedSecret(shared_key))
115+
SharedSecret(shared_key)
128116
}
129117

130118
/// Converts a secret into a byte array
@@ -133,16 +121,6 @@ impl Secret {
133121
}
134122
}
135123

136-
impl TryFrom<&[u8]> for Secret {
137-
type Error = TryFromSliceError;
138-
139-
fn try_from(bytes: &[u8]) -> Result<Secret, TryFromSliceError> {
140-
let mut secret = Secret(Array::try_from(bytes)?);
141-
secret.clamp();
142-
Ok(secret)
143-
}
144-
}
145-
146124
fn slice_to_array(bytes: &[u8]) -> [u8; 56] {
147125
let mut array: [u8; 56] = [0; 56];
148126
array.copy_from_slice(bytes);
@@ -157,14 +135,14 @@ fn slice_to_array(bytes: &[u8]) -> [u8; 56] {
157135
/// [1]: https://github.com/rust-lang/nomicon/issues/59
158136
pub fn x448(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> Option<[u8; 56]> {
159137
let point = PublicKey::from_bytes(&point_bytes)?;
160-
let scalar = Secret::from(scalar_bytes).as_scalar();
138+
let scalar = EphemeralSecret::new(scalar_bytes.into()).as_scalar();
161139
Some((&point.0 * &scalar).0)
162140
}
163141
/// An unchecked version of the x448 function defined in RFC448
164142
/// No checks are made on the points.
165143
pub fn x448_unchecked(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> [u8; 56] {
166144
let point = MontgomeryPoint(point_bytes);
167-
let scalar = Secret::from(scalar_bytes).as_scalar();
145+
let scalar = EphemeralSecret::new(scalar_bytes.into()).as_scalar();
168146
(&point * &scalar).0
169147
}
170148

@@ -176,45 +154,31 @@ mod test {
176154

177155
use super::*;
178156
use alloc::vec;
157+
use core::array::TryFromSliceError;
179158

180-
#[test]
181-
fn test_low_order() {
182-
// Notice, that this is the only way to add low order points into the system
183-
// and this is not exposed to the user. The user will use `from_bytes` which will check for low order points.
184-
let bad_key_a = PublicKey(MontgomeryPoint::LOW_A);
185-
let checked_bad_key_a = PublicKey::from_bytes(&MontgomeryPoint::LOW_A.0);
186-
assert!(checked_bad_key_a.is_none());
187-
188-
let bad_key_b = PublicKey(MontgomeryPoint::LOW_B);
189-
let checked_bad_key_b = PublicKey::from_bytes(&MontgomeryPoint::LOW_B.0);
190-
assert!(checked_bad_key_b.is_none());
191-
192-
let bad_key_c = PublicKey(MontgomeryPoint::LOW_C);
193-
let checked_bad_key_c = PublicKey::from_bytes(&MontgomeryPoint::LOW_C.0);
194-
assert!(checked_bad_key_c.is_none());
195-
196-
let mut rng = rand::rng();
197-
let bob_priv = Secret::new(&mut rng);
198-
199-
// If for some reason, these low order points are added to the system
200-
// The Diffie-Hellman key exchange for the honest party will return None.
201-
let shared_bob = bob_priv.as_diffie_hellman(&bad_key_a);
202-
assert!(shared_bob.is_none());
159+
/// Helpers to test properties of EphemeralSecret. Those `From` and `TryFrom` are not meant to
160+
/// be exposed outside tests.
161+
impl From<[u8; 56]> for EphemeralSecret {
162+
fn from(arr: [u8; 56]) -> Self {
163+
Self::new(arr.into())
164+
}
165+
}
203166

204-
let shared_bob = bob_priv.as_diffie_hellman(&bad_key_b);
205-
assert!(shared_bob.is_none());
167+
impl TryFrom<&[u8]> for EphemeralSecret {
168+
type Error = TryFromSliceError;
206169

207-
let shared_bob = bob_priv.as_diffie_hellman(&bad_key_c);
208-
assert!(shared_bob.is_none());
170+
fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceError> {
171+
Ok(Self::new(Array::try_from(bytes)?))
172+
}
209173
}
210174

211175
#[test]
212176
fn test_random_dh() {
213177
let mut rng = rand::rng();
214-
let alice_priv = Secret::new(&mut rng);
178+
let alice_priv = EphemeralSecret::random_from_rng(&mut rng);
215179
let alice_pub = PublicKey::from(&alice_priv);
216180

217-
let bob_priv = Secret::new(&mut rng);
181+
let bob_priv = EphemeralSecret::random_from_rng(&mut rng);
218182
let bob_pub = PublicKey::from(&bob_priv);
219183

220184
// Since Alice and Bob are both using the API correctly
@@ -225,15 +189,15 @@ mod test {
225189

226190
// Both Alice and Bob perform the DH key exchange.
227191
// As mentioned above, we unwrap because both Parties are using the API correctly.
228-
let shared_alice = alice_priv.as_diffie_hellman(&bob_pub).unwrap();
229-
let shared_bob = bob_priv.as_diffie_hellman(&alice_pub).unwrap();
192+
let shared_alice = alice_priv.diffie_hellman(&bob_pub);
193+
let shared_bob = bob_priv.diffie_hellman(&alice_pub);
230194

231195
assert_eq!(shared_alice.as_bytes()[..], shared_bob.as_bytes()[..]);
232196
}
233197

234198
#[test]
235199
fn test_rfc_test_vectors_alice_bob() {
236-
let alice_priv = Secret::from([
200+
let alice_priv = EphemeralSecret::from([
237201
0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58,
238202
0x0, 0xd4, 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d,
239203
0xd9, 0xc9, 0xba, 0xf5, 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x0, 0x63,
@@ -247,9 +211,9 @@ mod test {
247211
0xc5, 0xd9, 0xbb, 0xc8, 0x36, 0x64, 0x72, 0x41, 0xd9, 0x53, 0xd4, 0xc, 0x5b, 0x12,
248212
0xda, 0x88, 0x12, 0xd, 0x53, 0x17, 0x7f, 0x80, 0xe5, 0x32, 0xc4, 0x1f, 0xa0,
249213
];
250-
assert_eq!(got_alice_pub.as_bytes()[..], expected_alice_pub[..]);
214+
assert_eq!(got_alice_pub.0.as_bytes()[..], expected_alice_pub[..]);
251215

252-
let bob_priv = Secret::from([
216+
let bob_priv = EphemeralSecret::from([
253217
0x1c, 0x30, 0x6a, 0x7a, 0xc2, 0xa0, 0xe2, 0xe0, 0x99, 0xb, 0x29, 0x44, 0x70, 0xcb,
254218
0xa3, 0x39, 0xe6, 0x45, 0x37, 0x72, 0xb0, 0x75, 0x81, 0x1d, 0x8f, 0xad, 0xd, 0x1d,
255219
0x69, 0x27, 0xc1, 0x20, 0xbb, 0x5e, 0xe8, 0x97, 0x2b, 0xd, 0x3e, 0x21, 0x37, 0x4c,
@@ -263,10 +227,10 @@ mod test {
263227
0x27, 0xd8, 0xb9, 0x72, 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x6,
264228
0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, 0x7, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x9,
265229
];
266-
assert_eq!(got_bob_pub.as_bytes()[..], expected_bob_pub[..]);
230+
assert_eq!(got_bob_pub.0.as_bytes()[..], expected_bob_pub[..]);
267231

268-
let bob_shared = bob_priv.as_diffie_hellman(&got_alice_pub).unwrap();
269-
let alice_shared = alice_priv.as_diffie_hellman(&got_bob_pub).unwrap();
232+
let bob_shared = bob_priv.diffie_hellman(&got_alice_pub);
233+
let alice_shared = alice_priv.diffie_hellman(&got_bob_pub);
270234
assert_eq!(bob_shared.as_bytes()[..], alice_shared.as_bytes()[..]);
271235

272236
let expected_shared = [
@@ -338,11 +302,11 @@ mod test {
338302

339303
for vector in test_vectors {
340304
let public_key = PublicKey::from_bytes(&vector.point).unwrap();
341-
let secret = Secret::try_from(&vector.secret[..]).unwrap();
305+
let secret = EphemeralSecret::try_from(&vector.secret[..]).unwrap();
342306

343-
let got = secret.as_diffie_hellman(&public_key).unwrap();
307+
let got = secret.diffie_hellman(&public_key);
344308

345-
assert_eq!(got.as_bytes()[..], vector.expected[..])
309+
assert_eq!(got.0.as_bytes()[..], vector.expected[..])
346310
}
347311
}
348312

0 commit comments

Comments
 (0)