1use hkdf::Hkdf;
8use sha2::Sha256;
9use x25519_dalek::{PublicKey, StaticSecret};
10
11use crate::kdf::{RecipientSecretKey, SecretKey};
12use crate::keys::RecipientPubKey;
13
14pub const ECIES_INFO: &[u8] = b"void-ecies-v1";
16
17#[derive(Debug, thiserror::Error)]
19pub enum EciesError {
20 #[error("invalid shared secret: possible low-order point attack")]
21 InvalidSharedSecret,
22 #[error("key derivation failed")]
23 KeyDerivationFailed,
24}
25
26pub fn perform_dh_and_derive(
40 our_secret: &RecipientSecretKey,
41 their_public: &RecipientPubKey,
42) -> Result<SecretKey, EciesError> {
43 let our_x25519 = StaticSecret::from(*our_secret.as_bytes());
44 let their_x25519 = PublicKey::from(*their_public.as_bytes());
45
46 let shared_secret = our_x25519.diffie_hellman(&their_x25519);
47
48 if !is_valid_shared_secret(shared_secret.as_bytes()) {
50 return Err(EciesError::InvalidSharedSecret);
51 }
52
53 let hk = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
55 let mut key = [0u8; 32];
56 hk.expand(ECIES_INFO, &mut key)
57 .map_err(|_| EciesError::KeyDerivationFailed)?;
58
59 Ok(SecretKey::new(key))
60}
61
62fn is_valid_shared_secret(secret: &[u8; 32]) -> bool {
66 secret.iter().any(|&b| b != 0)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 fn random_recipient_keypair() -> (RecipientSecretKey, RecipientPubKey) {
74 let mut bytes = [0u8; 32];
75 rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut bytes);
76 let secret = StaticSecret::from(bytes);
77 let public = PublicKey::from(&secret);
78 (
79 RecipientSecretKey::from_bytes(bytes),
80 RecipientPubKey::from_bytes(public.to_bytes()),
81 )
82 }
83
84 #[test]
85 fn is_valid_shared_secret_rejects_zeros() {
86 let zero_secret = [0u8; 32];
87 assert!(!is_valid_shared_secret(&zero_secret));
88 }
89
90 #[test]
91 fn is_valid_shared_secret_accepts_nonzero() {
92 let mut secret = [0u8; 32];
93 secret[0] = 1;
94 assert!(is_valid_shared_secret(&secret));
95
96 secret[0] = 0;
97 secret[31] = 1;
98 assert!(is_valid_shared_secret(&secret));
99 }
100
101 #[test]
102 fn perform_dh_and_derive_roundtrip() {
103 let (alice_secret, alice_public) = random_recipient_keypair();
104 let (bob_secret, bob_public) = random_recipient_keypair();
105
106 let alice_key = perform_dh_and_derive(&alice_secret, &bob_public).unwrap();
108 let bob_key = perform_dh_and_derive(&bob_secret, &alice_public).unwrap();
109
110 assert_eq!(alice_key.as_bytes(), bob_key.as_bytes());
111 }
112
113 #[test]
114 fn perform_dh_and_derive_rejects_zero_public_key() {
115 let (our_secret, _) = random_recipient_keypair();
116 let zero_public = RecipientPubKey::from_bytes([0u8; 32]);
117
118 let result = perform_dh_and_derive(&our_secret, &zero_public);
119 assert!(matches!(result, Err(EciesError::InvalidSharedSecret)));
120 }
121}