use crate::ecash::bandwidth::importable::ImportableTicketBook;
use crate::ecash::bandwidth::serialiser::VersionedSerialise;
use crate::ecash::bandwidth::CredentialSpendingData;
use crate::ecash::utils::ecash_today;
use crate::error::Error;
use nym_credentials_interface::{
CoinIndexSignature, ExpirationDateSignature, NymPayInfo, PayInfo, SecretKeyUser, TicketType,
VerificationKeyAuth, Wallet, WalletSignatures,
};
use nym_ecash_time::EcashTime;
use nym_validator_client::nym_api::EpochId;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use time::Date;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub const CURRENT_SERIALIZATION_REVISION: u8 = 1;
#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct IssuedTicketBook {
signatures_wallet: WalletSignatures,
spent_tickets: u64,
epoch_id: EpochId,
ecash_secret_key: SecretKeyUser,
#[zeroize(skip)]
expiration_date: Date,
#[zeroize(skip)]
ticketbook_type: TicketType,
}
impl IssuedTicketBook {
pub fn new(
wallet: WalletSignatures,
epoch_id: EpochId,
ecash_secret_key: SecretKeyUser,
ticketbook_type: TicketType,
expiration_date: Date,
) -> Self {
IssuedTicketBook {
signatures_wallet: wallet,
spent_tickets: 0,
epoch_id,
ecash_secret_key,
expiration_date,
ticketbook_type,
}
}
pub fn from_parts(
signatures_wallet: WalletSignatures,
epoch_id: EpochId,
ecash_secret_key: SecretKeyUser,
ticketbook_type: TicketType,
expiration_date: Date,
spent_tickets: u64,
) -> Self {
IssuedTicketBook {
signatures_wallet,
spent_tickets,
epoch_id,
ecash_secret_key,
expiration_date,
ticketbook_type,
}
}
pub fn update_spent_tickets(&mut self, spent_tickets: u64) {
self.spent_tickets = spent_tickets
}
pub fn epoch_id(&self) -> EpochId {
self.epoch_id
}
pub fn ticketbook_type(&self) -> TicketType {
self.ticketbook_type
}
pub fn current_serialization_revision(&self) -> u8 {
CURRENT_SERIALIZATION_REVISION
}
pub fn expiration_date(&self) -> Date {
self.expiration_date
}
pub fn expired(&self) -> bool {
self.expiration_date < ecash_today().date()
}
pub fn global_total_tickets() -> u64 {
nym_credentials_interface::ecash_parameters().get_total_coins()
}
pub fn params_total_tickets(&self) -> u64 {
Self::global_total_tickets()
}
pub fn spent_tickets(&self) -> u64 {
self.spent_tickets
}
pub fn wallet(&self) -> &WalletSignatures {
&self.signatures_wallet
}
pub fn generate_pay_info(&self, provider_pk: [u8; 32]) -> NymPayInfo {
NymPayInfo::generate(provider_pk)
}
pub fn prepare_for_spending<BI, BE>(
&mut self,
verification_key: &VerificationKeyAuth,
pay_info: PayInfo,
coin_indices_signatures: &[BI],
expiration_date_signatures: &[BE],
tickets_to_spend: u64,
) -> Result<CredentialSpendingData, Error>
where
BI: Borrow<CoinIndexSignature>,
BE: Borrow<ExpirationDateSignature>,
{
let params = nym_credentials_interface::ecash_parameters();
let spend_date = ecash_today();
Wallet::ensure_allowance(params, self.spent_tickets, tickets_to_spend)?;
let payment = self.signatures_wallet.spend(
params,
verification_key,
&self.ecash_secret_key,
&pay_info,
self.spent_tickets,
tickets_to_spend,
expiration_date_signatures,
coin_indices_signatures,
spend_date.ecash_unix_timestamp(),
)?;
self.spent_tickets += tickets_to_spend;
Ok(CredentialSpendingData {
payment,
pay_info,
spend_date: spend_date.ecash_date(),
epoch_id: self.epoch_id,
})
}
pub fn begin_export(self) -> ImportableTicketBook {
self.into()
}
}
impl VersionedSerialise for IssuedTicketBook {
const CURRENT_SERIALISATION_REVISION: u8 = 1;
fn try_unpack(b: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
let revision = revision
.into()
.unwrap_or(<Self as VersionedSerialise>::CURRENT_SERIALISATION_REVISION);
match revision {
1 => Self::try_unpack_current(b),
_ => Err(Error::UnknownSerializationRevision { revision }),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
fn assert_zeroize<T: Zeroize>() {}
#[test]
fn credential_is_zeroized() {
assert_zeroize::<IssuedTicketBook>();
assert_zeroize_on_drop::<IssuedTicketBook>();
}
}