diff --git a/Cargo.toml b/Cargo.toml index 1509c8e6..864210c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,18 +117,32 @@ members = [ # to the extensive operations required for Bulletproofs [profile.dev.package] subtle = { opt-level = 3 } -curve25519-dalek = { opt-level = 3 } ff = { opt-level = 3 } group = { opt-level = 3 } crypto-bigint = { opt-level = 3 } +secp256k1 = { opt-level = 3 } +curve25519-dalek = { opt-level = 3 } dalek-ff-group = { opt-level = 3 } minimal-ed448 = { opt-level = 3 } multiexp = { opt-level = 3 } -monero-serai = { opt-level = 3 } +secq256k1 = { opt-level = 3 } +embedwards25519 = { opt-level = 3 } +generalized-bulletproofs = { opt-level = 3 } +generalized-bulletproofs-circuit-abstraction = { opt-level = 3 } +ec-divisors = { opt-level = 3 } +generalized-bulletproofs-ec-gadgets = { opt-level = 3 } + +dkg = { opt-level = 3 } + +monero-generators = { opt-level = 3 } +monero-borromean = { opt-level = 3 } +monero-bulletproofs = { opt-level = 3 } +monero-mlsag = { opt-level = 3 } +monero-clsag = { opt-level = 3 } [profile.release] panic = "unwind" diff --git a/crypto/dkg/src/evrf/mod.rs b/crypto/dkg/src/evrf/mod.rs index dd994b0a..01c56da2 100644 --- a/crypto/dkg/src/evrf/mod.rs +++ b/crypto/dkg/src/evrf/mod.rs @@ -192,12 +192,15 @@ fn share_verification_statements( } /// Struct to perform/verify the DKG with. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct EvrfDkg { t: u16, n: u16, evrf_public_keys: Vec<::G>, - participations: HashMap, EvrfVerifyResult)>, + group_key: C::G, + verification_shares: HashMap, + encrypted_secret_shares: + HashMap::G; 2], C::F)>>, } impl EvrfDkg @@ -255,6 +258,9 @@ where /// `Participation`s are valid and there's at least `t`, an instance of this struct (usable to /// obtain a threshold share of generated key) is returned. If all are valid and there's not at /// least `t`, an error of an empty list is returned after validation. + /// + /// This DKG is unbiased if all `n` people participate. This DKG is biased if only a threshold + /// participate. pub fn verify( rng: &mut (impl RngCore + CryptoRng), generators: &Generators, @@ -277,7 +283,7 @@ where } } - let mut res = HashMap::new(); + let mut valid = HashMap::new(); let mut faulty = HashSet::new(); let mut evrf_verifier = generators.batch_verifier(); @@ -299,9 +305,9 @@ where }; evrf_verifier = verifier_clone; - res.insert(*i, (participation.encrypted_secret_shares.clone(), data)); + valid.insert(*i, (participation.encrypted_secret_shares.clone(), data)); } - debug_assert_eq!(res.len() + faulty.len(), participations.len()); + debug_assert_eq!(valid.len() + faulty.len(), participations.len()); // Perform the batch verification of the eVRFs if !generators.verify(evrf_verifier) { @@ -323,20 +329,23 @@ where ) .expect("evrf failed basic checks yet prover wasn't prior marked faulty"); if !generators.verify(evrf_verifier) { - res.remove(i); + valid.remove(i); faulty.insert(*i); } } } - debug_assert_eq!(res.len() + faulty.len(), participations.len()); + debug_assert_eq!(valid.len() + faulty.len(), participations.len()); // Perform the batch verification of the shares + let mut sum_encrypted_secret_shares = HashMap::new(); + let mut sum_masks = HashMap::new(); + let mut all_encrypted_secret_shares = HashMap::new(); { - let mut share_verification_statements_actual = HashMap::with_capacity(res.len()); + let mut share_verification_statements_actual = HashMap::with_capacity(valid.len()); if !{ let mut g_scalar = C::F::ZERO; - let mut pairs = Vec::with_capacity(res.len() * (usize::from(t) + evrf_public_keys.len())); - for (i, (encrypted_secret_shares, data)) in &res { + let mut pairs = Vec::with_capacity(valid.len() * (usize::from(t) + evrf_public_keys.len())); + for (i, (encrypted_secret_shares, data)) in &valid { let (this_g_scalar, mut these_pairs) = share_verification_statements::( &mut *rng, &data.coefficients, @@ -347,11 +356,36 @@ where &data.encryption_commitments, encrypted_secret_shares, ); + // Queue this into our batch g_scalar += this_g_scalar; pairs.extend(&these_pairs); + // Also push this g_scalar onto these_pairs so these_pairs can be verified individually + // upon error these_pairs.push((this_g_scalar, generators.g())); share_verification_statements_actual.insert(*i, these_pairs); + + // Also format this data as we'd need it upon success + let mut formatted_encrypted_secret_shares = HashMap::new(); + for (j, enc_share) in encrypted_secret_shares { + /* + We calculcate verification shares as the sum of the encrypted scalars, minus their + masks. This only does one scalar multiplication, and `1+t` point additions (with + one negation), and is accordingly much cheaper than interpolating the commitments. + This is only possible because already interpolated the commitments to verify the + encrypted secret share. + */ + let sum_encrypted_secret_share = + sum_encrypted_secret_shares.get(j).copied().unwrap_or(C::F::ZERO); + let sum_mask = sum_masks.get(j).copied().unwrap_or(C::G::identity()); + sum_encrypted_secret_shares.insert(*j, sum_encrypted_secret_share + enc_share); + + let j_index = usize::from(u16::from(*j)) - 1; + sum_masks.insert(*j, sum_mask + data.encryption_commitments[j_index]); + + formatted_encrypted_secret_shares.insert(*j, (data.ecdh_keys[j_index], *enc_share)); + } + all_encrypted_secret_shares.insert(*i, formatted_encrypted_secret_shares); } pairs.push((g_scalar, generators.g())); bool::from(multiexp_vartime(&pairs).is_identity()) @@ -359,13 +393,13 @@ where // If the batch failed, verify them each individually for (i, pairs) in share_verification_statements_actual { if !bool::from(multiexp_vartime(&pairs).is_identity()) { - res.remove(&i); + valid.remove(&i); faulty.insert(i); } } } } - debug_assert_eq!(res.len() + faulty.len(), participations.len()); + debug_assert_eq!(valid.len() + faulty.len(), participations.len()); let mut faulty = faulty.into_iter().collect::>(); if !faulty.is_empty() { @@ -373,13 +407,33 @@ where Err(faulty)?; } - if res.len() < usize::from(t) { + if valid.len() < usize::from(t) { Err(vec![])?; } - Ok(EvrfDkg { t, n, evrf_public_keys: evrf_public_keys.to_vec(), participations: res }) + // If we now have >= t participations, calculate the group key and verification shares + + // The group key is the sum of the zero coefficients + let group_key = valid.values().map(|(_, evrf_data)| evrf_data.coefficients[0]).sum::(); + + // Calculate each user's verification share + let mut verification_shares = HashMap::new(); + for i in (1 ..= n).map(Participant) { + verification_shares + .insert(i, (C::generator() * sum_encrypted_secret_shares[&i]) - sum_masks[&i]); + } + + Ok(EvrfDkg { + t, + n, + evrf_public_keys: evrf_public_keys.to_vec(), + group_key, + verification_shares, + encrypted_secret_shares: all_encrypted_secret_shares, + }) } + // TODO: Return all keys for this participant, not just the first pub fn keys( &self, evrf_private_key: &Zeroizing<::F>, @@ -392,9 +446,11 @@ where let i = Participant(1 + i); let mut secret_share = Zeroizing::new(C::F::ZERO); - for (shares, evrf_data) in self.participations.values() { + for shares in self.encrypted_secret_shares.values() { + let (ecdh_keys, enc_share) = shares[&i]; + let mut ecdh = Zeroizing::new(C::F::ZERO); - for point in evrf_data.ecdh_keys[usize::from(u16::from(i)) - 1] { + for point in ecdh_keys { // TODO: Explicitly ban 0-ECDH commitments, 0-eVRF public keys, and gen non-zero keys let (mut x, mut y) = ::G::to_xy(point * evrf_private_key.deref()).unwrap(); @@ -402,45 +458,16 @@ where x.zeroize(); y.zeroize(); } - *secret_share += shares[&i] - ecdh.deref(); + *secret_share += enc_share - ecdh.deref(); } - // Stripe commitments per t and sum them in advance. Calculating verification shares relies on - // these sums so preprocessing them is a massive speedup - let mut stripes = Vec::with_capacity(usize::from(self.t)); - for t in 0 .. usize::from(self.t) { - stripes.push( - self.participations.values().map(|(_, evrf_data)| evrf_data.coefficients[t]).sum::(), - ); - } - - // Calculate each user's verification share - let mut verification_shares = HashMap::new(); - for j in (1 ..= self.n).map(Participant) { - verification_shares.insert( - j, - if j == i { - C::generator() * secret_share.deref() - } else { - fn exponential(i: Participant, values: &[C::G]) -> Vec<(C::F, C::G)> { - let i = C::F::from(u16::from(i).into()); - let mut res = Vec::with_capacity(values.len()); - (0 .. values.len()).fold(C::F::ONE, |exp, l| { - res.push((exp, values[l])); - exp * i - }); - res - } - multiexp_vartime(&exponential::(j, &stripes)) - }, - ); - } + debug_assert_eq!(self.verification_shares[&i], C::generator() * secret_share.deref()); Some(ThresholdCore { params: ThresholdParams::new(self.t, self.n, i).unwrap(), secret_share, - group_key: stripes[0], - verification_shares, + group_key: self.group_key, + verification_shares: self.verification_shares.clone(), }) } } diff --git a/crypto/dkg/src/evrf/proof.rs b/crypto/dkg/src/evrf/proof.rs index 2f8cd444..bc353baf 100644 --- a/crypto/dkg/src/evrf/proof.rs +++ b/crypto/dkg/src/evrf/proof.rs @@ -54,7 +54,6 @@ pub(crate) struct EvrfProveResult { } /// The result of verifying an eVRF. -#[derive(Clone)] pub(crate) struct EvrfVerifyResult { /// The commitments to the coefficients for use in the DKG. pub(crate) coefficients: Vec, diff --git a/crypto/dkg/src/tests/evrf/mod.rs b/crypto/dkg/src/tests/evrf/mod.rs index 0649d9ff..1b6fea38 100644 --- a/crypto/dkg/src/tests/evrf/mod.rs +++ b/crypto/dkg/src/tests/evrf/mod.rs @@ -9,7 +9,7 @@ use ciphersuite::{group::ff::Field, Ciphersuite}; use crate::{ Participant, ThresholdKeys, evrf::*, - tests::{THRESHOLD, PARTICIPANTS}, + tests::{THRESHOLD, PARTICIPANTS, recover_key}, }; mod proof; @@ -18,6 +18,7 @@ use proof::{Pallas, Vesta}; #[test] fn evrf_dkg() { let generators = EvrfDkg::::generators(THRESHOLD, PARTICIPANTS); + let context = [0; 32]; let mut priv_keys = vec![]; let mut pub_keys = vec![]; @@ -36,7 +37,7 @@ fn evrf_dkg() { EvrfDkg::::participate( &mut OsRng, &generators, - [0; 32], + context, THRESHOLD, &pub_keys, priv_key, @@ -48,17 +49,29 @@ fn evrf_dkg() { let dkg = EvrfDkg::::verify( &mut OsRng, &generators, - [0; 32], + context, THRESHOLD, &pub_keys, &participations, ) .unwrap(); + let mut group_key = None; + let mut verification_shares = None; + let mut all_keys = HashMap::new(); for (i, priv_key) in priv_keys { let keys = ThresholdKeys::from(dkg.keys(&priv_key).unwrap()); assert_eq!(keys.params().i(), i); assert_eq!(keys.params().t(), THRESHOLD); assert_eq!(keys.params().n(), PARTICIPANTS); + group_key = group_key.or(Some(keys.group_key())); + verification_shares = verification_shares.or(Some(keys.verification_shares())); + assert_eq!(Some(keys.group_key()), group_key); + assert_eq!(Some(keys.verification_shares()), verification_shares); + + all_keys.insert(i, keys); } + + // TODO: Test fo all possible combinations of keys + assert_eq!(Pallas::generator() * recover_key(&all_keys), group_key.unwrap()); }