diff --git a/fastcrypto-tbls/src/dl_verification.rs b/fastcrypto-tbls/src/dl_verification.rs index 917aaf2c18..21bda6f3d1 100644 --- a/fastcrypto-tbls/src/dl_verification.rs +++ b/fastcrypto-tbls/src/dl_verification.rs @@ -3,11 +3,39 @@ use crate::polynomial::{Eval, Poly}; use fastcrypto::error::{FastCryptoError, FastCryptoResult}; -use fastcrypto::groups::{GroupElement, MultiScalarMul}; +use fastcrypto::groups::{GroupElement, MultiScalarMul, Scalar}; use fastcrypto::traits::AllowedRng; /// Helper functions for checking relations between scalars and group elements. +fn dot(v1: &[S], v2: &[S]) -> S { + assert_eq!(v1.len(), v2.len()); + v1.iter() + .zip(v2.iter()) + .fold(S::zero(), |acc, (a, b)| acc + *a * *b) +} + +/// Given a set of indexes and a vector of random scalars , +/// returns the vector v such that = \sum ri * p(ai) for a polynomial p with coefficients c. +pub fn batch_coefficients(r: &[S], indexes: &[S], degree: u32) -> Vec { + let mut multiplies = r.to_vec(); + let mut res = Vec::::new(); + for i in 0..=degree { + let sum = multiplies.iter().fold(S::zero(), |acc, r| acc + *r); + res.push(sum); + if i == degree { + // Save some computation since we don't need multiplies anymore + break; + } + multiplies = multiplies + .iter() + .zip(indexes.iter()) + .map(|(r, c)| *r * c) + .collect::>(); + } + res +} + /// Checks that a given set of evaluations is consistent with a given polynomial in the exp by /// checking that (\sum r_i v_i)*G = \sum r_i p(i) for a random set of scalars r_i. pub fn verify_poly_evals( @@ -15,35 +43,15 @@ pub fn verify_poly_evals( poly: &Poly, rng: &mut R, ) -> FastCryptoResult<()> { - let rs = get_random_scalars::(evals.len() as u32, rng); + let rs = get_random_scalars::(evals.len() as u32, rng); - let lhs = G::generator() - * rs.iter() - .zip(evals.iter()) - .map(|(r, eval)| *r * eval.value) - .fold(G::ScalarType::zero(), |acc, r| acc + r); + let lhs = G::generator() * dot(&rs, &evals.iter().map(|e| e.value).collect::>()); - let mut multiplies = rs; - let mut coeffs = Vec::::new(); let evals_as_scalars = evals .iter() .map(|e| G::ScalarType::from(e.index.get().into())) .collect::>(); - for i in 0..poly.as_vec().len() { - let sum = multiplies - .iter() - .fold(G::ScalarType::zero(), |acc, r| acc + *r); - coeffs.push(sum); - if i == poly.as_vec().len() - 1 { - // Save some computation since we don't need multiplies anymore - break; - } - multiplies = multiplies - .iter() - .zip(evals_as_scalars.iter()) - .map(|(r, eval)| *r * eval) - .collect::>(); - } + let coeffs = batch_coefficients(&rs, &evals_as_scalars, poly.degree()); let rhs = G::multi_scalar_mul(&coeffs, poly.as_vec()).expect("sizes match"); if lhs != rhs { @@ -52,12 +60,7 @@ pub fn verify_poly_evals( Ok(()) } -pub fn get_random_scalars( - n: u32, - rng: &mut R, -) -> Vec<::ScalarType> { +pub fn get_random_scalars(n: u32, rng: &mut R) -> Vec { // TODO: can use 40 bits instead of 64 ("& 0x000F_FFFF_FFFF_FFFF" below) - (0..n) - .map(|_| G::ScalarType::from(rng.next_u64())) - .collect::>() + (0..n).map(|_| S::from(rng.next_u64())).collect::>() } diff --git a/fastcrypto-tbls/src/tbls.rs b/fastcrypto-tbls/src/tbls.rs index 084aa1ec27..8955ab8b98 100644 --- a/fastcrypto-tbls/src/tbls.rs +++ b/fastcrypto-tbls/src/tbls.rs @@ -4,10 +4,12 @@ // Some of the code below is based on code from https://github.com/celo-org/celo-threshold-bls-rs, // modified for our needs. +use crate::dl_verification::{batch_coefficients, get_random_scalars}; use crate::polynomial::Poly; use crate::types::IndexedValue; use fastcrypto::error::FastCryptoError; -use fastcrypto::groups::{GroupElement, HashToGroupElement, Scalar}; +use fastcrypto::groups::{GroupElement, HashToGroupElement, MultiScalarMul, Scalar}; +use fastcrypto::traits::AllowedRng; pub type Share = IndexedValue; pub type PartialSignature = IndexedValue; @@ -16,9 +18,9 @@ pub type PartialSignature = IndexedValue; pub trait ThresholdBls { type Private: Scalar; /// `Public` represents the group over which the public keys are represented. - type Public: GroupElement; + type Public: GroupElement + MultiScalarMul; /// `Signature` represents the group over which the signatures are represented. - type Signature: GroupElement + HashToGroupElement; + type Signature: GroupElement + HashToGroupElement + MultiScalarMul; /// Curve dependent implementation of computing and comparing the pairings as part of the /// signature verification. @@ -28,12 +30,6 @@ pub trait ThresholdBls { msg: &[u8], ) -> Result<(), FastCryptoError>; - /// Sign a message using the private key. - fn sign(private: &Self::Private, msg: &[u8]) -> Self::Signature { - let h = Self::Signature::hash_to_group_element(msg); - h * private - } - /// Verify a signature on a given message. fn verify( public: &Self::Public, @@ -45,10 +41,22 @@ pub trait ThresholdBls { /// Sign a message using the private share/partial key. fn partial_sign(share: &Share, msg: &[u8]) -> PartialSignature { - PartialSignature { - index: share.index, - value: Self::sign(&share.value, msg), - } + Self::partial_sign_batch(&[share.clone()], msg)[0].clone() + } + + /// Sign a message using one of more private share/partial keys. + fn partial_sign_batch( + shares: &[Share], + msg: &[u8], + ) -> Vec> { + let h = Self::Signature::hash_to_group_element(msg); + shares + .iter() + .map(|share| PartialSignature { + index: share.index, + value: h * share.value, + }) + .collect() } /// Verify a signature done by a partial key holder. @@ -61,6 +69,31 @@ pub trait ThresholdBls { Self::verify(&pk_i.value, msg, &partial_sig.value) } + /// Verify a set of signatures done by a partial key holder. + /// Randomly check if \sum r_i sig_i is a valid signature with public key \sum r_i p(i) G + /// where r_i are random scalars. + fn partial_verify_batch( + vss_pk: &Poly, + msg: &[u8], + partial_sigs: &[PartialSignature], + rng: &mut R, + ) -> Result<(), FastCryptoError> { + let rs = get_random_scalars::(partial_sigs.len() as u32, rng); + let evals_as_scalars = partial_sigs + .iter() + .map(|e| Self::Private::from(e.index.get().into())) + .collect::>(); + let coeffs = batch_coefficients(&rs, &evals_as_scalars, vss_pk.degree()); + let pk = Self::Public::multi_scalar_mul(&coeffs, vss_pk.as_vec()).expect("sizes match"); + let aggregated_sig = Self::Signature::multi_scalar_mul( + &rs, + &partial_sigs.iter().map(|s| s.value).collect::>(), + ) + .expect("sizes match"); + + Self::verify(&pk, msg, &aggregated_sig) + } + /// Interpolate partial signatures to recover the full signature. fn aggregate( threshold: u32, diff --git a/fastcrypto-tbls/src/tests/tbls_tests.rs b/fastcrypto-tbls/src/tests/tbls_tests.rs index 2260821f88..caf1f426e7 100644 --- a/fastcrypto-tbls/src/tests/tbls_tests.rs +++ b/fastcrypto-tbls/src/tests/tbls_tests.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::polynomial::Poly; +use crate::tbls::Share; use crate::{tbls::ThresholdBls, types::ThresholdBls12381MinSig}; use fastcrypto::groups::bls12381; use rand::prelude::*; @@ -39,6 +40,30 @@ fn test_tbls_e2e() { assert!(ThresholdBls12381MinSig::verify(public_poly.c0(), msg, &full_sig).is_ok()); assert_eq!( full_sig, - ThresholdBls12381MinSig::sign(private_poly.c0(), msg) + ThresholdBls12381MinSig::partial_sign( + &Share { + index: NonZeroU32::new(1234).unwrap(), + value: *private_poly.c0() + }, + msg + ) + .value ); + + // Check batches + let sigs = ThresholdBls12381MinSig::partial_sign_batch(&[share1, share2, share3], msg); + assert!(ThresholdBls12381MinSig::partial_verify_batch( + &public_poly, + msg, + &sigs, + &mut thread_rng() + ) + .is_ok()); + assert!(ThresholdBls12381MinSig::partial_verify_batch( + &public_poly, + b"other message", + &sigs, + &mut thread_rng() + ) + .is_err()); }