1use crate::common_types::{Signature, SignerIndex};
5use crate::error::{CompactEcashError, Result};
6use crate::helpers::{date_scalar, type_scalar};
7use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness};
8use crate::scheme::coin_indices_signatures::CoinIndexSignature;
9use crate::scheme::expiration_date_signatures::{find_index, ExpirationDateSignature};
10use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth};
11use crate::scheme::setup::{GroupParameters, Parameters};
12use crate::traits::Bytable;
13use crate::utils::{
14 batch_verify_signatures, check_bilinear_pairing, hash_to_scalar, try_deserialize_scalar,
15};
16use crate::{constants, ecash_group_parameters};
17use crate::{Base58, EncodedDate, EncodedTicketType};
18use group::Curve;
19use nym_bls12_381_fork::{G1Projective, G2Prepared, G2Projective, Scalar};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use std::borrow::Borrow;
22use zeroize::{Zeroize, ZeroizeOnDrop};
23
24pub mod aggregation;
25pub mod coin_indices_signatures;
26pub mod expiration_date_signatures;
27pub mod identify;
28pub mod keygen;
29pub mod setup;
30pub mod withdrawal;
31
32#[derive(Debug, Clone, PartialEq, Zeroize, ZeroizeOnDrop)]
39pub struct PartialWallet {
40 #[zeroize(skip)]
41 sig: Signature,
42 v: Scalar,
43 idx: SignerIndex,
44 expiration_date: Scalar,
45 t_type: Scalar,
46}
47
48impl PartialWallet {
49 pub fn signature(&self) -> &Signature {
50 &self.sig
51 }
52
53 pub fn index(&self) -> SignerIndex {
54 self.idx
55 }
56 pub fn expiration_date(&self) -> Scalar {
57 self.expiration_date
58 }
59 pub fn t_type(&self) -> Scalar {
60 self.t_type
61 }
62
63 pub fn to_bytes(&self) -> [u8; 200] {
74 let mut bytes = [0u8; 200];
75 bytes[0..96].copy_from_slice(&self.sig.to_bytes());
76 bytes[96..128].copy_from_slice(&self.v.to_bytes());
77 bytes[128..160].copy_from_slice(&self.expiration_date.to_bytes());
78 bytes[160..192].copy_from_slice(&self.t_type.to_bytes());
79 bytes[192..200].copy_from_slice(&self.idx.to_le_bytes());
80 bytes
81 }
82
83 pub fn from_bytes(bytes: &[u8]) -> Result<PartialWallet> {
97 const SIGNATURE_BYTES: usize = 96;
98 const V_BYTES: usize = 32;
99 const EXPIRATION_DATE_BYTES: usize = 32;
100 const T_TYPE_BYTES: usize = 32;
101 const IDX_BYTES: usize = 8;
102 const EXPECTED_LENGTH: usize =
103 SIGNATURE_BYTES + V_BYTES + EXPIRATION_DATE_BYTES + T_TYPE_BYTES + IDX_BYTES;
104
105 if bytes.len() != EXPECTED_LENGTH {
106 return Err(CompactEcashError::DeserializationLengthMismatch {
107 type_name: "PartialWallet".into(),
108 expected: EXPECTED_LENGTH,
109 actual: bytes.len(),
110 });
111 }
112
113 let mut j = 0;
114
115 let sig = Signature::try_from(&bytes[j..j + SIGNATURE_BYTES])?;
116 j += SIGNATURE_BYTES;
117
118 #[allow(clippy::unwrap_used)]
120 let v_bytes = bytes[j..j + V_BYTES].try_into().unwrap();
121 let v = try_deserialize_scalar(v_bytes)?;
122 j += V_BYTES;
123
124 #[allow(clippy::unwrap_used)]
126 let expiration_date_bytes = bytes[j..j + EXPIRATION_DATE_BYTES].try_into().unwrap();
127 let expiration_date = try_deserialize_scalar(expiration_date_bytes)?;
128 j += EXPIRATION_DATE_BYTES;
129 #[allow(clippy::unwrap_used)]
131 let t_type_bytes = bytes[j..j + T_TYPE_BYTES].try_into().unwrap();
132 let t_type = try_deserialize_scalar(t_type_bytes)?;
133 j += T_TYPE_BYTES;
134
135 #[allow(clippy::unwrap_used)]
137 let idx_bytes = bytes[j..].try_into().unwrap();
138 let idx = u64::from_le_bytes(idx_bytes);
139
140 Ok(PartialWallet {
141 sig,
142 v,
143 idx,
144 expiration_date,
145 t_type,
146 })
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Zeroize, Serialize, Deserialize)]
151pub struct Wallet {
152 signatures: WalletSignatures,
154
155 tickets_spent: u64,
157}
158
159impl Wallet {
160 pub fn new(signatures: WalletSignatures, tickets_spent: u64) -> Self {
161 Wallet {
162 signatures,
163 tickets_spent,
164 }
165 }
166
167 pub fn into_wallet_signatures(self) -> WalletSignatures {
168 self.into()
169 }
170
171 pub fn to_bytes(&self) -> [u8; WalletSignatures::SERIALISED_SIZE + 8] {
172 let mut bytes = [0u8; WalletSignatures::SERIALISED_SIZE + 8];
173 bytes[0..WalletSignatures::SERIALISED_SIZE].copy_from_slice(&self.signatures.to_bytes());
174 bytes[WalletSignatures::SERIALISED_SIZE..]
175 .copy_from_slice(&self.tickets_spent.to_be_bytes());
176 bytes
177 }
178
179 pub fn from_bytes(bytes: &[u8]) -> Result<Wallet> {
180 if bytes.len() != WalletSignatures::SERIALISED_SIZE + 8 {
181 return Err(CompactEcashError::DeserializationLengthMismatch {
182 type_name: "Wallet".into(),
183 expected: WalletSignatures::SERIALISED_SIZE + 8,
184 actual: bytes.len(),
185 });
186 }
187
188 #[allow(clippy::unwrap_used)]
190 let tickets_bytes = bytes[WalletSignatures::SERIALISED_SIZE..]
191 .try_into()
192 .unwrap();
193
194 let signatures = WalletSignatures::from_bytes(&bytes[..WalletSignatures::SERIALISED_SIZE])?;
195 let tickets_spent = u64::from_be_bytes(tickets_bytes);
196
197 Ok(Wallet {
198 signatures,
199 tickets_spent,
200 })
201 }
202
203 pub fn ensure_allowance(
204 params: &Parameters,
205 tickets_spent: u64,
206 spend_value: u64,
207 ) -> Result<()> {
208 if tickets_spent + spend_value > params.get_total_coins() {
209 Err(CompactEcashError::SpendExceedsAllowance {
210 spending: spend_value,
211 remaining: params.get_total_coins() - tickets_spent,
212 })
213 } else {
214 Ok(())
215 }
216 }
217
218 pub fn check_remaining_allowance(&self, params: &Parameters, spend_value: u64) -> Result<()> {
219 Self::ensure_allowance(params, self.tickets_spent, spend_value)
220 }
221
222 #[allow(clippy::too_many_arguments)]
223 pub fn spend(
224 &mut self,
225 params: &Parameters,
226 verification_key: &VerificationKeyAuth,
227 sk_user: &SecretKeyUser,
228 pay_info: &PayInfo,
229 spend_value: u64,
230 valid_dates_signatures: &[ExpirationDateSignature],
231 coin_indices_signatures: &[CoinIndexSignature],
232 spend_date_timestamp: EncodedDate,
233 ) -> Result<Payment> {
234 self.check_remaining_allowance(params, spend_value)?;
235
236 let payment = self.signatures.spend(
238 params,
239 verification_key,
240 sk_user,
241 pay_info,
242 self.tickets_spent,
243 spend_value,
244 valid_dates_signatures,
245 coin_indices_signatures,
246 spend_date_timestamp,
247 )?;
248
249 self.tickets_spent += spend_value;
251 Ok(payment)
252 }
253}
254
255impl From<Wallet> for WalletSignatures {
256 fn from(value: Wallet) -> Self {
257 value.signatures
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
269pub struct WalletSignatures {
270 #[zeroize(skip)]
271 sig: Signature,
272 v: Scalar,
273 expiration_date_timestamp: EncodedDate,
274 t_type: EncodedTicketType,
275}
276
277impl WalletSignatures {
278 pub fn with_tickets_spent(self, tickets_spent: u64) -> Wallet {
279 Wallet {
280 signatures: self,
281 tickets_spent,
282 }
283 }
284
285 pub fn new_wallet(self) -> Wallet {
286 self.with_tickets_spent(0)
287 }
288
289 pub fn encoded_expiration_date(&self) -> Scalar {
290 date_scalar(self.expiration_date_timestamp)
291 }
292}
293
294pub fn compute_pay_info_hash(pay_info: &PayInfo, k: u64) -> Scalar {
311 let mut bytes = Vec::new();
312 bytes.extend_from_slice(&pay_info.pay_info_bytes);
313 bytes.extend_from_slice(&k.to_le_bytes());
314 hash_to_scalar(bytes)
315}
316
317impl WalletSignatures {
318 pub const SERIALISED_SIZE: usize = 133;
320
321 pub fn signature(&self) -> &Signature {
322 &self.sig
323 }
324
325 pub fn to_bytes(&self) -> [u8; Self::SERIALISED_SIZE] {
336 let mut bytes = [0u8; Self::SERIALISED_SIZE];
337 bytes[0..96].copy_from_slice(&self.sig.to_bytes());
338 bytes[96..128].copy_from_slice(&self.v.to_bytes());
339 bytes[128..132].copy_from_slice(&self.expiration_date_timestamp.to_be_bytes());
340 bytes[132] = self.t_type;
341 bytes
342 }
343
344 pub fn from_bytes(bytes: &[u8]) -> Result<WalletSignatures> {
345 if bytes.len() != Self::SERIALISED_SIZE {
346 return Err(CompactEcashError::DeserializationLengthMismatch {
347 type_name: "WalletSignatures".into(),
348 expected: Self::SERIALISED_SIZE,
349 actual: bytes.len(),
350 });
351 }
352 #[allow(clippy::unwrap_used)]
354 let sig_bytes: &[u8; 96] = &bytes[..96].try_into().unwrap();
355
356 #[allow(clippy::unwrap_used)]
357 let v_bytes: &[u8; 32] = &bytes[96..128].try_into().unwrap();
358
359 #[allow(clippy::unwrap_used)]
360 let expiration_date_bytes = bytes[128..132].try_into().unwrap();
361
362 let sig = Signature::try_from(sig_bytes.as_slice())?;
363 let v = Scalar::from_bytes(v_bytes).unwrap();
364 let expiration_date_timestamp = EncodedDate::from_be_bytes(expiration_date_bytes);
365 let t_type = bytes[132];
366
367 Ok(WalletSignatures {
368 sig,
369 v,
370 expiration_date_timestamp,
371 t_type,
372 })
373 }
374
375 #[allow(clippy::too_many_arguments)]
392 pub fn spend<BI, BE>(
393 &self,
394 params: &Parameters,
395 verification_key: &VerificationKeyAuth,
396 sk_user: &SecretKeyUser,
397 pay_info: &PayInfo,
398 current_tickets_spent: u64,
399 spend_value: u64,
400 valid_dates_signatures: &[BE],
401 coin_indices_signatures: &[BI],
402 spend_date_timestamp: EncodedDate,
403 ) -> Result<Payment>
404 where
405 BI: Borrow<CoinIndexSignature>,
406 BE: Borrow<ExpirationDateSignature>,
407 {
408 let grp_params = params.grp();
410
411 if verification_key.beta_g2.is_empty() {
412 return Err(CompactEcashError::VerificationKeyTooShort);
413 }
414
415 if valid_dates_signatures.len() != constants::CRED_VALIDITY_PERIOD_DAYS as usize {
416 return Err(CompactEcashError::InsufficientNumberOfExpirationSignatures);
417 }
418
419 if coin_indices_signatures.len() != params.get_total_coins() as usize {
420 return Err(CompactEcashError::InsufficientNumberOfIndexSignatures);
421 }
422
423 Wallet::ensure_allowance(params, current_tickets_spent, spend_value)?;
424
425 let attributes = [&sk_user.sk, &self.v, &self.encoded_expiration_date()];
427
428 let (signature_prime, sign_blinding_factor) = self.signature().blind_and_randomise();
430
431 let kappa = compute_kappa(
433 grp_params,
434 verification_key,
435 &attributes,
436 sign_blinding_factor,
437 );
438
439 let date_signature_index =
442 find_index(spend_date_timestamp, self.expiration_date_timestamp)?;
443
444 #[allow(clippy::unwrap_used)]
446 let date_signature = valid_dates_signatures
447 .get(date_signature_index)
448 .unwrap()
449 .borrow();
450 let (date_signature_prime, date_sign_blinding_factor) =
451 date_signature.blind_and_randomise();
452 #[allow(clippy::unwrap_used)]
455 let kappa_e: G2Projective = grp_params.gen2() * date_sign_blinding_factor
456 + verification_key.alpha
457 + verification_key.beta_g2.first().unwrap() * self.encoded_expiration_date();
458
459 let o_c = grp_params.random_scalar();
461 #[allow(clippy::unwrap_used)]
463 let cc = grp_params.gen1() * o_c + grp_params.gamma_idx(1).unwrap() * self.v;
464
465 let mut aa: Vec<G1Projective> = Default::default();
466 let mut ss: Vec<G1Projective> = Default::default();
467 let mut tt: Vec<G1Projective> = Default::default();
468 let mut rr: Vec<Scalar> = Default::default();
469 let mut o_a: Vec<Scalar> = Default::default();
470 let mut o_mu: Vec<Scalar> = Default::default();
471 let mut mu: Vec<Scalar> = Default::default();
472 let r_k_vec: Vec<Scalar> = Default::default();
473 let mut kappa_k_vec: Vec<G2Projective> = Default::default();
474 let mut lk_vec: Vec<Scalar> = Default::default();
475
476 let mut coin_indices_signatures_prime: Vec<CoinIndexSignature> = Default::default();
477 for k in 0..spend_value {
478 let lk = current_tickets_spent + k;
479 lk_vec.push(Scalar::from(lk));
480
481 let rr_k = compute_pay_info_hash(pay_info, k);
483 rr.push(rr_k);
484
485 let o_a_k = grp_params.random_scalar();
486 o_a.push(o_a_k);
487 #[allow(clippy::unwrap_used)]
489 let aa_k =
490 grp_params.gen1() * o_a_k + grp_params.gamma_idx(1).unwrap() * Scalar::from(lk);
491 aa.push(aa_k);
492
493 let ss_k = pseudorandom_f_delta_v(grp_params, &self.v, lk)?;
495 ss.push(ss_k);
496 let tt_k = grp_params.gen1() * sk_user.sk
498 + pseudorandom_f_g_v(grp_params, &self.v, lk)? * rr_k;
499 tt.push(tt_k);
500
501 let maybe_mu_k: Option<Scalar> = (self.v + Scalar::from(lk) + Scalar::from(1))
503 .invert()
504 .into();
505 let mu_k = maybe_mu_k.ok_or(CompactEcashError::UnluckiestError)?;
506 mu.push(mu_k);
507
508 let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
509 o_mu.push(o_mu_k);
510
511 #[allow(clippy::unwrap_used)]
515 let coin_sign = coin_indices_signatures.get(lk as usize).unwrap().borrow();
516 let (coin_sign_prime, coin_sign_blinding_factor) = coin_sign.blind_and_randomise();
517 coin_indices_signatures_prime.push(coin_sign_prime);
518 #[allow(clippy::unwrap_used)]
520 let kappa_k: G2Projective = grp_params.gen2() * coin_sign_blinding_factor
521 + verification_key.alpha
522 + verification_key.beta_g2.first().unwrap() * Scalar::from(lk);
523 kappa_k_vec.push(kappa_k);
524 }
525
526 let spend_instance = SpendInstance {
528 kappa,
529 cc,
530 aa: aa.clone(),
531 ss: ss.clone(),
532 tt: tt.clone(),
533 kappa_k: kappa_k_vec.clone(),
534 kappa_e,
535 };
536 let spend_witness = SpendWitness {
537 attributes: &attributes,
538 r: sign_blinding_factor,
539 o_c,
540 lk: lk_vec,
541 o_a,
542 mu,
543 o_mu,
544 r_k: r_k_vec,
545 r_e: date_sign_blinding_factor,
546 };
547
548 let zk_proof = SpendProof::construct(
549 &spend_instance,
550 &spend_witness,
551 verification_key,
552 &rr,
553 pay_info,
554 spend_value,
555 );
556
557 let pay = Payment {
559 kappa,
560 kappa_e,
561 sig: signature_prime,
562 sig_exp: date_signature_prime,
563 kappa_k: kappa_k_vec.clone(),
564 omega: coin_indices_signatures_prime,
565 ss: ss.clone(),
566 tt: tt.clone(),
567 aa: aa.clone(),
568 spend_value,
569 cc,
570 t_type: self.t_type,
571 zk_proof,
572 };
573
574 Ok(pay)
575 }
576}
577
578fn pseudorandom_f_delta_v(params: &GroupParameters, v: &Scalar, l: u64) -> Result<G1Projective> {
579 let maybe_pow: Option<Scalar> = (v + Scalar::from(l) + Scalar::from(1)).invert().into();
580 Ok(params.delta() * maybe_pow.ok_or(CompactEcashError::UnluckiestError)?)
581}
582
583fn pseudorandom_f_g_v(params: &GroupParameters, v: &Scalar, l: u64) -> Result<G1Projective> {
584 let maybe_pow: Option<Scalar> = (v + Scalar::from(l) + Scalar::from(1)).invert().into();
585 Ok(params.gen1() * maybe_pow.ok_or(CompactEcashError::UnluckiestError)?)
586}
587
588fn compute_kappa(
604 params: &GroupParameters,
605 verification_key: &VerificationKeyAuth,
606 attributes: &[&Scalar],
607 blinding_factor: Scalar,
608) -> G2Projective {
609 params.gen2() * blinding_factor
610 + verification_key.alpha
611 + attributes
612 .iter()
613 .zip(verification_key.beta_g2.iter())
614 .map(|(&priv_attr, beta_i)| beta_i * priv_attr)
615 .sum::<G2Projective>()
616}
617
618#[derive(PartialEq, Eq, Debug, Clone, Copy)]
628pub struct PayInfo {
629 pub pay_info_bytes: [u8; 72],
630}
631
632impl Serialize for PayInfo {
633 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
634 where
635 S: Serializer,
636 {
637 self.pay_info_bytes.to_vec().serialize(serializer)
638 }
639}
640
641impl<'de> Deserialize<'de> for PayInfo {
642 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
643 where
644 D: Deserializer<'de>,
645 {
646 let pay_info_bytes = <Vec<u8>>::deserialize(deserializer)?;
647 Ok(PayInfo {
648 pay_info_bytes: pay_info_bytes
649 .try_into()
650 .map_err(|_| serde::de::Error::custom("invalid pay info bytes"))?,
651 })
652 }
653}
654
655impl Bytable for PayInfo {
656 fn to_byte_vec(&self) -> Vec<u8> {
657 self.pay_info_bytes.to_vec()
658 }
659
660 fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
661 if slice.len() != 72 {
662 return Err(CompactEcashError::DeserializationLengthMismatch {
663 type_name: "PayInfo".into(),
664 expected: 72,
665 actual: slice.len(),
666 });
667 }
668 #[allow(clippy::unwrap_used)]
670 Ok(Self {
671 pay_info_bytes: slice.try_into().unwrap(),
672 })
673 }
674}
675
676impl Base58 for PayInfo {}
677
678#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
679pub struct Payment {
680 pub kappa: G2Projective,
681 pub kappa_e: G2Projective,
682 pub sig: Signature,
683 pub sig_exp: ExpirationDateSignature,
684 pub kappa_k: Vec<G2Projective>,
685 pub omega: Vec<CoinIndexSignature>,
686 pub ss: Vec<G1Projective>,
687 pub tt: Vec<G1Projective>,
688 pub aa: Vec<G1Projective>,
689 pub spend_value: u64,
690 pub cc: G1Projective,
691 pub t_type: EncodedTicketType,
692 pub zk_proof: SpendProof,
693}
694
695impl Payment {
696 pub fn check_signature_validity(&self, verification_key: &VerificationKeyAuth) -> Result<()> {
717 let params = ecash_group_parameters();
718 if bool::from(self.sig.h.is_identity()) {
719 return Err(CompactEcashError::SpendSignaturesValidity);
720 }
721
722 let kappa_type = self.kappa + verification_key.beta_g2[3] * type_scalar(self.t_type);
723 if !check_bilinear_pairing(
724 &self.sig.h.to_affine(),
725 &G2Prepared::from(kappa_type.to_affine()),
726 &self.sig.s.to_affine(),
727 params.prepared_miller_g2(),
728 ) {
729 return Err(CompactEcashError::SpendSignaturesValidity);
730 }
731 Ok(())
732 }
733
734 pub fn check_exp_signature_validity(
757 &self,
758 verification_key: &VerificationKeyAuth,
759 spend_date: Scalar,
760 ) -> Result<()> {
761 let grp_params = ecash_group_parameters();
762 if bool::from(self.sig_exp.h.is_identity()) {
764 return Err(CompactEcashError::ExpirationDateSignatureValidity);
765 }
766
767 if verification_key.beta_g2.len() < 3 {
768 return Err(CompactEcashError::VerificationKeyTooShort);
769 }
770
771 let m1: Scalar = spend_date;
773 let m2: Scalar = constants::TYPE_EXP;
774
775 let combined_kappa_e =
778 self.kappa_e + verification_key.beta_g2[1] * m1 + verification_key.beta_g2[2] * m2;
779
780 if !check_bilinear_pairing(
781 &self.sig_exp.h.to_affine(),
782 &G2Prepared::from(combined_kappa_e.to_affine()),
783 &self.sig_exp.s.to_affine(),
784 grp_params.prepared_miller_g2(),
785 ) {
786 return Err(CompactEcashError::ExpirationDateSignatureValidity);
787 }
788
789 Ok(())
790 }
791
792 pub fn no_duplicate_serial_numbers(&self) -> Result<()> {
805 let mut seen_serial_numbers = Vec::new();
806
807 for serial_number in &self.ss {
808 if seen_serial_numbers.contains(serial_number) {
809 return Err(CompactEcashError::SpendDuplicateSerialNumber);
810 }
811 seen_serial_numbers.push(*serial_number);
812 }
813
814 Ok(())
815 }
816
817 pub fn batch_check_coin_index_signatures(
873 &self,
874 verification_key: &VerificationKeyAuth,
875 ) -> Result<()> {
876 if verification_key.beta_g2.len() < 3 {
877 return Err(CompactEcashError::VerificationKeyTooShort);
878 }
879
880 if self.omega.len() != self.kappa_k.len() {
881 return Err(CompactEcashError::SpendSignaturesVerification);
882 }
883
884 let partially_signed = verification_key.beta_g2[1] * constants::TYPE_IDX
885 + verification_key.beta_g2[2] * constants::TYPE_IDX;
886
887 let mut pairing_terms = Vec::with_capacity(self.omega.len());
888 for (sig, kappa_k) in self.omega.iter().zip(self.kappa_k.iter()) {
889 pairing_terms.push((sig, partially_signed + kappa_k))
890 }
891
892 if !batch_verify_signatures(pairing_terms.iter()) {
893 return Err(CompactEcashError::SpendSignaturesVerification);
894 }
895 Ok(())
896 }
897
898 pub fn verify_spend_proof(
900 &self,
901 verification_key: &VerificationKeyAuth,
902 pay_info: &PayInfo,
903 ) -> Result<()> {
904 let mut rr = Vec::with_capacity(self.spend_value as usize);
906 for k in 0..self.spend_value {
907 let rr_k = compute_pay_info_hash(pay_info, k);
909 rr.push(rr_k);
910 }
911
912 let instance = SpendInstance {
914 kappa: self.kappa,
915 cc: self.cc,
916 aa: self.aa.clone(),
917 ss: self.ss.clone(),
918 tt: self.tt.clone(),
919 kappa_k: self.kappa_k.clone(),
920 kappa_e: self.kappa_e,
921 };
922
923 if !self
925 .zk_proof
926 .verify(&instance, verification_key, &rr, pay_info, self.spend_value)
927 {
928 return Err(CompactEcashError::SpendZKProofVerification);
929 }
930
931 Ok(())
932 }
933
934 pub fn spend_verify(
949 &self,
950 verification_key: &VerificationKeyAuth,
951 pay_info: &PayInfo,
952 spend_date: EncodedDate,
953 ) -> Result<()> {
954 self.no_duplicate_serial_numbers()?;
956 self.verify_spend_proof(verification_key, pay_info)?;
958 self.check_signature_validity(verification_key)?;
960 self.check_exp_signature_validity(verification_key, date_scalar(spend_date))?;
962 self.batch_check_coin_index_signatures(verification_key)?;
964
965 Ok(())
966 }
967
968 pub fn encoded_serial_number(&self) -> Vec<u8> {
969 SerialNumberRef { inner: &self.ss }.to_bytes()
970 }
971
972 pub fn serial_number_bs58(&self) -> String {
973 SerialNumberRef { inner: &self.ss }.to_bs58()
974 }
975
976 }
982
983pub struct SerialNumberRef<'a> {
984 pub(crate) inner: &'a [G1Projective],
985}
986
987impl SerialNumberRef<'_> {
988 pub fn to_bytes(&self) -> Vec<u8> {
989 let ss_len = self.inner.len();
990 let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
991 for s in self.inner {
992 bytes.extend_from_slice(&s.to_affine().to_compressed());
993 }
994 bytes
995 }
996
997 pub fn to_bs58(&self) -> String {
998 bs58::encode(self.to_bytes()).into_string()
999 }
1000}