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