1use crate::signable::SignedMessage;
5use cosmrs::AccountId;
6use nym_coconut_dkg_common::types::EpochId;
7use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
8use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
9use nym_compact_ecash::utils::try_deserialize_g1_projective;
10use nym_compact_ecash::{Bytable, G1Projective};
11use nym_credentials_interface::TicketType;
12use nym_credentials_interface::{
13 BlindedSignature, CompactEcashError, CredentialSpendingData, PublicKeyUser,
14 VerificationKeyAuth, WithdrawalRequest,
15};
16use nym_crypto::asymmetric::ed25519;
17use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof};
18use serde::{Deserialize, Serialize};
19use sha2::Digest;
20use std::collections::BTreeMap;
21use std::ops::Deref;
22use thiserror::Error;
23use time::{Date, OffsetDateTime};
24use utoipa::ToSchema;
25
26#[derive(Serialize, Deserialize, Clone, ToSchema)]
27pub struct VerifyEcashTicketBody {
28 #[schema(value_type = String)]
30 pub credential: CredentialSpendingData,
31
32 #[schema(value_type = String)]
34 pub gateway_cosmos_addr: AccountId,
35}
36
37#[derive(Serialize, Deserialize, Clone, ToSchema)]
38pub struct VerifyEcashCredentialBody {
39 #[schema(value_type = openapi_schema::CredentialSpendingData)]
41 pub credential: CredentialSpendingData,
42
43 #[schema(value_type = String)]
45 pub gateway_cosmos_addr: AccountId,
46
47 pub proposal_id: u64,
49}
50
51impl VerifyEcashCredentialBody {
52 pub fn new(
53 credential: CredentialSpendingData,
54 gateway_cosmos_addr: AccountId,
55 proposal_id: u64,
56 ) -> VerifyEcashCredentialBody {
57 VerifyEcashCredentialBody {
58 credential,
59 gateway_cosmos_addr,
60 proposal_id,
61 }
62 }
63}
64
65#[derive(ToSchema)]
67pub enum EcashTicketVerificationResult {
68 Ok(()),
69 EcashTicketVerificationRejection,
70}
71
72#[derive(Debug, Serialize, Deserialize, ToSchema)]
73pub struct EcashTicketVerificationResponse {
74 #[schema(value_type = EcashTicketVerificationResult)]
75 pub verified: Result<(), EcashTicketVerificationRejection>,
76}
77
78impl EcashTicketVerificationResponse {
79 pub fn reject(reason: EcashTicketVerificationRejection) -> Self {
80 EcashTicketVerificationResponse {
81 verified: Err(reason),
82 }
83 }
84}
85
86#[derive(Debug, Error, Serialize, Deserialize, ToSchema)]
87pub enum EcashTicketVerificationRejection {
88 #[error("invalid ticket spent date. expected either today's ({today}) or yesterday's* ({yesterday}) date but got {received} instead\n*assuming it's before 1AM UTC"
89 )]
90 InvalidSpentDate {
91 #[serde(with = "crate::helpers::date_serde")]
92 #[schema(value_type = String, example = "1970-01-01")]
93 today: Date,
94 #[serde(with = "crate::helpers::date_serde")]
95 #[schema(value_type = String, example = "1970-01-01")]
96 yesterday: Date,
97 #[serde(with = "crate::helpers::date_serde")]
98 #[schema(value_type = String, example = "1970-01-01")]
99 received: Date,
100 },
101
102 #[error("this ticket has already been received before")]
103 ReplayedTicket,
104
105 #[error("this ticket has already been spent before")]
106 DoubleSpend,
107
108 #[error("failed to verify the provided ticket")]
109 InvalidTicket,
110
111 #[error(
112 "the received payment contained more than a single ticket. that's currently not supported"
113 )]
114 MultipleTickets,
115}
116
117#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
119pub struct BlindSignRequestBody {
120 #[schema(value_type = openapi_schema::WithdrawalRequest)]
121 pub inner_sign_request: WithdrawalRequest,
122
123 pub deposit_id: u32,
125
126 #[schema(value_type = String)]
128 pub signature: ed25519::Signature,
129
130 #[schema(value_type = openapi_schema::PublicKeyUser)]
131 pub ecash_pubkey: PublicKeyUser,
132
133 #[serde(with = "crate::helpers::date_serde")]
134 #[schema(value_type = String, example = "1970-01-01")]
135 pub expiration_date: Date,
136
137 #[schema(value_type = String)]
138 pub ticketbook_type: TicketType,
139}
140
141impl BlindSignRequestBody {
142 pub fn new(
143 inner_sign_request: WithdrawalRequest,
144 deposit_id: u32,
145 signature: ed25519::Signature,
146 ecash_pubkey: PublicKeyUser,
147 expiration_date: Date,
148 ticketbook_type: TicketType,
149 ) -> BlindSignRequestBody {
150 BlindSignRequestBody {
151 inner_sign_request,
152 deposit_id,
153 signature,
154 ecash_pubkey,
155 expiration_date,
156 ticketbook_type,
157 }
158 }
159
160 pub fn encode_join_commitments(&self) -> Vec<u8> {
161 self.inner_sign_request
162 .get_private_attributes_commitments()
163 .iter()
164 .flat_map(|c| c.to_byte_vec())
165 .collect()
166 }
167
168 pub fn try_decode_joined_commitments(
169 joined: &[u8],
170 ) -> Result<Vec<G1Projective>, CompactEcashError> {
171 if !joined.len().is_multiple_of(48) {
172 return Err(CompactEcashError::DeserializationLengthMismatch {
174 type_name: "joined commitments".to_string(),
175 expected: (joined.len() / 48) * 48,
176 actual: joined.len(),
177 });
178 };
179 let mut commitments = Vec::new();
180
181 for chunk in joined.chunks_exact(48) {
182 #[allow(clippy::unwrap_used)]
184 let bytes: &[u8; 48] = chunk.try_into().unwrap();
185 commitments.push(try_deserialize_g1_projective(bytes)?);
186 }
187
188 Ok(commitments)
189 }
190}
191
192#[derive(Debug, Serialize, Deserialize, ToSchema)]
193pub struct BlindedSignatureResponse {
194 #[schema(value_type = openapi_schema::BlindedSignature)]
195 pub blinded_signature: BlindedSignature,
196}
197
198impl BlindedSignatureResponse {
199 pub fn new(blinded_signature: BlindedSignature) -> BlindedSignatureResponse {
200 BlindedSignatureResponse { blinded_signature }
201 }
202
203 pub fn to_base58_string(&self) -> String {
204 bs58::encode(&self.to_bytes()).into_string()
205 }
206
207 pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, CompactEcashError> {
208 let bytes = bs58::decode(val).into_vec()?;
209 Self::from_bytes(&bytes)
210 }
211
212 pub fn to_bytes(&self) -> Vec<u8> {
213 self.blinded_signature.to_bytes().to_vec()
214 }
215
216 pub fn from_bytes(bytes: &[u8]) -> Result<Self, CompactEcashError> {
217 Ok(BlindedSignatureResponse {
218 blinded_signature: BlindedSignature::from_bytes(bytes)?,
219 })
220 }
221}
222
223#[derive(Serialize, Deserialize, ToSchema)]
224pub struct MasterVerificationKeyResponse {
225 #[schema(value_type = openapi_schema::VerificationKeyAuth)]
226 pub key: VerificationKeyAuth,
227}
228
229impl MasterVerificationKeyResponse {
230 pub fn new(key: VerificationKeyAuth) -> MasterVerificationKeyResponse {
231 MasterVerificationKeyResponse { key }
232 }
233}
234
235#[derive(Serialize, Deserialize, ToSchema)]
236pub struct VerificationKeyResponse {
237 #[schema(value_type = openapi_schema::VerificationKeyAuth)]
238 pub key: VerificationKeyAuth,
239}
240
241impl VerificationKeyResponse {
242 pub fn new(key: VerificationKeyAuth) -> VerificationKeyResponse {
243 VerificationKeyResponse { key }
244 }
245}
246
247#[derive(Serialize, Deserialize)]
248pub struct CosmosAddressResponse {
249 pub addr: AccountId,
250}
251
252impl CosmosAddressResponse {
253 pub fn new(addr: AccountId) -> CosmosAddressResponse {
254 CosmosAddressResponse { addr }
255 }
256}
257
258#[derive(Serialize, Deserialize, ToSchema)]
259pub struct PartialExpirationDateSignatureResponse {
260 pub epoch_id: u64,
261
262 #[serde(with = "crate::helpers::date_serde")]
263 #[schema(value_type = String, example = "1970-01-01")]
264 pub expiration_date: Date,
265 #[schema(value_type = openapi_schema::AnnotatedExpirationDateSignature)]
266 pub signatures: Vec<AnnotatedExpirationDateSignature>,
267}
268
269#[derive(Serialize, Deserialize, ToSchema)]
270pub struct PartialCoinIndicesSignatureResponse {
271 pub epoch_id: u64,
272 #[schema(value_type = openapi_schema::AnnotatedCoinIndexSignature)]
273 pub signatures: Vec<AnnotatedCoinIndexSignature>,
274}
275
276#[derive(Serialize, Deserialize, ToSchema)]
277pub struct AggregatedExpirationDateSignatureResponse {
278 pub epoch_id: u64,
279
280 #[serde(with = "crate::helpers::date_serde")]
281 #[schema(value_type = String, example = "1970-01-01")]
282 pub expiration_date: Date,
283
284 #[schema(value_type = Vec<openapi_schema::AnnotatedExpirationDateSignature>)]
285 pub signatures: Vec<AnnotatedExpirationDateSignature>,
286}
287
288#[derive(Serialize, Deserialize, ToSchema)]
289pub struct AggregatedCoinIndicesSignatureResponse {
290 pub epoch_id: u64,
291 #[schema(value_type = openapi_schema::Signature)]
292 pub signatures: Vec<AnnotatedCoinIndexSignature>,
293}
294
295pub mod openapi_schema {
297 #![allow(dead_code)]
298 use nym_compact_ecash::common_types::{G2Projective, Scalar};
299
300 use super::*;
301
302 #[derive(ToSchema)]
303 pub struct AnnotatedExpirationDateSignature {
304 pub signature: Signature,
305 pub expiration_timestamp: u32,
306 pub spending_timestamp: u32,
307 }
308
309 #[derive(ToSchema)]
310 pub struct AnnotatedCoinIndexSignature {
311 pub signature: Signature,
312 pub index: u64,
313 }
314
315 #[derive(ToSchema)]
316 pub struct Signature {
317 #[schema(value_type = String)]
318 pub(crate) h: G1Projective,
319 #[schema(value_type = String)]
320 pub(crate) s: G1Projective,
321 }
322
323 #[derive(ToSchema)]
324 pub struct PublicKeyUser {
325 #[schema(value_type = String)]
326 pub(crate) pk: G1Projective,
327 }
328
329 #[derive(ToSchema)]
330 pub struct BlindedSignature {
331 #[schema(value_type = String)]
332 pub h: G1Projective,
333 #[schema(value_type = String)]
334 pub c: G1Projective,
335 }
336
337 #[derive(ToSchema)]
338 pub struct VerificationKeyAuth {
339 #[schema(value_type = String)]
340 pub(crate) alpha: G2Projective,
341 #[schema(value_type = Vec<String>)]
342 pub(crate) beta_g1: Vec<G1Projective>,
343 #[schema(value_type = Vec<String>)]
344 pub(crate) beta_g2: Vec<G2Projective>,
345 }
346
347 #[derive(ToSchema)]
348 pub struct WithdrawalRequest {
349 #[schema(value_type = String)]
350 joined_commitment_hash: G1Projective,
351 #[schema(value_type = String)]
352 joined_commitment: G1Projective,
353 #[schema(value_type = Vec<String>)]
354 private_attributes_commitments: Vec<G1Projective>,
355 zk_proof: WithdrawalReqProof,
356 }
357
358 #[derive(ToSchema)]
359 pub struct WithdrawalReqProof {
360 #[schema(value_type = String)]
361 challenge: Scalar,
362 #[schema(value_type = String)]
363 response_opening: Scalar,
364 #[schema(value_type = Vec<String>)]
365 response_openings: Vec<Scalar>,
366 #[schema(value_type = Vec<String>)]
367 response_attributes: Vec<Scalar>,
368 }
369
370 #[derive(ToSchema)]
371 pub struct CredentialSpendingData {
372 pub payment: Payment,
373
374 #[schema(value_type = [u8; 72], format = Binary)]
375 pub pay_info: PayInfo,
376
377 pub spend_date: Date,
378
379 pub epoch_id: u64,
382 }
383
384 #[derive(ToSchema)]
385 pub struct Payment {
386 #[schema(value_type = String)]
387 pub kappa: G2Projective,
388 #[schema(value_type = String)]
389 pub kappa_e: G2Projective,
390 pub sig: Signature,
391
392 pub sig_exp: Signature,
393 #[schema(value_type = Vec<String>)]
394 pub kappa_k: Vec<G2Projective>,
395 pub omega: Vec<Signature>,
396 #[schema(value_type = Vec<String>)]
397 pub ss: Vec<G1Projective>,
398 #[schema(value_type = Vec<String>)]
399 pub tt: Vec<G1Projective>,
400 #[schema(value_type = Vec<String>)]
401 pub aa: Vec<G1Projective>,
402 pub spend_value: u64,
403 #[schema(value_type = String)]
404 pub cc: G1Projective,
405 pub t_type: u8,
406 pub zk_proof: SpendProof,
407 }
408
409 #[derive(PartialEq, Eq, Debug, Clone, Copy, ToSchema)]
410 pub struct PayInfo {
411 #[schema(content_encoding = "base16")]
412 pub pay_info_bytes: [u8; 72],
413 }
414
415 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
416 pub struct SpendProof {
417 #[schema(value_type = String)]
418 challenge: Scalar,
419 #[schema(value_type = String)]
420 response_r: Scalar,
421 #[schema(value_type = String)]
422 response_r_e: Scalar,
423 #[schema(value_type = Vec<String>)]
424 responses_r_k: Vec<Scalar>,
425 #[schema(value_type = Vec<String>)]
426 responses_l: Vec<Scalar>,
427 #[schema(value_type = Vec<String>)]
428 responses_o_a: Vec<Scalar>,
429 #[schema(value_type = String)]
430 response_o_c: Scalar,
431 #[schema(value_type = Vec<String>)]
432 responses_mu: Vec<Scalar>,
433 #[schema(value_type = Vec<String>)]
434 responses_o_mu: Vec<Scalar>,
435 #[schema(value_type = Vec<String>)]
436 responses_attributes: Vec<Scalar>,
437 }
438}
439
440#[derive(Clone, Serialize, Deserialize, Debug)]
441pub struct Pagination<T> {
442 pub last_key: Option<T>,
446
447 pub limit: Option<u32>,
450}
451
452#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
453pub struct SerialNumberWrapper(#[serde(with = "nym_serde_helpers::bs58")] Vec<u8>);
454
455impl Deref for SerialNumberWrapper {
456 type Target = Vec<u8>;
457 fn deref(&self) -> &Self::Target {
458 &self.0
459 }
460}
461
462impl AsRef<[u8]> for SerialNumberWrapper {
463 fn as_ref(&self) -> &[u8] {
464 self.0.as_ref()
465 }
466}
467
468impl From<Vec<u8>> for SerialNumberWrapper {
469 fn from(value: Vec<u8>) -> Self {
470 SerialNumberWrapper(value)
471 }
472}
473
474#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
475pub struct BatchRedeemTicketsBody {
476 #[serde(with = "nym_serde_helpers::bs58")]
477 pub digest: Vec<u8>,
478 pub included_serial_numbers: Vec<SerialNumberWrapper>,
479 pub proposal_id: u64,
480 #[schema(value_type = String)]
481 pub gateway_cosmos_addr: AccountId,
482}
483
484impl BatchRedeemTicketsBody {
485 pub fn make_digest<I, T>(serial_numbers: I) -> Vec<u8>
486 where
487 I: Iterator<Item = T>,
488 T: AsRef<[u8]>,
489 {
490 let mut hasher = sha2::Sha256::new();
491 for sn in serial_numbers {
492 hasher.update(sn)
493 }
494 hasher.finalize().to_vec()
495 }
496
497 pub fn new(
498 digest: Vec<u8>,
499 proposal_id: u64,
500 serial_numbers: Vec<impl Into<SerialNumberWrapper>>,
501 redeemer: AccountId,
502 ) -> Self {
503 BatchRedeemTicketsBody {
504 digest,
505 included_serial_numbers: serial_numbers.into_iter().map(Into::into).collect(),
506 proposal_id,
507 gateway_cosmos_addr: redeemer,
508 }
509 }
510
511 pub fn verify_digest(&self) -> bool {
512 Self::make_digest(self.included_serial_numbers.iter()) == self.digest
513 }
514}
515
516#[derive(Debug, Serialize, Deserialize, ToSchema)]
517pub struct EcashBatchTicketRedemptionResponse {
518 pub proposal_accepted: bool,
519}
520
521#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
522#[serde(rename_all = "camelCase")]
523pub struct SpentCredentialsResponse {
524 #[serde(with = "nym_serde_helpers::base64")]
525 #[schema(value_type = String)]
526 pub bitmap: Vec<u8>,
527}
528
529impl SpentCredentialsResponse {
530 pub fn new(bitmap: Vec<u8>) -> SpentCredentialsResponse {
531 SpentCredentialsResponse { bitmap }
532 }
533}
534
535pub type DepositId = u32;
536
537#[derive(Clone, Serialize, Deserialize, Debug, ToSchema, PartialEq, Eq)]
538#[serde(rename_all = "camelCase")]
539pub struct CommitedDeposit {
540 #[schema(value_type = u32)]
541 pub deposit_id: DepositId,
542 pub merkle_index: usize,
543}
544
545pub type IssuedTicketbooksDataRequest = SignedMessage<IssuedTicketbooksDataRequestBody>;
546pub type IssuedTicketbooksChallengeCommitmentRequest =
547 SignedMessage<IssuedTicketbooksChallengeCommitmentRequestBody>;
548
549pub type IssuedTicketbooksChallengeCommitmentResponse =
550 SignedMessage<IssuedTicketbooksChallengeCommitmentResponseBody>;
551pub type IssuedTicketbooksForResponse = SignedMessage<IssuedTicketbooksForResponseBody>;
552pub type IssuedTicketbooksDataResponse = SignedMessage<IssuedTicketbooksDataResponseBody>;
553
554mod maybe_merkle_root_serde {
555 use super::*;
556 use serde::de::Error;
557 use serde::{Deserializer, Serialize, Serializer};
558
559 pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Option<[u8; 32]>, D::Error>
560 where
561 D: Deserializer<'de>,
562 {
563 let inner = Option::<String>::deserialize(deserializer)?;
564 match inner {
565 Some(inner) => {
566 let decoded = hex::decode(inner).map_err(Error::custom)?;
567 let arr: [u8; 32] = decoded.as_slice().try_into().map_err(Error::custom)?;
568 Ok(Some(arr))
569 }
570 None => Ok(None),
571 }
572 }
573
574 pub(crate) fn serialize<S>(
575 maybe_root: &Option<[u8; 32]>,
576 serializer: S,
577 ) -> Result<S::Ok, S::Error>
578 where
579 S: Serializer,
580 {
581 maybe_root.map(hex::encode).serialize(serializer)
582 }
583}
584
585#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
586#[serde(rename_all = "camelCase")]
587pub struct IssuedTicketbooksForResponseBody {
588 #[serde(with = "crate::helpers::date_serde")]
589 #[schema(value_type = String, example = "1970-01-01")]
590 pub expiration_date: Date,
591 pub deposits: Vec<CommitedDeposit>,
592 #[serde(with = "maybe_merkle_root_serde")]
593 pub merkle_root: Option<[u8; 32]>,
594}
595
596impl IssuedTicketbooksForResponseBody {
597 pub fn merkle_root_hex(&self) -> Option<String> {
598 self.merkle_root.map(hex::encode)
599 }
600}
601
602#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
603#[serde(rename_all = "camelCase")]
604pub struct IssuedTicketbooksChallengeCommitmentRequestBody {
605 #[serde(with = "crate::helpers::date_serde")]
606 #[schema(value_type = String, example = "1970-01-01")]
607 pub expiration_date: Date,
608 #[schema(value_type = Vec<u32>)]
609 pub deposits: Vec<DepositId>,
610}
611
612impl IssuedTicketbooksChallengeCommitmentRequestBody {
613 pub fn new(
614 expiration_date: Date,
615 deposits: Vec<DepositId>,
616 ) -> IssuedTicketbooksChallengeCommitmentRequestBody {
617 IssuedTicketbooksChallengeCommitmentRequestBody {
618 expiration_date,
619 deposits,
620 }
621 }
622}
623
624#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
625#[serde(rename_all = "camelCase")]
626pub struct IssuedTicketbooksChallengeCommitmentResponseBody {
627 #[serde(with = "crate::helpers::date_serde")]
628 #[schema(value_type = String, example = "1970-01-01")]
629 pub expiration_date: Date,
630
631 pub original_request: IssuedTicketbooksChallengeCommitmentRequest,
635
636 pub max_data_response_size: usize,
639
640 pub merkle_proof: IssuedTicketbooksFullMerkleProof,
641}
642
643#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq)]
644#[serde(rename_all = "camelCase")]
645pub struct IssuedTicketbooksDataRequestBody {
646 #[serde(with = "crate::helpers::date_serde")]
647 #[schema(value_type = String, example = "1970-01-01")]
648 pub expiration_date: Date,
649 #[schema(value_type = Vec<u32>)]
650 pub deposits: Vec<DepositId>,
651}
652
653impl IssuedTicketbooksDataRequestBody {
654 pub fn new(
655 expiration_date: Date,
656 deposits: Vec<DepositId>,
657 ) -> IssuedTicketbooksDataRequestBody {
658 IssuedTicketbooksDataRequestBody {
659 expiration_date,
660 deposits,
661 }
662 }
663}
664
665#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
666#[serde(rename_all = "camelCase")]
667pub struct IssuedTicketbooksDataResponseBody {
668 #[serde(with = "crate::helpers::date_serde")]
669 #[schema(value_type = String, example = "1970-01-01")]
670 pub expiration_date: Date,
671
672 #[schema(value_type = BTreeMap<u32, IssuedTicketbook>)]
673 pub partial_ticketbooks: BTreeMap<DepositId, IssuedTicketbook>,
674
675 pub original_request: IssuedTicketbooksDataRequest,
679}
680
681impl IssuedTicketbooksDataResponseBody {
682 pub fn try_get_partial_credential(
684 issued: &IssuedTicketbook,
685 ) -> Result<BlindedSignature, CompactEcashError> {
686 BlindedSignature::from_bytes(&issued.blinded_partial_credential)
687 }
688
689 pub fn try_get_private_attributes_commitments(
690 issued: &IssuedTicketbook,
691 ) -> Result<Vec<G1Projective>, CompactEcashError> {
692 BlindSignRequestBody::try_decode_joined_commitments(
693 &issued.joined_encoded_private_attributes_commitments,
694 )
695 }
696}
697
698#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
699#[serde(rename_all = "camelCase")]
700pub struct IssuedTicketbooksCountResponse {
701 pub issued: Vec<IssuedTicketbooksCount>,
702}
703
704#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
705#[serde(rename_all = "camelCase")]
706pub struct IssuedTicketbooksCount {
707 #[serde(with = "crate::helpers::date_serde")]
708 #[schema(value_type = String, example = "1970-01-01")]
709 pub issuance_date: Date,
710
711 #[serde(with = "crate::helpers::date_serde")]
712 #[schema(value_type = String, example = "1970-01-01")]
713 pub expiration_date: Date,
714
715 pub count: u32,
716}
717
718#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
719#[serde(rename_all = "camelCase")]
720pub struct IssuedTicketbooksOnCountResponse {
721 #[serde(with = "crate::helpers::date_serde")]
722 #[schema(value_type = String, example = "1970-01-01")]
723 pub issuance_date: Date,
724
725 pub total: usize,
726
727 pub issued: Vec<IssuedTicketbooksOnCount>,
728}
729
730#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
731#[serde(rename_all = "camelCase")]
732pub struct IssuedTicketbooksForCountResponse {
733 #[serde(with = "crate::helpers::date_serde")]
734 #[schema(value_type = String, example = "1970-01-01")]
735 pub expiration_date: Date,
736
737 pub total: usize,
738
739 pub issued: Vec<IssuedTicketbooksForCount>,
740}
741
742#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
743#[serde(rename_all = "camelCase")]
744pub struct IssuedTicketbooksOnCount {
745 #[serde(with = "crate::helpers::date_serde")]
746 #[schema(value_type = String, example = "1970-01-01")]
747 pub expiration_date: Date,
748
749 pub count: u32,
750}
751
752#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
753#[serde(rename_all = "camelCase")]
754pub struct IssuedTicketbooksForCount {
755 #[serde(with = "crate::helpers::date_serde")]
756 #[schema(value_type = String, example = "1970-01-01")]
757 pub issuance_date: Date,
758
759 pub count: u32,
760}
761
762pub type EcashSignerStatusResponse = SignedMessage<EcashSignerStatusResponseBody>;
763
764#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)]
765#[serde(rename_all = "camelCase")]
766pub struct EcashSignerStatusResponseBody {
768 #[serde(with = "time::serde::rfc3339")]
769 #[schema(value_type = String)]
770 pub current_time: OffsetDateTime,
771
772 pub dkg_ecash_epoch_id: EpochId,
774
775 pub signer_disabled: bool,
777
778 pub is_ecash_signer: bool,
780
781 pub has_signing_keys: bool,
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790 use nym_compact_ecash::scheme::keygen::KeyPairUser;
791 use nym_compact_ecash::withdrawal_request;
792 use nym_ecash_time::{ecash_today_date, EcashTime};
793 use rand_chacha::rand_core::SeedableRng;
794 use rand_chacha::ChaCha20Rng;
795
796 pub fn test_rng() -> ChaCha20Rng {
797 let dummy_seed = [42u8; 32];
798 ChaCha20Rng::from_seed(dummy_seed)
799 }
800
801 #[test]
804 fn aggregated_expiration_date_signature_responses_deserialises_correctly() {
805 let raw = r#"{"epoch_id":0,"expiration_date":"2024-08-03","signatures":[{"signature":{"h":"9379384af5236bffd7d6c533782d74863f892cf6e0fc8b4b64647e8bd5bccfe63df639a4a556d21826ab7ef54e4adafe","s":"83124c907935eba10c791cbc2cedf8714b68c6a266efead7bc4897c3b9652680bd151493689577ac790b9ccef1112f9b"},"expiration_timestamp":1722643200,"spending_timestamp":1720137600},{"signature":{"h":"817b9fa0eef617ce751963d9c853df56fdf38a643b9ef8649b658d7ec3da61d49d9279f22509c66dd07051132a418c62","s":"af9fd22c2fde4cb36a0ee021c5a756f97f871ea404d639af1be845b277bed583f715d228d3556e79d15eba23a5b0d5e0"},"expiration_timestamp":1722643200,"spending_timestamp":1720224000},{"signature":{"h":"89f04f5ae1a1532551c2e17166775eac3c5ebba0198eff2919073efd733d6e17e0a6496e31061b0797f51b69d0fa17fc","s":"b752cdb2f4d8a4254d24d5773a8490de1ad84207cbe693638d1fdcbffd8426be96fae3586f19c570beb005bb6bf27b9b"},"expiration_timestamp":1722643200,"spending_timestamp":1720310400},{"signature":{"h":"b1072da2612a6ebc0c02c7f673898dfea88eeeeb076a9dd2ab981d47a8176b87804ca6090cfb5b0c128f06adc6fdf0de","s":"8746a62f6333c3948f1fbb05543f0095cd44f610926c71f339511bf3bb97d98fd019d44c4a4dfc6710a9d5c718c5bfc2"},"expiration_timestamp":1722643200,"spending_timestamp":1720396800},{"signature":{"h":"8d266e7e56af7364d79c4190b24d5f0a601d7771c359815f642115123f5418d0304a46226d70e873b10a051f15178630","s":"a11d67c3293b224abb1256050031b32236484dc0f0fe66a9363d322e6b8c9b24719f4d4da0584d6106eb0f59fa6bb3c8"},"expiration_timestamp":1722643200,"spending_timestamp":1720483200},{"signature":{"h":"8ee31ef821949be316528b7b75b81baa6d0cf2846b615ff6d34e478f25746a07287ae6944279550879735740803c7922","s":"aa26b0df4445d8a10a226f1c70908451d211f787458c0743ba891c46bbbbde3bc59777f4edb9cabdd5fd181d772cfa43"},"expiration_timestamp":1722643200,"spending_timestamp":1720569600},{"signature":{"h":"b8fbacde3f9d507fb5bf6d5f96a33dd5c9c40cc9a062e92f9cdc6a2bddf8cbd873ffcc5112de816f98fbf47b74330abb","s":"a791aee82bdcf3cc4203ce657fbd2c3e9c2f0decd032c4ab91713d3cfd0ac57ebb9bb20e133d9a8a4ba1b8692a3b2706"},"expiration_timestamp":1722643200,"spending_timestamp":1720656000},{"signature":{"h":"b9ed48b47c1a1e6a1ae847ffaad01e0b7d22b2c9f9075beedf4924958ca044579f1e9ca18e268e2a0ef743c214934692","s":"a3e9191d0c3651c656a376e99f3d8c121a742ba55522765479ce866df8aaecc0281ae08003439debf7977f23e450a45f"},"expiration_timestamp":1722643200,"spending_timestamp":1720742400},{"signature":{"h":"a6f1fbdcc28953f318f067cde6e3d6ad0362fd46a501a74492be0884308cf306a7f30006181710f8883a380a8d4885f7","s":"9260d035ea97fc1b93d2db3802678005c95a4acdb0ce0c947cb0b9866ba4028a3b833d742f02ed0227ed33cee4ed17b4"},"expiration_timestamp":1722643200,"spending_timestamp":1720828800},{"signature":{"h":"95109fd35f7808084f224aa1fde645728c0c163751fe24d97e0002a7c38dc5a3a592dcb770893ddd25fc67f84e29e0a1","s":"b85279c4566aa365d208b57817ce96f1a1245895fbe25363202da89559fc454a2cfb09972fc3d1c468b96e8bc11b424c"},"expiration_timestamp":1722643200,"spending_timestamp":1720915200},{"signature":{"h":"8f4b7f93871d8f995a248d31664b02e1fe7107d67512f248a8f550d477846ca9f9862a5d44f5da458c6b83f4d8e62494","s":"b87e46a5d25de574eb4e16cf24199b2e0a658e0c1ef965b632165ed2d695637df76d373484dc93cec30e2f0a29ac91f8"},"expiration_timestamp":1722643200,"spending_timestamp":1721001600},{"signature":{"h":"b740491d3527f8fa0b8bed76d234d0737c09a049f653bb1e8a552b29f568aabd077604b08e7337d44f551fbda565e52e","s":"a08634a2560b3a79c8b56ff5387ef76c397c277810d78cc6dfac2161673df761d0ba61e6075e8a4781ec1b2ba7715b93"},"expiration_timestamp":1722643200,"spending_timestamp":1721088000},{"signature":{"h":"8d4b92fe48257d2574223fbe12afcdabd00db976e8d20faaddb5fb570373edb2a15af4ad9a7c10ceda3bb1304b0210aa","s":"8f8592c45a1cca753e5d0b04ffc2947fd27c7716f2751e632f2bc1bf0e73b179ce8080325951beb0886c5cf0fca79372"},"expiration_timestamp":1722643200,"spending_timestamp":1721174400},{"signature":{"h":"891d412e631041d07115cacf847575462d2389402a0e243a2f106b5e1c8f1431fa8e1aedc55cf943aef9fa8125d105a1","s":"ad2038ab95b823a8119bcc473be22101c4b58fd44ad375235c3664341fc617fe129702aec8765d8dc32ac845aec58a6a"},"expiration_timestamp":1722643200,"spending_timestamp":1721260800},{"signature":{"h":"84b97558e7889af167ba01ed608418ddabfc7fa71ba8f9cb045027f994fe9b5f9bb0ccc15822d1c73a37a48b041cd759","s":"adc370bc182ff9c7057591a3d4afcb21c714093e480374a07608c9f9d2baeff8c14fc5de3f0f457ea340e445ecc28487"},"expiration_timestamp":1722643200,"spending_timestamp":1721347200},{"signature":{"h":"b29b245acf66082c0f4e52cbf9db00ecb9cee070ddb1f5b56a9e85e2563dae04186ed03b7b590e0a9f4b4304ae1cd370","s":"8196445495ed1ee098bbe6083ece3522d4669e23dc4f3e2226e0e63294f1bc791e66380deedd21e5679e3019e93c7727"},"expiration_timestamp":1722643200,"spending_timestamp":1721433600},{"signature":{"h":"9750b9bdf1e86e8e4e6de44392f2160059da64838357a16e64155a5b7989fe7958ae95501e7c1f4039cf8b372ac3b214","s":"80707aa94af054fedcb4947809dab7a9317e303a12b7fb30858115c95bf8e46fec7ea9b0ce5097b75d8f7905c205f757"},"expiration_timestamp":1722643200,"spending_timestamp":1721520000},{"signature":{"h":"828be64b5cd147b5c43e68a2084cb77a762967d5621769136a0894c3d1ee50890412120d7c765cb3c835435d57ac4438","s":"956ca67f8f8591e72b8feeb53cbe52a0f69202313aa729c09760fb2a19b346fda1b7afd326822889fb228ca77a503072"},"expiration_timestamp":1722643200,"spending_timestamp":1721606400},{"signature":{"h":"83ac2c778f0b847af8a4bcc37bd5c2734544fe4427ee7ec5c4690738005cc805864fd5944266417ba2795d7333bd8c4a","s":"88936b7f61a0febfbedc8c0c40dad8f9c4eacb41d276ba00b7d03602ca69614faacca0d945713a272e9748dd6dd71f81"},"expiration_timestamp":1722643200,"spending_timestamp":1721692800},{"signature":{"h":"911c126da663478023efd1157586681937ac7f6c5a33b34a102b3611a63780b244ecdf05ecad09701e65ad20c5246f6a","s":"a1188f3b6ced776890b038e2d07b6af67f1a2c08f54a32d48dbfce61be3295e8ea910b6197b111a6ff16dd668eeda9f5"},"expiration_timestamp":1722643200,"spending_timestamp":1721779200},{"signature":{"h":"8c03c749b424e8fb3f26d51e45d21a7e9f59e6836f89f4a1f27b952e2bc8c038f3372f593dfb3bd284feb36918c2f194","s":"b20d3fd194f73142f0dfc68981519b76aa013e6743b2bdf63d41f3aa1e754d1f74c46012adf0332723f861e5aaa69dba"},"expiration_timestamp":1722643200,"spending_timestamp":1721865600},{"signature":{"h":"872de60a19f3ece61a70519b1f7a63476d13a20721ff00ae923a984bd710703af55112092e209970f074a8950b9e246e","s":"854906bbce5a0c2e47fa829441a03f5c3ac9357dc6d107547198f17505cd17852806e6d69b78264921a8c13c4a9fe50c"},"expiration_timestamp":1722643200,"spending_timestamp":1721952000},{"signature":{"h":"a2a57fd2b675fccccae34f9ff465f36638ee3809291c9b37b5830015a1a7c4d873528105f030a05efd16c6411672fe88","s":"b42f31e4ed05d3b026a62c5df97e04568c0c92d3fafaf95fa732ce69fbe7ee091ac025d04e3ce526b3474edaaf278398"},"expiration_timestamp":1722643200,"spending_timestamp":1722038400},{"signature":{"h":"b9be127e812602ac7a612897fd95e5869b360046993139590c82c2844654b6a5a72e9d74ed38eea6ee99ba0978382a3b","s":"b027db49bb9b0ec3931fc1cb4a6a739c968c45a59698d199056ebfcbeca7ddb363a123e55d05fbf5635d915be2a16ffb"},"expiration_timestamp":1722643200,"spending_timestamp":1722124800},{"signature":{"h":"8c40b2a5e17e1f9cdbe85eaaa419126374d35a255b8338474d442d0d40a4e10c3f2668f579155dfdad3fd403523683df","s":"a40cec4b00c9a975db6c307f25425f3a4d0e49eca2c05d5122630c54bd5d114f74e4abb78a7261752a9dfd4c020f8b91"},"expiration_timestamp":1722643200,"spending_timestamp":1722211200},{"signature":{"h":"b72821bc38167e7f422dcc669c19e0aeffb8247192c4bd50da1503b6aa28041bb5605598639bdb8b945dbc804fc852aa","s":"9443cad7c9d6194b601daf21cbc5fe3019d3350dd725f6d9e3ae366162f93e3b738779617f77ec146f0a05f9a40b602b"},"expiration_timestamp":1722643200,"spending_timestamp":1722297600},{"signature":{"h":"8fe36994ac7e92b5ad0869f2b1bfa3247aac27599459cce9d94cf84eeead8bba1ca4b294d16db3588a0eb9d9b28d4051","s":"8eae0ef0e966f9f473bd829f18e28cf9730447452040aaef114701f409ff237965f203f7394a7d61ca745a4b2f058d9b"},"expiration_timestamp":1722643200,"spending_timestamp":1722384000},{"signature":{"h":"a9f91a83a1db0c5609fcacd1290f1650e4e5ab98c3dd9f0b8fa1c54664c12ed17478ae6e598f0e900ace30addf1b0559","s":"801658058f8de9ba1c25518905196d7e4e0bc5e26a7a810c9dce3ac141f01c538c62808b4728ff683dee78670b2846f9"},"expiration_timestamp":1722643200,"spending_timestamp":1722470400},{"signature":{"h":"94ecd95c1d5ae03270079476aad6334dafdcf615157a6a34b05bb755a047bc2515e33a7e099f9fddc4eec3def8904cdf","s":"b59ff6ded76926a9ae97447ba00eac53d9aa0e1168218d990c5b97cc128a4f5a0c42f961f84cad8ad344630b2a15f3a9"},"expiration_timestamp":1722643200,"spending_timestamp":1722556800},{"signature":{"h":"ace778e70775d349c1e679e5ccef634a533738e7af8b51daa45ccbd920f976d15c4ba744d6ae8fdf711b84b20703cef6","s":"80581faceda2bc35b81240017e1ce8f121476c5ba4d75acddfc58ba6aaa2f12d626eaaab185548e6dd716b6e582740b8"},"expiration_timestamp":1722643200,"spending_timestamp":1722643200}]}"#;
806 let _: AggregatedExpirationDateSignatureResponse = serde_json::from_str(raw).unwrap();
807 }
808
809 #[test]
810 fn batch_redemption_body_roundtrip() {
811 let sn = vec![b"foomp".to_vec(), b"bar".to_vec()];
812 let gateway: AccountId = "n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf".parse().unwrap();
813 let digest = [42u8; 32].to_vec();
814
815 let req = BatchRedeemTicketsBody::new(digest, 69, sn, gateway);
816 let bytes = serde_json::to_vec(&req).unwrap();
817
818 let de: BatchRedeemTicketsBody = serde_json::from_slice(&bytes).unwrap();
819 assert_eq!(req, de);
820 }
821
822 #[test]
823 fn decoding_attribute_commitments() {
824 let mut rng = test_rng();
825 let keys = ed25519::KeyPair::new(&mut rng);
826 let dummy_sig = keys.private_key().sign("foomp");
827 let dummy_keypair = KeyPairUser::new();
828 let date = ecash_today_date();
829 let typ = TicketType::V1MixnetEntry;
830
831 let (inner, _) = withdrawal_request(
832 dummy_keypair.secret_key(),
833 date.ecash_unix_timestamp(),
834 typ.encode(),
835 )
836 .unwrap();
837
838 let dummy = BlindSignRequestBody {
839 inner_sign_request: inner,
840 deposit_id: 42,
841 signature: dummy_sig,
842 ecash_pubkey: dummy_keypair.public_key(),
843 expiration_date: date,
844 ticketbook_type: typ,
845 };
846
847 let good = dummy.encode_join_commitments();
848 let recovered = BlindSignRequestBody::try_decode_joined_commitments(&good).unwrap();
849 assert_eq!(
850 recovered,
851 dummy
852 .inner_sign_request
853 .get_private_attributes_commitments()
854 );
855
856 let mut bad1 = good.clone();
857 let mut bad2 = good.clone();
858 bad1.push(42);
859 bad2.pop().unwrap();
860
861 assert!(BlindSignRequestBody::try_decode_joined_commitments(&bad1).is_err());
862 assert!(BlindSignRequestBody::try_decode_joined_commitments(&bad2).is_err());
863 }
864}