nym_credential_verification/
lib.rs1use crate::ecash::traits::EcashManager;
5use async_trait::async_trait;
6use bandwidth_storage_manager::BandwidthStorageManager;
7use nym_credentials::ecash::utils::{EcashTime, cred_exp_date, ecash_today};
8use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
9use nym_gateway_requests::models::CredentialSpendingRequest;
10use std::sync::Arc;
11use std::time::Instant;
12use time::{Date, OffsetDateTime};
13use tracing::*;
14
15pub use client_bandwidth::*;
16pub use error::*;
17pub use upgrade_mode::UpgradeModeState;
18
19pub mod bandwidth_storage_manager;
20mod client_bandwidth;
21pub mod ecash;
22pub mod error;
23pub mod upgrade_mode;
24
25const ECASH_VERIFICATION_DURATION_BUCKETS: &[f64] =
27 &[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0];
28
29pub struct CredentialVerifier {
30 credential: CredentialSpendingRequest,
31 ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
32 bandwidth_storage_manager: BandwidthStorageManager,
33}
34
35impl CredentialVerifier {
36 pub fn new(
37 credential: CredentialSpendingRequest,
38 ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
39 bandwidth_storage_manager: BandwidthStorageManager,
40 ) -> Self {
41 CredentialVerifier {
42 credential,
43 ecash_verifier,
44 bandwidth_storage_manager,
45 }
46 }
47
48 fn check_credential_spending_date(&self, today: Date) -> Result<()> {
49 let proposed = self.credential.data.spend_date;
50 trace!("checking ticket spending date...");
51
52 if today != proposed {
53 trace!("invalid credential spending date. received {proposed}");
54 return Err(Error::InvalidCredentialSpendingDate {
55 got: proposed,
56 expected: today,
57 });
58 }
59 Ok(())
60 }
61
62 async fn check_local_db_for_double_spending(&self, serial_number: &[u8]) -> Result<()> {
63 trace!("checking local db for double spending...");
64
65 let spent = self
66 .bandwidth_storage_manager
67 .storage
68 .contains_ticket(serial_number)
69 .await?;
70 if spent {
71 trace!("the credential has already been spent before at this gateway");
72 nym_metrics::inc!("ecash_verification_failures_double_spending");
73 return Err(Error::BandwidthCredentialAlreadySpent);
74 }
75 Ok(())
76 }
77
78 async fn cryptographically_verify_ticket(&self) -> Result<()> {
79 trace!("attempting to perform ticket verification...");
80
81 let aggregated_verification_key = self
82 .ecash_verifier
83 .verification_key(self.credential.data.epoch_id)
84 .await?;
85
86 self.ecash_verifier
87 .check_payment(&self.credential.data, &aggregated_verification_key)
88 .await?;
89 Ok(())
90 }
91
92 async fn store_received_ticket(&self, received_at: OffsetDateTime) -> Result<i64> {
93 trace!("storing received ticket");
94 let ticket_id = self
95 .bandwidth_storage_manager
96 .storage
97 .insert_received_ticket(
98 self.bandwidth_storage_manager.client_id,
99 received_at,
100 self.credential.encoded_serial_number(),
101 self.credential.to_bytes(),
102 )
103 .await?;
104 Ok(ticket_id)
105 }
106
107 fn async_verify_ticket(&self, ticket_id: i64) {
108 let client_ticket = ClientTicket::new(self.credential.data.clone(), ticket_id);
109
110 self.ecash_verifier.async_verify(client_ticket);
111 }
112
113 pub async fn verify(&mut self) -> Result<i64> {
114 let start = Instant::now();
115 nym_metrics::inc!("ecash_verification_attempts");
116
117 let received_at = OffsetDateTime::now_utc();
118 let spend_date = ecash_today();
119
120 let serial_number = self.credential.data.encoded_serial_number();
122 let credential_type = TicketType::try_from_encoded(self.credential.data.payment.t_type)?;
123
124 if self.credential.data.payment.spend_value != 1 {
125 nym_metrics::inc!("ecash_verification_failures_multiple_tickets");
126 return Err(Error::MultipleTickets);
127 }
128
129 if let Err(e) = self.check_credential_spending_date(spend_date.ecash_date()) {
130 nym_metrics::inc!("ecash_verification_failures_invalid_spend_date");
131 return Err(e);
132 }
133
134 self.check_local_db_for_double_spending(&serial_number)
135 .await?;
136
137 let verify_result = self.cryptographically_verify_ticket().await;
139
140 let duration = start.elapsed().as_secs_f64();
142 nym_metrics::add_histogram_obs!(
143 "ecash_verification_duration_seconds",
144 duration,
145 ECASH_VERIFICATION_DURATION_BUCKETS
146 );
147
148 let epoch_id = self.credential.data.epoch_id;
150 let epoch_metric = format!(
151 "nym_credential_verification_ecash_epoch_{}_verifications",
152 epoch_id
153 );
154 nym_metrics::metrics_registry().maybe_register_and_inc(&epoch_metric, None);
155
156 verify_result?;
158
159 let ticket_id = self.store_received_ticket(received_at).await?;
160 self.async_verify_ticket(ticket_id);
161
162 let bandwidth = Bandwidth::ticket_amount(credential_type.into());
166
167 self.bandwidth_storage_manager
168 .increase_bandwidth(bandwidth, cred_exp_date())
169 .await?;
170
171 nym_metrics::inc!("ecash_verification_success");
172
173 Ok(self
174 .bandwidth_storage_manager
175 .client_bandwidth
176 .available()
177 .await)
178 }
179}
180
181#[async_trait]
182pub trait TicketVerifier {
183 async fn verify(&mut self) -> Result<i64>;
187}
188
189#[async_trait]
190impl TicketVerifier for CredentialVerifier {
191 async fn verify(&mut self) -> Result<i64> {
192 self.verify().await
193 }
194}