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
142impl Serializable for EphemeralPublicKey {
143    fn write_into<W: ByteWriter>(&self, target: &mut W) {
144        // Compressed format
145        let encoded = self.inner.to_sec1_point(true);
146
147        target.write_bytes(encoded.as_bytes());
148    }
149}
150
151impl Deserializable for EphemeralPublicKey {
152    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
153        let bytes: [u8; PUBLIC_KEY_BYTES] = source.read_array()?;
154
155        let inner = k256::PublicKey::from_sec1_bytes(&bytes)
156            .map_err(|_| DeserializationError::InvalidValue("Invalid public key".to_string()))?;
157
158        Ok(Self { inner })
159    }
160}
161
162// KEY AGREEMENT TRAIT IMPLEMENTATION
163// ================================================================================================
164
165pub struct K256;
166
167impl KeyAgreementScheme for K256 {
168    type EphemeralSecretKey = EphemeralSecretKey;
169    type EphemeralPublicKey = EphemeralPublicKey;
170
171    type SecretKey = KeyExchangeKey;
172    type PublicKey = PublicKey;
173
174    type SharedSecret = SharedSecret;
175
176    fn generate_ephemeral_keypair<R: CryptoRng>(
177        rng: &mut R,
178    ) -> (Self::EphemeralSecretKey, Self::EphemeralPublicKey) {
179        let sk = EphemeralSecretKey::with_rng(rng);
180        let pk = sk.public_key();
181
182        (sk, pk)
183    }
184
185    fn exchange_ephemeral_static(
186        ephemeral_sk: Self::EphemeralSecretKey,
187        static_pk: &Self::PublicKey,
188    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
189        Ok(ephemeral_sk.diffie_hellman(static_pk.clone()))
190    }
191
192    fn exchange_static_ephemeral(
193        static_sk: &Self::SecretKey,
194        ephemeral_pk: &Self::EphemeralPublicKey,
195    ) -> Result<Self::SharedSecret, super::KeyAgreementError> {
196        Ok(static_sk.get_shared_secret(ephemeral_pk.clone()))
197    }
198
199    fn extract_key_material(
200        shared_secret: &Self::SharedSecret,
201        length: usize,
202        info: &[u8],
203    ) -> Result<Vec<u8>, super::KeyAgreementError> {
204        super::extract_key_material(shared_secret.as_ref(), None, length, info)
205    }
206}
207
208// TESTS
209// ================================================================================================
210
211#[cfg(test)]
212mod test {
213    use super::{EphemeralPublicKey, EphemeralSecretKey};
214    use crate::{
215        dsa::ecdsa_k256_keccak::KeyExchangeKey,
216        rand::test_utils::seeded_rng,
217        utils::{Deserializable, Serializable},
218    };
219
220    #[test]
221    fn key_agreement() {
222        let mut rng = seeded_rng([0u8; 32]);
223
224        // 1. Generate the static key-pair for Alice
225        let sk = KeyExchangeKey::with_rng(&mut rng);
226        let pk = sk.public_key();
227
228        // 2. Generate the ephemeral key-pair for Bob
229        let sk_e = EphemeralSecretKey::with_rng(&mut rng);
230        let pk_e = sk_e.public_key();
231
232        // 3. Bob computes the shared secret key (Bob will send pk_e with the encrypted note to
233        //    Alice)
234        let shared_secret_key_1 = sk_e.diffie_hellman(pk);
235
236        // 4. Alice uses its secret key and the ephemeral public key sent with the encrypted note by
237        //    Bob in order to create the shared secret key. This shared secret key will be used to
238        //    decrypt the encrypted note
239        let shared_secret_key_2 = sk.get_shared_secret(pk_e);
240
241        // Check that the computed shared secret keys are equal
242        assert_eq!(shared_secret_key_1.as_ref(), shared_secret_key_2.as_ref());
243    }
244
245    #[test]
246    fn test_serialization_round_trip() {
247        let mut rng = seeded_rng([1u8; 32]);
248
249        let sk_e = EphemeralSecretKey::with_rng(&mut rng);
250        let pk_e = sk_e.public_key();
251
252        let pk_e_bytes = pk_e.to_bytes();
253        let pk_e_serialized = EphemeralPublicKey::read_from_bytes(&pk_e_bytes)
254            .expect("failed to desrialize ephemeral public key");
255        assert_eq!(pk_e_serialized, pk_e);
256    }
257}