diff --git a/chain-vote/benches/shvzk.rs b/chain-vote/benches/shvzk.rs index da91f9c05..3ea9f1271 100644 --- a/chain-vote/benches/shvzk.rs +++ b/chain-vote/benches/shvzk.rs @@ -1,5 +1,6 @@ use chain_vote::*; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::Rng; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; @@ -23,12 +24,36 @@ fn encrypt_and_prove(c: &mut Criterion) { let crs = Crs::from_hash(&[0u8; 32]); let ek = common(&mut rng); - for &number_candidates in [2usize, 4, 8].iter() { + for &number_candidates in [2usize, 4, 8, 16, 32, 64, 128, 256, 512].iter() { let parameter_string = format!("{} candidates", number_candidates); group.bench_with_input( BenchmarkId::new("Encrypt and Prove", parameter_string), &number_candidates, - |b, &nr| b.iter(|| ek.encrypt_and_prove_vote(&mut rng, &crs, Vote::new(nr, 0))), + |b, &nr| { + b.iter(|| ek.encrypt_and_prove_vote(&mut rng, &crs, Vote::new(nr, 0))) + }, + ); + } + + group.finish(); +} + +fn prove(c: &mut Criterion) { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let mut group = c.benchmark_group("Prove encrypted vote"); + let crs = Crs::from_hash(&[0u8; 32]); + let ek = common(&mut rng); + + for &number_candidates in [2usize, 4, 8, 16, 32, 64, 128, 256, 512].iter() { + group.bench_with_input( + BenchmarkId::new("Prove with", format!("{} candidates", number_candidates)), + &{ + let vote = Vote::new(number_candidates, rng.gen_range(0.. number_candidates)); + (vote, ek.encrypt_vote(&mut rng, vote)) + }, + |b, (vote, (vote_enc, randomness))| { + b.iter(|| ek.prove_encrypted_vote(&mut rng, &crs, *vote, vote_enc, randomness)) + }, ); } @@ -41,7 +66,7 @@ fn verify(c: &mut Criterion) { let crs = Crs::from_hash(&[0u8; 32]); let ek = common(&mut rng); - for &number_candidates in [2usize, 4, 8].iter() { + for &number_candidates in [2usize, 4, 8, 16, 32, 64, 128, 256, 512].iter() { let (vote, proof) = ek.encrypt_and_prove_vote(&mut rng, &crs, Vote::new(number_candidates, 0)); let parameter_string = format!("{} candidates", number_candidates); @@ -60,7 +85,8 @@ criterion_group!( config = Criterion::default().sample_size(500); targets = encrypt_and_prove, + prove, verify, ); -criterion_main!(shvzk); +criterion_main!(shvzk); \ No newline at end of file diff --git a/chain-vote/src/committee.rs b/chain-vote/src/committee.rs index 7f90adf25..2c6988544 100644 --- a/chain-vote/src/committee.rs +++ b/chain-vote/src/committee.rs @@ -56,7 +56,42 @@ impl ElectionPublicKey { ); (ciphertexts, proof) } + //------------------------------------------------------------------------------------- + // The encrypt_vote and prove_encrypted_vote methods are not the part of the original ElectionPublicKey trait, + // they are added only for a more selective benchmarking in this crate + //------------------------------------------------------------------------------------- + pub fn encrypt_vote( + &self, + rng: &mut R, + vote: Vote, + ) -> (EncryptedVote, Vec) { + let encryption_randomness = vec![Scalar::random(rng); vote.len()]; + let vote_enc = encryption_randomness + .iter() + .zip(vote.iter()) + .map(|(r, v)| self.as_raw().encrypt_with_r(&Scalar::from(v), r)) + .collect(); + (vote_enc, encryption_randomness) + } + pub fn prove_encrypted_vote( + &self, + rng: &mut R, + crs: &Crs, + vote: Vote, + vote_enc: &EncryptedVote, + encryption_randomness: &[Scalar] + ) -> ProofOfCorrectVote { + ProofOfCorrectVote::generate( + rng, + crs, + &self.0, + &vote, + encryption_randomness, + vote_enc, + ) + } + //------------------------------------------------------------------------------------- /// Create an election public key from all the participants of this committee pub fn from_participants(pks: &[MemberPublicKey]) -> Self { let mut k = pks[0].0.pk.clone(); @@ -170,6 +205,10 @@ impl MemberState { &self.sk } + pub fn member_secret_key(&self) -> MemberSecretKey { + self.sk.clone() + } + pub fn public_key(&self) -> MemberPublicKey { MemberPublicKey(PublicKey { pk: self.apubs[0].clone(), @@ -313,4 +352,4 @@ impl Bech32 for MemberCommunicationPublicKey { fn to_bech32_str(&self) -> String { to_bech32_from_bytes::(&self.to_bytes()) } -} +} \ No newline at end of file diff --git a/chain-vote/src/cryptography/zkps/unit_vector/messages.rs b/chain-vote/src/cryptography/zkps/unit_vector/messages.rs index 8a71ba53d..a9519ef84 100644 --- a/chain-vote/src/cryptography/zkps/unit_vector/messages.rs +++ b/chain-vote/src/cryptography/zkps/unit_vector/messages.rs @@ -2,8 +2,6 @@ //! same notation defined in Figure 8 use crate::cryptography::CommitmentKey; -use crate::encrypted_vote::binrep; -use crate::math::polynomial::Polynomial; use crate::{GroupElement, Scalar}; use rand_core::{CryptoRng, RngCore}; @@ -119,42 +117,69 @@ impl ResponseRandomness { } /// Generate the polynomials used in Step 5, of the proof generation in Figure 8. +/// Denoting unit-vector's size as N, this method takes 2(N - 2) polynomial multiplications +/// instead of N * (logN - 1) for the direct implementation pub(crate) fn generate_polys( - ciphers_len: usize, idx_binary_rep: &[bool], bits: usize, blinding_randomness_vec: &[BlindingRandomness], -) -> Vec { - // Compute polynomials pj(x) - let polys = idx_binary_rep - .iter() - .zip(blinding_randomness_vec.iter()) - .map(|(ix, abcd)| { - let z1 = Polynomial::new(bits).set2(abcd.beta.clone(), (*ix).into()); - let z0 = Polynomial::new(bits).set2(abcd.beta.negate(), (!ix).into()); - (z0, z1) - }) - .collect::>(); - - let mut pjs = Vec::new(); - for i in 0..ciphers_len { - let j = binrep(i, bits as u32); - - let mut acc = if j[0] { - polys[0].1.clone() +) -> Vec> { + // Multiplication of an arbitrary-degree polynomial on a degree-1 polynomial with a binary non-const term + // By being tailored for a given specific type of poly_deg1-multiplier it + // has better performance than naive polynomials multiplication: + // at most poly.len() - 1 additions and poly.len() multiplications instead of 2 * poly.len() + // + // NOTE: should be replaced with naive polynomial multiplication, if const-time (data-independent) + // multiplication complexity is needed + #[inline] + fn mul(poly: &[Scalar], poly_deg1: &(Scalar, bool)) -> Vec { + let mut result = poly.iter().map(|p|p * &poly_deg1.0).collect::>(); + if poly_deg1.1 == true { + for i in 0.. poly.len() - 1 { + result[i + 1] = &result[i + 1] + &poly[i]; + } + result.push(poly.last().unwrap().clone()); + } + result + } + // Binary tree which leaves are the polynomials corresponding to the indices in range [0, bits) + fn polynomials_bin_tree(parent: &[Scalar], current_level: usize, params: &TreeParams) -> Vec> { + if current_level != params.max_level { + let next_level = current_level + 1; + let left_subtree = polynomials_bin_tree( + &mul(&parent, ¶ms.deltas_0[current_level]), + next_level, + params + ); + let right_subtree = polynomials_bin_tree( + &mul(&parent, ¶ms.deltas_1[current_level]), + next_level, + params + ); + left_subtree.into_iter().chain(right_subtree.into_iter()).collect() } else { - polys[0].0.clone() - }; - for k in 1..bits { - let t = if j[k] { - polys[k].1.clone() - } else { - polys[k].0.clone() - }; - acc = acc * t; + vec![parent.to_vec()] } - pjs.push(acc) } - - pjs -} + // Precomputed degree-1 polynomials with values of the Kronecker delta function (for both possible inputs: 0 and 1) + // and with corresponding beta-randomness for each bit of a given unit vector + let deltas_0 = (0.. bits).map(|i| { + (blinding_randomness_vec[i].beta.clone().negate(), !idx_binary_rep[i]) + }).collect::>(); + + let deltas_1 = (0.. bits).map(|i| { + (blinding_randomness_vec[i].beta.clone(), idx_binary_rep[i]) + }).collect::>(); + + struct TreeParams { max_level: usize, deltas_0: Vec<(Scalar, bool)>, deltas_1: Vec<(Scalar, bool)> } + let tp = TreeParams{ max_level: bits, deltas_0, deltas_1 }; + // Building 2 subtrees from delta_0[0] and delta_1[0] to avoid 2 excessive multiplications with 1 as it would be with + // polynomials_bin_tree(&[Scalar::one()], 0, &tp) + let left_subtree = polynomials_bin_tree( + &[tp.deltas_0[0].0.clone(), Scalar::from(tp.deltas_0[0].1)], 1, &tp + ); + let right_subtree = polynomials_bin_tree( + &[tp.deltas_1[0].0.clone(), Scalar::from(tp.deltas_1[0].1)], 1, &tp + ); + left_subtree.into_iter().chain(right_subtree.into_iter()).collect() +} \ No newline at end of file diff --git a/chain-vote/src/cryptography/zkps/unit_vector/zkp.rs b/chain-vote/src/cryptography/zkps/unit_vector/zkp.rs index ef3477362..7e5dbf39c 100644 --- a/chain-vote/src/cryptography/zkps/unit_vector/zkp.rs +++ b/chain-vote/src/cryptography/zkps/unit_vector/zkp.rs @@ -72,14 +72,17 @@ impl Zkp { // Generate First verifier challenge let mut cc = ChallengeContext::new(&ck, public_key, ciphers.as_ref()); let cy = cc.first_challenge(&first_announcement_vec); + // The `cy_powers` is used multiple times so compute their values just once and store them + let cy_powers = cy.exp_iter().take(ciphers.len()).collect::>(); let (poly_coeff_enc, rs) = { - let pjs = generate_polys( - ciphers.len(), + let mut pjs = generate_polys( &idx_binary_rep, bits, &blinding_randomness_vec, ); + // Padding with zeroes the high-order coefficients to make all polynomials of 'bits' length + pjs.iter_mut().for_each(|p| p.resize(bits, Scalar::zero())); // Generate new Rs for Ds let mut rs = Vec::with_capacity(bits); @@ -87,11 +90,10 @@ impl Zkp { for i in 0..bits { let sum = - cy.exp_iter() + cy_powers.iter() .zip(pjs.iter()) .fold(Scalar::zero(), |sum, (c_pows, pj)| { - let s = sum + c_pows * pj.get_coefficient_at(i); - s + sum + c_pows * &pj[i] }); let (d, r) = public_key.encrypt_return_r(&sum, rng); @@ -111,22 +113,23 @@ impl Zkp { .map(|(abcd, index)| abcd.gen_response(&cx, index)) .collect::>(); + // Getting cx^logN from exp_iter instead of separate binary exponentiation with Scalar.power() + let cx_pows = cx.exp_iter().take(bits + 1).collect::>(); + // Compute R let response = { - let cx_pow = cx.power(cipher_randoms.bits()); - let p1 = cipher_randoms.iter().zip(cy.exp_iter()).fold( + let cx_pow = &cx_pows[bits]; + let p1 = cipher_randoms.iter().zip(cy_powers.iter()).fold( Scalar::zero(), |acc, (r, cy_pows)| { - let el = r * &cx_pow * cy_pows; - el + acc + acc + r * cx_pow * cy_pows }, ); let p2 = rs .iter() - .zip(cx.exp_iter()) - .fold(Scalar::zero(), |acc, (r, cx_pows)| { - let el = r * cx_pows; - el + acc + .zip(cx_pows.iter()) + .fold(Scalar::zero(), |acc, (r, cx_power)| { + acc + r * cx_power }); p1 + p2 }; @@ -177,27 +180,19 @@ impl Zkp { ) -> bool { let bits = ciphertexts.bits(); let length = ciphertexts.len(); - let cx_pow = challenge_x.power(bits); - - let powers_cx = challenge_x.exp_iter(); - let powers_cy = challenge_y.exp_iter(); - - let powers_z_iterator = powers_z_encs_iter(&self.zwvs, challenge_x, &(bits as u32)); - - let zero = public_key.encrypt_with_r(&Scalar::zero(), &self.r); // Challenge value for batching two equations into a single multiscalar mult. - let batch_challenge = Scalar::random(&mut thread_rng()); + let batch_challenge1 = Scalar::random(&mut thread_rng()); for (zwv, iba) in self.zwvs.iter().zip(self.ibas.iter()) { if GroupElement::vartime_multiscalar_multiplication( iter::once(zwv.z.clone()) - .chain(iter::once(&zwv.w + &batch_challenge * &zwv.v)) + .chain(iter::once(&zwv.w + &batch_challenge1 * &zwv.v)) .chain(iter::once( - &batch_challenge * (&zwv.z - challenge_x) - challenge_x, + &batch_challenge1 * (&zwv.z - challenge_x) - challenge_x, )) .chain(iter::once(Scalar::one().negate())) - .chain(iter::once(batch_challenge.negate())), + .chain(iter::once(batch_challenge1.negate())), iter::once(GroupElement::generator()) .chain(iter::once(commitment_key.h.clone())) .chain(iter::once(iba.i.clone())) @@ -209,29 +204,38 @@ impl Zkp { } } - let mega_check = GroupElement::vartime_multiscalar_multiplication( - powers_cy - .clone() - .take(length) - .map(|s| s * &cx_pow) - .chain(powers_cy.clone().take(length).map(|s| s * &cx_pow)) - .chain(powers_cy.take(length)) - .chain(powers_cx.clone().take(bits)) - .chain(powers_cx.take(bits)) - .chain(iter::once(Scalar::one().negate())) - .chain(iter::once(Scalar::one().negate())), - ciphertexts - .iter() - .map(|ctxt| ctxt.e2.clone()) - .chain(ciphertexts.iter().map(|ctxt| ctxt.e1.clone())) - .chain(powers_z_iterator.take(length)) - .chain(self.ds.iter().map(|ctxt| ctxt.e1.clone())) - .chain(self.ds.iter().map(|ctxt| ctxt.e2.clone())) - .chain(iter::once(zero.e1.clone())) - .chain(iter::once(zero.e2)), - ); + let products_z_iter = powers_z_encs_iter(&self.zwvs, challenge_x, &(bits as u32)); + let powers_cy = challenge_y.exp_iter().take(length).collect::>(); - mega_check == GroupElement::zero() + let products_z_mul_powers_cy_sum = + powers_cy.iter().zip(products_z_iter) + .fold(Scalar::zero(), |sum, (cy_i, z_i)|{ + sum + &z_i * cy_i + }); + + // The `powers_cx` and `powers_cy_mul_cx_log_n` are needed multiple times + // so computing right away their values from exp_iter. + // Also getting cx^logN from exp_iter instead of separate exponentiation with Scalar.power() + let mut powers_cx = challenge_x.exp_iter().take(bits + 1).collect::>(); + let cx_log_n = powers_cx.pop().unwrap(); // cx^logN + let powers_cy_mul_cx_log_n = powers_cy.iter().map(|cy_i| cy_i * &cx_log_n).collect::>(); + + let batch_challenge2 = Scalar::random(&mut thread_rng()); + + GroupElement::vartime_multiscalar_multiplication( + powers_cy_mul_cx_log_n.iter().map(|p| p * &batch_challenge2).collect::>().into_iter() + .chain(powers_cx.iter().map(|p| p * &batch_challenge2).collect::>()) + .chain(iter::once(products_z_mul_powers_cy_sum - self.r.clone() * &batch_challenge2)) + .chain(powers_cy_mul_cx_log_n) + .chain(powers_cx) + .chain(iter::once(self.r.negate())), + ciphertexts.iter().map(|ct| ct.e1.clone()) + .chain(self.ds.iter().map(|d| d.e1.clone())) + .chain(iter::once(GroupElement::generator())) + .chain(ciphertexts.iter().map(|ct| ct.e2.clone())) + .chain(self.ds.iter().map(|d| d.e2.clone())) + .chain(iter::once(public_key.pk.clone())) + ) == GroupElement::zero() } /// Try to generate a `Proof` from a buffer @@ -293,6 +297,17 @@ impl Zkp { self.ibas.iter() } + /// Return announcement commitments group elements + pub fn announcments_group_elements(&self) -> Vec { + let mut announcements = Vec::new(); + for g in self.ibas.clone() { + announcements.push(g.i); + announcements.push(g.b); + announcements.push(g.a) + } + announcements + } + /// Return an iterator of the encryptions of the polynomial coefficients pub fn ds(&self) -> impl Iterator { self.ds.iter() @@ -303,6 +318,18 @@ impl Zkp { self.zwvs.iter() } + /// Return an iterator of the response related to the randomness + pub fn response_randomness_group_elements(&self) -> Vec { + let mut response = Vec::new(); + for z in self.zwvs.iter().clone() { + response.push(z.z.clone()); + response.push(z.w.clone()); + response.push(z.v.clone()); + } + + response + } + /// Return R pub fn r(&self) -> &Scalar { &self.r @@ -316,7 +343,7 @@ fn powers_z_encs( index: usize, bit_size: u32, ) -> Scalar { - let idx = binrep(index, bit_size as u32); + let idx = binrep(index, bit_size); let multz = z.iter().enumerate().fold(Scalar::one(), |acc, (j, zwv)| { let m = if idx[j] { @@ -340,19 +367,18 @@ struct ZPowExp { } impl Iterator for ZPowExp { - type Item = GroupElement; + type Item = Scalar; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { let z_pow = powers_z_encs(&self.z, self.challenge_x.clone(), self.index, self.bit_size); self.index += 1; - Some(z_pow.negate() * GroupElement::generator()) + Some(z_pow.negate()) } fn size_hint(&self) -> (usize, Option) { (usize::MAX, None) } } - // Return an iterator of the powers of `ZPowExp`. #[allow(dead_code)] // can be removed if the default flag is ristretto instead of sec2 fn powers_z_encs_iter(z: &[ResponseRandomness], challenge_x: &Scalar, bit_size: &u32) -> ZPowExp { @@ -462,7 +488,21 @@ mod tests { Ciphertext::zero(), Ciphertext::zero(), ]; - assert!(!proof.verify(&crs, &public_key, &fake_encryption)) + assert!(!proof.verify(&crs, &public_key, &fake_encryption)); + + // Testing a case with swapped components (e1, e2) in UV's ElGamal ciphertexts + let ciphertexts_swapped = ciphertexts.into_iter() + .map(|ct|Ciphertext{e1: ct.e2, e2: ct.e1}).collect::>(); + + let proof_swapped = Zkp::generate( + &mut r, + &crs, + &public_key, + &unit_vector, + &encryption_randomness, + &ciphertexts_swapped, + ); + assert!(!proof_swapped.verify(&crs, &public_key, &ciphertexts_swapped)); } #[test] @@ -537,4 +577,4 @@ mod tests { assert_eq!(cy1, cy5); assert_ne!(cx1, cx5); } -} +} \ No newline at end of file