nym_credentials/ecash/bandwidth/
issued.rs

1// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::ecash::bandwidth::importable::ImportableTicketBook;
5use crate::ecash::bandwidth::serialiser::VersionedSerialise;
6use crate::ecash::bandwidth::CredentialSpendingData;
7use crate::ecash::utils::ecash_today;
8use crate::error::Error;
9use nym_credentials_interface::{
10    CoinIndexSignature, ExpirationDateSignature, NymPayInfo, PayInfo, SecretKeyUser, TicketType,
11    VerificationKeyAuth, Wallet, WalletSignatures,
12};
13use nym_ecash_time::EcashTime;
14use nym_validator_client::nym_api::EpochId;
15use serde::{Deserialize, Serialize};
16use std::borrow::Borrow;
17use time::Date;
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20pub const CURRENT_SERIALIZATION_REVISION: u8 = 1;
21
22// the only important thing to zeroize here are the private attributes, the rest can be made fully public for what we're concerned
23#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
24pub struct IssuedTicketBook {
25    /// the underlying wallet signatures
26    signatures_wallet: WalletSignatures,
27
28    /// the counter indicating how many tickets have been spent so far
29    spent_tickets: u64,
30
31    /// Specifies the (DKG) epoch id when this credential has been issued
32    epoch_id: EpochId,
33
34    /// secret ecash key used to generate this wallet
35    ecash_secret_key: SecretKeyUser,
36
37    /// expiration_date for easier discarding
38    #[zeroize(skip)]
39    expiration_date: Date,
40
41    /// the type of the ticketbook to got issued
42    #[zeroize(skip)]
43    ticketbook_type: TicketType,
44}
45
46impl IssuedTicketBook {
47    pub fn new(
48        wallet: WalletSignatures,
49        epoch_id: EpochId,
50        ecash_secret_key: SecretKeyUser,
51        ticketbook_type: TicketType,
52        expiration_date: Date,
53    ) -> Self {
54        IssuedTicketBook {
55            signatures_wallet: wallet,
56            spent_tickets: 0,
57            epoch_id,
58            ecash_secret_key,
59            expiration_date,
60            ticketbook_type,
61        }
62    }
63
64    pub fn from_parts(
65        signatures_wallet: WalletSignatures,
66        epoch_id: EpochId,
67        ecash_secret_key: SecretKeyUser,
68        ticketbook_type: TicketType,
69        expiration_date: Date,
70        spent_tickets: u64,
71    ) -> Self {
72        IssuedTicketBook {
73            signatures_wallet,
74            spent_tickets,
75            epoch_id,
76            ecash_secret_key,
77            expiration_date,
78            ticketbook_type,
79        }
80    }
81
82    pub fn update_spent_tickets(&mut self, spent_tickets: u64) {
83        self.spent_tickets = spent_tickets
84    }
85
86    pub fn epoch_id(&self) -> EpochId {
87        self.epoch_id
88    }
89
90    pub fn ticketbook_type(&self) -> TicketType {
91        self.ticketbook_type
92    }
93
94    pub fn current_serialization_revision(&self) -> u8 {
95        CURRENT_SERIALIZATION_REVISION
96    }
97
98    pub fn expiration_date(&self) -> Date {
99        self.expiration_date
100    }
101
102    pub fn expired(&self) -> bool {
103        self.expiration_date < ecash_today().date()
104    }
105
106    pub fn global_total_tickets() -> u64 {
107        nym_credentials_interface::ecash_parameters().get_total_coins()
108    }
109
110    pub fn params_total_tickets(&self) -> u64 {
111        Self::global_total_tickets()
112    }
113
114    pub fn spent_tickets(&self) -> u64 {
115        self.spent_tickets
116    }
117
118    pub fn wallet(&self) -> &WalletSignatures {
119        &self.signatures_wallet
120    }
121
122    pub fn generate_pay_info(&self, provider_pk: [u8; 32]) -> NymPayInfo {
123        NymPayInfo::generate(provider_pk)
124    }
125
126    pub fn prepare_for_spending<BI, BE>(
127        &mut self,
128        verification_key: &VerificationKeyAuth,
129        pay_info: PayInfo,
130        coin_indices_signatures: &[BI],
131        expiration_date_signatures: &[BE],
132        tickets_to_spend: u64,
133    ) -> Result<CredentialSpendingData, Error>
134    where
135        BI: Borrow<CoinIndexSignature>,
136        BE: Borrow<ExpirationDateSignature>,
137    {
138        let params = nym_credentials_interface::ecash_parameters();
139        let spend_date = ecash_today();
140
141        // make sure we still have enough tickets to spend
142        Wallet::ensure_allowance(params, self.spent_tickets, tickets_to_spend)?;
143
144        let payment = self.signatures_wallet.spend(
145            params,
146            verification_key,
147            &self.ecash_secret_key,
148            &pay_info,
149            self.spent_tickets,
150            tickets_to_spend,
151            expiration_date_signatures,
152            coin_indices_signatures,
153            spend_date.ecash_unix_timestamp(),
154        )?;
155
156        self.spent_tickets += tickets_to_spend;
157
158        Ok(CredentialSpendingData {
159            payment,
160            pay_info,
161            spend_date: spend_date.ecash_date(),
162            epoch_id: self.epoch_id,
163        })
164    }
165
166    pub fn begin_export(self) -> ImportableTicketBook {
167        self.into()
168    }
169}
170
171impl VersionedSerialise for IssuedTicketBook {
172    const CURRENT_SERIALISATION_REVISION: u8 = 1;
173
174    fn try_unpack(b: &[u8], revision: impl Into<Option<u8>>) -> Result<Self, Error> {
175        let revision = revision
176            .into()
177            .unwrap_or(<Self as VersionedSerialise>::CURRENT_SERIALISATION_REVISION);
178
179        match revision {
180            1 => Self::try_unpack_current(b),
181            _ => Err(Error::UnknownSerializationRevision { revision }),
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
191
192    fn assert_zeroize<T: Zeroize>() {}
193
194    #[test]
195    fn credential_is_zeroized() {
196        assert_zeroize::<IssuedTicketBook>();
197        assert_zeroize_on_drop::<IssuedTicketBook>();
198    }
199}