nym_compact_ecash/scheme/
expiration_date_signatures.rs1use crate::common_types::{Signature, SignerIndex};
5use crate::error::{CompactEcashError, Result};
6use crate::helpers::date_scalar;
7use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth};
8use crate::utils::generate_lagrangian_coefficients_at_origin;
9use crate::utils::{batch_verify_signatures, hash_g1};
10use crate::{constants, EncodedDate};
11use itertools::Itertools;
12use nym_bls12_381_fork::{G1Projective, Scalar};
13use serde::{Deserialize, Serialize};
14use std::borrow::Borrow;
15
16pub type ExpirationDateSignature = Signature;
18pub type PartialExpirationDateSignature = ExpirationDateSignature;
19
20#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
21pub struct AnnotatedExpirationDateSignature {
22 pub signature: ExpirationDateSignature,
23 pub expiration_timestamp: EncodedDate,
24 pub spending_timestamp: EncodedDate,
25}
26
27impl Borrow<ExpirationDateSignature> for AnnotatedExpirationDateSignature {
28 fn borrow(&self) -> &ExpirationDateSignature {
29 &self.signature
30 }
31}
32
33impl From<AnnotatedExpirationDateSignature> for ExpirationDateSignature {
34 fn from(value: AnnotatedExpirationDateSignature) -> Self {
35 value.signature
36 }
37}
38
39pub struct ExpirationDateSignatureShare<B = PartialExpirationDateSignature>
40where
41 B: Borrow<PartialExpirationDateSignature> + Send + Sync,
42{
43 pub index: SignerIndex,
44 pub key: VerificationKeyAuth,
45 pub signatures: Vec<B>,
46}
47
48pub fn sign_expiration_date(
69 sk_auth: &SecretKeyAuth,
70 expiration_unix_timestamp: EncodedDate,
71) -> Result<Vec<AnnotatedExpirationDateSignature>> {
72 if sk_auth.ys.len() < 3 {
73 return Err(CompactEcashError::KeyTooShort);
74 }
75 let m0: Scalar = date_scalar(expiration_unix_timestamp);
76 let m2: Scalar = constants::TYPE_EXP;
77
78 #[allow(clippy::indexing_slicing)]
80 let partial_s_exponent = sk_auth.x + sk_auth.ys[0] * m0 + sk_auth.ys[2] * m2;
81
82 let sign_expiration = |offset: u32| {
83 let spending_unix_timestamp = expiration_unix_timestamp
89 - ((constants::CRED_VALIDITY_PERIOD_DAYS - offset - 1) * constants::SECONDS_PER_DAY);
90 let m1: Scalar = date_scalar(spending_unix_timestamp);
91 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
93 #[allow(clippy::indexing_slicing)]
95 let s_exponent = partial_s_exponent + sk_auth.ys[1] * m1;
96
97 let signature = PartialExpirationDateSignature {
99 h,
100 s: h * s_exponent,
101 };
102
103 AnnotatedExpirationDateSignature {
104 signature,
105 expiration_timestamp: expiration_unix_timestamp,
106 spending_timestamp: spending_unix_timestamp,
107 }
108 };
109
110 cfg_if::cfg_if! {
111 if #[cfg(feature = "par_signing")] {
112 use rayon::prelude::*;
113
114 Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS)
115 .into_par_iter()
116 .map(sign_expiration)
117 .collect())
118 } else {
119 Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS).map(sign_expiration).collect())
120 }
121 }
122}
123
124pub fn verify_valid_dates_signatures<B>(
141 vk: &VerificationKeyAuth,
142 signatures: &[B],
143 expiration_date: EncodedDate,
144) -> Result<()>
145where
146 B: Borrow<ExpirationDateSignature>,
147{
148 if vk.beta_g2.len() < 3 {
149 return Err(CompactEcashError::VerificationKeyTooShort);
150 }
151
152 if signatures.len() > constants::CRED_VALIDITY_PERIOD_DAYS as usize {
154 return Err(CompactEcashError::ExpirationDateSignatureVerification);
155 }
156
157 let m0: Scalar = date_scalar(expiration_date);
158 let m2: Scalar = constants::TYPE_EXP;
159
160 #[allow(clippy::indexing_slicing)]
162 let partially_signed = vk.alpha + vk.beta_g2[0] * m0 + vk.beta_g2[2] * m2;
163 let mut pairing_terms = Vec::with_capacity(signatures.len());
164
165 for (i, sig) in signatures.iter().enumerate() {
166 let l = i as u32;
167 let valid_date = expiration_date
168 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
169 let m1: Scalar = date_scalar(valid_date);
170
171 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
173
174 let sig = *sig.borrow();
175 if sig.h != h {
177 return Err(CompactEcashError::ExpirationDateSignatureVerification);
178 }
179
180 #[allow(clippy::indexing_slicing)]
182 pairing_terms.push((sig, partially_signed + vk.beta_g2[1] * m1));
183 }
184
185 if !batch_verify_signatures(pairing_terms.iter()) {
186 return Err(CompactEcashError::ExpirationDateSignatureVerification);
187 }
188 Ok(())
189}
190
191fn _aggregate_expiration_signatures<B>(
217 vk: &VerificationKeyAuth,
218 expiration_date: EncodedDate,
219 signatures_shares: &[ExpirationDateSignatureShare<B>],
220 validate_shares: bool,
221) -> Result<Vec<ExpirationDateSignature>>
222where
223 B: Borrow<ExpirationDateSignature> + Send + Sync,
224{
225 if signatures_shares
227 .iter()
228 .map(|share| share.index)
229 .unique()
230 .count()
231 != signatures_shares.len()
232 {
233 return Err(CompactEcashError::AggregationDuplicateIndices);
234 }
235
236 let coefficients = generate_lagrangian_coefficients_at_origin(
238 &signatures_shares
239 .iter()
240 .map(|share| share.index)
241 .collect::<Vec<_>>(),
242 );
243
244 if validate_shares {
246 cfg_if::cfg_if! {
247 if #[cfg(feature = "par_verify")] {
248 use rayon::prelude::*;
249
250 signatures_shares.par_iter().try_for_each(|share| {
251 verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date)
252 })?;
253 } else {
254 signatures_shares.iter().try_for_each(|share| verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date))?;
255 }
256 }
257 }
258
259 let mut aggregated_date_signatures: Vec<ExpirationDateSignature> =
261 Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
262
263 let m0: Scalar = date_scalar(expiration_date);
264
265 for l in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
266 let valid_date = expiration_date
267 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
268 let m1: Scalar = date_scalar(valid_date);
269 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
271
272 let collected_at_l: Vec<_> = signatures_shares
274 .iter()
275 .filter_map(|share| share.signatures.get(l as usize))
276 .collect();
277
278 let aggr_s: G1Projective = coefficients
280 .iter()
281 .zip(collected_at_l.iter())
282 .map(|(coeff, &sig)| sig.borrow().s * coeff)
283 .sum();
284 let aggr_sig = ExpirationDateSignature { h, s: aggr_s };
285 aggregated_date_signatures.push(aggr_sig);
286 }
287 verify_valid_dates_signatures(vk, &aggregated_date_signatures, expiration_date)?;
288 Ok(aggregated_date_signatures)
289}
290
291pub fn aggregate_expiration_signatures<B>(
317 vk: &VerificationKeyAuth,
318 expiration_date: EncodedDate,
319 signatures_shares: &[ExpirationDateSignatureShare<B>],
320) -> Result<Vec<ExpirationDateSignature>>
321where
322 B: Borrow<PartialExpirationDateSignature> + Send + Sync,
323{
324 _aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
325}
326
327pub fn aggregate_annotated_expiration_signatures(
332 vk: &VerificationKeyAuth,
333 expiration_date: EncodedDate,
334 signatures_shares: &[ExpirationDateSignatureShare<AnnotatedExpirationDateSignature>],
335) -> Result<Vec<AnnotatedExpirationDateSignature>> {
336 let Some(share) = signatures_shares.first() else {
339 return Ok(Vec::new());
340 };
341
342 if share.signatures.len() != constants::CRED_VALIDITY_PERIOD_DAYS as usize {
343 return Err(CompactEcashError::ExpirationDateSignatureVerification);
344 }
345
346 for (i, sig) in share.signatures.iter().enumerate() {
347 if sig.expiration_timestamp != expiration_date {
348 return Err(CompactEcashError::ExpirationDateSignatureVerification);
349 }
350
351 let l = i as u32;
352 let expected_spending = sig.expiration_timestamp
353 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
354
355 if sig.spending_timestamp != expected_spending {
356 return Err(CompactEcashError::ExpirationDateSignatureVerification);
357 }
358 }
359
360 let aggregated = aggregate_expiration_signatures(vk, expiration_date, signatures_shares)?;
361 assert_eq!(aggregated.len(), share.signatures.len());
362
363 Ok(aggregated
364 .into_iter()
365 .zip(share.signatures.iter())
366 .map(|(signature, sh)| AnnotatedExpirationDateSignature {
367 signature,
368 expiration_timestamp: sh.expiration_timestamp,
369 spending_timestamp: sh.spending_timestamp,
370 })
371 .collect())
372}
373
374pub fn unchecked_aggregate_expiration_signatures(
379 vk: &VerificationKeyAuth,
380 expiration_date: EncodedDate,
381 signatures_shares: &[ExpirationDateSignatureShare],
382) -> Result<Vec<ExpirationDateSignature>> {
383 _aggregate_expiration_signatures(vk, expiration_date, signatures_shares, false)
384}
385
386pub fn find_index(spend_date: EncodedDate, expiration_date: EncodedDate) -> Result<usize> {
403 let start_date =
404 expiration_date - ((constants::CRED_VALIDITY_PERIOD_DAYS - 1) * constants::SECONDS_PER_DAY);
405
406 if spend_date >= start_date {
407 let index_a = ((spend_date - start_date) / constants::SECONDS_PER_DAY) as usize;
408 if index_a as u32 >= constants::CRED_VALIDITY_PERIOD_DAYS {
409 Err(CompactEcashError::SpendDateTooLate)
410 } else {
411 Ok(index_a)
412 }
413 } else {
414 Err(CompactEcashError::SpendDateTooEarly)
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421 use crate::scheme::aggregation::aggregate_verification_keys;
422 use crate::scheme::keygen::ttp_keygen;
423
424 #[test]
425 fn test_find_index() {
426 let expiration_date = 1701993600; for i in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
428 let current_spend_date = expiration_date - i * 86400;
429 assert_eq!(
430 find_index(current_spend_date, expiration_date).unwrap(),
431 (constants::CRED_VALIDITY_PERIOD_DAYS - 1 - i) as usize
432 )
433 }
434
435 let late_spend_date = expiration_date + 86400;
436 assert!(find_index(late_spend_date, expiration_date).is_err());
437
438 let early_spend_date = expiration_date - (constants::CRED_VALIDITY_PERIOD_DAYS) * 86400;
439 assert!(find_index(early_spend_date, expiration_date).is_err());
440 }
441
442 #[test]
443 fn test_sign_expiration_date() {
444 let expiration_date = 1702050209; let authorities_keys = ttp_keygen(2, 3).unwrap();
447 let sk_i_auth = authorities_keys[0].secret_key();
448 let vk_i_auth = authorities_keys[0].verification_key();
449 let partial_exp_sig = sign_expiration_date(sk_i_auth, expiration_date).unwrap();
450
451 assert!(
452 verify_valid_dates_signatures(&vk_i_auth, &partial_exp_sig, expiration_date).is_ok()
453 );
454 }
455
456 #[test]
457 fn test_aggregate_expiration_signatures() {
458 let expiration_date = 1702050209; let authorities_keypairs = ttp_keygen(2, 3).unwrap();
461 let indices: [u64; 3] = [1, 2, 3];
462 let secret_keys_authorities: Vec<&SecretKeyAuth> = authorities_keypairs
464 .iter()
465 .map(|keypair| keypair.secret_key())
466 .collect();
467 let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
469 .iter()
470 .map(|keypair| keypair.verification_key())
471 .collect();
472 let verification_key =
474 aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
475
476 let mut edt_partial_signatures: Vec<Vec<_>> =
477 Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
478 for sk_auth in secret_keys_authorities.iter() {
479 let sign = sign_expiration_date(sk_auth, expiration_date).unwrap();
480 edt_partial_signatures.push(sign);
481 }
482
483 let combined_data = indices
484 .iter()
485 .zip(
486 verification_keys_auth
487 .iter()
488 .zip(edt_partial_signatures.iter()),
489 )
490 .map(|(i, (vk, sigs))| ExpirationDateSignatureShare {
491 index: *i,
492 key: vk.clone(),
493 signatures: sigs.clone(),
494 })
495 .collect::<Vec<_>>();
496
497 assert!(aggregate_expiration_signatures(
498 &verification_key,
499 expiration_date,
500 &combined_data,
501 )
502 .is_ok());
503 }
504}