Skip to main content

ecdh_omr/
curves.rs

1// SPDX-FileCopyrightText: 2024-2026 eaon <eaon@posteo.net>
2// SPDX-License-Identifier: EUPL-1.2
3
4//! Trait implementations for elliptic curves.
5
6use rand_core::CryptoRng;
7
8use crate::{Decoy, KeyPair, RandomSecretKey};
9
10/// Alias-ish type for X25519-dalek key pairs.
11#[cfg(feature = "dalek-x25519")]
12#[derive(Debug, Clone)]
13pub struct DalekX25519 {}
14
15#[cfg(feature = "dalek-x25519")]
16pub(crate) mod dalek_x25519 {
17    use super::*;
18
19    use typenum::U32;
20
21    type PublicKey = x25519_dalek::PublicKey;
22    type StaticSecret = x25519_dalek::StaticSecret;
23
24    impl KeyPair for DalekX25519 {
25        type SecretKey = StaticSecret;
26        type PublicKey = PublicKey;
27        type PublicKeySize = U32;
28    }
29
30    impl Decoy for PublicKey {
31        fn random_decoy(csprng: &mut impl CryptoRng) -> Self {
32            let secret = StaticSecret::random_from_rng(csprng);
33
34            PublicKey::from(&secret)
35        }
36    }
37
38    impl RandomSecretKey for StaticSecret {
39        fn random_secret_key(csprng: &mut impl CryptoRng) -> Self {
40            StaticSecret::random_from_rng(csprng)
41        }
42    }
43}
44
45/// Alias-ish type for Ristretto255-dalek key pairs.
46#[cfg(feature = "dalek-ristretto255")]
47#[derive(Debug, Clone)]
48pub struct DalekRistretto255 {}
49
50#[cfg(feature = "dalek-ristretto255")]
51/// Ristretto255 helper structures
52pub mod dalek_ristretto255 {
53    use super::*;
54
55    use curve25519_dalek::{RistrettoPoint, traits::IsIdentity};
56    use hybrid_array::Array;
57    use typenum::U32;
58    #[cfg(feature = "zeroize")]
59    use zeroize::Zeroize;
60
61    /// A Ristretto255 Static Secret
62    #[cfg_attr(feature = "zeroize", derive(Zeroize))]
63    pub struct StaticSecret(pub(crate) curve25519_dalek::Scalar);
64
65    impl StaticSecret {
66        /// Generate a new [`StaticSecret`] with the supplied RNG.
67        pub fn random_from_rng(csprng: &mut impl CryptoRng) -> Self {
68            StaticSecret(curve25519_dalek::Scalar::random(csprng))
69        }
70
71        /// Perform a Diffie-Hellman key agreement between `self` and `their_public` key to produce
72        /// a [`SharedSecret`].
73        pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
74            SharedSecret(self.0 * their_public.0)
75        }
76
77        /// Convert this key to its byte representation.
78        pub fn to_bytes(&self) -> [u8; 32] {
79            self.0.to_bytes()
80        }
81
82        /// View this key as a byte array.
83        pub fn as_bytes(&self) -> &[u8; 32] {
84            self.0.as_bytes()
85        }
86    }
87
88    impl From<[u8; 32]> for StaticSecret {
89        fn from(bytes: [u8; 32]) -> Self {
90            StaticSecret(curve25519_dalek::Scalar::from_bytes_mod_order(bytes))
91        }
92    }
93
94    /// The result of a Diffie-Hellman key exchange.
95    #[cfg_attr(feature = "zeroize", derive(Zeroize))]
96    pub struct SharedSecret(pub(crate) RistrettoPoint);
97
98    impl SharedSecret {
99        /// Convert this shared secret to a byte array.
100        pub fn to_bytes(&self) -> Array<u8, U32> {
101            Array::from(self.0.compress().to_bytes())
102        }
103    }
104
105    /// A Ristretto255 Public Key
106    #[derive(Debug, Clone)]
107    #[cfg_attr(feature = "zeroize", derive(Zeroize))]
108    pub struct PublicKey(pub(crate) RistrettoPoint);
109
110    impl PublicKey {
111        /// Convert this public key to a byte array.
112        pub fn to_bytes(&self) -> [u8; 32] {
113            self.0.compress().to_bytes()
114        }
115    }
116
117    impl From<&StaticSecret> for PublicKey {
118        fn from(value: &StaticSecret) -> Self {
119            PublicKey(RistrettoPoint::mul_base(&value.0))
120        }
121    }
122
123    impl Decoy for PublicKey {
124        fn random_decoy(csprng: &mut impl CryptoRng) -> Self {
125            #[allow(unsafe_code)]
126            unsafe {
127                // SAFETY: Can never be None as it would run infinitely instead
128                std::iter::repeat_with(|| RistrettoPoint::random(csprng))
129                    .find(|p| !p.is_identity())
130                    .map(PublicKey)
131                    .unwrap_unchecked()
132            }
133        }
134    }
135
136    impl KeyPair for DalekRistretto255 {
137        type SecretKey = StaticSecret;
138        type PublicKey = PublicKey;
139        type PublicKeySize = U32;
140    }
141
142    impl RandomSecretKey for StaticSecret {
143        fn random_secret_key(csprng: &mut impl CryptoRng) -> Self {
144            StaticSecret::random_from_rng(csprng)
145        }
146    }
147}
148
149/// Alias-ish type for key pairs of RustCrypto's curve-agnostic ECDH implementation.
150#[cfg(feature = "rustcrypto-ec")]
151#[derive(Debug, Clone)]
152pub struct EllipticCurve<C> {
153    marker: std::marker::PhantomData<C>,
154}
155
156#[cfg(feature = "rustcrypto-ec")]
157mod rustcrypto_ec {
158    use super::*;
159
160    use elliptic_curve::{
161        Curve, CurveArithmetic, Generate, ProjectivePoint, PublicKey, SecretKey,
162        point::NonIdentity,
163        sec1::{CompressedPointSize, ModulusSize},
164    };
165
166    impl<C: CurveArithmetic> KeyPair for EllipticCurve<C>
167    where
168        <C as Curve>::FieldBytesSize: ModulusSize,
169    {
170        type SecretKey = SecretKey<C>;
171        type PublicKey = PublicKey<C>;
172        type PublicKeySize = CompressedPointSize<C>;
173    }
174
175    impl<C: CurveArithmetic> Decoy for PublicKey<C> {
176        fn random_decoy(csprng: &mut impl CryptoRng) -> Self {
177            PublicKey::<C>::from(NonIdentity::<ProjectivePoint<C>>::generate_from_rng(
178                &mut *csprng,
179            ))
180        }
181    }
182
183    impl<C: CurveArithmetic> RandomSecretKey for SecretKey<C> {
184        fn random_secret_key(csprng: &mut impl CryptoRng) -> Self {
185            SecretKey::<C>::generate_from_rng(csprng)
186        }
187    }
188}