nym_compact_ecash/scheme/
aggregation.rs1use crate::common_types::{PartialSignature, Signature, SignatureShare, SignerIndex};
5use crate::error::{CompactEcashError, Result};
6use crate::helpers::{scalar_date, scalar_type};
7use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
8use crate::scheme::withdrawal::RequestInfo;
9use crate::scheme::{PartialWallet, Wallet, WalletSignatures};
10use crate::utils::{check_bilinear_pairing, perform_lagrangian_interpolation_at_origin};
11use crate::{ecash_group_parameters, Attribute};
12use core::iter::Sum;
13use core::ops::Mul;
14use group::Curve;
15use itertools::Itertools;
16use nym_bls12_381_fork::{G2Prepared, G2Projective, Scalar};
17use zeroize::Zeroizing;
18
19pub(crate) trait Aggregatable: Sized {
20 fn aggregate(aggregatable: &[Self], indices: Option<&[SignerIndex]>) -> Result<Self>;
21
22 fn check_unique_indices(indices: &[SignerIndex]) -> bool {
23 indices.iter().unique_by(|&index| index).count() == indices.len()
25 }
26}
27
28impl<T> Aggregatable for T
29where
30 T: Sum,
31 for<'a> T: Sum<&'a T>,
32 for<'a> &'a T: Mul<Scalar, Output = T>,
33{
34 fn aggregate(aggregatable: &[T], indices: Option<&[u64]>) -> Result<T> {
35 if aggregatable.is_empty() {
36 return Err(CompactEcashError::AggregationEmptySet);
37 }
38
39 if let Some(indices) = indices {
40 if !Self::check_unique_indices(indices) {
41 return Err(CompactEcashError::AggregationDuplicateIndices);
42 }
43 perform_lagrangian_interpolation_at_origin(indices, aggregatable)
44 } else {
45 Ok(aggregatable.iter().sum())
47 }
48 }
49}
50
51impl Aggregatable for PartialSignature {
52 fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result<Signature> {
53 if sigs.is_empty() {
55 return Err(CompactEcashError::AggregationEmptySet);
56 }
57
58 for sig in sigs {
60 if bool::from(sig.is_at_infinity()) {
61 return Err(CompactEcashError::IdentitySignature);
62 }
63 }
64 let h = sigs
65 .first()
66 .ok_or(CompactEcashError::AggregationEmptySet)?
67 .sig1();
68
69 let sigmas = sigs.iter().map(|sig| *sig.sig2()).collect::<Vec<_>>();
71 let aggr_sigma = Aggregatable::aggregate(&sigmas, indices)?;
72
73 Ok(Signature {
74 h: *h,
75 s: aggr_sigma,
76 })
77 }
78}
79
80fn check_same_key_size(keys: &[VerificationKeyAuth]) -> bool {
82 keys.iter().map(|vk| vk.beta_g1.len()).all_equal()
83 && keys.iter().map(|vk| vk.beta_g2.len()).all_equal()
84}
85
86pub fn aggregate_verification_keys(
87 keys: &[VerificationKeyAuth],
88 indices: Option<&[SignerIndex]>,
89) -> Result<VerificationKeyAuth> {
90 if !check_same_key_size(keys) {
91 return Err(CompactEcashError::AggregationSizeMismatch);
92 }
93 Aggregatable::aggregate(keys, indices)
94}
95
96pub fn aggregate_signature_shares(
97 verification_key: &VerificationKeyAuth,
98 attributes: &[Attribute],
99 shares: &[SignatureShare],
100) -> Result<Signature> {
101 let (signatures, indices): (Vec<_>, Vec<_>) = shares
102 .iter()
103 .map(|share| (*share.signature(), share.index()))
104 .unzip();
105
106 aggregate_signatures(verification_key, attributes, &signatures, Some(&indices))
107}
108
109pub fn aggregate_signatures(
110 verification_key: &VerificationKeyAuth,
111 attributes: &[Attribute],
112 signatures: &[PartialSignature],
113 indices: Option<&[SignerIndex]>,
114) -> Result<Signature> {
115 let params = ecash_group_parameters();
116 let signature = Aggregatable::aggregate(signatures, indices)?;
119
120 if bool::from(signature.is_at_infinity()) {
122 return Err(CompactEcashError::IdentitySignature);
123 }
124
125 let tmp = attributes
127 .iter()
128 .zip(verification_key.beta_g2.iter())
129 .map(|(attr, beta_i)| beta_i * attr)
130 .sum::<G2Projective>();
131
132 if !check_bilinear_pairing(
133 &signature.h.to_affine(),
134 &G2Prepared::from((verification_key.alpha + tmp).to_affine()),
135 &signature.s.to_affine(),
136 params.prepared_miller_g2(),
137 ) {
138 return Err(CompactEcashError::AggregationVerification);
139 }
140 Ok(signature)
141}
142
143pub fn aggregate_wallets(
144 verification_key: &VerificationKeyAuth,
145 sk_user: &SecretKeyUser,
146 wallets: &[PartialWallet],
147 req_info: &RequestInfo,
148) -> Result<Wallet> {
149 let signature_shares: Vec<SignatureShare> = wallets
151 .iter()
152 .map(|wallet| SignatureShare::new(*wallet.signature(), wallet.index()))
153 .collect();
154
155 let attributes = Zeroizing::new(vec![
156 sk_user.sk,
157 *req_info.get_v(),
158 *req_info.get_expiration_date(),
159 *req_info.get_t_type(),
160 ]);
161 let aggregated_signature =
162 aggregate_signature_shares(verification_key, &attributes, &signature_shares)?;
163
164 let expiration_date_timestamp = req_info.get_expiration_date();
165 let t_type = req_info.get_t_type();
166
167 Ok(Wallet {
168 signatures: WalletSignatures {
169 sig: aggregated_signature,
170 v: *req_info.get_v(),
171 expiration_date_timestamp: scalar_date(expiration_date_timestamp),
172 t_type: scalar_type(t_type),
173 },
174 tickets_spent: 0,
175 })
176}