ecies_ed25519_morus/
lib.rs

1//! Experimental [ECIES](https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme) on [Twisted Edwards Curve25519](https://en.wikipedia.org/wiki/Curve25519) and [MORUS-1280-128](https://competitions.cr.yp.to/round3/morusv2.pdf)
2//!
3//! ## Notes
4//!
5//! - [Flexible Symmetric Cryptography - Impractical plaintext recovery attack](https://eprint.iacr.org/2018/464.pdf).
6//! - This work misuses the `sign & verify` keypair in the `ed25519` scheme for accomplishing `ECIES`. We call this, a perversion because we should only use the `ephemeral ones` (except for the recipient).
7//! - No security audits, and perhaps will not happen.
8//!
9//! ## Features
10//!
11//! - `no-std` environment (for example: [wasm](https://en.wikipedia.org/wiki/WebAssembly)):
12//!
13//! ```bash
14//! cargo add ecies-ed25519-morus --no-default-features --features="pure"
15//! ```
16//!
17//! - `std` environment (default):
18//!
19//! ```bash
20//! cargo add ecies-ed25519-morus
21//! ```
22//!
23//! - `std` and `aarch64` environment (for example: [Apple Silicon](https://en.wikipedia.org/wiki/Apple_silicon))
24//!
25//! ```bash
26//! cargo add ecies-ed25519-morus --features="aarch64-optimizations"
27//! ```
28//!
29//! ## Example
30//!
31//! ```rust
32//! use rand_core::RngCore;
33//! use ecies_ed25519_morus::{encrypt_into, decrypt_into};
34//!
35//! const BUFFER_SIZE: usize = 512 * 1024; // avoid higher than this to prevent stackoverflow
36//! let mut rng = rand_core::OsRng::default();
37//! let sender_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
38//! let receiver_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
39//! let sender_public = sender_keypair.verifying_key();
40//! let receiver_public = receiver_keypair.verifying_key();
41//! let mut random_message = [0u8; BUFFER_SIZE];
42//! let mut decrypted_message = [0u8; BUFFER_SIZE];
43//! let mut ciphertext = [0u8; BUFFER_SIZE];
44//! rng.fill_bytes(&mut random_message);
45//!
46//! let decrypt_materials = encrypt_into(
47//!     &mut rng,
48//!     &sender_keypair,
49//!     &receiver_public,
50//!     &[],
51//!     &random_message[..],
52//!     &mut ciphertext[..],
53//! )
54//! .unwrap();
55//! decrypt_into(
56//!     &decrypt_materials,
57//!     &receiver_keypair,
58//!     &sender_public,
59//!     &[],
60//!     &ciphertext[..],
61//!     &mut decrypted_message[..],
62//! )
63//! .unwrap();
64//!
65//! assert_eq!(random_message, decrypted_message);
66//! assert_ne!(sender_public, receiver_public);
67//! ```
68
69#![cfg_attr(not(feature = "std"), no_std)]
70#![forbid(unsafe_code)]
71#![deny(warnings)]
72
73pub mod errors;
74pub mod morus;
75
76pub(crate) mod helper;
77
78pub type DecryptTag = morus::Tag;
79pub type Nonce = morus::Nonce;
80
81pub const KDF_CONTEXT: &str = "ecies-ed25519-morus/kdf";
82
83#[doc(inline)]
84pub use ed25519_dalek::{SecretKey, SigningKey, VerifyingKey};
85pub use errors::Error;
86
87/// A struct containing `nonce` and `tag` for decryption purpose, a result of [encrypt_into]
88#[derive(Clone, Copy, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
89pub struct DecryptionMaterials {
90    pub nonce: Nonce,
91    pub tag: DecryptTag,
92}
93
94/// Encrypt function
95///
96/// ## Warning
97///
98/// Subsequent call will yield different [DecryptionMaterials], hence will produce different `ciphertext`
99///
100/// ## Returns
101///
102/// - Success : [DecryptionMaterials], only valid for decrypting `ciphertext` of this function.
103/// - Errors  :
104///   * [Error::BufferSizeMismatch], happens when `message` and `ciphertext` buffer slice has different length.
105///   * [Error::RandomNumbersGenerationError], happens when [rng](rand_core::RngCore) can't produce numbers
106///   * [Error::EdwardsPointDecompressionFailure], happens when invalid [sender_keypair](SigningKey) or [receiver_public_key](VerifyingKey)
107///
108pub fn encrypt_into<RNG: rand_core::CryptoRng + rand_core::RngCore>(
109    rng: &mut RNG,
110    sender_keypair: &SigningKey,
111    receiver_public_key: &VerifyingKey,
112    associated_data: &[u8],
113    message: &[u8],
114    ciphertext: &mut [u8],
115) -> Result<DecryptionMaterials, Error> {
116    let cipher_key = CipherKey::sender_generate(sender_keypair, receiver_public_key)?;
117
118    helper::morus_encrypt(rng, &cipher_key, associated_data, message, ciphertext)
119}
120
121/// Decrypt function
122///
123/// ## Returns
124///
125/// - Success : [Ok(())]
126/// - Errors  :
127///   * [Error::BufferSizeMismatch], happens when `ciphertext` and `decrypted_message` buffer slice has different length.
128///   * [Error::InvalidTag], happens when invalid [DecryptionMaterials] supplied
129///   * [Error::EdwardsPointDecompressionFailure], happens when invalid [receiver_keypair](SigningKey) or [sender_public_key](VerifyingKey)
130///
131pub fn decrypt_into(
132    decryption_materials: &DecryptionMaterials,
133    receiver_keypair: &SigningKey,
134    sender_public_key: &VerifyingKey,
135    associated_data: &[u8],
136    ciphertext: &[u8],
137    decrypted_message: &mut [u8],
138) -> Result<(), Error> {
139    let cipher_key = CipherKey::receiver_generate(receiver_keypair, sender_public_key)?;
140
141    helper::morus_decrypt(
142        decryption_materials,
143        &cipher_key,
144        associated_data,
145        ciphertext,
146        decrypted_message,
147    )
148}
149
150#[derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
151pub(crate) struct CipherKey {
152    pub(crate) inner: morus::Key,
153}
154
155impl CipherKey {
156    fn sender_generate(
157        sender_keypair: &SigningKey,
158        receiver_public_key: &VerifyingKey,
159    ) -> Result<Self, Error> {
160        let shared_secret = Self::generate_shared_secret(sender_keypair, receiver_public_key)?;
161        let mut master_key = [0u8; 64];
162        master_key[..32].copy_from_slice(&sender_keypair.verifying_key().as_bytes()[..]);
163        master_key[32..].copy_from_slice(&shared_secret[..]);
164        let inner = helper::derive_key(&master_key);
165
166        Ok(Self { inner })
167    }
168
169    fn receiver_generate(
170        receiver_keypair: &SigningKey,
171        sender_public_key: &VerifyingKey,
172    ) -> Result<Self, Error> {
173        let shared_secret = Self::generate_shared_secret(receiver_keypair, sender_public_key)?;
174        let mut master_key = [0u8; 64];
175        master_key[..32].copy_from_slice(&sender_public_key.as_bytes()[..]);
176        master_key[32..].copy_from_slice(&shared_secret[..]);
177        let inner = helper::derive_key(&master_key);
178
179        Ok(Self { inner })
180    }
181
182    fn generate_shared_secret(
183        keypair_alice: &SigningKey,
184        public_bob: &VerifyingKey,
185    ) -> Result<[u8; 32], Error> {
186        let pk_compressed = curve25519_dalek::edwards::CompressedEdwardsY(public_bob.to_bytes());
187        let pk_point = pk_compressed
188            .decompress()
189            .ok_or(Error::EdwardsPointDecompressionFailure)?;
190        let sk_scalar = curve25519_dalek::Scalar::from_bytes_mod_order(
191            curve25519_dalek::scalar::clamp_integer(keypair_alice.to_scalar_bytes()),
192        );
193        let shared_point = pk_point * sk_scalar;
194        let shared_point_compressed = shared_point.compress();
195
196        Ok(shared_point_compressed.to_bytes())
197    }
198}
199
200#[cfg(test)]
201mod unit_tests {
202    use super::*;
203    use rand_core::RngCore;
204
205    const REPETITION: usize = 32;
206    const BUFFER_SIZE: usize = 512 * 1024; // avoid higher than this to prevent stackoverflow
207
208    #[test]
209    fn test_encrypt_decrypt_always_successful() {
210        for _ in 0..REPETITION {
211            let mut rng = rand_core::OsRng::default();
212            let sender_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
213            let receiver_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
214            let sender_public = sender_keypair.verifying_key();
215            let receiver_public = receiver_keypair.verifying_key();
216            let mut random_message = [0u8; BUFFER_SIZE];
217            let mut decrypted_message = [0u8; BUFFER_SIZE];
218            let mut ciphertext = [0u8; BUFFER_SIZE];
219            rng.fill_bytes(&mut random_message);
220
221            let decrypt_materials = encrypt_into(
222                &mut rng,
223                &sender_keypair,
224                &receiver_public,
225                &[],
226                &random_message[..],
227                &mut ciphertext[..],
228            )
229            .unwrap();
230            decrypt_into(
231                &decrypt_materials,
232                &receiver_keypair,
233                &sender_public,
234                &[],
235                &ciphertext[..],
236                &mut decrypted_message[..],
237            )
238            .unwrap();
239
240            assert_eq!(random_message, decrypted_message);
241            assert_ne!(sender_public, receiver_public);
242        }
243    }
244
245    #[test]
246    fn test_idempotent_keys_yield_different() {
247        for _ in 0..REPETITION {
248            let mut rng = rand_core::OsRng::default();
249            let sender_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
250            let receiver_keypair = ed25519_dalek::SigningKey::generate(&mut rng);
251            let receiver_public = receiver_keypair.verifying_key();
252            let mut random_message = [0u8; BUFFER_SIZE];
253            let mut ciphertext_0 = [0u8; BUFFER_SIZE];
254            let mut ciphertext_1 = [0u8; BUFFER_SIZE];
255            rng.fill_bytes(&mut random_message);
256
257            let _ = encrypt_into(
258                &mut rng,
259                &sender_keypair,
260                &receiver_public,
261                &[],
262                &random_message[..],
263                &mut ciphertext_0[..],
264            )
265            .unwrap();
266            let _ = encrypt_into(
267                &mut rng,
268                &sender_keypair,
269                &receiver_public,
270                &[],
271                &random_message[..],
272                &mut ciphertext_1[..],
273            )
274            .unwrap();
275
276            assert_ne!(ciphertext_0, ciphertext_1);
277        }
278    }
279}