Lint FROST key gen and optimize sign for the success path

This commit is contained in:
Luke Parker 2022-05-30 01:46:30 -04:00
parent 5a1f273cd5
commit 614badfef7
No known key found for this signature in database
GPG key ID: F9F1386DB1E119B6
2 changed files with 51 additions and 43 deletions

View file

@ -186,7 +186,7 @@ fn generate_key_r2<R: RngCore + CryptoRng, C: Curve>(
fn complete_r2<R: RngCore + CryptoRng, C: Curve>( fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
rng: &mut R, rng: &mut R,
params: MultisigParams, params: MultisigParams,
share: C::F, mut secret_share: C::F,
commitments: HashMap<u16, Vec<C::G>>, commitments: HashMap<u16, Vec<C::G>>,
// Vec to preserve ownership // Vec to preserve ownership
mut serialized: HashMap<u16, Vec<u8>>, mut serialized: HashMap<u16, Vec<u8>>,
@ -194,7 +194,7 @@ fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
validate_map( validate_map(
&mut serialized, &mut serialized,
&(1 ..= params.n()).into_iter().collect::<Vec<_>>(), &(1 ..= params.n()).into_iter().collect::<Vec<_>>(),
(params.i(), C::F_to_bytes(&share)) (params.i(), C::F_to_bytes(&secret_share))
)?; )?;
// Step 2. Verify each share // Step 2. Verify each share
@ -203,52 +203,66 @@ fn complete_r2<R: RngCore + CryptoRng, C: Curve>(
shares.insert(l, C::F_from_slice(&share).map_err(|_| FrostError::InvalidShare(params.i()))?); shares.insert(l, C::F_from_slice(&share).map_err(|_| FrostError::InvalidShare(params.i()))?);
} }
// Calculate the exponent for a given participant and apply it to a series of commitments
// Initially used with the actual commitments to verify the secret share, later used with stripes
// to generate the verification shares
let exponential = |i: u16, values: &[_]| {
let i = C::F::from(i.into());
let mut res = Vec::with_capacity(params.t().into());
(0 .. usize::from(params.t())).into_iter().fold(
C::F::one(),
|exp, l| {
res.push((exp, values[l]));
exp * i
}
);
res
};
let mut batch = BatchVerifier::new(shares.len(), C::little_endian()); let mut batch = BatchVerifier::new(shares.len(), C::little_endian());
for (l, share) in &shares { for (l, share) in &shares {
if *l == params.i() { if *l == params.i() {
continue; continue;
} }
let i_scalar = C::F::from(params.i.into()); secret_share += share;
let mut exp = C::F::one();
let mut values = Vec::with_capacity(usize::from(params.t()) + 1);
for lt in 0 .. params.t() {
values.push((exp, commitments[&l][usize::from(lt)]));
exp *= i_scalar;
}
values.push((-*share, C::generator()));
// This can be insecurely linearized from n * t to just n using the below sums for a given
// stripe. Doing so uses naive addition which is subject to malleability. The only way to
// ensure that malleability isn't present is to use this n * t algorithm, which runs
// per sender and not as an aggregate of all senders, which also enables blame
let mut values = exponential(params.i, &commitments[l]);
values.push((-*share, C::generator()));
batch.queue(rng, *l, values); batch.queue(rng, *l, values);
} }
batch.verify_with_vartime_blame().map_err(|l| FrostError::InvalidCommitment(l))?; batch.verify_with_vartime_blame().map_err(|l| FrostError::InvalidCommitment(l))?;
// TODO: Clear the original share // 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 secret_share = C::F::zero(); // If these weren't just sums, yet the tables used in multiexp, this would be further optimized
for (_, share) in shares { let mut stripes = Vec::with_capacity(usize::from(params.t()));
secret_share += share; for t in 0 .. usize::from(params.t()) {
stripes.push(commitments.values().map(|commitments| commitments[t]).sum());
} }
// Calculate each user's verification share
let mut verification_shares = HashMap::new(); let mut verification_shares = HashMap::new();
for i in 1 ..= params.n() { for i in 1 ..= params.n() {
let i_scalar = C::F::from(i.into()); verification_shares.insert(i, multiexp_vartime(exponential(i, &stripes), C::little_endian()));
let mut values = vec![];
(0 .. params.t()).into_iter().fold(C::F::one(), |exp, j| {
values.push((
exp,
(1 ..= params.n()).into_iter().map(|l| commitments[&l][usize::from(j)]).sum()
));
exp * i_scalar
});
verification_shares.insert(i, multiexp_vartime(values, C::little_endian()));
} }
debug_assert_eq!(C::generator_table() * secret_share, verification_shares[&params.i()]); debug_assert_eq!(C::generator_table() * secret_share, verification_shares[&params.i()]);
let group_key = commitments.iter().map(|(_, commitments)| commitments[0]).sum();
// TODO: Clear serialized and shares // TODO: Clear serialized and shares
Ok(MultisigKeys { params, secret_share, group_key, verification_shares, offset: None } ) Ok(
MultisigKeys {
params,
secret_share,
group_key: stripes[0],
verification_shares,
offset: None
}
)
} }
/// State of a Key Generation machine /// State of a Key Generation machine

View file

@ -4,7 +4,6 @@ use std::{rc::Rc, collections::HashMap};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
use ff::Field; use ff::Field;
use group::Group;
use transcript::Transcript; use transcript::Transcript;
@ -99,7 +98,8 @@ fn preprocess<R: RngCore + CryptoRng, C: Curve, A: Algorithm<C>>(
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct Package<C: Curve> { struct Package<C: Curve> {
Ris: HashMap<u16, C::G>, B: HashMap<u16, [C::G; 2]>,
binding: C::F,
R: C::G, R: C::G,
share: Vec<u8> share: Vec<u8>
} }
@ -170,16 +170,9 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
let mut Ris = HashMap::with_capacity(params.view.included.len()); let R = {
#[allow(non_snake_case)] B.values().map(|B| B[0]).sum::<C::G>() + (B.values().map(|B| B[1]).sum::<C::G>() * binding)
let mut R = C::G::identity(); };
for l in &params.view.included {
#[allow(non_snake_case)]
let this_R = B[l][0] + (B[l][1] * binding);
Ris.insert(*l, this_R);
R += this_R;
}
let share = C::F_to_bytes( let share = C::F_to_bytes(
&params.algorithm.sign_share( &params.algorithm.sign_share(
&params.view, &params.view,
@ -190,7 +183,7 @@ fn sign_with_share<C: Curve, A: Algorithm<C>>(
) )
); );
Ok((Package { Ris, R, share: share.clone() }, share)) Ok((Package { B, binding, R, share: share.clone() }, share))
} }
// This doesn't check the signing set is as expected and unexpected changes can cause false blames // This doesn't check the signing set is as expected and unexpected changes can cause false blames
@ -220,11 +213,12 @@ fn complete<C: Curve, A: Algorithm<C>>(
return Ok(res); return Ok(res);
} }
// Find out who misbehaved // Find out who misbehaved. It may be beneficial to randomly sort this to have detection be
// within n / 2 on average, and not gameable to n, though that should be minor
for l in &sign_params.view.included { for l in &sign_params.view.included {
if !sign_params.algorithm.verify_share( if !sign_params.algorithm.verify_share(
sign_params.view.verification_share(*l), sign_params.view.verification_share(*l),
sign.Ris[l], sign.B[l][0] + (sign.B[l][1] * sign.binding),
responses[l] responses[l]
) { ) {
Err(FrostError::InvalidShare(*l))?; Err(FrostError::InvalidShare(*l))?;