miden_crypto/ecdh/
k256.rs

1//! ECDH (Elliptic Curve Diffie-Hellman) key agreement implementation over k256
2//! i.e., secp256k1 curve.
3//!
4//! Note that the intended use is in the context of a one-way, sender initiated key agreement
5//! scenario. Namely, when the sender knows the (static) public key of the receiver and it
6//! uses that, together with an ephemeral secret key that it generates, to derive a shared
7//! secret.
8//!
9//! This shared secret will then be used to encrypt some message (using for example a key
10//! derivation function).
11//!
12//! The public key associated with the ephemeral secret key will be sent alongside the encrypted
13//! message.
14
15use alloc::{string::ToString, vec::Vec};
16
17use hkdf::{Hkdf, hmac::SimpleHmac};
18use k256::{AffinePoint, elliptic_curve::sec1::ToEncodedPoint, sha2::Sha256};
19use rand::{CryptoRng, RngCore};
20
21use crate::{
22    dsa::ecdsa_k256_keccak::{PUBLIC_KEY_BYTES, PublicKey, SecretKey},
23    ecdh::KeyAgreementScheme,
24    utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
25    zeroize::{Zeroize, ZeroizeOnDrop},
26};
27
28// SHARED SECRET
29// ================================================================================================
30
31/// A shared secret computed using the ECDH (Elliptic Curve Diffie-Hellman) key agreement.
32///
33/// This type implements `ZeroizeOnDrop` because the inner `k256::ecdh::SharedSecret`
34/// implements it, ensuring the shared secret is securely wiped from memory when dropped.
35pub struct SharedSecret {
36    pub(crate) inner: k256::ecdh::SharedSecret,
37}
38
39impl SharedSecret {
40    pub(crate) fn new(inner: k256::ecdh::SharedSecret) -> SharedSecret {
41        Self { inner }
42    }
43
44    /// Returns a HKDF (HMAC-based Extract-and-Expand Key Derivation Function) that can be used
45    /// to extract entropy from the shared secret.
46    ///
47    /// This basically converts a shared secret into uniformly random values that are appropriate
48    /// for use as key material.
49    pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf<Sha256, SimpleHmac<Sha256>> {
50        self.inner.extract(salt)
51    }
52}
53
54impl AsRef<[u8]> for SharedSecret {
55    fn as_ref(&self) -> &[u8] {
56        self.inner.raw_secret_bytes()
57    }
58}
59
60impl Zeroize for SharedSecret {
61    /// Securely clears the shared secret from memory.
62    ///
63    /// # Security
64    ///
65    /// This implementation follows the same security methodology as the `zeroize` crate to ensure
66    /// that sensitive cryptographic material is reliably cleared from memory:
67    ///
68    /// - **Volatile writes**: Uses `ptr::write_volatile` to prevent dead store elimination and
69    ///   other compiler optimizations that might remove the zeroing operation.
70    /// - **Memory ordering**: Includes a sequentially consistent compiler fence (`SeqCst`) to
71    ///   prevent instruction reordering that could expose the secret data after this function
72    ///   returns.
73    fn zeroize(&mut self) {
74        let bytes = self.inner.raw_secret_bytes();
75        for byte in
76            unsafe { core::slice::from_raw_parts_mut(bytes.as_ptr() as *mut u8, bytes.len()) }
77        {
78            unsafe {
79                core::ptr::write_volatile(byte, 0u8);
80            }
81        }
82        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
83    }
84}
85
86// Safe to derive ZeroizeOnDrop because we implement Zeroize above
87impl ZeroizeOnDrop for SharedSecret {}
88
89// EPHEMERAL SECRET KEY
90// ================================================================================================
91
92/// Ephemeral secret key for ECDH key agreement over secp256k1 curve.
93///
94/// This type implements `ZeroizeOnDrop` because the inner `k256::ecdh::EphemeralSecret`
95/// implements it, ensuring the secret key material is securely wiped from memory when dropped.
96pub struct EphemeralSecretKey {
97    inner: k256::ecdh::EphemeralSecret,
98}
99
100impl EphemeralSecretKey {
101    /// Generates a new random ephemeral secret key using the OS random number generator.
102    #[cfg(feature = "std")]
103    #[allow(clippy::new_without_default)]
104    pub fn new() -> Self {
105        let mut rng = rand::rng();
106
107        Self::with_rng(&mut rng)
108    }
109
110    /// Generates a new ephemeral secret key using the provided random number generator.
111    pub fn with_rng<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
112        // we use a seedable CSPRNG and seed it with `rng`
113        // this is a work around the fact that the version of the `rand` dependency in our crate
114        // is different than the one used in the `k256` one. This solution will no longer be needed
115        // once `k256` gets a new release with a version of the `rand` dependency matching ours
116        use k256::elliptic_curve::rand_core::SeedableRng;
117        let mut seed = [0_u8; 32];
118        rand::RngCore::fill_bytes(rng, &mut seed);
119        let mut rng = rand_hc::Hc128Rng::from_seed(seed);
120
121        let sk_e = k256::ecdh::EphemeralSecret::random(&mut rng);
122        Self { inner: sk_e }
123    }
124
125    /// Gets the corresponding ephemeral public key for this ephemeral secret key.
126    pub fn public_key(&self) -> EphemeralPublicKey {
127        let pk = self.inner.public_key();
128        EphemeralPublicKey { inner: pk }
129    }
130
131    /// Computes a Diffie-Hellman shared secret from an ephemeral secret key and the (static) public
132    /// key of the other party.
133    pub fn diffie_hellman(&self, pk_other: PublicKey) -> SharedSecret {
134        let shared_secret_inner = self.inner.diffie_hellman(&pk_other.inner.into());
135
136        SharedSecret { inner: shared_secret_inner }
137    }
138}
139
140impl ZeroizeOnDrop for EphemeralSecretKey {}
141
142// EPHEMERAL PUBLIC KEY
143// ================================================================================================
144
145/// Ephemeral public key for ECDH key agreement over secp256k1 curve.
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct EphemeralPublicKey {
148    pub(crate) inner: k256::PublicKey,
149}
150
151impl EphemeralPublicKey {
152    /// Returns a reference to this ephemeral public key as an elliptic curve point in affine
153    /// coordinates.
154    pub fn as_affine(&self) -> &AffinePoint {
155        self.inner.as_affine()
156    }
157}
158
159impl Serializable for EphemeralPublicKey {
160    fn write_into<W: ByteWriter>(&self, target: &mut W) {
161        // Compressed format
162        let encoded = self.inner.to_encoded_point(true);
163
164        target.write_bytes(encoded.as_bytes());
165    }
166}
167
168impl Deserializable for EphemeralPublicKey {
169    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
170        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
171
172        let inner = k256::PublicKey::from_sec1_bytes(&bytes)
173            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
174
175        Ok(Self { inner })
176    }
177}
178
179// KEY AGREEMENT TRAIT IMPLEMENTATION
180// ================================================================================================
181
182pub struct K256;
183
184impl KeyAgreementScheme for K256 {
185    type EphemeralSecretKey = EphemeralSecretKey;
186    type EphemeralPublicKey = EphemeralPublicKey;
187
188    type SecretKey = SecretKey;
189    type PublicKey = PublicKey;
190
191    type SharedSecret = SharedSecret;
192
193    fn generate_ephemeral_keypair<R: CryptoRng + RngCore>(
194        rng: &mut R,
195    ) -> (Self::EphemeralSecretKey, Self::EphemeralPublicKey) {
196        let sk = EphemeralSecretKey::with_rng(rng);
197        let pk = sk.public_key();
198
199        (sk, pk)
200    }
201
202    fn exchange_ephemeral_static(
203        ephemeral_sk: Self::EphemeralSecretKey,
204        static_pk: &Self::PublicKey,
205    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
206        Ok(ephemeral_sk.diffie_hellman(static_pk.clone()))
207    }
208
209    fn exchange_static_ephemeral(
210        static_sk: &Self::SecretKey,
211        ephemeral_pk: &Self::EphemeralPublicKey,
212    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
213        Ok(static_sk.get_shared_secret(ephemeral_pk.clone()))
214    }
215
216    fn extract_key_material(
217        shared_secret: &Self::SharedSecret,
218        length: usize,
219    ) -> Result<Vec<u8>, super::KeyAgreementError> {
220        let hkdf = shared_secret.extract(None);
221        let mut buf = vec![0_u8; length];
222        hkdf.expand(&[], &mut buf)
223            .map_err(|_| super::KeyAgreementError::HkdfExpansionFailed)?;
224        Ok(buf)
225    }
226}
227
228// TESTS
229// ================================================================================================
230
231#[cfg(test)]
232mod test {
233
234    use rand::rng;
235    use winter_utils::{Deserializable, Serializable};
236
237    use super::{EphemeralPublicKey, EphemeralSecretKey};
238    use crate::dsa::ecdsa_k256_keccak::SecretKey;
239
240    #[test]
241    fn key_agreement() {
242        let mut rng = rng();
243
244        // 1. Generate the static key-pair for Alice
245        let sk = SecretKey::with_rng(&mut rng);
246        let pk = sk.public_key();
247
248        // 2. Generate the ephemeral key-pair for Bob
249        let sk_e = EphemeralSecretKey::with_rng(&mut rng);
250        let pk_e = sk_e.public_key();
251
252        // 3. Bob computes the shared secret key (Bob will send pk_e with the encrypted note to
253        //    Alice)
254        let shared_secret_key_1 = sk_e.diffie_hellman(pk);
255
256        // 4. Alice uses its secret key and the ephemeral public key sent with the encrypted note by
257        //    Bob in order to create the shared secret key. This shared secret key will be used to
258        //    decrypt the encrypted note
259        let shared_secret_key_2 = sk.get_shared_secret(pk_e);
260
261        // Check that the computed shared secret keys are equal
262        assert_eq!(
263            shared_secret_key_1.inner.raw_secret_bytes(),
264            shared_secret_key_2.inner.raw_secret_bytes()
265        );
266    }
267
268    #[test]
269    fn test_serialization_round_trip() {
270        let mut rng = rng();
271
272        let sk_e = EphemeralSecretKey::with_rng(&mut rng);
273        let pk_e = sk_e.public_key();
274
275        let pk_e_bytes = pk_e.to_bytes();
276        let pk_e_serialized = EphemeralPublicKey::read_from_bytes(&pk_e_bytes)
277            .expect("failed to desrialize ephemeral public key");
278        assert_eq!(pk_e_serialized, pk_e);
279    }
280}