Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tBLS - batch sign and verify #644

Merged
merged 3 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 34 additions & 31 deletions fastcrypto-tbls/src/dl_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,55 @@

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<S: Scalar>(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 <a1, a2, ..., an> and a vector of random scalars <r1, r2, ..., rn>,
/// returns the vector v such that <v, c> = \sum ri * p(ai) for a polynomial p with coefficients c.
pub fn batch_coefficients<S: Scalar>(r: &[S], indexes: &[S], degree: u32) -> Vec<S> {
let mut multiplies = r.to_vec();
let mut res = Vec::<S>::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::<Vec<_>>();
}
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<G: GroupElement + MultiScalarMul, R: AllowedRng>(
evals: &[Eval<G::ScalarType>],
poly: &Poly<G>,
rng: &mut R,
) -> FastCryptoResult<()> {
let rs = get_random_scalars::<G, R>(evals.len() as u32, rng);
let rs = get_random_scalars::<G::ScalarType, R>(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::<Vec<_>>());

let mut multiplies = rs;
let mut coeffs = Vec::<G::ScalarType>::new();
let evals_as_scalars = evals
.iter()
.map(|e| G::ScalarType::from(e.index.get().into()))
.collect::<Vec<_>>();
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::<Vec<_>>();
}
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 {
Expand All @@ -52,12 +60,7 @@ pub fn verify_poly_evals<G: GroupElement + MultiScalarMul, R: AllowedRng>(
Ok(())
}

pub fn get_random_scalars<G: GroupElement, R: AllowedRng>(
n: u32,
rng: &mut R,
) -> Vec<<G as GroupElement>::ScalarType> {
pub fn get_random_scalars<S: Scalar, R: AllowedRng>(n: u32, rng: &mut R) -> Vec<S> {
// TODO: can use 40 bits instead of 64 ("& 0x000F_FFFF_FFFF_FFFF" below)
(0..n)
.map(|_| G::ScalarType::from(rng.next_u64()))
.collect::<Vec<_>>()
(0..n).map(|_| S::from(rng.next_u64())).collect::<Vec<_>>()
}
59 changes: 46 additions & 13 deletions fastcrypto-tbls/src/tbls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S> = IndexedValue<S>;
pub type PartialSignature<S> = IndexedValue<S>;
Expand All @@ -16,9 +18,9 @@ pub type PartialSignature<S> = IndexedValue<S>;
pub trait ThresholdBls {
type Private: Scalar;
/// `Public` represents the group over which the public keys are represented.
type Public: GroupElement<ScalarType = Self::Private>;
type Public: GroupElement<ScalarType = Self::Private> + MultiScalarMul;
/// `Signature` represents the group over which the signatures are represented.
type Signature: GroupElement<ScalarType = Self::Private> + HashToGroupElement;
type Signature: GroupElement<ScalarType = Self::Private> + HashToGroupElement + MultiScalarMul;

/// Curve dependent implementation of computing and comparing the pairings as part of the
/// signature verification.
Expand All @@ -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,
Expand All @@ -45,10 +41,22 @@ pub trait ThresholdBls {

/// Sign a message using the private share/partial key.
fn partial_sign(share: &Share<Self::Private>, msg: &[u8]) -> PartialSignature<Self::Signature> {
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<Self::Private>],
msg: &[u8],
) -> Vec<PartialSignature<Self::Signature>> {
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.
Expand All @@ -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<R: AllowedRng>(
vss_pk: &Poly<Self::Public>,
msg: &[u8],
partial_sigs: &[PartialSignature<Self::Signature>],
rng: &mut R,
) -> Result<(), FastCryptoError> {
let rs = get_random_scalars::<Self::Private, R>(partial_sigs.len() as u32, rng);
let evals_as_scalars = partial_sigs
.iter()
.map(|e| Self::Private::from(e.index.get().into()))
.collect::<Vec<_>>();
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::<Vec<_>>(),
)
.expect("sizes match");

Self::verify(&pk, msg, &aggregated_sig)
}

/// Interpolate partial signatures to recover the full signature.
fn aggregate(
threshold: u32,
Expand Down
27 changes: 26 additions & 1 deletion fastcrypto-tbls/src/tests/tbls_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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());
}
Loading