use crate::common_types::{PartialSignature, Signature, SignatureShare, SignerIndex};
use crate::error::{CompactEcashError, Result};
use crate::helpers::{scalar_date, scalar_type};
use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
use crate::scheme::withdrawal::RequestInfo;
use crate::scheme::{PartialWallet, Wallet, WalletSignatures};
use crate::utils::{check_bilinear_pairing, perform_lagrangian_interpolation_at_origin};
use crate::{ecash_group_parameters, Attribute};
use core::iter::Sum;
use core::ops::Mul;
use group::Curve;
use itertools::Itertools;
use nym_bls12_381_fork::{G2Prepared, G2Projective, Scalar};
use zeroize::Zeroizing;
pub(crate) trait Aggregatable: Sized {
fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
fn check_unique_indices(indices: &[SignerIndex]) -> bool {
indices.iter().unique_by(|&index| index).count() == indices.len()
}
}
impl<T> Aggregatable for T
where
T: Sum,
for<'a> T: Sum<&'a T>,
for<'a> &'a T: Mul<Scalar, Output = T>,
{
fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
if aggregatable.is_empty() {
return Err(CompactEcashError::AggregationEmptySet);
}
if let Some(indices) = indices {
if !Self::check_unique_indices(indices) {
return Err(CompactEcashError::AggregationDuplicateIndices);
}
perform_lagrangian_interpolation_at_origin(indices, aggregatable)
} else {
Ok(aggregatable.iter().sum())
}
}
}
impl Aggregatable for PartialSignature {
fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
if sigs.is_empty() {
return Err(CompactEcashError::AggregationEmptySet);
}
for sig in sigs {
if bool::from(sig.is_at_infinity()) {
return Err(CompactEcashError::IdentitySignature);
}
}
let h = sigs
.first()
.ok_or(CompactEcashError::AggregationEmptySet)?
.sig1();
let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
Ok(Signature {
h: *h,
s: aggr_sigma,
})
}
}
fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
&& keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
}
pub fn aggregate_verification_keys(
keys: &[VerificationKeyAuth],
indices: Option<&[SignerIndex]>,
) -> Result<VerificationKeyAuth> {
if !check_same_key_size(keys) {
return Err(CompactEcashError::AggregationSizeMismatch);
}
Aggregatable::aggregate(keys, indices)
}
pub fn aggregate_signature_shares(
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
shares: &[SignatureShare],
) -> Result<Signature> {
let (signatures, indices): (Vec<_>, Vec<_>) = shares
.iter()
.map(|share| (*share.signature(), share.index()))
.unzip();
aggregate_signatures(verification_key, attributes, &signatures, Some(&indices))
}
pub fn aggregate_signatures(
verification_key: &VerificationKeyAuth,
attributes: &[Attribute],
signatures: &[PartialSignature],
indices: Option<&[SignerIndex]>,
) -> Result<Signature> {
let params = ecash_group_parameters();
let signature = Aggregatable::aggregate(signatures, indices)?;
if bool::from(signature.is_at_infinity()) {
return Err(CompactEcashError::IdentitySignature);
}
let tmp = attributes
.iter()
.zip(verification_key.beta_g2.iter())
.map(|(attr, beta_i)| beta_i * attr)
.sum::<G2Projective>();
if !check_bilinear_pairing(
&signature.h.to_affine(),
&G2Prepared::from((verification_key.alpha + tmp).to_affine()),
&signature.s.to_affine(),
params.prepared_miller_g2(),
) {
return Err(CompactEcashError::AggregationVerification);
}
Ok(signature)
}
pub fn aggregate_wallets(
verification_key: &VerificationKeyAuth,
sk_user: &SecretKeyUser,
wallets: &[PartialWallet],
req_info: &RequestInfo,
) -> Result<Wallet> {
let signature_shares: Vec<SignatureShare> = wallets
.iter()
.map(|wallet| SignatureShare::new(*wallet.signature(), wallet.index()))
.collect();
let attributes = Zeroizing::new(vec![
sk_user.sk,
*req_info.get_v(),
*req_info.get_expiration_date(),
*req_info.get_t_type(),
]);
let aggregated_signature =
aggregate_signature_shares(verification_key, &attributes, &signature_shares)?;
let expiration_date_timestamp = req_info.get_expiration_date();
let t_type = req_info.get_t_type();
Ok(Wallet {
signatures: WalletSignatures {
sig: aggregated_signature,
v: *req_info.get_v(),
expiration_date_timestamp: scalar_date(expiration_date_timestamp),
t_type: scalar_type(t_type),
},
tickets_spent: 0,
})
}