threshold_bls/
ecies.rs

1//! # ECIES
2//!
3//! Implements an Elliptic Curve Integrated Encryption Scheme using SHA256 as the Key Derivation
4//! Function.
5//!
6//! # Examples
7//!
8//! ```rust
9//! use threshold_bls::{
10//!     ecies::{encrypt, decrypt},
11//!     curve::bls12381::G2Curve,
12//!     group::{Curve, Element}
13//! };
14//!
15//! let message = b"hello";
16//! let rng = &mut rand::thread_rng();
17//! let secret_key = <G2Curve as Curve>::Scalar::rand(rng);
18//! let mut public_key = <G2Curve as Curve>::Point::one();
19//! public_key.mul(&secret_key);
20//!
21//! // encrypt the message with the receiver's public key
22//! let ciphertext = encrypt::<G2Curve, _>(&public_key, &message[..], rng);
23//!
24//! // the receiver can then decrypt the ciphertext with their secret key
25//! let cleartext = decrypt(&secret_key, &ciphertext).unwrap();
26//!
27//! assert_eq!(&message[..], &cleartext[..]);
28//! ```
29
30// crypto imports
31use chacha20poly1305::{
32    aead::{Aead, Error as AError, NewAead},
33    ChaCha20Poly1305, Key,
34};
35// re-export for usage by dkg primitives
36pub use chacha20poly1305::aead::Error as EciesError;
37use hkdf::Hkdf;
38use rand_core::RngCore;
39use serde::{Deserialize, Serialize};
40use sha2::Sha256;
41
42use crate::group::{Curve, Element};
43
44/// The nonce length
45const NONCE_LEN: usize = 12;
46
47/// The ephemeral key length
48const KEY_LEN: usize = 32;
49
50/// A domain separator
51const DOMAIN: [u8; 4] = [1, 9, 6, 9];
52
53/// An ECIES encrypted cipher. Contains the ciphertext's bytes as well as the
54/// ephemeral public key
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct EciesCipher<C: Curve> {
57    /// The ciphertext which was encrypted
58    aead: Vec<u8>,
59    /// The ephemeral public key corresponding to the scalar which was used to
60    /// encrypt the plaintext
61    ephemeral: C::Point,
62    /// The nonce used to encrypt the ciphertext
63    nonce: [u8; NONCE_LEN],
64}
65
66/// Encrypts the message with a public key (curve point) and returns a ciphertext
67pub fn encrypt<C: Curve, R: RngCore>(to: &C::Point, msg: &[u8], rng: &mut R) -> EciesCipher<C> {
68    let eph_secret = C::Scalar::rand(rng);
69
70    let mut ephemeral = C::Point::one();
71    ephemeral.mul(&eph_secret);
72
73    // dh = eph(yG) = eph * public
74    let mut dh = to.clone();
75    dh.mul(&eph_secret);
76
77    // derive an ephemeral key from the public key
78    let ephemeral_key = derive::<C>(&dh);
79
80    // instantiate the AEAD scheme
81    let aead = ChaCha20Poly1305::new(Key::from_slice(ephemeral_key.as_slice()));
82
83    // generate a random nonce
84    let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
85    rng.fill_bytes(&mut nonce);
86
87    // do the encryption
88    let aead = aead
89        .encrypt(&nonce.into(), msg)
90        .expect("aead should not fail");
91
92    EciesCipher {
93        aead,
94        nonce,
95        ephemeral,
96    }
97}
98
99/// Decrypts the message with a secret key (curve scalar) and returns the cleartext
100pub fn decrypt<C: Curve>(private: &C::Scalar, cipher: &EciesCipher<C>) -> Result<Vec<u8>, AError> {
101    // dh = private * (eph * G) = private * ephPublic
102    let mut dh = cipher.ephemeral.clone();
103    dh.mul(private);
104
105    let ephemeral_key = derive::<C>(&dh);
106
107    let aead = ChaCha20Poly1305::new(Key::from_slice(ephemeral_key.as_slice()));
108
109    aead.decrypt(&cipher.nonce.into(), &cipher.aead[..])
110}
111
112/// Derives an ephemeral key from the provided public key
113fn derive<C: Curve>(dh: &C::Point) -> [u8; KEY_LEN] {
114    let serialized = bincode::serialize(dh).expect("could not serialize element");
115
116    // no salt is fine since we use ephemeral - static DH
117    let h = Hkdf::<Sha256>::new(None, &serialized);
118    let mut ephemeral_key = [0u8; KEY_LEN];
119    h.expand(&DOMAIN, &mut ephemeral_key)
120        .expect("hkdf should not fail");
121
122    debug_assert!(ephemeral_key.len() == KEY_LEN);
123
124    ephemeral_key
125}
126
127#[cfg(feature = "bls12_381")]
128#[cfg(test)]
129mod tests {
130    use rand::thread_rng;
131
132    use crate::curve::bls12381::{G1Curve as Curve, Scalar, G1};
133
134    use super::*;
135
136    fn kp() -> (Scalar, G1) {
137        let secret = Scalar::rand(&mut thread_rng());
138        let mut public = G1::one();
139        public.mul(&secret);
140        (secret, public)
141    }
142
143    #[test]
144    fn test_decryption() {
145        let (s1, _) = kp();
146        let (s2, p2) = kp();
147        let data = vec![1, 2, 3, 4];
148
149        // decryption with the right key OK
150        let mut cipher = encrypt::<Curve, _>(&p2, &data, &mut thread_rng());
151        let deciphered = decrypt::<Curve>(&s2, &cipher).unwrap();
152        assert_eq!(data, deciphered);
153
154        // decrypting with wrong private key should fail
155        decrypt::<Curve>(&s1, &cipher).unwrap_err();
156
157        // having an invalid ciphertext should fail
158        cipher.aead = vec![0; 32];
159        decrypt::<Curve>(&s2, &cipher).unwrap_err();
160    }
161}