diff --git a/mod.rs b/mod.rs index 1829598..5889519 100644 --- a/mod.rs +++ b/mod.rs @@ -3,7 +3,7 @@ mod error; use byteorder::{BigEndian, ByteOrder}; use init_with::InitWith; use pairing::{CurveAffine, CurveProjective, Engine, Field, PrimeField}; -use rand::{ChaChaRng, Rand, Rng, SeedableRng}; +use rand::{ChaChaRng, OsRng, Rng, SeedableRng}; use ring::digest; use self::error::{ErrorKind, Result}; @@ -11,29 +11,13 @@ use self::error::{ErrorKind, Result}; /// The number of words (`u32`) in a ChaCha RNG seed. const CHACHA_RNG_SEED_SIZE: usize = 8; -/// Returns a hash of the given message in `G2`. -pub fn hash_g2(msg: M) -> E::G2 -where - E: Engine, - ::G2: Rand, - M: AsRef<[u8]>, -{ - let digest = digest::digest(&digest::SHA256, msg.as_ref()); - let seed = <[u32; CHACHA_RNG_SEED_SIZE]>::init_with_indices(|i| { - BigEndian::read_u32(&digest.as_ref()[(4 * i)..(4 * i + 4)]) - }); - let mut rng = ChaChaRng::from_seed(&seed); - rng.gen() -} +const ERR_OS_RNG: &str = "could not initialize the OS random number generator"; /// A public key, or a public key share. #[derive(Debug)] pub struct PublicKey(E::G1); -impl PartialEq for PublicKey -where - E::G2: PartialEq, -{ +impl PartialEq for PublicKey { fn eq(&self, other: &PublicKey) -> bool { self.0 == other.0 } @@ -49,16 +33,31 @@ impl PublicKey { pub fn verify>(&self, sig: &Signature, msg: M) -> bool { self.verify_g2(sig, hash_g2::(msg)) } + + /// Encrypts the message. + pub fn encrypt>(&self, msg: M) -> Ciphertext { + let r: E::Fr = OsRng::new().expect(ERR_OS_RNG).gen(); + let u = E::G1Affine::one().mul(r); + let v: Vec = { + let mut g = self.0; + g.mul_assign(r); + hash_bytes::(g, msg.as_ref().len()) + .into_iter() + .zip(msg.as_ref()) + .map(|(x, y)| x ^ y) + .collect() + }; + let mut w = hash_g1_g2::(u, &v); + w.mul_assign(r); + Ciphertext(u, v, w) + } } /// A signature, or a signature share. #[derive(Debug)] pub struct Signature(E::G2); -impl PartialEq for Signature -where - E::G2: PartialEq, -{ +impl PartialEq for Signature { fn eq(&self, other: &Signature) -> bool { self.0 == other.0 } @@ -68,10 +67,7 @@ where #[derive(Debug)] pub struct SecretKey(E::Fr); -impl PartialEq for SecretKey -where - E::G2: PartialEq, -{ +impl PartialEq for SecretKey { fn eq(&self, other: &SecretKey) -> bool { self.0 == other.0 } @@ -97,6 +93,42 @@ impl SecretKey { pub fn sign>(&self, msg: M) -> Signature { self.sign_g2(hash_g2::(msg)) } + + /// Returns the decrypted text, or `None`, if the ciphertext isn't valid. + pub fn decrypt(&self, ct: &Ciphertext) -> Option> { + if !ct.verify() { + return None; + } + let Ciphertext(ref u, ref v, _) = *ct; + let mut g = *u; + g.mul_assign(self.0); + let decrypted = hash_bytes::(g, v.len()) + .into_iter() + .zip(v) + .map(|(x, y)| x ^ y) + .collect(); + Some(decrypted) + } +} + +/// An encrypted message. +#[derive(Debug)] +pub struct Ciphertext(E::G1, Vec, E::G2); + +impl PartialEq for Ciphertext { + fn eq(&self, other: &Ciphertext) -> bool { + self.0 == other.0 && self.1 == other.1 && self.2 == other.2 + } +} + +impl Ciphertext { + /// Returns `true` if this is a valid ciphertext. This check is necessary to prevent + /// chosen-ciphertext attacks. + pub fn verify(&self) -> bool { + let Ciphertext(ref u, ref v, ref w) = *self; + let hash = hash_g1_g2::(*u, v); + E::pairing(E::G1Affine::one(), w.into_affine()) == E::pairing(u.into_affine(), hash) + } } /// A public key and an associated set of public key shares. @@ -222,6 +254,40 @@ impl SecretKeySet { } } +/// Returns a hash of the given message in `G2`. +fn hash_g2>(msg: M) -> E::G2 { + let digest = digest::digest(&digest::SHA256, msg.as_ref()); + let seed = <[u32; CHACHA_RNG_SEED_SIZE]>::init_with_indices(|i| { + BigEndian::read_u32(&digest.as_ref()[(4 * i)..(4 * i + 4)]) + }); + let mut rng = ChaChaRng::from_seed(&seed); + rng.gen() +} + +/// Returns a hash of the group element and message, in the second group. +fn hash_g1_g2>(g1: E::G1, msg: M) -> E::G2 { + // If the message is large, hash it, otherwise copy it. + // TODO: Benchmark and optimize the threshold. + let mut msg = if msg.as_ref().len() > 64 { + let digest = digest::digest(&digest::SHA256, msg.as_ref()); + digest.as_ref().to_vec() + } else { + msg.as_ref().to_vec() + }; + msg.extend(g1.into_affine().into_compressed().as_ref()); + hash_g2::(&msg) +} + +/// Returns a hash of the group element with the specified length in bytes. +fn hash_bytes(g1: E::G1, len: usize) -> Vec { + let digest = digest::digest(&digest::SHA256, g1.into_affine().into_compressed().as_ref()); + let seed = <[u32; CHACHA_RNG_SEED_SIZE]>::init_with_indices(|i| { + BigEndian::read_u32(&digest.as_ref()[(4 * i)..(4 * i + 4)]) + }); + let mut rng = ChaChaRng::from_seed(&seed); + rng.gen_iter().take(len).collect() +} + #[cfg(test)] mod tests { use super::*; @@ -275,6 +341,31 @@ mod tests { assert_eq!(sig, sig2); } + #[test] + fn test_simple_enc() { + let mut rng = rand::thread_rng(); + let sk_bob = SecretKey::::new(&mut rng); + let sk_eve = SecretKey::::new(&mut rng); + let pk_bob = sk_bob.public_key(); + let msg = b"Muffins in the canteen today! Don't tell Eve!"; + let ciphertext = pk_bob.encrypt(&msg[..]); + assert!(ciphertext.verify()); + + // Bob can decrypt the message. + let decrypted = sk_bob.decrypt(&ciphertext).expect("valid ciphertext"); + assert_eq!(msg[..], decrypted[..]); + + // Eve can't. + let decrypted_eve = sk_eve.decrypt(&ciphertext).expect("valid ciphertext"); + assert_ne!(msg[..], decrypted_eve[..]); + + // Eve tries to trick Bob into decrypting `msg` xor `v`, but it doesn't validate. + let Ciphertext(u, v, w) = ciphertext; + let fake_ciphertext = Ciphertext::(u, vec![0; v.len()], w); + assert!(!fake_ciphertext.verify()); + assert_eq!(None, sk_bob.decrypt(&fake_ciphertext)); + } + /// Some basic sanity checks for the hash function. #[test] fn test_hash_g2() {