Skip to main content

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