#![cfg_attr(not(feature = "std"), no_std)]
#![allow(non_snake_case)]
#![allow(clippy::derive_partial_eq_without_eq)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![deny(clippy::indexing_slicing)]
#![deny(clippy::unwrap_used)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc = document_features::document_features!()]
#[macro_use]
extern crate alloc;
use core::marker::PhantomData;
use alloc::{
collections::{BTreeMap, BTreeSet},
fmt::{self, Debug},
vec::Vec,
};
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use serialization::SerializableScalar;
use zeroize::Zeroize;
pub mod batch;
#[cfg(any(test, feature = "test-impl"))]
pub mod benches;
mod error;
mod identifier;
pub mod keys;
pub mod round1;
pub mod round2;
mod scalar_mul;
pub mod serialization;
mod signature;
mod signing_key;
#[cfg(any(test, feature = "test-impl"))]
pub mod tests;
mod traits;
mod verifying_key;
pub use error::{Error, FieldError, GroupError};
pub use identifier::Identifier;
use scalar_mul::VartimeMultiscalarMul;
#[cfg(feature = "serde")]
pub use serde;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use traits::{Ciphersuite, Element, Field, Group, Scalar};
pub use verifying_key::VerifyingKey;
#[derive(Copy, Clone)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct Challenge<C: Ciphersuite>(
pub(crate) <<C::Group as Group>::Field as Field>::Scalar,
);
impl<C> Challenge<C>
where
C: Ciphersuite,
{
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
#[allow(dead_code)]
pub(crate) fn from_scalar(
scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
) -> Self {
Self(scalar)
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn to_scalar(
self,
) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
self.0
}
}
impl<C> Debug for Challenge<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Secret")
.field(&hex::encode(<<C::Group as Group>::Field>::serialize(
&self.0,
)))
.finish()
}
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn challenge<C>(
R: &Element<C>,
verifying_key: &VerifyingKey<C>,
msg: &[u8],
) -> Result<Challenge<C>, Error<C>>
where
C: Ciphersuite,
{
let mut preimage = Vec::new();
preimage.extend_from_slice(<C::Group>::serialize(R)?.as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.to_element())?.as_ref());
preimage.extend_from_slice(msg);
Ok(Challenge(C::H2(&preimage[..])))
}
pub(crate) fn random_nonzero<C: Ciphersuite, R: RngCore + CryptoRng>(rng: &mut R) -> Scalar<C> {
loop {
let scalar = <<C::Group as Group>::Field>::random(rng);
if scalar != <<C::Group as Group>::Field>::zero() {
return scalar;
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Zeroize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
struct Header<C: Ciphersuite> {
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::serialization::version_deserialize::<_>")
)]
version: u8,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serialization::ciphersuite_serialize::<_, C>")
)]
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "crate::serialization::ciphersuite_deserialize::<_, C>")
)]
ciphersuite: (),
#[cfg_attr(feature = "serde", serde(skip))]
phantom: PhantomData<C>,
}
impl<C> Default for Header<C>
where
C: Ciphersuite,
{
fn default() -> Self {
Self {
version: Default::default(),
ciphersuite: Default::default(),
phantom: Default::default(),
}
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct BindingFactor<C: Ciphersuite>(Scalar<C>);
impl<C> BindingFactor<C>
where
C: Ciphersuite,
{
pub fn serialize(&self) -> Vec<u8> {
SerializableScalar::<C>(self.0).serialize()
}
}
impl<C> Debug for BindingFactor<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("BindingFactor")
.field(&hex::encode(self.serialize()))
.finish()
}
}
#[derive(Clone)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct BindingFactorList<C: Ciphersuite>(BTreeMap<Identifier<C>, BindingFactor<C>>);
impl<C> BindingFactorList<C>
where
C: Ciphersuite,
{
#[cfg(feature = "internals")]
pub fn new(binding_factors: BTreeMap<Identifier<C>, BindingFactor<C>>) -> Self {
Self(binding_factors)
}
pub fn get(&self, key: &Identifier<C>) -> Option<&BindingFactor<C>> {
self.0.get(key)
}
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn compute_binding_factor_list<C>(
signing_package: &SigningPackage<C>,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> Result<BindingFactorList<C>, Error<C>>
where
C: Ciphersuite,
{
let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix)?;
Ok(BindingFactorList(
preimages
.iter()
.map(|(identifier, preimage)| {
let binding_factor = C::H1(preimage);
(*identifier, BindingFactor(binding_factor))
})
.collect(),
))
}
#[cfg(any(test, feature = "test-impl"))]
impl<C> FromHex for BindingFactor<C>
where
C: Ciphersuite,
{
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let v: Vec<u8> = FromHex::from_hex(hex).map_err(|_| "invalid hex")?;
match v.try_into() {
Ok(bytes) => <<C::Group as Group>::Field>::deserialize(&bytes)
.map(|scalar| Self(scalar))
.map_err(|_| "malformed scalar encoding"),
Err(_) => Err("malformed scalar encoding"),
}
}
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_lagrange_coefficient<C: Ciphersuite>(
x_set: &BTreeSet<Identifier<C>>,
x: Option<Identifier<C>>,
x_i: Identifier<C>,
) -> Result<Scalar<C>, Error<C>> {
if x_set.is_empty() {
return Err(Error::IncorrectNumberOfIdentifiers);
}
let mut num = <<C::Group as Group>::Field>::one();
let mut den = <<C::Group as Group>::Field>::one();
let mut x_i_found = false;
for x_j in x_set.iter() {
if x_i == *x_j {
x_i_found = true;
continue;
}
if let Some(x) = x {
num = num * (x.to_scalar() - x_j.to_scalar());
den = den * (x_i.to_scalar() - x_j.to_scalar());
} else {
num = num * x_j.to_scalar();
den = den * (x_j.to_scalar() - x_i.to_scalar());
}
}
if !x_i_found {
return Err(Error::UnknownIdentifier);
}
Ok(
num * <<C::Group as Group>::Field>::invert(&den)
.map_err(|_| Error::DuplicatedIdentifier)?,
)
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn derive_interpolating_value<C: Ciphersuite>(
signer_id: &Identifier<C>,
signing_package: &SigningPackage<C>,
) -> Result<Scalar<C>, Error<C>> {
compute_lagrange_coefficient(
&signing_package
.signing_commitments()
.keys()
.cloned()
.collect(),
None,
*signer_id,
)
}
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct SigningPackage<C: Ciphersuite> {
#[getter(skip)]
pub(crate) header: Header<C>,
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)
)]
message: Vec<u8>,
}
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
pub fn new(
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
message: &[u8],
) -> SigningPackage<C> {
SigningPackage {
header: Header::default(),
signing_commitments,
message: message.to_vec(),
}
}
pub fn signing_commitment(
&self,
identifier: &Identifier<C>,
) -> Option<round1::SigningCommitments<C>> {
self.signing_commitments.get(identifier).copied()
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
#[allow(clippy::type_complexity)]
pub fn binding_factor_preimages(
&self,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> Result<Vec<(Identifier<C>, Vec<u8>)>, Error<C>> {
let mut binding_factor_input_prefix = Vec::new();
binding_factor_input_prefix.extend_from_slice(verifying_key.serialize()?.as_ref());
binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref());
binding_factor_input_prefix.extend_from_slice(
C::H5(&round1::encode_group_commitments(self.signing_commitments())?[..]).as_ref(),
);
binding_factor_input_prefix.extend_from_slice(additional_prefix);
Ok(self
.signing_commitments()
.keys()
.map(|identifier| {
let mut binding_factor_input = Vec::new();
binding_factor_input.extend_from_slice(&binding_factor_input_prefix);
binding_factor_input.extend_from_slice(identifier.serialize().as_ref());
(*identifier, binding_factor_input)
})
.collect())
}
}
#[cfg(feature = "serialization")]
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
serialization::Serialize::serialize(&self)
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
serialization::Deserialize::deserialize(bytes)
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
impl<C> GroupCommitment<C>
where
C: Ciphersuite,
{
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn to_element(self) -> <C::Group as Group>::Element {
self.0
}
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_group_commitment<C>(
signing_package: &SigningPackage<C>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<GroupCommitment<C>, Error<C>>
where
C: Ciphersuite,
{
let identity = <C::Group as Group>::identity();
let mut group_commitment = <C::Group as Group>::identity();
let n = signing_package.signing_commitments().len();
let mut binding_scalars = Vec::with_capacity(n);
let mut binding_elements = Vec::with_capacity(n);
for (commitment_identifier, commitment) in signing_package.signing_commitments() {
if identity == commitment.binding.value() || identity == commitment.hiding.value() {
return Err(Error::IdentityCommitment);
}
let binding_factor = binding_factor_list
.get(commitment_identifier)
.ok_or(Error::UnknownIdentifier)?;
binding_elements.push(commitment.binding.value());
binding_scalars.push(binding_factor.0);
group_commitment = group_commitment + commitment.hiding.value();
}
let accumulated_binding_commitment: Element<C> =
VartimeMultiscalarMul::<C>::vartime_multiscalar_mul(binding_scalars, binding_elements);
group_commitment = group_commitment + accumulated_binding_commitment;
Ok(GroupCommitment(group_commitment))
}
pub fn aggregate<C>(
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
pubkeys: &keys::PublicKeyPackage<C>,
) -> Result<Signature<C>, Error<C>>
where
C: Ciphersuite,
{
if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}
if !signing_package.signing_commitments().keys().all(|id| {
#[cfg(feature = "cheater-detection")]
return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
#[cfg(not(feature = "cheater-detection"))]
return signature_shares.contains_key(id);
}) {
return Err(Error::UnknownIdentifier);
}
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?;
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
let mut z = <<C::Group as Group>::Field>::zero();
for signature_share in signature_shares.values() {
z = z + signature_share.to_scalar();
}
let signature = Signature {
R: group_commitment.0,
z,
};
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
#[cfg(feature = "cheater-detection")]
if verification_result.is_err() {
detect_cheater(
group_commitment,
pubkeys,
signing_package,
signature_shares,
&binding_factor_list,
)?;
}
#[cfg(not(feature = "cheater-detection"))]
verification_result?;
Ok(signature)
}
#[cfg(feature = "cheater-detection")]
fn detect_cheater<C: Ciphersuite>(
group_commitment: GroupCommitment<C>,
pubkeys: &keys::PublicKeyPackage<C>,
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<(), Error<C>> {
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
)?;
for (identifier, signature_share) in signature_shares {
let verifying_share = pubkeys
.verifying_shares
.get(identifier)
.ok_or(Error::UnknownIdentifier)?;
verify_signature_share_precomputed(
*identifier,
signing_package,
binding_factor_list,
signature_share,
verifying_share,
challenge,
)?;
}
Err(Error::InvalidSignature)
}
pub fn verify_signature_share<C: Ciphersuite>(
identifier: Identifier<C>,
verifying_share: &keys::VerifyingShare<C>,
signature_share: &round2::SignatureShare<C>,
signing_package: &SigningPackage<C>,
verifying_key: &VerifyingKey<C>,
) -> Result<(), Error<C>> {
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, verifying_key, &[])?;
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
let challenge = crate::challenge::<C>(
&group_commitment.to_element(),
verifying_key,
signing_package.message().as_slice(),
)?;
verify_signature_share_precomputed(
identifier,
signing_package,
&binding_factor_list,
signature_share,
verifying_share,
challenge,
)
}
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn verify_signature_share_precomputed<C: Ciphersuite>(
signature_share_identifier: Identifier<C>,
signing_package: &SigningPackage<C>,
binding_factor_list: &BindingFactorList<C>,
signature_share: &round2::SignatureShare<C>,
verifying_share: &keys::VerifyingShare<C>,
challenge: Challenge<C>,
) -> Result<(), Error<C>> {
let lambda_i = derive_interpolating_value(&signature_share_identifier, signing_package)?;
let binding_factor = binding_factor_list
.get(&signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
let R_share = signing_package
.signing_commitment(&signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);
signature_share.verify(
signature_share_identifier,
&R_share,
verifying_share,
lambda_i,
&challenge,
)?;
Ok(())
}