dcrypt_kem/ecdh/p521/
mod.rs1use super::concat_kdf_ikm;
17use crate::error::Error as KemError;
18use alloc::vec::Vec;
19use dcrypt_algorithms::ec::p521 as ec_p521;
20use dcrypt_api::{
21 error::Error as ApiError,
22 traits::serialize::{Serialize, SerializeSecret},
23 Kem, Key as ApiKey, Result as ApiResult, ZeroizingBytes,
24};
25use dcrypt_common::security::SecretBuffer;
26use dcrypt_internal::random::{CryptoRng, RngCore};
27use dcrypt_internal::zeroing::Zeroizing;
28
29const KDF_INFO: &[u8] = b"dcrypt-v3/ECDH-P521-KEM/shared-secret";
30
31pub struct EcdhP521;
33
34#[derive(Clone)]
36pub struct EcdhP521PublicKey([u8; ec_p521::P521_POINT_COMPRESSED_SIZE]);
37
38impl_zeroize_tuple!(EcdhP521PublicKey);
39
40impl AsRef<[u8]> for EcdhP521PublicKey {
41 fn as_ref(&self) -> &[u8] {
42 &self.0
43 }
44}
45
46impl AsMut<[u8]> for EcdhP521PublicKey {
47 fn as_mut(&mut self) -> &mut [u8] {
48 &mut self.0
49 }
50}
51
52#[derive(Clone)]
54pub struct EcdhP521SecretKey(SecretBuffer<{ ec_p521::P521_SCALAR_SIZE }>);
55
56impl_zeroize_on_drop_tuple!(EcdhP521SecretKey);
57
58impl AsRef<[u8]> for EcdhP521SecretKey {
59 fn as_ref(&self) -> &[u8] {
60 self.0.as_ref()
61 }
62}
63
64#[derive(Clone)]
66pub struct EcdhP521SharedSecret(ApiKey);
67
68impl_zeroize_on_drop_tuple!(EcdhP521SharedSecret);
69
70impl AsRef<[u8]> for EcdhP521SharedSecret {
71 fn as_ref(&self) -> &[u8] {
72 self.0.as_ref()
73 }
74}
75
76#[derive(Clone)]
78pub struct EcdhP521Ciphertext([u8; ec_p521::P521_POINT_COMPRESSED_SIZE]);
79
80impl AsRef<[u8]> for EcdhP521Ciphertext {
81 fn as_ref(&self) -> &[u8] {
82 &self.0
83 }
84}
85
86impl AsMut<[u8]> for EcdhP521Ciphertext {
87 fn as_mut(&mut self) -> &mut [u8] {
88 &mut self.0
89 }
90}
91
92impl EcdhP521PublicKey {
94 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
95 if bytes.len() != ec_p521::P521_POINT_COMPRESSED_SIZE {
96 return Err(ApiError::InvalidLength {
97 context: "EcdhP521PublicKey::from_bytes",
98 expected: ec_p521::P521_POINT_COMPRESSED_SIZE,
99 actual: bytes.len(),
100 });
101 }
102 let point = ec_p521::Point::deserialize_compressed(bytes)
103 .map_err(|e| ApiError::from(KemError::from(e)))?;
104 if point.is_identity() {
105 return Err(ApiError::InvalidKey {
106 context: "EcdhP521PublicKey::from_bytes",
107 #[cfg(feature = "std")]
108 message: "Public key cannot be the identity point".to_string(),
109 });
110 }
111 let mut key_bytes = [0u8; ec_p521::P521_POINT_COMPRESSED_SIZE];
112 key_bytes.copy_from_slice(bytes);
113 Ok(Self(key_bytes))
114 }
115 pub fn to_bytes(&self) -> Vec<u8> {
116 self.0.to_vec()
117 }
118}
119
120impl Serialize for EcdhP521PublicKey {
121 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
122 Self::from_bytes(bytes)
123 }
124 fn to_bytes(&self) -> Vec<u8> {
125 self.to_bytes()
126 }
127}
128
129impl EcdhP521SecretKey {
131 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
132 if bytes.len() != ec_p521::P521_SCALAR_SIZE {
133 return Err(ApiError::InvalidLength {
134 context: "EcdhP521SecretKey::from_bytes",
135 expected: ec_p521::P521_SCALAR_SIZE,
136 actual: bytes.len(),
137 });
138 }
139 let mut buffer = SecretBuffer::zeroed();
140 buffer.as_mut().copy_from_slice(bytes);
141 let scalar = ec_p521::Scalar::from_secret_buffer(buffer.clone())
142 .map_err(|e| ApiError::from(KemError::from(e)))?;
143 drop(scalar);
144 Ok(Self(buffer))
145 }
146 pub fn to_bytes(&self) -> ZeroizingBytes {
147 self.0.to_bytes_zeroizing_boxed()
148 }
149}
150
151impl SerializeSecret for EcdhP521SecretKey {
152 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
153 Self::from_bytes(bytes)
154 }
155 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
156 self.to_bytes()
157 }
158}
159
160impl EcdhP521SharedSecret {
162 pub fn to_bytes(&self) -> ZeroizingBytes {
163 self.0.to_bytes_zeroizing_boxed()
164 }
165}
166
167impl SerializeSecret for EcdhP521SharedSecret {
168 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
169 if bytes.len() != ec_p521::P521_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE {
170 return Err(ApiError::InvalidLength {
171 context: "EcdhP521SharedSecret::from_bytes",
172 expected: ec_p521::P521_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE,
173 actual: bytes.len(),
174 });
175 }
176 Ok(Self(ApiKey::new(bytes)))
177 }
178 fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
179 self.to_bytes()
180 }
181}
182
183impl EcdhP521Ciphertext {
185 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
186 if bytes.len() != ec_p521::P521_POINT_COMPRESSED_SIZE {
187 return Err(ApiError::InvalidLength {
188 context: "EcdhP521Ciphertext::from_bytes",
189 expected: ec_p521::P521_POINT_COMPRESSED_SIZE,
190 actual: bytes.len(),
191 });
192 }
193 let point = ec_p521::Point::deserialize_compressed(bytes)
194 .map_err(|e| ApiError::from(KemError::from(e)))?;
195 if point.is_identity() {
196 return Err(ApiError::InvalidCiphertext {
197 context: "EcdhP521Ciphertext::from_bytes",
198 #[cfg(feature = "std")]
199 message: "Ephemeral public key cannot be the identity point".to_string(),
200 });
201 }
202 let mut ct_bytes = [0u8; ec_p521::P521_POINT_COMPRESSED_SIZE];
203 ct_bytes.copy_from_slice(bytes);
204 Ok(Self(ct_bytes))
205 }
206 pub fn to_bytes(&self) -> Vec<u8> {
207 self.0.to_vec()
208 }
209}
210
211impl Serialize for EcdhP521Ciphertext {
212 fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
213 Self::from_bytes(bytes)
214 }
215 fn to_bytes(&self) -> Vec<u8> {
216 self.to_bytes()
217 }
218}
219
220impl Kem for EcdhP521 {
221 type PublicKey = EcdhP521PublicKey;
222 type SecretKey = EcdhP521SecretKey;
223 type SharedSecret = EcdhP521SharedSecret;
224 type Ciphertext = EcdhP521Ciphertext;
225 type KeyPair = (Self::PublicKey, Self::SecretKey);
226
227 fn name() -> &'static str {
228 "ECDH-P521"
229 }
230
231 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
232 let (sk_scalar, pk_point) =
233 ec_p521::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
234 let public_key = EcdhP521PublicKey(pk_point.serialize_compressed());
235 let secret_key = EcdhP521SecretKey(sk_scalar.as_secret_buffer().clone());
236 Ok((public_key, secret_key))
237 }
238
239 fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
240 keypair.0.clone()
241 }
242
243 fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
244 keypair.1.clone()
245 }
246
247 fn encapsulate<R: CryptoRng + RngCore>(
248 rng: &mut R,
249 public_key_recipient: &Self::PublicKey,
250 ) -> ApiResult<(Self::Ciphertext, Self::SharedSecret)> {
251 let pk_r_point = ec_p521::Point::deserialize_compressed(&public_key_recipient.0)
252 .map_err(|e| ApiError::from(KemError::from(e)))?;
253 if pk_r_point.is_identity() {
254 return Err(ApiError::InvalidKey {
255 context: "ECDH-P521 encapsulate",
256 #[cfg(feature = "std")]
257 message: "Recipient public key cannot be the identity point".to_string(),
258 });
259 }
260 let (ephemeral_scalar, ephemeral_point) =
261 ec_p521::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
262 let ciphertext = EcdhP521Ciphertext(ephemeral_point.serialize_compressed());
263 let shared_point = ec_p521::scalar_mult(&ephemeral_scalar, &pk_r_point)
264 .map_err(|e| ApiError::from(KemError::from(e)))?;
265 if shared_point.is_identity() {
266 return Err(ApiError::DecryptionFailed {
267 context: "ECDH-P521 encapsulate",
268 #[cfg(feature = "std")]
269 message: "Shared point is the identity".to_string(),
270 });
271 }
272 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
273 let kdf_ikm = concat_kdf_ikm(
274 x_coord_bytes.as_ref(),
275 &ephemeral_point.serialize_compressed(),
276 &public_key_recipient.0,
277 );
278 let ss_bytes = ec_p521::kdf_hkdf_sha512_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
279 .map_err(|e| ApiError::from(KemError::from(e)))?;
280 let shared_secret = EcdhP521SharedSecret(ApiKey::new(&ss_bytes[..]));
281 drop(ephemeral_scalar);
282 Ok((ciphertext, shared_secret))
283 }
284
285 fn decapsulate(
286 secret_key_recipient: &Self::SecretKey,
287 ciphertext_ephemeral_pk: &Self::Ciphertext,
288 ) -> ApiResult<Self::SharedSecret> {
289 let scalar_result = ec_p521::Scalar::from_secret_buffer(secret_key_recipient.0.clone());
290 let sk_r_scalar = match scalar_result {
291 Ok(scalar) => scalar,
292 Err(e) => return Err(ApiError::from(KemError::from(e))),
293 };
294 let q_e_point = ec_p521::Point::deserialize_compressed(&ciphertext_ephemeral_pk.0)
295 .map_err(|e| ApiError::from(KemError::from(e)))?;
296 if q_e_point.is_identity() {
297 return Err(ApiError::InvalidCiphertext {
298 context: "ECDH-P521 decapsulate",
299 #[cfg(feature = "std")]
300 message: "Ephemeral public key cannot be the identity point".to_string(),
301 });
302 }
303 let shared_point = ec_p521::scalar_mult(&sk_r_scalar, &q_e_point)
304 .map_err(|e| ApiError::from(KemError::from(e)))?;
305 if shared_point.is_identity() {
306 return Err(ApiError::DecryptionFailed {
307 context: "ECDH-P521 decapsulate",
308 #[cfg(feature = "std")]
309 message: "Shared point is the identity".to_string(),
310 });
311 }
312 let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
313 let q_r_point = ec_p521::scalar_mult_base_g(&sk_r_scalar)
314 .map_err(|e| ApiError::from(KemError::from(e)))?;
315 let kdf_ikm = concat_kdf_ikm(
316 x_coord_bytes.as_ref(),
317 &ciphertext_ephemeral_pk.0,
318 &q_r_point.serialize_compressed(),
319 );
320 let ss_bytes = ec_p521::kdf_hkdf_sha512_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
321 .map_err(|e| ApiError::from(KemError::from(e)))?;
322 let shared_secret = EcdhP521SharedSecret(ApiKey::new(&ss_bytes[..]));
323 Ok(shared_secret)
324 }
325}
326
327#[cfg(test)]
328mod tests;