Skip to main content

calimero_crypto/
lib.rs

1use calimero_primitives::identity::{PrivateKey, PublicKey};
2use ed25519_dalek::{SecretKey, SigningKey};
3use ring::aead;
4use thiserror::Error;
5
6pub const NONCE_LEN: usize = 12;
7
8pub type Nonce = [u8; NONCE_LEN];
9
10/// Error type for shared key creation failures.
11#[derive(Debug, Error)]
12#[non_exhaustive]
13pub enum SharedKeyError {
14    /// The public key bytes do not represent a valid Edwards Y coordinate.
15    #[error("invalid public key: not a valid Edwards Y coordinate")]
16    InvalidPublicKey,
17}
18
19#[derive(Copy, Clone, Debug)]
20pub struct SharedKey {
21    key: SecretKey,
22}
23
24impl SharedKey {
25    /// Creates a new shared key from a private key and a public key.
26    ///
27    /// # Errors
28    ///
29    /// Returns [`SharedKeyError::InvalidPublicKey`] if the public key bytes
30    /// do not represent a valid Edwards Y coordinate.
31    pub fn new(sk: &PrivateKey, pk: &PublicKey) -> Result<Self, SharedKeyError> {
32        let decompressed = curve25519_dalek::edwards::CompressedEdwardsY(**pk)
33            .decompress()
34            .ok_or(SharedKeyError::InvalidPublicKey)?;
35
36        Ok(Self {
37            key: (SigningKey::from_bytes(sk).to_scalar() * decompressed)
38                .compress()
39                .to_bytes(),
40        })
41    }
42
43    #[must_use]
44    pub fn from_sk(sk: &PrivateKey) -> Self {
45        Self { key: **sk }
46    }
47
48    #[must_use]
49    pub fn encrypt(&self, payload: Vec<u8>, nonce: Nonce) -> Option<Vec<u8>> {
50        let encryption_key =
51            aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).ok()?);
52
53        let mut cipher_text = payload;
54        encryption_key
55            .seal_in_place_append_tag(
56                aead::Nonce::assume_unique_for_key(nonce),
57                aead::Aad::empty(),
58                &mut cipher_text,
59            )
60            .ok()?;
61
62        Some(cipher_text)
63    }
64
65    #[must_use]
66    pub fn decrypt(&self, cipher_text: Vec<u8>, nonce: Nonce) -> Option<Vec<u8>> {
67        let decryption_key =
68            aead::LessSafeKey::new(aead::UnboundKey::new(&aead::AES_256_GCM, &self.key).ok()?);
69
70        let mut payload = cipher_text;
71        let decrypted_len = decryption_key
72            .open_in_place(
73                aead::Nonce::assume_unique_for_key(nonce),
74                aead::Aad::empty(),
75                &mut payload,
76            )
77            .ok()?
78            .len();
79
80        payload.truncate(decrypted_len);
81
82        Some(payload)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use eyre::OptionExt;
89    use rand::thread_rng;
90
91    use super::*;
92
93    #[test]
94    fn test_encrypt_decrypt() -> eyre::Result<()> {
95        let mut csprng = thread_rng();
96
97        let signer = PrivateKey::random(&mut csprng);
98        let verifier = PrivateKey::random(&mut csprng);
99
100        let signer_shared_key = SharedKey::new(&signer, &verifier.public_key())?;
101        let verifier_shared_key = SharedKey::new(&verifier, &signer.public_key())?;
102
103        let payload = b"privacy is important";
104        let nonce = [0u8; NONCE_LEN];
105
106        let encrypted_payload = signer_shared_key
107            .encrypt(payload.to_vec(), nonce)
108            .ok_or_eyre("encryption failed")?;
109
110        let decrypted_payload = verifier_shared_key
111            .decrypt(encrypted_payload, nonce)
112            .ok_or_eyre("decryption failed")?;
113
114        assert_eq!(decrypted_payload, payload);
115        assert_ne!(decrypted_payload, b"privacy is not important");
116
117        Ok(())
118    }
119
120    #[test]
121    fn test_decrypt_with_invalid_key() -> eyre::Result<()> {
122        let mut csprng = thread_rng();
123
124        let signer = PrivateKey::random(&mut csprng);
125        let verifier = PrivateKey::random(&mut csprng);
126        let invalid = PrivateKey::random(&mut csprng);
127
128        let signer_shared_key = SharedKey::new(&signer, &verifier.public_key())?;
129        let invalid_shared_key = SharedKey::new(&invalid, &invalid.public_key())?;
130
131        let token = b"privacy is important";
132        let nonce = [0u8; NONCE_LEN];
133
134        let encrypted_token = signer_shared_key
135            .encrypt(token.to_vec(), nonce)
136            .ok_or_eyre("encryption failed")?;
137
138        let decrypted_data = invalid_shared_key.decrypt(encrypted_token, nonce);
139
140        assert!(decrypted_data.is_none());
141
142        Ok(())
143    }
144
145    #[test]
146    fn test_new_with_invalid_public_key() {
147        let mut csprng = thread_rng();
148        let signer = PrivateKey::random(&mut csprng);
149
150        // Create an invalid public key. Not all 32-byte sequences represent valid
151        // Edwards Y coordinates. We need a value where the computed x^2 has no
152        // square root in the field. This specific value (2 followed by zeros)
153        // is known to fail decompression on the Ed25519 curve.
154        let mut invalid_pk_bytes = [0u8; 32];
155        invalid_pk_bytes[0] = 2;
156        let invalid_pk = PublicKey::from(invalid_pk_bytes);
157
158        let result = SharedKey::new(&signer, &invalid_pk);
159        assert!(result.is_err());
160        assert!(matches!(result, Err(SharedKeyError::InvalidPublicKey)));
161    }
162}