nym_credentials_interface/
lib.rs1use rand::Rng;
5use serde::{Deserialize, Serialize};
6use std::fmt::Debug;
7use strum::IntoEnumIterator as _;
8use thiserror::Error;
9use time::{Date, OffsetDateTime};
10
11pub use nym_compact_ecash::{
12 Base58, BlindedSignature, Bytable, EncodedDate, EncodedTicketType, PartialWallet, PayInfo,
13 PublicKeyUser, SecretKeyUser, VerificationKeyAuth, WithdrawalRequest,
14 aggregate_verification_keys, aggregate_wallets, constants, ecash_parameters,
15 error::CompactEcashError,
16 generate_keypair_user, generate_keypair_user_from_seed, issue_verify,
17 scheme::Payment,
18 scheme::coin_indices_signatures::aggregate_indices_signatures,
19 scheme::coin_indices_signatures::{
20 AnnotatedCoinIndexSignature, CoinIndexSignature, CoinIndexSignatureShare,
21 PartialCoinIndexSignature,
22 },
23 scheme::expiration_date_signatures::aggregate_expiration_signatures,
24 scheme::expiration_date_signatures::{
25 AnnotatedExpirationDateSignature, ExpirationDateSignature, ExpirationDateSignatureShare,
26 PartialExpirationDateSignature,
27 },
28 scheme::keygen::KeyPairUser,
29 scheme::withdrawal::RequestInfo,
30 scheme::{Wallet, WalletSignatures},
31 withdrawal_request,
32};
33pub use nym_ecash_time::{EcashTime, ecash_today};
34pub use nym_network_defaults::TicketTypeRepr;
35use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
36
37pub const DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD: i64 =
42 (V1MixnetEntry.bandwidth_value() / 5) as i64;
43
44#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
45pub enum BandwidthCredential {
46 ZkNym(Box<CredentialSpendingData>),
47 UpgradeModeJWT { token: String },
48}
49
50impl BandwidthCredential {
51 pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
52 match self {
53 BandwidthCredential::ZkNym(credential) => Some(credential),
54 _ => None,
55 }
56 }
57}
58
59impl From<CredentialSpendingData> for BandwidthCredential {
60 fn from(credential: CredentialSpendingData) -> Self {
61 Self::ZkNym(Box::new(credential))
62 }
63}
64
65#[derive(Debug, Clone)]
66pub struct CredentialSigningData {
67 pub withdrawal_request: WithdrawalRequest,
68
69 pub request_info: RequestInfo,
70
71 pub ecash_pub_key: PublicKeyUser,
72
73 pub expiration_date: Date,
74
75 pub ticketbook_type: TicketType,
76}
77
78#[derive(Serialize, Deserialize, PartialEq, Clone)]
79pub struct CredentialSpendingData {
80 pub payment: Payment,
81
82 pub pay_info: PayInfo,
83
84 pub spend_date: Date,
85
86 pub epoch_id: u64,
89}
90
91impl Debug for CredentialSpendingData {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 f.debug_struct("CredentialSpendingData")
97 .field("payment", &"[REDACTED]")
98 .field("pay_info", &self.pay_info)
99 .field("spend_date", &self.spend_date)
100 .field("epoch_id", &self.epoch_id)
101 .finish()
102 }
103}
104
105impl CredentialSpendingData {
106 pub fn verify(&self, verification_key: &VerificationKeyAuth) -> Result<(), CompactEcashError> {
107 self.payment.spend_verify(
108 verification_key,
109 &self.pay_info,
110 self.spend_date.ecash_unix_timestamp(),
111 )
112 }
113
114 pub fn encoded_serial_number(&self) -> Vec<u8> {
115 self.payment.encoded_serial_number()
116 }
117
118 pub fn serial_number_b58(&self) -> String {
119 self.payment.serial_number_bs58()
120 }
121
122 pub fn to_bytes(&self) -> Vec<u8> {
123 let mut bytes = Vec::new();
126 let payment_bytes = self.payment.to_bytes();
127
128 bytes.extend_from_slice(&(payment_bytes.len() as u32).to_be_bytes());
129 bytes.extend_from_slice(&payment_bytes);
130 bytes.extend_from_slice(&self.pay_info.pay_info_bytes); bytes.extend_from_slice(&self.spend_date.to_julian_day().to_be_bytes());
132 bytes.extend_from_slice(&self.epoch_id.to_be_bytes());
133
134 bytes
135 }
136
137 pub fn try_from_bytes(raw: &[u8]) -> Result<Self, CompactEcashError> {
138 if raw.len() < 72 + 8 + 4 + 4 {
140 return Err(CompactEcashError::DeserializationFailure {
141 object: "EcashCredential".into(),
142 });
143 }
144 let mut index = 0;
145 let payment_len = u32::from_be_bytes(raw[index..index + 4].try_into().unwrap()) as usize;
147 index += 4;
148
149 if raw[index..].len() != payment_len + 84 {
150 return Err(CompactEcashError::DeserializationFailure {
151 object: "EcashCredential".into(),
152 });
153 }
154 let payment = Payment::try_from(&raw[index..index + payment_len])?;
155 index += payment_len;
156
157 let pay_info = PayInfo {
158 pay_info_bytes: raw[index..index + 72].try_into().unwrap(),
160 };
161 index += 72;
162
163 let spend_date_julian = i32::from_be_bytes(raw[index..index + 4].try_into().unwrap());
165 let spend_date = Date::from_julian_day(spend_date_julian).map_err(|_| {
166 CompactEcashError::DeserializationFailure {
167 object: "CredentialSpendingData".into(),
168 }
169 })?;
170 index += 4;
171
172 if raw[index..].len() != 8 {
173 return Err(CompactEcashError::DeserializationFailure {
174 object: "EcashCredential".into(),
175 });
176 }
177
178 let epoch_id = u64::from_be_bytes(raw[index..].try_into().unwrap());
180
181 Ok(CredentialSpendingData {
182 payment,
183 pay_info,
184 spend_date,
185 epoch_id,
186 })
187 }
188}
189
190impl Bytable for CredentialSpendingData {
191 fn to_byte_vec(&self) -> Vec<u8> {
192 self.to_bytes()
193 }
194
195 fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
196 Self::try_from_bytes(slice)
197 }
198}
199
200impl Base58 for CredentialSpendingData {}
201
202#[derive(PartialEq, Eq, Debug, Clone, Copy)]
203pub struct NymPayInfo {
204 randomness: [u8; 32],
205 timestamp: i64,
206 provider_public_key: [u8; 32],
207}
208
209impl NymPayInfo {
210 pub fn generate(provider_pk: [u8; 32], spend_time: OffsetDateTime) -> Self {
221 let mut randomness = [0u8; 32];
222 rand::thread_rng().fill(&mut randomness[..32]);
223
224 let timestamp = spend_time.unix_timestamp();
225
226 NymPayInfo {
227 randomness,
228 timestamp,
229 provider_public_key: provider_pk,
230 }
231 }
232
233 pub fn timestamp(&self) -> i64 {
234 self.timestamp
235 }
236
237 pub fn pk(&self) -> [u8; 32] {
238 self.provider_public_key
239 }
240}
241
242impl From<NymPayInfo> for PayInfo {
243 fn from(value: NymPayInfo) -> Self {
244 let mut pay_info_bytes = [0u8; 72];
245
246 pay_info_bytes[..32].copy_from_slice(&value.randomness);
247 pay_info_bytes[32..40].copy_from_slice(&value.timestamp.to_be_bytes());
248 pay_info_bytes[40..].copy_from_slice(&value.provider_public_key);
249
250 PayInfo { pay_info_bytes }
251 }
252}
253
254impl From<PayInfo> for NymPayInfo {
255 fn from(value: PayInfo) -> Self {
256 let randomness = value.pay_info_bytes[..32].try_into().unwrap();
258 let timestamp = i64::from_be_bytes(value.pay_info_bytes[32..40].try_into().unwrap());
259 let provider_public_key = value.pay_info_bytes[40..].try_into().unwrap();
260
261 NymPayInfo {
262 randomness,
263 timestamp,
264 provider_public_key,
265 }
266 }
267}
268
269#[derive(
270 Copy,
271 Clone,
272 Debug,
273 PartialEq,
274 Eq,
275 Serialize,
276 Deserialize,
277 Hash,
278 strum_macros::Display,
279 strum_macros::EnumString,
280 strum_macros::EnumIter,
281)]
282#[serde(rename_all = "kebab-case")]
283#[strum(serialize_all = "kebab-case")]
284pub enum TicketType {
285 V1MixnetEntry,
286 V1MixnetExit,
287 V1WireguardEntry,
288 V1WireguardExit,
289}
290
291#[derive(Debug, Copy, Clone, Error)]
292#[error("provided unknown ticketbook type")]
293pub struct UnknownTicketType;
294
295impl TicketType {
296 pub fn to_repr(&self) -> TicketTypeRepr {
297 (*self).into()
298 }
299
300 pub fn encode(&self) -> EncodedTicketType {
301 self.to_repr() as EncodedTicketType
302 }
303
304 pub fn try_from_encoded(val: EncodedTicketType) -> Result<Self, UnknownTicketType> {
305 match val {
306 n if n == TicketTypeRepr::V1MixnetEntry as u8 => {
307 Ok(TicketTypeRepr::V1MixnetEntry.into())
308 }
309 n if n == TicketTypeRepr::V1MixnetExit as u8 => Ok(TicketTypeRepr::V1MixnetExit.into()),
310 n if n == TicketTypeRepr::V1WireguardEntry as u8 => {
311 Ok(TicketTypeRepr::V1WireguardEntry.into())
312 }
313 n if n == TicketTypeRepr::V1WireguardExit as u8 => {
314 Ok(TicketTypeRepr::V1WireguardExit.into())
315 }
316 _ => Err(UnknownTicketType),
317 }
318 }
319
320 pub fn exposed_iter() -> impl Iterator<Item = TicketType> {
321 TicketType::iter()
322 }
323}
324
325impl From<TicketType> for TicketTypeRepr {
326 fn from(value: TicketType) -> Self {
327 match value {
328 TicketType::V1MixnetEntry => TicketTypeRepr::V1MixnetEntry,
329 TicketType::V1MixnetExit => TicketTypeRepr::V1MixnetExit,
330 TicketType::V1WireguardEntry => TicketTypeRepr::V1WireguardEntry,
331 TicketType::V1WireguardExit => TicketTypeRepr::V1WireguardExit,
332 }
333 }
334}
335
336impl From<TicketTypeRepr> for TicketType {
337 fn from(value: TicketTypeRepr) -> Self {
338 match value {
339 TicketTypeRepr::V1MixnetEntry => TicketType::V1MixnetEntry,
340 TicketTypeRepr::V1MixnetExit => TicketType::V1MixnetExit,
341 TicketTypeRepr::V1WireguardEntry => TicketType::V1WireguardEntry,
342 TicketTypeRepr::V1WireguardExit => TicketType::V1WireguardExit,
343 }
344 }
345}
346
347#[derive(Clone)]
348pub struct ClientTicket {
349 pub spending_data: CredentialSpendingData,
350 pub ticket_id: i64,
351}
352
353impl ClientTicket {
354 pub fn new(spending_data: CredentialSpendingData, ticket_id: i64) -> Self {
355 ClientTicket {
356 spending_data,
357 ticket_id,
358 }
359 }
360}
361
362#[derive(Debug, Clone, Copy)]
363pub struct AvailableBandwidth {
364 pub bytes: i64,
365 pub expiration: OffsetDateTime,
366}
367
368impl AvailableBandwidth {
369 pub fn expired(&self) -> bool {
370 self.expiration < ecash_today()
371 }
372}
373
374impl Default for AvailableBandwidth {
375 fn default() -> Self {
376 Self {
377 bytes: 0,
378 expiration: OffsetDateTime::UNIX_EPOCH,
379 }
380 }
381}
382
383#[derive(Debug, Copy, Clone)]
384pub struct Bandwidth {
385 value: u64,
386}
387
388impl Bandwidth {
389 pub const fn new_unchecked(value: u64) -> Bandwidth {
390 Bandwidth { value }
391 }
392
393 pub fn ticket_amount(typ: TicketTypeRepr) -> Self {
394 Bandwidth {
395 value: typ.bandwidth_value(),
396 }
397 }
398
399 pub fn value(&self) -> u64 {
400 self.value
401 }
402}