diff --git a/mod.rs b/mod.rs index 5889519..a9f21ba 100644 --- a/mod.rs +++ b/mod.rs @@ -26,7 +26,7 @@ impl PartialEq for PublicKey { impl PublicKey { /// Returns `true` if the signature matches the element of `E::G2`. pub fn verify_g2>(&self, sig: &Signature, hash: H) -> bool { - E::pairing(self.0, hash) == E::pairing(E::G1::one(), sig.0) + E::pairing(self.0, hash) == E::pairing(E::G1Affine::one(), sig.0) } /// Returns `true` if the signature matches the message. @@ -34,21 +34,22 @@ impl PublicKey { self.verify_g2(sig, hash_g2::(msg)) } + /// Returns `true` if the decryption share matches the ciphertext. + pub fn verify_decryption_share(&self, share: &DecryptionShare, ct: &Ciphertext) -> bool { + let Ciphertext(ref u, ref v, ref w) = *ct; + let hash = hash_g1_g2::(*u, v); + E::pairing(share.0, hash) == E::pairing(self.0, *w) + } + /// 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 g = self.0.into_affine().mul(r); + xor_vec(&hash_bytes::(g, msg.as_ref().len()), msg.as_ref()) }; - let mut w = hash_g1_g2::(u, &v); - w.mul_assign(r); + let w = hash_g1_g2::(u, &v).into_affine().mul(r); Ciphertext(u, v, w) } } @@ -100,14 +101,16 @@ impl SecretKey { 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) + let g = u.into_affine().mul(self.0); + Some(xor_vec(&hash_bytes::(g, v.len()), v)) + } + + /// Returns a decryption share, or `None`, if the ciphertext isn't valid. + pub fn decrypt_share(&self, ct: &Ciphertext) -> Option> { + if !ct.verify() { + return None; + } + Some(DecryptionShare(ct.0.into_affine().mul(self.0))) } } @@ -127,7 +130,17 @@ impl Ciphertext { 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) + E::pairing(E::G1Affine::one(), *w) == E::pairing(*u, hash) + } +} + +/// A decryption share. A threshold of decryption shares can be used to decrypt a message. +#[derive(Debug)] +pub struct DecryptionShare(E::G1); + +impl PartialEq for DecryptionShare { + fn eq(&self, other: &DecryptionShare) -> bool { + self.0 == other.0 } } @@ -166,45 +179,25 @@ impl PublicKeySet { PublicKey(pk) } - /// Verifies that the given signatures are correct and combines them into a signature that can - /// be verified with the main public key. - pub fn combine_signatures<'a, ITR, IND>(&self, items: ITR) -> Result> + /// Combines the shares into a signature that can be verified with the main public key. + pub fn combine_signatures<'a, ITR, IND>(&self, shares: ITR) -> Result> where ITR: IntoIterator)>, IND: Into<::Repr> + Clone + 'a, { - let sigs: Vec<_> = items - .into_iter() - .map(|(i, sig)| { - let mut x = E::Fr::one(); - x.add_assign(&E::Fr::from_repr(i.clone().into()).expect("invalid index")); - (x, sig) - }) - .collect(); - if sigs.len() < self.coeff.len() { - return Err(ErrorKind::NotEnoughShares.into()); - } - let mut result = E::G2::zero(); - let mut indexes = Vec::new(); - for (x, sig) in sigs.iter().take(self.coeff.len()) { - if indexes.contains(x) { - return Err(ErrorKind::DuplicateEntry.into()); - } - indexes.push(x.clone()); - // Compute the value at 0 of the Lagrange polynomial that is `0` at the other data - // points but `1` at `x`. - let mut l0 = E::Fr::one(); - for (x0, _) in sigs.iter().take(self.coeff.len()).filter(|(x0, _)| x0 != x) { - let mut denom = *x0; - denom.sub_assign(x); - l0.mul_assign(x0); - l0.mul_assign(&denom.inverse().expect("indices are different")); - } - let mut summand = sig.0; - summand.mul_assign(l0); - result.add_assign(&summand); - } - Ok(Signature(result)) + let samples = shares.into_iter().map(|(i, share)| (i, &share.0)); + Ok(Signature(interpolate(self.coeff.len(), samples)?)) + } + + /// Combines the shares to decrypt the ciphertext. + pub fn decrypt<'a, ITR, IND>(&self, shares: ITR, ct: &Ciphertext) -> Result> + where + ITR: IntoIterator)>, + IND: Into<::Repr> + Clone + 'a, + { + let samples = shares.into_iter().map(|(i, share)| (i, &share.0)); + let g = interpolate(self.coeff.len(), samples)?; + Ok(xor_vec(&hash_bytes::(g, ct.1.len()), &ct.1)) } } @@ -235,8 +228,7 @@ impl SecretKeySet { where T: Into<::Repr>, { - let mut x = E::Fr::one(); - x.add_assign(&E::Fr::from_repr(i.into()).expect("invalid index")); + let x = from_repr_plus_1(i.into()); let mut pk = *self.coeff.last().expect("at least one coefficient"); for c in self.coeff.iter().rev().skip(1) { pk.mul_assign(&x); @@ -288,6 +280,53 @@ fn hash_bytes(g1: E::G1, len: usize) -> Vec { rng.gen_iter().take(len).collect() } +/// Returns the bitwise xor. +fn xor_vec(x: &[u8], y: &[u8]) -> Vec { + x.iter().zip(y).map(|(a, b)| a ^ b).collect() +} + +/// Given a list of `t` samples `(i - 1, f(i) * g)` for a polynomial `f` of degree `t - 1`, and a +/// group generator `g`, returns `f(0) * g`. +pub fn interpolate<'a, C, ITR, IND>(t: usize, items: ITR) -> Result +where + C: CurveProjective, + ITR: IntoIterator, + IND: Into<::Repr> + Clone + 'a, +{ + let samples: Vec<_> = items + .into_iter() + .map(|(i, sample)| (from_repr_plus_1::(i.clone().into()), sample)) + .collect(); + if samples.len() < t { + return Err(ErrorKind::NotEnoughShares.into()); + } + let mut result = C::zero(); + let mut indexes = Vec::new(); + for (x, sample) in samples.iter().take(t) { + if indexes.contains(x) { + return Err(ErrorKind::DuplicateEntry.into()); + } + indexes.push(x.clone()); + // Compute the value at 0 of the Lagrange polynomial that is `0` at the other data + // points but `1` at `x`. + let mut l0 = C::Scalar::one(); + for (x0, _) in samples.iter().take(t).filter(|(x0, _)| x0 != x) { + let mut denom = *x0; + denom.sub_assign(x); + l0.mul_assign(x0); + l0.mul_assign(&denom.inverse().expect("indices are different")); + } + result.add_assign(&sample.into_affine().mul(l0)); + } + Ok(result) +} + +fn from_repr_plus_1(repr: F::Repr) -> F { + let mut x = F::one(); + x.add_assign(&F::from_repr(repr).expect("invalid index")); + x +} + #[cfg(test)] mod tests { use super::*; @@ -366,6 +405,38 @@ mod tests { assert_eq!(None, sk_bob.decrypt(&fake_ciphertext)); } + #[test] + fn test_threshold_enc() { + let mut rng = rand::thread_rng(); + let sk_set = SecretKeySet::::new(3, &mut rng); + let pk_set = sk_set.public_keys(); + let msg = b"Totally real news"; + let ciphertext = pk_set.public_key().encrypt(&msg[..]); + + // The threshold is 3, so 4 signature shares will suffice to decrypt. + let shares: BTreeMap<_, _> = [5, 8, 7, 10] + .into_iter() + .map(|i| { + let ski = sk_set.secret_key_share(*i); + let share = ski.decrypt_share(&ciphertext).expect("ciphertext is valid"); + (*i, share) + }) + .collect(); + + // Each of the shares is valid matching its public key share. + for (i, share) in &shares { + pk_set + .public_key_share(*i) + .verify_decryption_share(share, &ciphertext); + } + + // Combined, they can decrypt the message. + let decrypted = pk_set + .decrypt(&shares, &ciphertext) + .expect("decryption shares match"); + assert_eq!(msg[..], decrypted[..]); + } + /// Some basic sanity checks for the hash function. #[test] fn test_hash_g2() { @@ -402,7 +473,7 @@ mod tests { mod serde { use pairing::{CurveAffine, CurveProjective, EncodedPoint, Engine}; - use super::{PublicKey, Signature}; + use super::{DecryptionShare, PublicKey, Signature}; use serde::de::Error as DeserializeError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -433,6 +504,18 @@ mod serde { } } + impl Serialize for DecryptionShare { + fn serialize(&self, s: S) -> Result { + serialize_projective(&self.0, s) + } + } + + impl<'de, E: Engine> Deserialize<'de> for DecryptionShare { + fn deserialize>(d: D) -> Result { + Ok(DecryptionShare(deserialize_projective(d)?)) + } + } + /// Serializes the compressed representation of a group element. fn serialize_projective(c: &C, s: S) -> Result where