Skip to main content

tightbeam/crypto/
ecies.rs

1//! ECIES (Elliptic Curve Integrated Encryption Scheme) implementation
2//!
3//! This module provides a generic, trait-based ECIES implementation that can
4//! work with multiple elliptic curves (secp256k1, curve25519, P-256, etc.).
5//!
6//! # Architecture
7//!
8//! The implementation is split into:
9//! - Generic traits for ECIES key operations
10//! - Concrete implementations for specific curves (currently secp256k1)
11//! - Curve-agnostic encryption/decryption functions
12//!
13//! # ECIES Protocol
14//!
15//! **Encryption:**
16//! 1. Generate ephemeral keypair (r, R = r·G)
17//! 2. Compute shared secret: S = r·P (where P is recipient's public key)
18//! 3. Derive key: k_enc = KDF(C0, S) where C0 is ephemeral pubkey
19//! 4. Encrypt: c = AEAD.Encrypt(k_enc, plaintext) with a random nonce
20//! 5. Output: [R || nonce || ciphertext || tag]
21//!
22//! **Decryption:**
23//! 1. Parse [R || nonce || ciphertext || tag] from ciphertext
24//! 2. Compute shared secret: S = d·R (where d is recipient's private key)
25//! 3. Derive key: k_enc = KDF(C0, S)
26//! 4. Decrypt: plaintext = AEAD.Decrypt(k_enc, nonce, ciphertext, tag)
27//!
28//! # Security
29//!
30//! This implementation uses constant-time cryptographic primitives
31//! - ECDH operations (k256): constant-time scalar multiplication
32//! - AES-256-GCM: constant-time encryption and tag verification  
33//! - HKDF-SHA3-256: constant-time key derivation
34
35use rand_core::{CryptoRng, CryptoRngCore, OsRng, RngCore};
36
37use crate::asn1::ObjectIdentifier;
38use crate::constants::{AES_GCM_NONCE_SIZE, AES_GCM_TAG_SIZE, EC_PUBKEY_COMPRESSED_SIZE, TIGHTBEAM_ECIES_KDF_INFO};
39use crate::der::oid::AssociatedOid;
40
41use crate::crypto::aead::{Aead, Aes256Gcm, KeyInit, Payload};
42use crate::crypto::kdf::{ecies_kdf, HkdfSha3_256, KdfError};
43use crate::crypto::secret::{Secret, SecretSlice};
44use crate::crypto::sign::ecdsa::k256::ecdh::EphemeralSecret;
45use crate::crypto::sign::ecdsa::k256::elliptic_curve::sec1::ToEncodedPoint;
46use crate::crypto::sign::ecdsa::k256::{PublicKey, SecretKey};
47
48// ============================================================================
49// RNG Wrapper for Trait Object Compatibility
50// ============================================================================
51
52/// Wrapper to adapt `dyn CryptoRngCore` to the `CryptoRng + RngCore` bounds
53/// required by `EphemeralSecret::random`.
54///
55/// `EphemeralSecret::random` requires a `Sized` RNG parameter, but we want to
56/// accept trait objects for flexibility. This wrapper forwards all RNG methods
57/// to the underlying trait object.
58struct RngWrapper<'a>(&'a mut dyn CryptoRngCore);
59
60impl RngCore for RngWrapper<'_> {
61	fn next_u32(&mut self) -> u32 {
62		self.0.next_u32()
63	}
64
65	fn next_u64(&mut self) -> u64 {
66		self.0.next_u64()
67	}
68
69	fn fill_bytes(&mut self, dest: &mut [u8]) {
70		self.0.fill_bytes(dest)
71	}
72
73	fn try_fill_bytes(&mut self, dest: &mut [u8]) -> core::result::Result<(), rand_core::Error> {
74		self.0.try_fill_bytes(dest)
75	}
76}
77
78impl CryptoRng for RngWrapper<'_> {}
79
80// ============================================================================
81// Generic ECIES Traits
82// ============================================================================
83/// Trait for ECIES public keys with key exchange capability
84pub trait EciesPublicKeyOps: Clone + PartialEq + Eq {
85	/// Associated secret key type for this public key
86	type SecretKey: EciesSecretKeyOps<PublicKey = Self>;
87
88	/// The byte representation size for this public key
89	const PUBLIC_KEY_SIZE: usize;
90
91	/// Deserialize a public key from bytes
92	fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self>
93	where
94		Self: Sized;
95
96	/// Serialize the public key to bytes
97	fn to_bytes(&self) -> Vec<u8>;
98}
99
100/// Trait for ECIES secret keys with key exchange and generation capability
101pub trait EciesSecretKeyOps: Clone {
102	/// Associated public key type
103	type PublicKey: EciesPublicKeyOps;
104
105	/// The byte representation size for this secret key
106	const SECRET_KEY_SIZE: usize;
107
108	/// Generate a new random secret key
109	fn random<R: CryptoRng + RngCore>(rng: &mut R) -> Self;
110
111	/// Get the corresponding public key
112	fn public_key(&self) -> Self::PublicKey;
113
114	/// Perform ECDH key agreement with a public key, returning raw shared secret bytes
115	fn diffie_hellman(&self, public_key: &Self::PublicKey) -> SecretSlice<u8>;
116}
117
118/// Trait for ephemeral key generation in ECIES encryption
119pub trait EciesEphemeral {
120	/// Associated public key type
121	type PublicKey: EciesPublicKeyOps;
122
123	/// Generate a new ephemeral keypair and return (public_key_bytes, shared_secret_bytes)
124	fn generate_ephemeral(
125		recipient_pubkey: &Self::PublicKey,
126		rng: &mut dyn rand_core::CryptoRngCore,
127	) -> Result<(Vec<u8>, SecretSlice<u8>)>;
128}
129
130// ============================================================================
131// secp256k1 Implementation
132// ============================================================================
133
134#[cfg(feature = "derive")]
135use crate::Errorizable;
136
137/// Errors specific to ECIES operations
138#[cfg_attr(feature = "derive", derive(Errorizable))]
139#[derive(Debug, Clone)]
140pub enum EciesError {
141	/// Invalid ciphertext format
142	#[cfg_attr(feature = "derive", error("Invalid ECIES ciphertext format"))]
143	InvalidCiphertext,
144
145	/// Invalid public key
146	#[cfg_attr(feature = "derive", error("Invalid ECIES public key: {0}"))]
147	InvalidPublicKey(crate::crypto::sign::ecdsa::k256::elliptic_curve::Error),
148
149	/// Invalid secret key
150	#[cfg_attr(feature = "derive", error("Invalid ECIES secret key: {0}"))]
151	InvalidSecretKey(crate::crypto::sign::ecdsa::k256::elliptic_curve::Error),
152
153	/// Encryption failed
154	#[cfg_attr(feature = "derive", error("ECIES encryption failed: {0}"))]
155	EncryptionFailed(crate::crypto::aead::Error),
156
157	/// Decryption failed
158	#[cfg_attr(feature = "derive", error("ECIES decryption failed: {0}"))]
159	DecryptionFailed(crate::crypto::aead::Error),
160
161	/// Key derivation failed
162	#[cfg_attr(feature = "derive", error("ECIES key derivation failed: {0}"))]
163	#[cfg_attr(feature = "derive", from)]
164	Kdf(#[cfg_attr(feature = "derive", from)] KdfError),
165}
166
167crate::impl_error_display!(EciesError {
168	InvalidCiphertext => "Invalid ECIES ciphertext format",
169	InvalidPublicKey(e) => "Invalid ECIES public key: {e}",
170	InvalidSecretKey(e) => "Invalid ECIES secret key: {e}",
171	EncryptionFailed(e) => "ECIES encryption failed: {e}",
172	DecryptionFailed(e) => "ECIES decryption failed: {e}",
173	Kdf(e) => "ECIES key derivation failed: {e}",
174});
175
176/// A specialized Result type for ECIES operations
177pub type Result<T> = core::result::Result<T, EciesError>;
178
179// ============================================================================
180// Trait implementations for k256 types (secp256k1)
181// ============================================================================
182
183impl EciesPublicKeyOps for PublicKey {
184	type SecretKey = SecretKey;
185
186	const PUBLIC_KEY_SIZE: usize = EC_PUBKEY_COMPRESSED_SIZE;
187
188	fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
189		PublicKey::from_sec1_bytes(bytes.as_ref()).map_err(EciesError::InvalidPublicKey)
190	}
191
192	fn to_bytes(&self) -> Vec<u8> {
193		let point = self.to_encoded_point(true);
194		point.as_bytes().to_vec()
195	}
196}
197
198impl EciesSecretKeyOps for SecretKey {
199	type PublicKey = PublicKey;
200
201	const SECRET_KEY_SIZE: usize = 32;
202
203	fn random<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
204		SecretKey::random(rng)
205	}
206
207	fn public_key(&self) -> Self::PublicKey {
208		SecretKey::public_key(self)
209	}
210
211	fn diffie_hellman(&self, public_key: &Self::PublicKey) -> SecretSlice<u8> {
212		let shared_secret = k256::ecdh::diffie_hellman(self.to_nonzero_scalar(), public_key.as_affine());
213		let v = shared_secret.raw_secret_bytes().to_vec().into_boxed_slice();
214		Secret::from(v)
215	}
216}
217
218impl core::convert::TryFrom<SecretSlice<u8>> for SecretKey {
219	type Error = EciesError;
220	fn try_from(bytes: SecretSlice<u8>) -> Result<Self> {
221		use crate::crypto::secret::ToInsecure;
222		let raw = bytes.to_insecure().map_err(EciesError::from)?;
223		SecretKey::from_slice(&raw).map_err(EciesError::InvalidSecretKey)
224	}
225}
226
227impl From<&SecretKey> for SecretSlice<u8> {
228	fn from(sk: &SecretKey) -> Self {
229		let v = SecretKey::to_bytes(sk).to_vec();
230		Secret::from(v.into_boxed_slice())
231	}
232}
233
234impl EciesEphemeral for SecretKey {
235	type PublicKey = PublicKey;
236
237	fn generate_ephemeral(
238		recipient_pubkey: &Self::PublicKey,
239		rng: &mut dyn CryptoRngCore,
240	) -> Result<(Vec<u8>, SecretSlice<u8>)> {
241		let mut wrapper = RngWrapper(rng);
242		let ephemeral_secret = EphemeralSecret::random(&mut wrapper);
243		let ephemeral_pubkey = ephemeral_secret.public_key();
244
245		// Perform ECDH to get shared secret
246		let shared_secret = ephemeral_secret.diffie_hellman(recipient_pubkey);
247
248		let ephemeral_point = ephemeral_pubkey.to_encoded_point(true);
249		let ephemeral_bytes = ephemeral_point.as_bytes().to_vec();
250		let shared_bytes = Secret::from(shared_secret.raw_secret_bytes().to_vec().into_boxed_slice());
251		Ok((ephemeral_bytes, shared_bytes))
252	}
253}
254
255/// Trait for ECIES encrypted messages with curve-specific sizes
256pub trait EciesMessageOps: Sized {
257	/// Size of the ephemeral public key in bytes (curve-specific)
258	const PUBKEY_SIZE: usize;
259
260	/// Parse from wire format: [ephemeral_pubkey || ciphertext_with_tag]
261	fn from_bytes(bytes: &[u8]) -> Result<Self>;
262
263	/// Serialize to wire format: [ephemeral_pubkey || ciphertext_with_tag]
264	fn to_bytes(&self) -> Vec<u8>;
265
266	/// Get ephemeral public key bytes
267	fn ephemeral_pubkey(&self) -> &[u8];
268
269	/// Get ciphertext bytes (nonce || encrypted_data || tag)
270	fn ciphertext(&self) -> &[u8];
271}
272
273/// ECIES encrypted message for secp256k1 curve
274///
275/// The wire format consists of:
276/// - `ephemeral_pubkey`: 33 bytes (compressed secp256k1 public key)
277/// - `nonce`: 12 bytes (AES-GCM nonce)
278/// - `ciphertext`: variable length (encrypted plaintext)
279/// - `tag`: 16 bytes (AES-GCM authentication tag, appended to ciphertext)
280pub struct Secp256k1EciesMessage {
281	/// Ephemeral public key (serialized, compressed SEC1 format)
282	ephemeral_pubkey: Vec<u8>,
283	/// Nonce + encrypted data + authentication tag
284	ciphertext: Vec<u8>,
285}
286
287impl Secp256k1EciesMessage {
288	/// Minimum ciphertext size (nonce + tag)
289	const MIN_CIPHERTEXT_SIZE: usize = AES_GCM_NONCE_SIZE + AES_GCM_TAG_SIZE;
290
291	/// Parse from wire format: [ephemeral_pubkey || ciphertext_with_tag]
292	pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
293		let bytes = bytes.as_ref();
294		if bytes.len() < EC_PUBKEY_COMPRESSED_SIZE + Self::MIN_CIPHERTEXT_SIZE {
295			return Err(EciesError::InvalidCiphertext);
296		}
297
298		let ephemeral_pubkey = bytes[0..EC_PUBKEY_COMPRESSED_SIZE].to_vec();
299		let ciphertext = bytes[EC_PUBKEY_COMPRESSED_SIZE..].to_vec();
300		if ciphertext.len() < Self::MIN_CIPHERTEXT_SIZE {
301			return Err(EciesError::InvalidCiphertext);
302		}
303
304		Ok(Self { ephemeral_pubkey, ciphertext })
305	}
306
307	/// Serialize to wire format: [ephemeral_pubkey || ciphertext_with_tag]
308	pub fn to_bytes(&self) -> Vec<u8> {
309		let mut bytes = Vec::with_capacity(self.ephemeral_pubkey.len() + self.ciphertext.len());
310		bytes.extend_from_slice(&self.ephemeral_pubkey);
311		bytes.extend_from_slice(&self.ciphertext);
312		bytes
313	}
314
315	/// Mutable access to ephemeral public key bytes (for tampering tests)
316	#[cfg(test)]
317	pub(crate) fn ephemeral_pubkey_mut(&mut self) -> &mut Vec<u8> {
318		&mut self.ephemeral_pubkey
319	}
320
321	/// Mutable access to ciphertext bytes (for tampering tests)
322	#[cfg(test)]
323	pub(crate) fn ciphertext_mut(&mut self) -> &mut Vec<u8> {
324		&mut self.ciphertext
325	}
326}
327
328impl EciesMessageOps for Secp256k1EciesMessage {
329	const PUBKEY_SIZE: usize = EC_PUBKEY_COMPRESSED_SIZE;
330
331	fn from_bytes(bytes: &[u8]) -> Result<Self> {
332		Self::from_bytes(bytes)
333	}
334
335	fn to_bytes(&self) -> Vec<u8> {
336		Self::to_bytes(self)
337	}
338
339	fn ephemeral_pubkey(&self) -> &[u8] {
340		&self.ephemeral_pubkey
341	}
342
343	fn ciphertext(&self) -> &[u8] {
344		&self.ciphertext
345	}
346}
347
348/// Encrypt plaintext using ECIES with recipient's public key
349///
350/// # Arguments
351/// * `recipient_pubkey` - Recipient's public key (implements EciesPublicKeyOps)
352/// * `plaintext` - Data to encrypt
353/// * `associated_data` - Optional authenticated associated data (AAD)
354/// * `rng` - Optional cryptographically secure random number generator (uses OsRng if not provided)
355///
356/// # Returns
357/// Encrypted message containing ephemeral public key and ciphertext
358///
359/// # Type Parameters
360/// * `PK` - Public key type implementing EciesPublicKeyOps
361/// * `P` - Plaintext type that can be converted to bytes
362/// * `R` - Random number generator type (optional, defaults to OsRng)
363/// * `M` - Message type implementing EciesMessageOps
364pub fn encrypt<PK, P, R, M>(
365	recipient_pubkey: &PK,
366	plaintext: P,
367	associated_data: Option<&[u8]>,
368	rng: Option<&mut R>,
369) -> Result<M>
370where
371	PK: EciesPublicKeyOps,
372	PK::SecretKey: EciesEphemeral<PublicKey = PK>,
373	P: AsRef<[u8]>,
374	R: CryptoRng + RngCore,
375	M: EciesMessageOps,
376{
377	let plaintext = plaintext.as_ref();
378
379	// Helper macro to avoid code duplication
380	macro_rules! do_encrypt {
381		($rng:expr) => {{
382			// Use the trait method to generate ephemeral key and shared secret
383			let (ephemeral_bytes, shared_secret) = PK::SecretKey::generate_ephemeral(recipient_pubkey, $rng)?;
384
385			// Derive encryption key using KDF (includes C0 for non-malleability)
386			let k_enc = ecies_kdf::<HkdfSha3_256>(&ephemeral_bytes, shared_secret, TIGHTBEAM_ECIES_KDF_INFO, None)?;
387
388			// Encrypt using AES-256-GCM
389			let key = crate::crypto::utils::key_from_slice(&k_enc[..32]);
390			let cipher = Aes256Gcm::new(&key);
391
392			// Generate random nonce (96 bits for GCM)
393			let mut nonce_bytes = [0u8; AES_GCM_NONCE_SIZE];
394			$rng.fill_bytes(&mut nonce_bytes);
395			let nonce = crate::crypto::utils::nonce_from_slice::<Aes256Gcm>(&nonce_bytes);
396
397			// Prepare payload with optional AAD
398			let payload = match associated_data {
399				Some(aad) => Payload { msg: plaintext, aad },
400				None => Payload { msg: plaintext, aad: b"" },
401			};
402
403			// Encrypt and prepend nonce in a single allocation
404			let ciphertext = cipher.encrypt(&nonce, payload).map_err(EciesError::EncryptionFailed)?;
405			let encrypted_len = ciphertext.len();
406			let mut final_ciphertext = Vec::with_capacity(AES_GCM_NONCE_SIZE + encrypted_len);
407			final_ciphertext.extend_from_slice(&nonce_bytes);
408			final_ciphertext.extend_from_slice(&ciphertext);
409
410			// Construct message from concatenated bytes (avoid extra allocation)
411			let total_len = ephemeral_bytes.len() + final_ciphertext.len();
412			let mut wire_bytes = Vec::with_capacity(total_len);
413			wire_bytes.extend_from_slice(&ephemeral_bytes);
414			wire_bytes.extend_from_slice(&final_ciphertext);
415			M::from_bytes(&wire_bytes)
416		}};
417	}
418
419	// Use provided RNG or default to OsRng
420	match rng {
421		Some(r) => do_encrypt!(r),
422		None => do_encrypt!(&mut OsRng),
423	}
424}
425
426pub fn decrypt<SK, M>(recipient_seckey: &SK, message: &M, associated_data: Option<&[u8]>) -> Result<SecretSlice<u8>>
427where
428	SK: EciesSecretKeyOps,
429	M: EciesMessageOps,
430{
431	// 1. Parse ephemeral public key
432	let ephemeral_pubkey = <SK::PublicKey as EciesPublicKeyOps>::from_bytes(message.ephemeral_pubkey())?;
433	// 2. Perform ECDH to get shared secret
434	let shared_secret = recipient_seckey.diffie_hellman(&ephemeral_pubkey);
435	// 3. Derive encryption key using KDF (includes C0 for non-malleability)
436	// Uses SHA3-256 with protocol versioning via info parameter
437	// Derives 32-byte key for AES-256-GCM authenticated encryption
438	let k_enc = ecies_kdf::<HkdfSha3_256>(message.ephemeral_pubkey(), shared_secret, TIGHTBEAM_ECIES_KDF_INFO, None)?;
439
440	// 4. Extract nonce and ciphertext
441	let ciphertext_bytes = message.ciphertext();
442	if ciphertext_bytes.len() < AES_GCM_NONCE_SIZE + AES_GCM_TAG_SIZE {
443		return Err(EciesError::InvalidCiphertext);
444	}
445	let nonce = crate::crypto::utils::nonce_from_slice::<Aes256Gcm>(&ciphertext_bytes[0..AES_GCM_NONCE_SIZE]);
446	let ciphertext_with_tag = &ciphertext_bytes[AES_GCM_NONCE_SIZE..];
447
448	// 5. Decrypt using AES-256-GCM
449	let key = crate::crypto::utils::key_from_slice(&k_enc[..32]);
450	let cipher = Aes256Gcm::new(&key);
451
452	// Prepare payload with optional AAD
453	let payload = match associated_data {
454		Some(aad) => Payload { msg: ciphertext_with_tag, aad },
455		None => Payload { msg: ciphertext_with_tag, aad: b"" },
456	};
457
458	// Decrypt and verify tag
459	let plaintext = cipher.decrypt(&nonce, payload).map_err(EciesError::DecryptionFailed)?;
460	Ok(Secret::from(plaintext.into_boxed_slice()))
461}
462
463#[cfg(feature = "x509")]
464crate::define_oid_wrapper!(
465	/// OID wrapper for ECIES with secp256k1
466	EciesSecp256k1Oid,
467	"1.3.132.1.12.0"
468);
469
470// ============================================================================
471// Encryptor/Decryptor Trait Implementations
472// ============================================================================
473
474/// ECIES encryptor - encrypts messages to a recipient's public key.
475///
476/// This type implements the `Encryptor` trait, allowing it to be used
477/// with `FrameBuilder::with_encryptor()` for asymmetric message encryption.
478///
479/// # Example
480/// ```ignore
481/// let encryptor = EciesEncryptor::new(recipient_pubkey);
482/// let frame = compose! {
483///     V2: id: b"msg-001",
484///         message: payload,
485///         encryptor<EciesSecp256k1Oid, _>: encryptor
486/// }?;
487/// ```
488#[cfg(feature = "x509")]
489pub struct EciesEncryptor {
490	recipient_pubkey: PublicKey,
491}
492
493#[cfg(feature = "x509")]
494impl EciesEncryptor {
495	/// Create a new ECIES encryptor for the given recipient's public key.
496	pub fn new(recipient_pubkey: PublicKey) -> Self {
497		Self { recipient_pubkey }
498	}
499
500	/// Create from raw public key bytes (SEC1 format).
501	pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
502		let pubkey = PublicKey::from_bytes(bytes)?;
503		Ok(Self::new(pubkey))
504	}
505}
506
507#[cfg(feature = "x509")]
508impl crate::crypto::aead::Encryptor<EciesSecp256k1Oid> for EciesEncryptor {
509	fn encrypt_content(
510		&self,
511		data: impl AsRef<[u8]>,
512		_nonce: impl AsRef<[u8]>, // Ignored - ECIES generates its own nonce
513		content_type: Option<ObjectIdentifier>,
514	) -> crate::error::Result<crate::EncryptedContentInfo> {
515		// Use existing ECIES encrypt function
516		let ecies_msg: Secp256k1EciesMessage =
517			encrypt(&self.recipient_pubkey, data.as_ref(), None, None::<&mut OsRng>)?;
518
519		// Store the full ECIES message (ephemeral pubkey + ciphertext) as encrypted content
520		let encrypted_bytes = ecies_msg.to_bytes();
521		let content_type = content_type.unwrap_or(crate::oids::DATA);
522
523		// No nonce parameter needed - ECIES embeds the ephemeral pubkey
524		let content_enc_alg = crate::AlgorithmIdentifier { oid: EciesSecp256k1Oid::OID, parameters: None };
525		let encrypted_content = Some(crate::der::asn1::OctetString::new(encrypted_bytes)?);
526
527		Ok(crate::EncryptedContentInfo { content_type, content_enc_alg, encrypted_content })
528	}
529}
530
531/// ECIES decryptor - decrypts messages with recipient's secret key.
532///
533/// This type implements the `Decryptor` trait for decrypting ECIES-encrypted
534/// messages.
535///
536/// # Example
537/// ```ignore
538/// let decryptor = EciesDecryptor::new(my_secret_key);
539/// let plaintext = frame.decrypt(&decryptor)?;
540/// ```
541#[cfg(feature = "x509")]
542pub struct EciesDecryptor {
543	secret_key: SecretKey,
544}
545
546#[cfg(feature = "x509")]
547impl EciesDecryptor {
548	/// Create a new ECIES decryptor with the given secret key.
549	pub fn new(secret_key: SecretKey) -> Self {
550		Self { secret_key }
551	}
552}
553
554#[cfg(feature = "x509")]
555impl crate::crypto::aead::Decryptor for EciesDecryptor {
556	fn decrypt_content(&self, info: &crate::EncryptedContentInfo) -> crate::error::Result<Vec<u8>> {
557		// Extract the encrypted bytes
558		let encrypted_bytes = info
559			.encrypted_content
560			.as_ref()
561			.ok_or(crate::TightBeamError::MissingEncryptionInfo)?
562			.as_bytes();
563
564		// Parse as ECIES message
565		let ecies_msg = Secp256k1EciesMessage::from_bytes(encrypted_bytes)?;
566		// Decrypt using ECIES
567		let plaintext = decrypt(&self.secret_key, &ecies_msg, None)?;
568
569		// Convert SecretSlice to Vec<u8>
570		use crate::crypto::secret::ToInsecure;
571		Ok(plaintext.to_insecure()?.to_vec())
572	}
573}
574
575#[cfg(test)]
576mod tests {
577	use super::*;
578	use crate::crypto::secret::ToInsecure;
579	use rand_core::OsRng;
580
581	// Helper to create a key pair
582	fn keypair() -> (SecretKey, PublicKey) {
583		let mut rng = OsRng;
584		let secret = SecretKey::random(&mut rng);
585		let public = secret.public_key();
586		(secret, public)
587	}
588
589	// Helper for encryption roundtrip
590	fn roundtrip(plaintext: &[u8], aad: Option<&[u8]>) -> Result<()> {
591		let (secret, public) = keypair();
592		let encrypted = encrypt::<_, _, _, Secp256k1EciesMessage>(&public, plaintext, aad, None::<&mut OsRng>)?;
593		let decrypted = decrypt(&secret, &encrypted, aad)?;
594		assert_eq!(plaintext, &decrypted.to_insecure().map_err(EciesError::from)?[..]);
595		Ok(())
596	}
597
598	#[test]
599	fn test_ecies_encryption() -> Result<()> {
600		// Test cases: (plaintext, aad)
601		let cases = [
602			(&b"Hello, ECIES!"[..], None),
603			(b"Secret message", Some(&b"authenticated data"[..])),
604			(b"", None),
605			// cspell:disable-next-line
606			(b"The quick brown fox jumps over the lazy dog. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", None),
607			(b"\x00\x01\x02\x03\xFF\xFE\xFD\xFC", None),
608			(b"Payload", Some(&b"version:1|timestamp:12345|nonce:abcdef"[..])),
609		];
610
611		for (plaintext, aad) in cases {
612			roundtrip(plaintext, aad)?;
613		}
614
615		Ok(())
616	}
617
618	#[test]
619	fn test_aad_validation() -> Result<()> {
620		let mut rng = OsRng;
621		let (secret, public) = keypair();
622		let plaintext = b"Secret message";
623		let correct_aad = b"authenticated data";
624
625		// Test with explicit RNG to verify RNG parameter works
626		let encrypted =
627			encrypt::<_, _, _, Secp256k1EciesMessage>(&public, plaintext, Some(correct_aad), Some(&mut rng))?;
628		// Test cases: (aad, should_succeed)
629		let cases = [
630			(Some(&correct_aad[..]), true),
631			(Some(&b"wrong data"[..]), false),
632			(None, false),
633			(Some(&b""[..]), false),
634		];
635
636		for (aad, should_succeed) in cases {
637			let result = decrypt(&secret, &encrypted, aad);
638			assert_eq!(result.is_ok(), should_succeed);
639			if should_succeed {
640				assert_eq!(&plaintext[..], &result?.to_insecure().map_err(EciesError::from)?[..]);
641			}
642		}
643
644		Ok(())
645	}
646
647	#[test]
648	fn test_serialization() -> Result<()> {
649		let (secret, public) = keypair();
650		let plaintext = b"Test serialization";
651
652		// Message serialization roundtrip
653		let encrypted = encrypt::<_, _, _, Secp256k1EciesMessage>(&public, plaintext, None, None::<&mut OsRng>)?;
654		let bytes = encrypted.to_bytes();
655		let parsed = Secp256k1EciesMessage::from_bytes(&bytes)?;
656		let decrypted = decrypt(&secret, &parsed, None)?;
657		assert_eq!(&plaintext[..], &decrypted.to_insecure().map_err(EciesError::from)?[..]);
658
659		// Key serialization roundtrip using traits
660		let secret_bytes: SecretSlice<u8> = (&secret).into();
661		let public_bytes = public.to_bytes();
662
663		let secret2 = SecretKey::try_from(secret_bytes)?;
664		let public2 = PublicKey::from_bytes(&public_bytes)?;
665
666		assert_eq!(public.to_bytes(), public2.to_bytes());
667		assert_eq!(secret.public_key().to_bytes(), secret2.public_key().to_bytes());
668
669		Ok(())
670	}
671
672	#[test]
673	fn test_security_properties() -> Result<()> {
674		let plaintext = b"Sensitive data";
675
676		// Wrong recipient cannot decrypt
677		let (_, public1) = keypair();
678		let (secret2, _) = keypair();
679
680		let encrypted = encrypt::<_, _, _, Secp256k1EciesMessage>(&public1, plaintext, None, None::<&mut OsRng>)?;
681		let result = decrypt(&secret2, &encrypted, None);
682
683		if let Ok(decrypted) = result {
684			assert_ne!(&plaintext[..], &decrypted.to_insecure().map_err(EciesError::from)?[..]);
685		}
686
687		// Tampered ciphertext should fail authentication
688		let (secret, public) = keypair();
689
690		let tamper_functions: [fn(&mut Secp256k1EciesMessage); 4] = [
691			|msg| {
692				if let Some(byte) = msg.ciphertext_mut().last_mut() {
693					*byte ^= 0xFF;
694				}
695			},
696			|msg| {
697				if let Some(byte) = msg.ciphertext_mut().first_mut() {
698					*byte ^= 0xFF;
699				}
700			},
701			|msg| {
702				let len = msg.ciphertext_mut().len().saturating_sub(1);
703				msg.ciphertext_mut().truncate(len);
704			},
705			|msg| {
706				if let Some(byte) = msg.ephemeral_pubkey_mut().first_mut() {
707					*byte ^= 0xFF;
708				}
709			},
710		];
711
712		for tamper_fn in tamper_functions {
713			let mut encrypted =
714				encrypt::<_, _, _, Secp256k1EciesMessage>(&public, plaintext, None, None::<&mut OsRng>)?;
715			tamper_fn(&mut encrypted);
716			assert!(decrypt(&secret, &encrypted, None).is_err());
717		}
718
719		Ok(())
720	}
721
722	#[test]
723	fn test_edge_cases() -> Result<()> {
724		// Invalid ciphertext formats
725		let invalid_ciphertexts = [
726			vec![],
727			vec![0u8; 32],
728			vec![0u8; 33], // Missing ciphertext
729			vec![0u8; 45], // 33 + 12 (less than min 33+16)
730		];
731
732		for data in invalid_ciphertexts {
733			assert!(Secp256k1EciesMessage::from_bytes(&data).is_err());
734		}
735
736		// Invalid key formats
737		assert!(PublicKey::from_bytes([0xFFu8; 33]).is_err());
738		assert!(SecretKey::try_from(Secret::from(vec![0x00u8; 32].into_boxed_slice())).is_err());
739
740		Ok(())
741	}
742}