use crate::common_types::{Signature, SignerIndex};
use crate::error::{CompactEcashError, Result};
use crate::helpers::date_scalar;
use crate::scheme::keygen::{SecretKeyAuth, VerificationKeyAuth};
use crate::utils::generate_lagrangian_coefficients_at_origin;
use crate::utils::{batch_verify_signatures, hash_g1};
use crate::{constants, EncodedDate};
use itertools::Itertools;
use nym_bls12_381_fork::{G1Projective, Scalar};
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
pub type ExpirationDateSignature = Signature;
pub type PartialExpirationDateSignature = ExpirationDateSignature;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AnnotatedExpirationDateSignature {
pub signature: ExpirationDateSignature,
pub expiration_timestamp: EncodedDate,
pub spending_timestamp: EncodedDate,
}
impl Borrow<ExpirationDateSignature> for AnnotatedExpirationDateSignature {
fn borrow(&self) -> &ExpirationDateSignature {
&self.signature
}
}
impl From<AnnotatedExpirationDateSignature> for ExpirationDateSignature {
fn from(value: AnnotatedExpirationDateSignature) -> Self {
value.signature
}
}
pub struct ExpirationDateSignatureShare<B = PartialExpirationDateSignature>
where
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
pub index: SignerIndex,
pub key: VerificationKeyAuth,
pub signatures: Vec<B>,
}
pub fn sign_expiration_date(
sk_auth: &SecretKeyAuth,
expiration_unix_timestamp: EncodedDate,
) -> Result<Vec<AnnotatedExpirationDateSignature>> {
if sk_auth.ys.len() < 3 {
return Err(CompactEcashError::KeyTooShort);
}
let m0: Scalar = date_scalar(expiration_unix_timestamp);
let m2: Scalar = constants::TYPE_EXP;
let partial_s_exponent = sk_auth.x + sk_auth.ys[0] * m0 + sk_auth.ys[2] * m2;
let sign_expiration = |offset: u32| {
let spending_unix_timestamp = expiration_unix_timestamp
- ((constants::CRED_VALIDITY_PERIOD_DAYS - offset - 1) * constants::SECONDS_PER_DAY);
let m1: Scalar = date_scalar(spending_unix_timestamp);
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
let s_exponent = partial_s_exponent + sk_auth.ys[1] * m1;
let signature = PartialExpirationDateSignature {
h,
s: h * s_exponent,
};
AnnotatedExpirationDateSignature {
signature,
expiration_timestamp: expiration_unix_timestamp,
spending_timestamp: spending_unix_timestamp,
}
};
cfg_if::cfg_if! {
if #[cfg(feature = "par_signing")] {
use rayon::prelude::*;
Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS)
.into_par_iter()
.map(sign_expiration)
.collect())
} else {
Ok((0..constants::CRED_VALIDITY_PERIOD_DAYS).map(sign_expiration).collect())
}
}
}
pub fn verify_valid_dates_signatures<B>(
vk: &VerificationKeyAuth,
signatures: &[B],
expiration_date: EncodedDate,
) -> Result<()>
where
B: Borrow<ExpirationDateSignature>,
{
let m0: Scalar = date_scalar(expiration_date);
let m2: Scalar = constants::TYPE_EXP;
let partially_signed = vk.alpha + vk.beta_g2[0] * m0 + vk.beta_g2[2] * m2;
let mut pairing_terms = Vec::with_capacity(signatures.len());
for (i, sig) in signatures.iter().enumerate() {
let l = i as u32;
let valid_date = expiration_date
- ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
let m1: Scalar = date_scalar(valid_date);
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
let sig = *sig.borrow();
if sig.h != h {
return Err(CompactEcashError::ExpirationDateSignatureVerification);
}
pairing_terms.push((sig, partially_signed + vk.beta_g2[1] * m1));
}
if !batch_verify_signatures(pairing_terms.iter()) {
return Err(CompactEcashError::ExpirationDateSignatureVerification);
}
Ok(())
}
fn _aggregate_expiration_signatures<B>(
vk: &VerificationKeyAuth,
expiration_date: EncodedDate,
signatures_shares: &[ExpirationDateSignatureShare<B>],
validate_shares: bool,
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<ExpirationDateSignature> + Send + Sync,
{
if signatures_shares
.iter()
.map(|share| share.index)
.unique()
.count()
!= signatures_shares.len()
{
return Err(CompactEcashError::AggregationDuplicateIndices);
}
let coefficients = generate_lagrangian_coefficients_at_origin(
&signatures_shares
.iter()
.map(|share| share.index)
.collect::<Vec<_>>(),
);
if validate_shares {
cfg_if::cfg_if! {
if #[cfg(feature = "par_verify")] {
use rayon::prelude::*;
signatures_shares.par_iter().try_for_each(|share| {
verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date)
})?;
} else {
signatures_shares.iter().try_for_each(|share| verify_valid_dates_signatures(&share.key, &share.signatures, expiration_date))?;
}
}
}
let mut aggregated_date_signatures: Vec<ExpirationDateSignature> =
Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
let m0: Scalar = date_scalar(expiration_date);
for l in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
let valid_date = expiration_date
- ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
let m1: Scalar = date_scalar(valid_date);
let h = hash_g1([m0.to_bytes(), m1.to_bytes()].concat());
let collected_at_l: Vec<_> = signatures_shares
.iter()
.filter_map(|share| share.signatures.get(l as usize))
.collect();
let aggr_s: G1Projective = coefficients
.iter()
.zip(collected_at_l.iter())
.map(|(coeff, &sig)| sig.borrow().s * coeff)
.sum();
let aggr_sig = ExpirationDateSignature { h, s: aggr_s };
aggregated_date_signatures.push(aggr_sig);
}
verify_valid_dates_signatures(vk, &aggregated_date_signatures, expiration_date)?;
Ok(aggregated_date_signatures)
}
pub fn aggregate_expiration_signatures<B>(
vk: &VerificationKeyAuth,
expiration_date: EncodedDate,
signatures_shares: &[ExpirationDateSignatureShare<B>],
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
_aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
}
pub fn aggregate_annotated_expiration_signatures(
vk: &VerificationKeyAuth,
expiration_date: EncodedDate,
signatures_shares: &[ExpirationDateSignatureShare<AnnotatedExpirationDateSignature>],
) -> Result<Vec<AnnotatedExpirationDateSignature>> {
let Some(share) = signatures_shares.first() else {
return Ok(Vec::new());
};
if share.signatures.len() != constants::CRED_VALIDITY_PERIOD_DAYS as usize {
return Err(CompactEcashError::ExpirationDateSignatureVerification);
}
for (i, sig) in share.signatures.iter().enumerate() {
if sig.expiration_timestamp != expiration_date {
return Err(CompactEcashError::ExpirationDateSignatureVerification);
}
let l = i as u32;
let expected_spending = sig.expiration_timestamp
- ((constants::CRED_VALIDITY_PERIOD_DAYS - l - 1) * constants::SECONDS_PER_DAY);
if sig.spending_timestamp != expected_spending {
return Err(CompactEcashError::ExpirationDateSignatureVerification);
}
}
let aggregated = aggregate_expiration_signatures(vk, expiration_date, signatures_shares)?;
assert_eq!(aggregated.len(), share.signatures.len());
Ok(aggregated
.into_iter()
.zip(share.signatures.iter())
.map(|(signature, sh)| AnnotatedExpirationDateSignature {
signature,
expiration_timestamp: sh.expiration_timestamp,
spending_timestamp: sh.spending_timestamp,
})
.collect())
}
pub fn unchecked_aggregate_expiration_signatures(
vk: &VerificationKeyAuth,
expiration_date: EncodedDate,
signatures_shares: &[ExpirationDateSignatureShare],
) -> Result<Vec<ExpirationDateSignature>> {
_aggregate_expiration_signatures(vk, expiration_date, signatures_shares, false)
}
pub fn find_index(spend_date: EncodedDate, expiration_date: EncodedDate) -> Result<usize> {
let start_date =
expiration_date - ((constants::CRED_VALIDITY_PERIOD_DAYS - 1) * constants::SECONDS_PER_DAY);
if spend_date >= start_date {
let index_a = ((spend_date - start_date) / constants::SECONDS_PER_DAY) as usize;
if index_a as u32 >= constants::CRED_VALIDITY_PERIOD_DAYS {
Err(CompactEcashError::SpendDateTooLate)
} else {
Ok(index_a)
}
} else {
Err(CompactEcashError::SpendDateTooEarly)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scheme::aggregation::aggregate_verification_keys;
use crate::scheme::keygen::ttp_keygen;
#[test]
fn test_find_index() {
let expiration_date = 1701993600; for i in 0..constants::CRED_VALIDITY_PERIOD_DAYS {
let current_spend_date = expiration_date - i * 86400;
assert_eq!(
find_index(current_spend_date, expiration_date).unwrap(),
(constants::CRED_VALIDITY_PERIOD_DAYS - 1 - i) as usize
)
}
let late_spend_date = expiration_date + 86400;
assert!(find_index(late_spend_date, expiration_date).is_err());
let early_spend_date = expiration_date - (constants::CRED_VALIDITY_PERIOD_DAYS) * 86400;
assert!(find_index(early_spend_date, expiration_date).is_err());
}
#[test]
fn test_sign_expiration_date() {
let expiration_date = 1702050209;
let authorities_keys = ttp_keygen(2, 3).unwrap();
let sk_i_auth = authorities_keys[0].secret_key();
let vk_i_auth = authorities_keys[0].verification_key();
let partial_exp_sig = sign_expiration_date(sk_i_auth, expiration_date).unwrap();
assert!(
verify_valid_dates_signatures(&vk_i_auth, &partial_exp_sig, expiration_date).is_ok()
);
}
#[test]
fn test_aggregate_expiration_signatures() {
let expiration_date = 1702050209;
let authorities_keypairs = ttp_keygen(2, 3).unwrap();
let indices: [u64; 3] = [1, 2, 3];
let secret_keys_authorities: Vec<&SecretKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.secret_key())
.collect();
let verification_keys_auth: Vec<VerificationKeyAuth> = authorities_keypairs
.iter()
.map(|keypair| keypair.verification_key())
.collect();
let verification_key =
aggregate_verification_keys(&verification_keys_auth, Some(&indices)).unwrap();
let mut edt_partial_signatures: Vec<Vec<_>> =
Vec::with_capacity(constants::CRED_VALIDITY_PERIOD_DAYS as usize);
for sk_auth in secret_keys_authorities.iter() {
let sign = sign_expiration_date(sk_auth, expiration_date).unwrap();
edt_partial_signatures.push(sign);
}
let combined_data = indices
.iter()
.zip(
verification_keys_auth
.iter()
.zip(edt_partial_signatures.iter()),
)
.map(|(i, (vk, sigs))| ExpirationDateSignatureShare {
index: *i,
key: vk.clone(),
signatures: sigs.clone(),
})
.collect::<Vec<_>>();
assert!(aggregate_expiration_signatures(
&verification_key,
expiration_date,
&combined_data,
)
.is_ok());
}
}