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