mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-14 15:09:23 +00:00
Improve eVRF DKG
Updates how we calculcate verification shares, improves performance when extracting multiple sets of keys, and adds more to the test for it.
This commit is contained in:
18
Cargo.toml
18
Cargo.toml
@@ -117,18 +117,32 @@ members = [
|
|||||||
# to the extensive operations required for Bulletproofs
|
# to the extensive operations required for Bulletproofs
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
subtle = { opt-level = 3 }
|
subtle = { opt-level = 3 }
|
||||||
curve25519-dalek = { opt-level = 3 }
|
|
||||||
|
|
||||||
ff = { opt-level = 3 }
|
ff = { opt-level = 3 }
|
||||||
group = { opt-level = 3 }
|
group = { opt-level = 3 }
|
||||||
|
|
||||||
crypto-bigint = { opt-level = 3 }
|
crypto-bigint = { opt-level = 3 }
|
||||||
|
secp256k1 = { opt-level = 3 }
|
||||||
|
curve25519-dalek = { opt-level = 3 }
|
||||||
dalek-ff-group = { opt-level = 3 }
|
dalek-ff-group = { opt-level = 3 }
|
||||||
minimal-ed448 = { opt-level = 3 }
|
minimal-ed448 = { opt-level = 3 }
|
||||||
|
|
||||||
multiexp = { 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]
|
[profile.release]
|
||||||
panic = "unwind"
|
panic = "unwind"
|
||||||
|
|||||||
@@ -192,12 +192,15 @@ fn share_verification_statements<C: Ciphersuite>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Struct to perform/verify the DKG with.
|
/// Struct to perform/verify the DKG with.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvrfDkg<C: EvrfCurve> {
|
pub struct EvrfDkg<C: EvrfCurve> {
|
||||||
t: u16,
|
t: u16,
|
||||||
n: u16,
|
n: u16,
|
||||||
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
|
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
|
||||||
participations: HashMap<Participant, (HashMap<Participant, C::F>, EvrfVerifyResult<C>)>,
|
group_key: C::G,
|
||||||
|
verification_shares: HashMap<Participant, C::G>,
|
||||||
|
encrypted_secret_shares:
|
||||||
|
HashMap<Participant, HashMap<Participant, ([<C::EmbeddedCurve as Ciphersuite>::G; 2], C::F)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: EvrfCurve> EvrfDkg<C>
|
impl<C: EvrfCurve> EvrfDkg<C>
|
||||||
@@ -255,6 +258,9 @@ where
|
|||||||
/// `Participation`s are valid and there's at least `t`, an instance of this struct (usable to
|
/// `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
|
/// 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.
|
/// 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(
|
pub fn verify(
|
||||||
rng: &mut (impl RngCore + CryptoRng),
|
rng: &mut (impl RngCore + CryptoRng),
|
||||||
generators: &Generators<C>,
|
generators: &Generators<C>,
|
||||||
@@ -277,7 +283,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res = HashMap::new();
|
let mut valid = HashMap::new();
|
||||||
let mut faulty = HashSet::new();
|
let mut faulty = HashSet::new();
|
||||||
|
|
||||||
let mut evrf_verifier = generators.batch_verifier();
|
let mut evrf_verifier = generators.batch_verifier();
|
||||||
@@ -299,9 +305,9 @@ where
|
|||||||
};
|
};
|
||||||
evrf_verifier = verifier_clone;
|
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
|
// Perform the batch verification of the eVRFs
|
||||||
if !generators.verify(evrf_verifier) {
|
if !generators.verify(evrf_verifier) {
|
||||||
@@ -323,20 +329,23 @@ where
|
|||||||
)
|
)
|
||||||
.expect("evrf failed basic checks yet prover wasn't prior marked faulty");
|
.expect("evrf failed basic checks yet prover wasn't prior marked faulty");
|
||||||
if !generators.verify(evrf_verifier) {
|
if !generators.verify(evrf_verifier) {
|
||||||
res.remove(i);
|
valid.remove(i);
|
||||||
faulty.insert(*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
|
// 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 !{
|
if !{
|
||||||
let mut g_scalar = C::F::ZERO;
|
let mut g_scalar = C::F::ZERO;
|
||||||
let mut pairs = Vec::with_capacity(res.len() * (usize::from(t) + evrf_public_keys.len()));
|
let mut pairs = Vec::with_capacity(valid.len() * (usize::from(t) + evrf_public_keys.len()));
|
||||||
for (i, (encrypted_secret_shares, data)) in &res {
|
for (i, (encrypted_secret_shares, data)) in &valid {
|
||||||
let (this_g_scalar, mut these_pairs) = share_verification_statements::<C>(
|
let (this_g_scalar, mut these_pairs) = share_verification_statements::<C>(
|
||||||
&mut *rng,
|
&mut *rng,
|
||||||
&data.coefficients,
|
&data.coefficients,
|
||||||
@@ -347,11 +356,36 @@ where
|
|||||||
&data.encryption_commitments,
|
&data.encryption_commitments,
|
||||||
encrypted_secret_shares,
|
encrypted_secret_shares,
|
||||||
);
|
);
|
||||||
|
// Queue this into our batch
|
||||||
g_scalar += this_g_scalar;
|
g_scalar += this_g_scalar;
|
||||||
pairs.extend(&these_pairs);
|
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()));
|
these_pairs.push((this_g_scalar, generators.g()));
|
||||||
share_verification_statements_actual.insert(*i, these_pairs);
|
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()));
|
pairs.push((g_scalar, generators.g()));
|
||||||
bool::from(multiexp_vartime(&pairs).is_identity())
|
bool::from(multiexp_vartime(&pairs).is_identity())
|
||||||
@@ -359,13 +393,13 @@ where
|
|||||||
// If the batch failed, verify them each individually
|
// If the batch failed, verify them each individually
|
||||||
for (i, pairs) in share_verification_statements_actual {
|
for (i, pairs) in share_verification_statements_actual {
|
||||||
if !bool::from(multiexp_vartime(&pairs).is_identity()) {
|
if !bool::from(multiexp_vartime(&pairs).is_identity()) {
|
||||||
res.remove(&i);
|
valid.remove(&i);
|
||||||
faulty.insert(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::<Vec<_>>();
|
let mut faulty = faulty.into_iter().collect::<Vec<_>>();
|
||||||
if !faulty.is_empty() {
|
if !faulty.is_empty() {
|
||||||
@@ -373,13 +407,33 @@ where
|
|||||||
Err(faulty)?;
|
Err(faulty)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.len() < usize::from(t) {
|
if valid.len() < usize::from(t) {
|
||||||
Err(vec![])?;
|
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::<C::G>();
|
||||||
|
|
||||||
|
// 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(
|
pub fn keys(
|
||||||
&self,
|
&self,
|
||||||
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
|
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
|
||||||
@@ -392,9 +446,11 @@ where
|
|||||||
let i = Participant(1 + i);
|
let i = Participant(1 + i);
|
||||||
|
|
||||||
let mut secret_share = Zeroizing::new(C::F::ZERO);
|
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);
|
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
|
// TODO: Explicitly ban 0-ECDH commitments, 0-eVRF public keys, and gen non-zero keys
|
||||||
let (mut x, mut y) =
|
let (mut x, mut y) =
|
||||||
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(point * evrf_private_key.deref()).unwrap();
|
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(point * evrf_private_key.deref()).unwrap();
|
||||||
@@ -402,45 +458,16 @@ where
|
|||||||
x.zeroize();
|
x.zeroize();
|
||||||
y.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
|
debug_assert_eq!(self.verification_shares[&i], C::generator() * secret_share.deref());
|
||||||
// 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::<C::G>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<C: Ciphersuite>(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::<C>(j, &stripes))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(ThresholdCore {
|
Some(ThresholdCore {
|
||||||
params: ThresholdParams::new(self.t, self.n, i).unwrap(),
|
params: ThresholdParams::new(self.t, self.n, i).unwrap(),
|
||||||
secret_share,
|
secret_share,
|
||||||
group_key: stripes[0],
|
group_key: self.group_key,
|
||||||
verification_shares,
|
verification_shares: self.verification_shares.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ pub(crate) struct EvrfProveResult<C: Ciphersuite> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The result of verifying an eVRF.
|
/// The result of verifying an eVRF.
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct EvrfVerifyResult<C: EvrfCurve> {
|
pub(crate) struct EvrfVerifyResult<C: EvrfCurve> {
|
||||||
/// The commitments to the coefficients for use in the DKG.
|
/// The commitments to the coefficients for use in the DKG.
|
||||||
pub(crate) coefficients: Vec<C::G>,
|
pub(crate) coefficients: Vec<C::G>,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ciphersuite::{group::ff::Field, Ciphersuite};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Participant, ThresholdKeys,
|
Participant, ThresholdKeys,
|
||||||
evrf::*,
|
evrf::*,
|
||||||
tests::{THRESHOLD, PARTICIPANTS},
|
tests::{THRESHOLD, PARTICIPANTS, recover_key},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod proof;
|
mod proof;
|
||||||
@@ -18,6 +18,7 @@ use proof::{Pallas, Vesta};
|
|||||||
#[test]
|
#[test]
|
||||||
fn evrf_dkg() {
|
fn evrf_dkg() {
|
||||||
let generators = EvrfDkg::<Pallas>::generators(THRESHOLD, PARTICIPANTS);
|
let generators = EvrfDkg::<Pallas>::generators(THRESHOLD, PARTICIPANTS);
|
||||||
|
let context = [0; 32];
|
||||||
|
|
||||||
let mut priv_keys = vec![];
|
let mut priv_keys = vec![];
|
||||||
let mut pub_keys = vec![];
|
let mut pub_keys = vec![];
|
||||||
@@ -36,7 +37,7 @@ fn evrf_dkg() {
|
|||||||
EvrfDkg::<Pallas>::participate(
|
EvrfDkg::<Pallas>::participate(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&generators,
|
&generators,
|
||||||
[0; 32],
|
context,
|
||||||
THRESHOLD,
|
THRESHOLD,
|
||||||
&pub_keys,
|
&pub_keys,
|
||||||
priv_key,
|
priv_key,
|
||||||
@@ -48,17 +49,29 @@ fn evrf_dkg() {
|
|||||||
let dkg = EvrfDkg::<Pallas>::verify(
|
let dkg = EvrfDkg::<Pallas>::verify(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
&generators,
|
&generators,
|
||||||
[0; 32],
|
context,
|
||||||
THRESHOLD,
|
THRESHOLD,
|
||||||
&pub_keys,
|
&pub_keys,
|
||||||
&participations,
|
&participations,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut group_key = None;
|
||||||
|
let mut verification_shares = None;
|
||||||
|
let mut all_keys = HashMap::new();
|
||||||
for (i, priv_key) in priv_keys {
|
for (i, priv_key) in priv_keys {
|
||||||
let keys = ThresholdKeys::from(dkg.keys(&priv_key).unwrap());
|
let keys = ThresholdKeys::from(dkg.keys(&priv_key).unwrap());
|
||||||
assert_eq!(keys.params().i(), i);
|
assert_eq!(keys.params().i(), i);
|
||||||
assert_eq!(keys.params().t(), THRESHOLD);
|
assert_eq!(keys.params().t(), THRESHOLD);
|
||||||
assert_eq!(keys.params().n(), PARTICIPANTS);
|
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());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user