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 let partial_s_exponent = sk_auth.x + sk_auth.ys[0] * m0 + sk_auth.ys[2] * m2;
79
80 let sign_expiration = |offset: u32| {
81 let spending_unix_timestamp = expiration_unix_timestamp
87 - ((constants::CRED_VALIDITY_PERIOD_DAYS - offset - 1) * constants::SECONDS_PER_DAY);
88 let m1: Scalar = date_scalar(spending_unix_timestamp);
89 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
91 let s_exponent = partial_s_exponent + sk_auth.ys[1] * m1;
93
94 let signature = PartialExpirationDateSignature {
96 h,
97 s: h * s_exponent,
98 };
99
100 AnnotatedExpirationDateSignature {
101 signature,
102 expiration_timestamp: expiration_unix_timestamp,
103 spending_timestamp: spending_unix_timestamp,
104 }
105 };
106
107 cfg_if::cfg_if! {
108 if #[cfg(feature = "par_signing")] {
109 use rayon::prelude::*;
110
111 Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS)
112 .into_par_iter()
113 .map(sign_expiration)
114 .collect())
115 } else {
116 Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS).map(sign_expiration).collect())
117 }
118 }
119}
120
121pub fn verify_valid_dates_signatures<B>(
138 vk: &VerificationKeyAuth,
139 signatures: &[B],
140 expiration_date: EncodedDate,
141) -> Result<()>
142where
143 B: Borrow<ExpirationDateSignature>,
144{
145 let m0: Scalar = date_scalar(expiration_date);
146 let m2: Scalar = constants::TYPE_EXP;
147
148 let partially_signed = vk.alpha + vk.beta_g2[0] * m0 + vk.beta_g2[2] * m2;
149 let mut pairing_terms = Vec::with_capacity(signatures.len());
150
151 for (i, sig) in signatures.iter().enumerate() {
152 let l = i as u32;
153 let valid_date = expiration_date
154 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
155 let m1: Scalar = date_scalar(valid_date);
156
157 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
159
160 let sig = *sig.borrow();
161 if sig.h != h {
163 return Err(CompactEcashError::ExpirationDateSignatureVerification);
164 }
165
166 pairing_terms.push((sig, partially_signed + vk.beta_g2[1] * m1));
168 }
169
170 if !batch_verify_signatures(pairing_terms.iter()) {
171 return Err(CompactEcashError::ExpirationDateSignatureVerification);
172 }
173 Ok(())
174}
175
176fn _aggregate_expiration_signatures<B>(
202 vk: &VerificationKeyAuth,
203 expiration_date: EncodedDate,
204 signatures_shares: &[ExpirationDateSignatureShare<B>],
205 validate_shares: bool,
206) -> Result<Vec<ExpirationDateSignature>>
207where
208 B: Borrow<ExpirationDateSignature> + Send + Sync,
209{
210 if signatures_shares
212 .iter()
213 .map(|share| share.index)
214 .unique()
215 .count()
216 != signatures_shares.len()
217 {
218 return Err(CompactEcashError::AggregationDuplicateIndices);
219 }
220
221 let coefficients = generate_lagrangian_coefficients_at_origin(
223 &signatures_shares
224 .iter()
225 .map(|share| share.index)
226 .collect::<Vec<_>>(),
227 );
228
229 if validate_shares {
231 cfg_if::cfg_if! {
232 if #[cfg(feature = "par_verify")] {
233 use rayon::prelude::*;
234
235 signatures_shares.par_iter().try_for_each(|share| {
236 verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date)
237 })?;
238 } else {
239 signatures_shares.iter().try_for_each(|share| verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date))?;
240 }
241 }
242 }
243
244 let mut aggregated_date_signatures: Vec<ExpirationDateSignature> =
246 Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
247
248 let m0: Scalar = date_scalar(expiration_date);
249
250 for l in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
251 let valid_date = expiration_date
252 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
253 let m1: Scalar = date_scalar(valid_date);
254 let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
256
257 let collected_at_l: Vec<_> = signatures_shares
259 .iter()
260 .filter_map(|share| share.signatures.get(l as usize))
261 .collect();
262
263 let aggr_s: G1Projective = coefficients
265 .iter()
266 .zip(collected_at_l.iter())
267 .map(|(coeff, &sig)| sig.borrow().s * coeff)
268 .sum();
269 let aggr_sig = ExpirationDateSignature { h, s: aggr_s };
270 aggregated_date_signatures.push(aggr_sig);
271 }
272 verify_valid_dates_signatures(vk, &aggregated_date_signatures, expiration_date)?;
273 Ok(aggregated_date_signatures)
274}
275
276pub fn aggregate_expiration_signatures<B>(
302 vk: &VerificationKeyAuth,
303 expiration_date: EncodedDate,
304 signatures_shares: &[ExpirationDateSignatureShare<B>],
305) -> Result<Vec<ExpirationDateSignature>>
306where
307 B: Borrow<PartialExpirationDateSignature> + Send + Sync,
308{
309 _aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
310}
311
312pub fn aggregate_annotated_expiration_signatures(
317 vk: &VerificationKeyAuth,
318 expiration_date: EncodedDate,
319 signatures_shares: &[ExpirationDateSignatureShare<AnnotatedExpirationDateSignature>],
320) -> Result<Vec<AnnotatedExpirationDateSignature>> {
321 let Some(share) = signatures_shares.first() else {
324 return Ok(Vec::new());
325 };
326
327 if share.signatures.len() != constants::CRED_VALIDITY_PERIOD_DAYS as usize {
328 return Err(CompactEcashError::ExpirationDateSignatureVerification);
329 }
330
331 for (i, sig) in share.signatures.iter().enumerate() {
332 if sig.expiration_timestamp != expiration_date {
333 return Err(CompactEcashError::ExpirationDateSignatureVerification);
334 }
335
336 let l = i as u32;
337 let expected_spending = sig.expiration_timestamp
338 - ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
339
340 if sig.spending_timestamp != expected_spending {
341 return Err(CompactEcashError::ExpirationDateSignatureVerification);
342 }
343 }
344
345 let aggregated = aggregate_expiration_signatures(vk, expiration_date, signatures_shares)?;
346 assert_eq!(aggregated.len(), share.signatures.len());
347
348 Ok(aggregated
349 .into_iter()
350 .zip(share.signatures.iter())
351 .map(|(signature, sh)| AnnotatedExpirationDateSignature {
352 signature,
353 expiration_timestamp: sh.expiration_timestamp,
354 spending_timestamp: sh.spending_timestamp,
355 })
356 .collect())
357}
358
359pub fn unchecked_aggregate_expiration_signatures(
364 vk: &VerificationKeyAuth,
365 expiration_date: EncodedDate,
366 signatures_shares: &[ExpirationDateSignatureShare],
367) -> Result<Vec<ExpirationDateSignature>> {
368 _aggregate_expiration_signatures(vk, expiration_date, signatures_shares, false)
369}
370
371pub fn find_index(spend_date: EncodedDate, expiration_date: EncodedDate) -> Result<usize> {
388 let start_date =
389 expiration_date - ((constants::CRED_VALIDITY_PERIOD_DAYS - 1) * constants::SECONDS_PER_DAY);
390
391 if spend_date >= start_date {
392 let index_a = ((spend_date - start_date) / constants::SECONDS_PER_DAY) as usize;
393 if index_a as u32 >= constants::CRED_VALIDITY_PERIOD_DAYS {
394 Err(CompactEcashError::SpendDateTooLate)
395 } else {
396 Ok(index_a)
397 }
398 } else {
399 Err(CompactEcashError::SpendDateTooEarly)
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406 use crate::scheme::aggregation::aggregate_verification_keys;
407 use crate::scheme::keygen::ttp_keygen;
408
409 #[test]
410 fn test_find_index() {
411 let expiration_date = 1701993600; for i in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
413 let current_spend_date = expiration_date - i * 86400;
414 assert_eq!(
415 find_index(current_spend_date, expiration_date).unwrap(),
416 (constants::CRED_VALIDITY_PERIOD_DAYS - 1 - i) as usize
417 )
418 }
419
420 let late_spend_date = expiration_date + 86400;
421 assert!(find_index(late_spend_date, expiration_date).is_err());
422
423 let early_spend_date = expiration_date - (constants::CRED_VALIDITY_PERIOD_DAYS) * 86400;
424 assert!(find_index(early_spend_date, expiration_date).is_err());
425 }
426
427 #[test]
428 fn test_sign_expiration_date() {
429 let expiration_date = 1702050209; let authorities_keys = ttp_keygen(2, 3).unwrap();
432 let sk_i_auth = authorities_keys[0].secret_key();
433 let vk_i_auth = authorities_keys[0].verification_key();
434 let partial_exp_sig = sign_expiration_date(sk_i_auth, expiration_date).unwrap();
435
436 assert!(
437 verify_valid_dates_signatures(&vk_i_auth, &partial_exp_sig, expiration_date).is_ok()
438 );
439 }
440
441 #[test]
442 fn test_aggregate_expiration_signatures() {
443 let expiration_date = 1702050209; let authorities_keypairs = ttp_keygen(2, 3).unwrap();
446 let indices: [u64; 3] = [1, 2, 3];
447 let secret_keys_authorities: Vec<&SecretKeyAuth> = authorities_keypairs
449 .iter()
450 .map(|keypair| keypair.secret_key())
451 .collect();
452 let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
454 .iter()
455 .map(|keypair| keypair.verification_key())
456 .collect();
457 let verification_key =
459 aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
460
461 let mut edt_partial_signatures: Vec<Vec<_>> =
462 Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
463 for sk_auth in secret_keys_authorities.iter() {
464 let sign = sign_expiration_date(sk_auth, expiration_date).unwrap();
465 edt_partial_signatures.push(sign);
466 }
467
468 let combined_data = indices
469 .iter()
470 .zip(
471 verification_keys_auth
472 .iter()
473 .zip(edt_partial_signatures.iter()),
474 )
475 .map(|(i, (vk, sigs))| ExpirationDateSignatureShare {
476 index: *i,
477 key: vk.clone(),
478 signatures: sigs.clone(),
479 })
480 .collect::<Vec<_>>();
481
482 assert!(aggregate_expiration_signatures(
483 &verification_key,
484 expiration_date,
485 &combined_data,
486 )
487 .is_ok());
488 }
489}