diem_crypto/
x25519.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8//! An abstraction of x25519 elliptic curve keys required for
9//! [Diffie-Hellman key exchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange)
10//! in the Diem project.
11//! Ideally, only `x25519::PrivateKey` and `x25519::PublicKey` should be used
12//! throughout the codebase, until the bytes are actually used in cryptographic
13//! operations.
14//!
15//! # Examples
16//!
17//! ```
18//! use diem_crypto::{test_utils::TEST_SEED, x25519, Uniform};
19//! use rand::{rngs::StdRng, SeedableRng};
20//!
21//! // Derive an X25519 private key for testing.
22//! let mut rng: StdRng = SeedableRng::from_seed(TEST_SEED);
23//! let private_key = x25519::PrivateKey::generate(&mut rng);
24//! let public_key = private_key.public_key();
25//!
26//! // Deserialize an hexadecimal private or public key
27//! use diem_crypto::traits::ValidCryptoMaterialStringExt;
28//! # fn main() -> Result<(), diem_crypto::traits::CryptoMaterialError> {
29//! let private_key =
30//!     "404acc8ec6a0f18df7359a6ee7823f19dd95616b10fed8bdb0de030e891b945a";
31//! let private_key = x25519::PrivateKey::from_encoded_string(&private_key)?;
32//! let public_key =
33//!     "080e287879c918794170e258bfaddd75acac5b3e350419044655e4983a487120";
34//! let public_key = x25519::PublicKey::from_encoded_string(&public_key)?;
35//! # Ok(())
36//! # }
37//! ```
38
39use crate::{
40    traits::{
41        self, CryptoMaterialError, ValidCryptoMaterial,
42        ValidCryptoMaterialStringExt,
43    },
44    x25519,
45};
46use diem_crypto_derive::{
47    DeserializeKey, SerializeKey, SilentDebug, SilentDisplay,
48};
49use rand::{CryptoRng, RngCore};
50use std::convert::{TryFrom, TryInto};
51
52#[cfg(any(test, feature = "fuzzing"))]
53use proptest_derive::Arbitrary;
54
55//
56// Underlying Implementation
57// =========================
58//
59// We re-export the dalek-x25519 library,
60// This makes it easier to uniformalize build dalek-x25519 in diem-core.
61//
62
63pub use x25519_dalek;
64
65//
66// Main types and constants
67// ========================
68//
69
70/// Size of a X25519 private key
71pub const PRIVATE_KEY_SIZE: usize = 32;
72
73/// Size of a X25519 public key
74pub const PUBLIC_KEY_SIZE: usize = 32;
75
76/// Size of a X25519 shared secret
77pub const SHARED_SECRET_SIZE: usize = 32;
78
79/// This type should be used to deserialize a received private key
80#[derive(DeserializeKey, SilentDisplay, SilentDebug, SerializeKey)]
81#[cfg_attr(any(test, feature = "fuzzing"), derive(Clone))]
82pub struct PrivateKey(x25519_dalek::StaticSecret);
83
84/// This type should be used to deserialize a received public key
85#[derive(
86    Default,
87    Clone,
88    Copy,
89    PartialEq,
90    Eq,
91    Hash,
92    PartialOrd,
93    Ord,
94    SerializeKey,
95    DeserializeKey,
96)]
97#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
98pub struct PublicKey([u8; PUBLIC_KEY_SIZE]);
99
100//
101// Handy implementations
102// =====================
103//
104
105impl PrivateKey {
106    /// Obtain the public key part of a private key
107    pub fn public_key(&self) -> PublicKey {
108        let public_key: x25519_dalek::PublicKey = (&self.0).into();
109        PublicKey(public_key.as_bytes().to_owned())
110    }
111
112    /// To perform a key exchange with another public key
113    pub fn diffie_hellman(
114        &self, remote_public_key: &PublicKey,
115    ) -> [u8; SHARED_SECRET_SIZE] {
116        let remote_public_key =
117            x25519_dalek::PublicKey::from(remote_public_key.0);
118        let shared_secret = self.0.diffie_hellman(&remote_public_key);
119        shared_secret.as_bytes().to_owned()
120    }
121
122    /// Deserialize an X25119 PrivateKey given the sha512 pre-image of a hash
123    /// whose least significant half is a canonical X25519 scalar, following
124    /// the XEdDSA approach.
125    ///
126    /// This will FAIL if the passed-in byte representation converts to a
127    /// non-canonical scalar in the X25519 sense (and thus cannot correspond to
128    /// a X25519 valid key without bit-mangling).
129    ///
130    /// This is meant to compensate for the poor key storage capabilities of
131    /// some key management solutions, and NOT to promote double usage of
132    /// keys under several schemes, which would lead to BAD vulnerabilities.
133    pub fn from_ed25519_private_bytes(
134        private_slice: &[u8],
135    ) -> Result<Self, CryptoMaterialError> {
136        let ed25519_secretkey =
137            ed25519_dalek::SecretKey::from_bytes(private_slice)
138                .map_err(|_| CryptoMaterialError::DeserializationError)?;
139        let expanded_key =
140            ed25519_dalek::ExpandedSecretKey::from(&ed25519_secretkey);
141
142        let mut expanded_keypart = [0u8; 32];
143        expanded_keypart.copy_from_slice(&expanded_key.to_bytes()[..32]);
144        let potential_x25519 = x25519::PrivateKey::from(expanded_keypart);
145
146        // This checks for x25519 clamping & reduction, which is an RFC
147        // requirement
148        if potential_x25519.to_bytes()[..] != expanded_key.to_bytes()[..32] {
149            Err(CryptoMaterialError::DeserializationError)
150        } else {
151            Ok(potential_x25519)
152        }
153    }
154}
155
156impl PublicKey {
157    /// Obtain a slice reference to the underlying bytearray
158    pub fn as_slice(&self) -> &[u8] { &self.0 }
159
160    /// Deserialize an X25119 PublicKey from its representation as an
161    /// Ed25519PublicKey, following the XEdDSA approach. This is meant to
162    /// compensate for the poor key storage capabilities of key management
163    /// solutions, and NOT to promote double usage of keys under several
164    /// schemes, which would lead to BAD vulnerabilities.
165    pub fn from_ed25519_public_bytes(
166        ed25519_bytes: &[u8],
167    ) -> Result<Self, CryptoMaterialError> {
168        if ed25519_bytes.len() != 32 {
169            return Err(CryptoMaterialError::DeserializationError);
170        }
171        let ed_point =
172            curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
173                ed25519_bytes,
174            )
175            .decompress()
176            .ok_or(CryptoMaterialError::DeserializationError)?;
177
178        Ok(x25519::PublicKey::from(ed_point.to_montgomery().to_bytes()))
179    }
180}
181
182//
183// Traits implementations
184// ======================
185//
186
187// private key part
188
189impl std::convert::From<[u8; PRIVATE_KEY_SIZE]> for PrivateKey {
190    fn from(private_key_bytes: [u8; PRIVATE_KEY_SIZE]) -> Self {
191        Self(x25519_dalek::StaticSecret::from(private_key_bytes))
192    }
193}
194
195impl std::convert::TryFrom<&[u8]> for PrivateKey {
196    type Error = traits::CryptoMaterialError;
197
198    fn try_from(private_key_bytes: &[u8]) -> Result<Self, Self::Error> {
199        let private_key_bytes: [u8; PRIVATE_KEY_SIZE] = private_key_bytes
200            .try_into()
201            .map_err(|_| traits::CryptoMaterialError::DeserializationError)?;
202        Ok(Self(x25519_dalek::StaticSecret::from(private_key_bytes)))
203    }
204}
205
206impl traits::PrivateKey for PrivateKey {
207    type PublicKeyMaterial = PublicKey;
208}
209
210impl traits::Uniform for PrivateKey {
211    fn generate<R>(rng: &mut R) -> Self
212    where R: RngCore + CryptoRng {
213        Self(x25519_dalek::StaticSecret::new(rng))
214    }
215}
216
217// TODO: should this be gated under test flag? (mimoo)
218impl traits::ValidCryptoMaterial for PrivateKey {
219    fn to_bytes(&self) -> Vec<u8> { self.0.to_bytes().to_vec() }
220}
221
222#[cfg(any(test, feature = "fuzzing"))]
223impl PartialEq for PrivateKey {
224    fn eq(&self, other: &Self) -> bool { self.to_bytes() == other.to_bytes() }
225}
226
227#[cfg(any(test, feature = "fuzzing"))]
228impl proptest::arbitrary::Arbitrary for PrivateKey {
229    type Parameters = ();
230    type Strategy = proptest::strategy::BoxedStrategy<Self>;
231
232    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
233        use proptest::strategy::Strategy as _;
234        proptest::arbitrary::any::<[u8; 32]>()
235            .prop_map(PrivateKey::from)
236            .boxed()
237    }
238}
239
240// public key part
241
242impl From<&PrivateKey> for PublicKey {
243    fn from(private_key: &PrivateKey) -> Self { private_key.public_key() }
244}
245
246impl std::convert::From<[u8; PUBLIC_KEY_SIZE]> for PublicKey {
247    fn from(public_key_bytes: [u8; PUBLIC_KEY_SIZE]) -> Self {
248        Self(public_key_bytes)
249    }
250}
251
252impl std::convert::TryFrom<&[u8]> for PublicKey {
253    type Error = traits::CryptoMaterialError;
254
255    fn try_from(public_key_bytes: &[u8]) -> Result<Self, Self::Error> {
256        let public_key_bytes: [u8; PUBLIC_KEY_SIZE] = public_key_bytes
257            .try_into()
258            .map_err(|_| traits::CryptoMaterialError::WrongLengthError)?;
259        Ok(Self(public_key_bytes))
260    }
261}
262
263impl traits::PublicKey for PublicKey {
264    type PrivateKeyMaterial = PrivateKey;
265}
266
267impl traits::ValidCryptoMaterial for PublicKey {
268    fn to_bytes(&self) -> Vec<u8> { self.0.to_vec() }
269}
270
271impl std::fmt::Display for PublicKey {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        write!(f, "{}", hex::encode(&self.0))
274    }
275}
276
277impl std::fmt::Debug for PublicKey {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        write!(f, "x25519::PublicKey({})", self)
280    }
281}
282
283#[cfg(any(test, feature = "fuzzing"))]
284use crate::test_utils::{self, KeyPair};
285#[cfg(any(test, feature = "fuzzing"))]
286use proptest::prelude::*;
287
288/// Produces a uniformly random ed25519 keypair from a seed
289#[cfg(any(test, feature = "fuzzing"))]
290pub fn keypair_strategy(
291) -> impl Strategy<Value = KeyPair<PrivateKey, PublicKey>> {
292    test_utils::uniform_keypair_strategy::<PrivateKey, PublicKey>()
293}