diff --git a/crypto/frost/src/algorithm.rs b/crypto/frost/src/algorithm.rs index 992687bf..2d00f508 100644 --- a/crypto/frost/src/algorithm.rs +++ b/crypto/frost/src/algorithm.rs @@ -2,11 +2,10 @@ use core::{marker::PhantomData, fmt::Debug}; use rand_core::{RngCore, CryptoRng}; -use group::Group; - use transcript::Transcript; -use crate::{Curve, FrostError, MultisigView}; +use crate::{Curve, FrostError, MultisigView, schnorr}; +pub use schnorr::SchnorrSignature; /// Algorithm to use FROST with pub trait Algorithm: Clone { @@ -103,13 +102,6 @@ impl> Schnorr { } } -#[allow(non_snake_case)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct SchnorrSignature { - pub R: C::G, - pub s: C::F, -} - /// Implementation of Schnorr signatures for use with FROST impl> Algorithm for Schnorr { type Transcript = IetfTranscript; @@ -148,13 +140,13 @@ impl> Algorithm for Schnorr { ) -> C::F { let c = H::hram(&nonce_sum, ¶ms.group_key(), msg); self.c = Some(c); - - nonce + (params.secret_share() * c) + schnorr::sign::(params.secret_share(), nonce, c).s } fn verify(&self, group_key: C::G, nonce: C::G, sum: C::F) -> Option { - if (C::generator_table() * sum) + (C::G::identity() - (group_key * self.c.unwrap())) == nonce { - Some(SchnorrSignature { R: nonce, s: sum }) + let sig = SchnorrSignature { R: nonce, s: sum }; + if schnorr::verify::(group_key, self.c.unwrap(), &sig) { + Some(sig) } else { None } @@ -166,6 +158,10 @@ impl> Algorithm for Schnorr { nonce: C::G, share: C::F, ) -> bool { - (C::generator_table() * share) == (nonce + (verification_share * self.c.unwrap())) + schnorr::verify::( + verification_share, + self.c.unwrap(), + &SchnorrSignature { R: nonce, s: share} + ) } } diff --git a/crypto/frost/src/key_gen.rs b/crypto/frost/src/key_gen.rs index 9d0ea9d6..6480e8f3 100644 --- a/crypto/frost/src/key_gen.rs +++ b/crypto/frost/src/key_gen.rs @@ -4,9 +4,12 @@ use std::collections::HashMap; use rand_core::{RngCore, CryptoRng}; use ff::{Field, PrimeField}; -use group::Group; -use crate::{Curve, MultisigParams, MultisigKeys, FrostError, validate_map}; +use crate::{ + Curve, MultisigParams, MultisigKeys, FrostError, + schnorr::{self, SchnorrSignature}, + validate_map +}; #[allow(non_snake_case)] fn challenge(l: u16, context: &str, R: &[u8], Am: &[u8]) -> C::F { @@ -42,19 +45,24 @@ fn generate_key_r1( } // Step 2: Provide a proof of knowledge - // This can be deterministic as the PoK is a singleton never opened up to cooperative discussion - // There's also no reason to spend the time and effort to make this deterministic besides a - // general obsession with canonicity and determinism let r = C::F::random(rng); - #[allow(non_snake_case)] - let R = C::generator_table() * r; - let s = r + ( - coefficients[0] * challenge::(params.i(), context, &C::G_to_bytes(&R), &serialized) + serialized.extend( + schnorr::sign::( + coefficients[0], + // This could be deterministic as the PoK is a singleton never opened up to cooperative + // discussion + // There's no reason to spend the time and effort to make this deterministic besides a + // general obsession with canonicity and determinism though + r, + challenge::( + params.i(), + context, + &C::G_to_bytes(&(C::generator_table() * r)), + &serialized + ) + ).serialize() ); - serialized.extend(&C::G_to_bytes(&R)); - serialized.extend(&C::F_to_bytes(&s)); - // Step 4: Broadcast (coefficients, serialized) } @@ -88,9 +96,7 @@ fn verify_r1( &serialized[&l][commitments_len + C::G_len() ..] ).map_err(|_| FrostError::InvalidProofOfKnowledge(l)); - let mut first = true; - let mut scalars = Vec::with_capacity((usize::from(params.n()) - 1) * 3); - let mut points = Vec::with_capacity((usize::from(params.n()) - 1) * 3); + let mut signatures = Vec::with_capacity(usize::from(params.n() - 1)); for l in 1 ..= params.n() { let mut these_commitments = vec![]; for c in 0 .. usize::from(params.t()) { @@ -100,54 +106,29 @@ fn verify_r1( ).map_err(|_| FrostError::InvalidCommitment(l.try_into().unwrap()))? ); } - commitments.insert(l, these_commitments); // Don't bother validating our own proof of knowledge - if l == params.i() { - continue; + if l != params.i() { + // Step 5: Validate each proof of knowledge + // This is solely the prep step for the latter batch verification + signatures.push(( + l, + these_commitments[0], + challenge::(l, context, R_bytes(l), Am(l)), + SchnorrSignature:: { R: R(l)?, s: s(l)? } + )); } - // Step 5: Validate each proof of knowledge (prep) - let mut u = C::F::one(); - if !first { - u = C::F::random(&mut *rng); - } - - // uR - scalars.push(u); - points.push(R(l)?); - - // -usG - scalars.push(-s(l)? * u); - points.push(C::generator()); - - // ucA - let c = challenge::(l, context, R_bytes(l), Am(l)); - scalars.push(if first { first = false; c } else { c * u}); - points.push(commitments[&l][0]); + commitments.insert(l, these_commitments); } - // Step 5: Implementation - // Uses batch verification to optimize the success case dramatically - // On failure, the cost is now this + blame, yet that should happen infrequently - // s = r + ca - // sG == R + cA - // R + cA - sG == 0 - if C::multiexp_vartime(&scalars, &points) != C::G::identity() { - for l in 1 ..= params.n() { - if l == params.i() { - continue; - } - - if (C::generator_table() * s(l)?) != ( - R(l)? + (commitments[&l][0] * challenge::(l, context, R_bytes(l), Am(l))) - ) { - Err(FrostError::InvalidProofOfKnowledge(l))?; - } + schnorr::batch_verify(rng, &signatures).map_err( + |l| if l == 0 { + FrostError::InternalError("batch validation is broken".to_string()) + } else { + FrostError::InvalidProofOfKnowledge(l) } - - Err(FrostError::InternalError("batch validation is broken".to_string()))?; - } + )?; Ok(commitments) } diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 06830bb3..64a6a1d0 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -8,6 +8,8 @@ use group::{Group, GroupOps, ScalarMul}; pub use multiexp::multiexp_vartime; +mod schnorr; + pub mod key_gen; pub mod algorithm; pub mod sign; diff --git a/crypto/frost/src/schnorr.rs b/crypto/frost/src/schnorr.rs new file mode 100644 index 00000000..10408e2d --- /dev/null +++ b/crypto/frost/src/schnorr.rs @@ -0,0 +1,82 @@ +use rand_core::{RngCore, CryptoRng}; + +use ff::Field; +use group::Group; + +use crate::Curve; + +#[allow(non_snake_case)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct SchnorrSignature { + pub R: C::G, + pub s: C::F, +} + +impl SchnorrSignature { + pub fn serialize(&self) -> Vec { + let mut res = Vec::with_capacity(C::G_len() + C::F_len()); + res.extend(C::G_to_bytes(&self.R)); + res.extend(C::F_to_bytes(&self.s)); + res + } +} + +pub(crate) fn sign( + private_key: C::F, + nonce: C::F, + challenge: C::F +) -> SchnorrSignature { + SchnorrSignature { + R: C::generator_table() * nonce, + s: nonce + (private_key * challenge) + } +} + +pub(crate) fn verify( + public_key: C::G, + challenge: C::F, + signature: &SchnorrSignature +) -> bool { + (C::generator_table() * signature.s) == (signature.R + (public_key * challenge)) +} + +pub(crate) fn batch_verify( + rng: &mut R, + triplets: &[(u16, C::G, C::F, SchnorrSignature)] +) -> Result<(), u16> { + let mut first = true; + let mut scalars = Vec::with_capacity(triplets.len() * 3); + let mut points = Vec::with_capacity(triplets.len() * 3); + for triple in triplets { + let mut u = C::F::one(); + if !first { + u = C::F::random(&mut *rng); + } + + // uR + scalars.push(u); + points.push(triple.3.R); + + // -usG + scalars.push(-triple.3.s * u); + points.push(C::generator()); + + // ucA + scalars.push(if first { first = false; triple.2 } else { triple.2 * u}); + points.push(triple.1); + } + + // s = r + ca + // sG == R + cA + // R + cA - sG == 0 + if C::multiexp_vartime(&scalars, &points) == C::G::identity() { + Ok(()) + } else { + for triple in triplets { + if !verify::(triple.1, triple.2, &triple.3) { + Err(triple.0)?; + } + } + Err(0) + } +}