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    bytes: [u8; 32],
42}
43
44impl SharedSecret {
45    pub(crate) fn new(inner: k256::ecdh::SharedSecret) -> SharedSecret {
46        let mut bytes = [0u8; 32];
47        bytes.copy_from_slice(inner.raw_secret_bytes());
48        Self { bytes }
49    }
50
51    /// Returns a HKDF (HMAC-based Extract-and-Expand Key Derivation Function) that can be used
52    /// to extract entropy from the shared secret.
53    ///
54    /// This basically converts a shared secret into uniformly random values that are appropriate
55    /// for use as key material.
56    pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf<Sha256> {
57        Hkdf::new(salt, &self.bytes)
58    }
59}
60
61impl AsRef<[u8]> for SharedSecret {
62    fn as_ref(&self) -> &[u8] {
63        &self.bytes
64    }
65}
66
67impl Zeroize for SharedSecret {
68    fn zeroize(&mut self) {
69        self.bytes.zeroize();
70    }
71}
72
73impl Drop for SharedSecret {
74    fn drop(&mut self) {
75        self.zeroize();
76    }
77}
78
79impl ZeroizeOnDrop for SharedSecret {}
80
81// EPHEMERAL SECRET KEY
82// ================================================================================================
83
84/// Ephemeral secret key for ECDH key agreement over secp256k1 curve.
85///
86/// This type implements `ZeroizeOnDrop` because the inner `k256::ecdh::EphemeralSecret`
87/// implements it, ensuring the secret key material is securely wiped from memory when dropped.
88pub struct EphemeralSecretKey {
89    inner: k256::ecdh::EphemeralSecret,
90}
91
92impl EphemeralSecretKey {
93    /// Generates a new random ephemeral secret key using the OS random number generator.
94    #[cfg(feature = "std")]
95    #[allow(clippy::new_without_default)]
96    pub fn new() -> Self {
97        let mut rng = rand::rng();
98
99        Self::with_rng(&mut rng)
100    }
101
102    /// Generates a new ephemeral secret key using the provided random number generator.
103    pub fn with_rng<R: CryptoRng>(rng: &mut R) -> Self {
104        let sk_e = k256::ecdh::EphemeralSecret::generate_from_rng(rng);
105        Self { inner: sk_e }
106    }
107
108    /// Gets the corresponding ephemeral public key for this ephemeral secret key.
109    pub fn public_key(&self) -> EphemeralPublicKey {
110        let pk = self.inner.public_key();
111        EphemeralPublicKey { inner: pk }
112    }
113
114    /// Computes a Diffie-Hellman shared secret from an ephemeral secret key and the (static) public
115    /// key of the other party.
116    pub fn diffie_hellman(&self, pk_other: PublicKey) -> SharedSecret {
117        let shared_secret_inner = self.inner.diffie_hellman(&pk_other.inner.into());
118
119        SharedSecret::new(shared_secret_inner)
120    }
121}
122
123impl ZeroizeOnDrop for EphemeralSecretKey {}
124
125// EPHEMERAL PUBLIC KEY
126// ================================================================================================
127
128/// Ephemeral public key for ECDH key agreement over secp256k1 curve.
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct EphemeralPublicKey {
131    pub(crate) inner: k256::PublicKey,
132}
133
134impl EphemeralPublicKey {
135    /// Returns a reference to this ephemeral public key as an elliptic curve point in affine
136    /// coordinates.
137    pub fn as_affine(&self) -> &AffinePoint {
138        self.inner.as_affine()
139    }
140}
141
142#[cfg(any(test, feature = "arbitrary"))]
143mod ephemeral_key_arbitrary {
144    use proptest::prelude::*;
145    use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
146
147    use super::EphemeralPublicKey;
148    use crate::ecdh::k256::EphemeralSecretKey;
149
150    impl Arbitrary for EphemeralPublicKey {
151        type Parameters = ();
152        type Strategy = BoxedStrategy<Self>;
153
154        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
155            // The reader decompresses the curve point; derive it from a deterministically
156            // seeded ephemeral secret - valid by construction.
157            any::<[u8; 32]>()
158                .prop_map(|seed| {
159                    let mut rng = ChaCha20Rng::from_seed(seed);
160                    EphemeralSecretKey::with_rng(&mut rng).public_key()
161                })
162                .boxed()
163        }
164    }
165}
166
167impl Serializable for EphemeralPublicKey {
168    fn write_into<W: ByteWriter>(&self, target: &mut W) {
169        // Compressed format
170        let encoded = self.inner.to_sec1_point(true);
171
172        target.write_bytes(encoded.as_bytes());
173    }
174}
175
176impl Deserializable for EphemeralPublicKey {
177    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
178        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
179
180        let inner = k256::PublicKey::from_sec1_bytes(&bytes)
181            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
182
183        Ok(Self { inner })
184    }
185}
186
187// KEY AGREEMENT TRAIT IMPLEMENTATION
188// ================================================================================================
189
190pub struct K256;
191
192impl KeyAgreementScheme for K256 {
193    type EphemeralSecretKey = EphemeralSecretKey;
194    type EphemeralPublicKey = EphemeralPublicKey;
195
196    type SecretKey = KeyExchangeKey;
197    type PublicKey = PublicKey;
198
199    type SharedSecret = SharedSecret;
200
201    fn generate_ephemeral_keypair<R: CryptoRng>(
202        rng: &mut R,
203    ) -> (Self::EphemeralSecretKey, Self::EphemeralPublicKey) {
204        let sk = EphemeralSecretKey::with_rng(rng);
205        let pk = sk.public_key();
206
207        (sk, pk)
208    }
209
210    fn exchange_ephemeral_static(
211        ephemeral_sk: Self::EphemeralSecretKey,
212        static_pk: &Self::PublicKey,
213    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
214        Ok(ephemeral_sk.diffie_hellman(static_pk.clone()))
215    }
216
217    fn exchange_static_ephemeral(
218        static_sk: &Self::SecretKey,
219        ephemeral_pk: &Self::EphemeralPublicKey,
220    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
221        Ok(static_sk.get_shared_secret(ephemeral_pk.clone()))
222    }
223
224    fn extract_key_material(
225        shared_secret: &Self::SharedSecret,
226        length: usize,
227        info: &[u8],
228    ) -> Result<Vec<u8>, super::KeyAgreementError> {
229        super::extract_key_material(shared_secret.as_ref(), None, length, info)
230    }
231}
232
233// TESTS
234// ================================================================================================
235
236#[cfg(test)]
237mod test {
238    use super::{EphemeralPublicKey, EphemeralSecretKey};
239    use crate::{
240        dsa::ecdsa_k256_keccak::KeyExchangeKey,
241        rand::test_utils::seeded_rng,
242        utils::{Deserializable, Serializable},
243    };
244
245    #[test]
246    fn key_agreement() {
247        let mut rng = seeded_rng([0u8; 32]);
248
249        // 1. Generate the static key-pair for Alice
250        let sk = KeyExchangeKey::with_rng(&mut rng);
251        let pk = sk.public_key();
252
253        // 2. Generate the ephemeral key-pair for Bob
254        let sk_e = EphemeralSecretKey::with_rng(&mut rng);
255        let pk_e = sk_e.public_key();
256
257        // 3. Bob computes the shared secret key (Bob will send pk_e with the encrypted note to
258        //    Alice)
259        let shared_secret_key_1 = sk_e.diffie_hellman(pk);
260
261        // 4. Alice uses its secret key and the ephemeral public key sent with the encrypted note by
262        //    Bob in order to create the shared secret key. This shared secret key will be used to
263        //    decrypt the encrypted note
264        let shared_secret_key_2 = sk.get_shared_secret(pk_e);
265
266        // Check that the computed shared secret keys are equal
267        assert_eq!(shared_secret_key_1.as_ref(), shared_secret_key_2.as_ref());
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}