#![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!()]
use std::{
collections::{BTreeMap, BTreeSet},
default::Default,
fmt::{self, Debug},
marker::PhantomData,
};
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
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(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(feature = "internals")]
pub fn from_scalar(
scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
) -> Self {
Self(scalar)
}
#[cfg(feature = "internals")]
pub 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 std::fmt::Formatter<'_>) -> std::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]) -> Challenge<C>
where
C: Ciphersuite,
{
let mut preimage = vec![];
preimage.extend_from_slice(<C::Group>::serialize(R).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.element).as_ref());
preimage.extend_from_slice(msg);
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: (),
#[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) -> <<C::Group as Group>::Field as Field>::Serialization {
<<C::Group as Group>::Field>::serialize(&self.0)
}
}
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],
) -> BindingFactorList<C>
where
C: Ciphersuite,
{
let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix);
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 *= x - *x_j;
den *= x_i - *x_j;
} else {
num *= *x_j;
den *= *x_j - x_i;
}
}
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")))]
pub fn binding_factor_preimages(
&self,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> Vec<(Identifier<C>, Vec<u8>)> {
let mut binding_factor_input_prefix = vec![];
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);
self.signing_commitments()
.keys()
.map(|identifier| {
let mut binding_factor_input = vec![];
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(feature = "internals")]
pub 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.0 || identity == commitment.hiding.0 {
return Err(Error::IdentityCommitment);
}
let binding_factor = binding_factor_list
.get(commitment_identifier)
.ok_or(Error::UnknownIdentifier)?;
binding_elements.push(commitment.binding.0);
binding_scalars.push(binding_factor.0);
group_commitment = group_commitment + commitment.hiding.0;
}
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.share;
}
let signature = Signature {
R: group_commitment.0,
z,
};
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
for (signature_share_identifier, signature_share) in signature_shares {
let signer_pubkey = pubkeys
.verifying_shares
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
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,
signer_pubkey,
lambda_i,
&challenge,
)?;
}
return Err(err);
}
#[cfg(not(feature = "cheater-detection"))]
verification_result?;
Ok(signature)
}