crypto/keys/
x25519.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4// https://tools.ietf.org/html/rfc7748
5// https://cr.yp.to/ecdh/curve25519-20060209.pdf
6
7// nomenclature compromise:
8// RFC7748   Bernstein's 2006 paper
9// scalar/u  secret/public
10// X25519    Curve25519
11
12#[cfg(feature = "ed25519")]
13use core::convert::TryFrom;
14use core::convert::TryInto;
15
16use zeroize::{Zeroize, ZeroizeOnDrop};
17
18#[cfg(feature = "ed25519")]
19use crate::signatures::ed25519;
20
21pub const PUBLIC_KEY_LENGTH: usize = 32;
22pub const SECRET_KEY_LENGTH: usize = 32;
23
24/// An X25519 Shared Secret - the result of a Diffie-Hellman key exchange.
25///
26/// Each party computes this from an (ephemeral) [`SecretKey`] and their counterparty's [`PublicKey`].
27pub type SharedSecret = x25519_dalek::SharedSecret;
28
29/// An X25519 Public Key
30#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
31pub struct PublicKey(x25519_dalek::PublicKey);
32
33impl PublicKey {
34    pub fn from_bytes(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self {
35        Self(bytes.into())
36    }
37
38    /// Load a [`PublicKey`] from a slice of bytes.
39    pub fn try_from_slice(slice: &[u8]) -> crate::Result<Self> {
40        let bytes: [u8; PUBLIC_KEY_LENGTH] = slice.try_into().map_err(|_| crate::Error::ConvertError {
41            from: "bytes",
42            to: "X25519 Public Key",
43        })?;
44
45        Ok(Self::from_bytes(bytes))
46    }
47
48    /// Returns the [`PublicKey`] as an array of bytes.
49    pub fn to_bytes(self) -> [u8; PUBLIC_KEY_LENGTH] {
50        self.0.to_bytes()
51    }
52
53    /// Returns the [`PublicKey`] as a slice of bytes.
54    pub fn as_slice(&self) -> &[u8] {
55        self.0.as_bytes()
56    }
57}
58
59#[cfg(feature = "ed25519")]
60impl TryFrom<&ed25519::PublicKey> for PublicKey {
61    type Error = crate::Error;
62    fn try_from(pk: &ed25519::PublicKey) -> crate::Result<Self> {
63        // We need to get `EdwardsPoint` from `pk`.
64        // `pk.as_slice()` returns compressed Edwards Y coordinate bytes.
65        let mut y_bytes = [0_u8; 32];
66        y_bytes.copy_from_slice(pk.as_slice());
67        // Try reconstruct X,Y,Z,T coordinates of `EdwardsPoint` from it.
68        match curve25519_dalek::edwards::CompressedEdwardsY(y_bytes).decompress() {
69            Some(decompressed_edwards) => {
70                // `pk` is a valid `ed25519::PublicKey` hence contains valid `EdwardsPoint`.
71                // x25519 uses Montgomery form, and `x25519::PublicKey` is just a `MontgomeryPoint`.
72                // `MontgomeryPoint` can be constructed from `EdwardsPoint` with `to_montgomery()` method.
73                // Can't construct `x25519::PublicKey` directly from `MontgomeryPoint`,
74                // do it via intermediate bytes.
75                Ok(PublicKey::from_bytes(decompressed_edwards.to_montgomery().to_bytes()))
76            }
77            None => Err(crate::error::Error::ConvertError {
78                from: "ed25519 public key",
79                to: "x25519 public key",
80            }),
81        }
82    }
83}
84
85impl AsRef<[u8]> for PublicKey {
86    fn as_ref(&self) -> &[u8] {
87        self.0.as_bytes()
88    }
89}
90
91impl From<[u8; PUBLIC_KEY_LENGTH]> for PublicKey {
92    fn from(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self {
93        Self::from_bytes(bytes)
94    }
95}
96
97impl From<PublicKey> for [u8; PUBLIC_KEY_LENGTH] {
98    fn from(pk: PublicKey) -> Self {
99        pk.to_bytes()
100    }
101}
102
103/// An X25519 Secret Key
104#[derive(Zeroize, ZeroizeOnDrop)]
105pub struct SecretKey(x25519_dalek::StaticSecret);
106
107impl SecretKey {
108    /// Generate a new random [`SecretKey`].
109    #[cfg(feature = "random")]
110    #[cfg_attr(docsrs, doc(cfg(feature = "random")))]
111    pub fn generate() -> crate::Result<Self> {
112        let mut bytes: [u8; SECRET_KEY_LENGTH] = [0; SECRET_KEY_LENGTH];
113        crate::utils::rand::fill(&mut bytes[..])?;
114        Ok(Self::from_bytes(bytes))
115    }
116
117    #[cfg(feature = "rand")]
118    pub fn generate_with<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
119        let mut bs = [0_u8; SECRET_KEY_LENGTH];
120        rng.fill_bytes(&mut bs);
121        Self::from_bytes(bs)
122    }
123
124    pub fn from_bytes(bytes: [u8; SECRET_KEY_LENGTH]) -> Self {
125        Self(bytes.into())
126    }
127
128    /// Load a [`SecretKey`] from a slice of bytes.
129    pub fn try_from_slice(slice: &[u8]) -> crate::Result<Self> {
130        let bytes: [u8; SECRET_KEY_LENGTH] = slice.try_into().map_err(|_| crate::Error::ConvertError {
131            from: "bytes",
132            to: "X25519 Secret Key",
133        })?;
134
135        Ok(Self::from_bytes(bytes))
136    }
137
138    /// Returns the [`SecretKey`] as an array of bytes.
139    pub fn to_bytes(&self) -> [u8; SECRET_KEY_LENGTH] {
140        self.0.to_bytes()
141    }
142
143    /// Returns the [`PublicKey`] which corresponds to this [`SecretKey`].
144    pub fn public_key(&self) -> PublicKey {
145        PublicKey((&self.0).into())
146    }
147
148    /// Computes the Diffie-Hellman [`SharedSecret`] from the [`SecretKey`] and the given [`PublicKey`].
149    pub fn diffie_hellman(&self, public: &PublicKey) -> SharedSecret {
150        self.0.diffie_hellman(&public.0)
151    }
152}
153
154#[cfg(all(feature = "ed25519", feature = "sha"))]
155impl From<&ed25519::SecretKey> for SecretKey {
156    fn from(sk: &ed25519::SecretKey) -> SecretKey {
157        // We need to extract scalar from `sk`.
158        // It's not directly accessible from `sk`,
159        // nor can `x25519::SecretKey` be constructed directly with a scalar.
160        // `ed25519::SecretKey` only exposes seed bytes,
161        // we have to reconstruct scalar bytes from seed.
162        use crate::hashes::Digest;
163        let h = crate::hashes::sha::Sha512::digest(sk.as_slice());
164        // The low half of hash is used as scalar bytes.
165        let mut scalar_bytes = [0u8; 32];
166        scalar_bytes[..].copy_from_slice(&h.as_slice()[0..32]);
167        // No need to do "clamping" here, it's done in `x25519::SecretKey::from_bytes()` constructor.
168        SecretKey::from_bytes(scalar_bytes)
169    }
170}