Skip to main content

aptos_crypto_link/ed25519/
ed25519_keys.rs

1// Copyright (c) Aptos
2// SPDX-License-Identifier: Apache-2.0
3
4//! This file implements traits for Ed25519 private keys and public keys.
5
6use crate::{
7    ed25519::{Ed25519Signature, ED25519_PRIVATE_KEY_LENGTH, ED25519_PUBLIC_KEY_LENGTH},
8    hash::CryptoHash,
9    traits::*,
10};
11use aptos_crypto_derive::{DeserializeKey, SerializeKey, SilentDebug, SilentDisplay};
12use core::convert::TryFrom;
13use serde::Serialize;
14use std::fmt;
15
16#[cfg(any(test, feature = "fuzzing"))]
17use crate::test_utils::{self, KeyPair};
18#[cfg(any(test, feature = "fuzzing"))]
19use proptest::prelude::*;
20
21/// An Ed25519 private key
22#[derive(DeserializeKey, SerializeKey, SilentDebug, SilentDisplay)]
23pub struct Ed25519PrivateKey(pub(crate) ed25519_dalek::SecretKey);
24
25#[cfg(feature = "assert-private-keys-not-cloneable")]
26static_assertions::assert_not_impl_any!(Ed25519PrivateKey: Clone);
27
28#[cfg(any(test, feature = "cloneable-private-keys"))]
29impl Clone for Ed25519PrivateKey {
30    fn clone(&self) -> Self {
31        let serialized: &[u8] = &(self.to_bytes());
32        Ed25519PrivateKey::try_from(serialized).unwrap()
33    }
34}
35
36/// An Ed25519 public key
37#[derive(DeserializeKey, Clone, SerializeKey)]
38pub struct Ed25519PublicKey(pub(crate) ed25519_dalek::PublicKey);
39
40impl Ed25519PrivateKey {
41    /// The length of the Ed25519PrivateKey
42    pub const LENGTH: usize = ed25519_dalek::SECRET_KEY_LENGTH;
43
44    /// Serialize an Ed25519PrivateKey.
45    pub fn to_bytes(&self) -> [u8; ED25519_PRIVATE_KEY_LENGTH] {
46        self.0.to_bytes()
47    }
48
49    /// Deserialize an Ed25519PrivateKey without any validation checks apart from expected key size.
50    fn from_bytes_unchecked(
51        bytes: &[u8],
52    ) -> std::result::Result<Ed25519PrivateKey, CryptoMaterialError> {
53        match ed25519_dalek::SecretKey::from_bytes(bytes) {
54            Ok(dalek_secret_key) => Ok(Ed25519PrivateKey(dalek_secret_key)),
55            Err(_) => Err(CryptoMaterialError::DeserializationError),
56        }
57    }
58
59    /// Private function aimed at minimizing code duplication between sign
60    /// methods of the SigningKey implementation. This should remain private.
61    fn sign_arbitrary_message(&self, message: &[u8]) -> Ed25519Signature {
62        let secret_key: &ed25519_dalek::SecretKey = &self.0;
63        let public_key: Ed25519PublicKey = self.into();
64        let expanded_secret_key: ed25519_dalek::ExpandedSecretKey =
65            ed25519_dalek::ExpandedSecretKey::from(secret_key);
66        let sig = expanded_secret_key.sign(message.as_ref(), &public_key.0);
67        Ed25519Signature(sig)
68    }
69}
70
71impl Ed25519PublicKey {
72    /// Serialize an Ed25519PublicKey.
73    pub fn to_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_LENGTH] {
74        self.0.to_bytes()
75    }
76
77    /// Deserialize an Ed25519PublicKey without any validation checks apart from expected key size
78    /// and valid curve point, although not necessarily in the prime-order subgroup.
79    ///
80    /// This function does NOT check the public key for membership in a small subgroup.
81    pub(crate) fn from_bytes_unchecked(
82        bytes: &[u8],
83    ) -> std::result::Result<Ed25519PublicKey, CryptoMaterialError> {
84        match ed25519_dalek::PublicKey::from_bytes(bytes) {
85            Ok(dalek_public_key) => Ok(Ed25519PublicKey(dalek_public_key)),
86            Err(_) => Err(CryptoMaterialError::DeserializationError),
87        }
88    }
89
90    /// Deserialize an Ed25519PublicKey from its representation as an x25519
91    /// public key, along with an indication of sign. This is meant to
92    /// compensate for the poor key storage capabilities of key management
93    /// solutions, and NOT to promote double usage of keys under several
94    /// schemes, which would lead to BAD vulnerabilities.
95    ///
96    /// This function does NOT check if the public key lies in a small subgroup.
97    ///
98    /// Arguments:
99    /// - `x25519_bytes`: bit representation of a public key in clamped
100    ///            Montgomery form, a.k.a. the x25519 public key format.
101    /// - `negative`: whether to interpret the given point as a negative point,
102    ///               as the Montgomery form erases the sign byte. By XEdDSA
103    ///               convention, if you expect to ever convert this back to an
104    ///               x25519 public key, you should pass `false` for this
105    ///               argument.
106    #[cfg(test)]
107    pub(crate) fn from_x25519_public_bytes(
108        x25519_bytes: &[u8],
109        negative: bool,
110    ) -> Result<Self, CryptoMaterialError> {
111        if x25519_bytes.len() != 32 {
112            return Err(CryptoMaterialError::DeserializationError);
113        }
114        let key_bits = {
115            let mut bits = [0u8; 32];
116            bits.copy_from_slice(x25519_bytes);
117            bits
118        };
119        let mtg_point = curve25519_dalek::montgomery::MontgomeryPoint(key_bits);
120        let sign = if negative { 1u8 } else { 0u8 };
121        let ed_point = mtg_point
122            .to_edwards(sign)
123            .ok_or(CryptoMaterialError::DeserializationError)?;
124        Ed25519PublicKey::try_from(&ed_point.compress().as_bytes()[..])
125    }
126}
127
128///////////////////////
129// PrivateKey Traits //
130///////////////////////
131
132impl PrivateKey for Ed25519PrivateKey {
133    type PublicKeyMaterial = Ed25519PublicKey;
134}
135
136impl SigningKey for Ed25519PrivateKey {
137    type VerifyingKeyMaterial = Ed25519PublicKey;
138    type SignatureMaterial = Ed25519Signature;
139
140    fn sign<T: CryptoHash + Serialize>(
141        &self,
142        message: &T,
143    ) -> Result<Ed25519Signature, CryptoMaterialError> {
144        Ok(Ed25519PrivateKey::sign_arbitrary_message(
145            self,
146            signing_message(message)?.as_ref(),
147        ))
148    }
149
150    #[cfg(any(test, feature = "fuzzing"))]
151    fn sign_arbitrary_message(&self, message: &[u8]) -> Ed25519Signature {
152        Ed25519PrivateKey::sign_arbitrary_message(self, message)
153    }
154}
155
156impl Uniform for Ed25519PrivateKey {
157    fn generate<R>(rng: &mut R) -> Self
158    where
159        R: ::rand::RngCore + ::rand::CryptoRng + ::rand_core::CryptoRng + ::rand_core::RngCore,
160    {
161        Ed25519PrivateKey(ed25519_dalek::SecretKey::generate(rng))
162    }
163}
164
165impl PartialEq<Self> for Ed25519PrivateKey {
166    fn eq(&self, other: &Self) -> bool {
167        self.to_bytes() == other.to_bytes()
168    }
169}
170
171impl Eq for Ed25519PrivateKey {}
172
173// We could have a distinct kind of validation for the PrivateKey: e.g., checking the derived
174// PublicKey is valid?
175impl TryFrom<&[u8]> for Ed25519PrivateKey {
176    type Error = CryptoMaterialError;
177
178    /// Deserialize an Ed25519PrivateKey. This method will check for private key validity: i.e.,
179    /// correct key length.
180    fn try_from(bytes: &[u8]) -> std::result::Result<Ed25519PrivateKey, CryptoMaterialError> {
181        // Note that the only requirement is that the size of the key is 32 bytes, something that
182        // is already checked during deserialization of ed25519_dalek::SecretKey
183        //
184        // Also, the underlying ed25519_dalek implementation ensures that the derived public key
185        // is safe and it will not lie in a small-order group, thus no extra check for PublicKey
186        // validation is required.
187        Ed25519PrivateKey::from_bytes_unchecked(bytes)
188    }
189}
190
191impl Length for Ed25519PrivateKey {
192    fn length(&self) -> usize {
193        Self::LENGTH
194    }
195}
196
197impl ValidCryptoMaterial for Ed25519PrivateKey {
198    fn to_bytes(&self) -> Vec<u8> {
199        self.to_bytes().to_vec()
200    }
201}
202
203impl Genesis for Ed25519PrivateKey {
204    fn genesis() -> Self {
205        let mut buf = [0u8; ED25519_PRIVATE_KEY_LENGTH];
206        buf[ED25519_PRIVATE_KEY_LENGTH - 1] = 1;
207        Self::try_from(buf.as_ref()).unwrap()
208    }
209}
210
211//////////////////////
212// PublicKey Traits //
213//////////////////////
214
215// Implementing From<&PrivateKey<...>> allows to derive a public key in a more elegant fashion
216impl From<&Ed25519PrivateKey> for Ed25519PublicKey {
217    fn from(private_key: &Ed25519PrivateKey) -> Self {
218        let secret: &ed25519_dalek::SecretKey = &private_key.0;
219        let public: ed25519_dalek::PublicKey = secret.into();
220        Ed25519PublicKey(public)
221    }
222}
223
224// We deduce PublicKey from this
225impl PublicKey for Ed25519PublicKey {
226    type PrivateKeyMaterial = Ed25519PrivateKey;
227}
228
229impl std::hash::Hash for Ed25519PublicKey {
230    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
231        let encoded_pubkey = self.to_bytes();
232        state.write(&encoded_pubkey);
233    }
234}
235
236// Those are required by the implementation of hash above
237impl PartialEq for Ed25519PublicKey {
238    fn eq(&self, other: &Ed25519PublicKey) -> bool {
239        self.to_bytes() == other.to_bytes()
240    }
241}
242
243impl Eq for Ed25519PublicKey {}
244
245// We deduce VerifyingKey from pointing to the signature material
246// we get the ability to do `pubkey.validate(msg, signature)`
247impl VerifyingKey for Ed25519PublicKey {
248    type SigningKeyMaterial = Ed25519PrivateKey;
249    type SignatureMaterial = Ed25519Signature;
250}
251
252impl fmt::Display for Ed25519PublicKey {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        write!(f, "{}", hex::encode(&self.0.as_bytes()))
255    }
256}
257
258impl fmt::Debug for Ed25519PublicKey {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        write!(f, "Ed25519PublicKey({})", self)
261    }
262}
263
264impl TryFrom<&[u8]> for Ed25519PublicKey {
265    type Error = CryptoMaterialError;
266
267    /// Deserialize an Ed25519PublicKey. This method will NOT check for key validity, which means
268    /// the returned public key could be in a small subgroup. Nonetheless, our signature
269    /// verification implicitly checks if the public key lies in a small subgroup, so canonical
270    /// uses of this library will not be susceptible to small subgroup attacks.
271    fn try_from(bytes: &[u8]) -> std::result::Result<Ed25519PublicKey, CryptoMaterialError> {
272        Ed25519PublicKey::from_bytes_unchecked(bytes)
273    }
274}
275
276impl Length for Ed25519PublicKey {
277    fn length(&self) -> usize {
278        ED25519_PUBLIC_KEY_LENGTH
279    }
280}
281
282impl ValidCryptoMaterial for Ed25519PublicKey {
283    fn to_bytes(&self) -> Vec<u8> {
284        self.0.to_bytes().to_vec()
285    }
286}
287
288/////////////
289// Fuzzing //
290/////////////
291
292/// Produces a uniformly random Ed25519 keypair from a seed
293#[cfg(any(test, feature = "fuzzing"))]
294pub fn keypair_strategy() -> impl Strategy<Value = KeyPair<Ed25519PrivateKey, Ed25519PublicKey>> {
295    test_utils::uniform_keypair_strategy::<Ed25519PrivateKey, Ed25519PublicKey>()
296}
297
298/// Produces a uniformly random Ed25519 public key
299#[cfg(any(test, feature = "fuzzing"))]
300impl proptest::arbitrary::Arbitrary for Ed25519PublicKey {
301    type Parameters = ();
302    type Strategy = BoxedStrategy<Self>;
303
304    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
305        crate::test_utils::uniform_keypair_strategy::<Ed25519PrivateKey, Ed25519PublicKey>()
306            .prop_map(|v| v.public_key)
307            .boxed()
308    }
309}