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 #[allow(clippy::indexing_slicing)]
117 let sig = Signature::try_from(&bytes[j..j + SIGNATURE_BYTES])?;
118 j += SIGNATURE_BYTES;
119
120 #[allow(clippy::unwrap_used)]
122 #[allow(clippy::indexing_slicing)]
123 let v_bytes = bytes[j..j + V_BYTES].try_into().unwrap();
124 let v = try_deserialize_scalar(v_bytes)?;
125 j += V_BYTES;
126
127 #[allow(clippy::unwrap_used)]
129 #[allow(clippy::indexing_slicing)]
130 let expiration_date_bytes = bytes[j..j + EXPIRATION_DATE_BYTES].try_into().unwrap();
131 let expiration_date = try_deserialize_scalar(expiration_date_bytes)?;
132 j += EXPIRATION_DATE_BYTES;
133 #[allow(clippy::unwrap_used)]
135 #[allow(clippy::indexing_slicing)]
136 let t_type_bytes = bytes[j..j + T_TYPE_BYTES].try_into().unwrap();
137 let t_type = try_deserialize_scalar(t_type_bytes)?;
138 j += T_TYPE_BYTES;
139
140 #[allow(clippy::unwrap_used)]
142 #[allow(clippy::indexing_slicing)]
143 let idx_bytes = bytes[j..].try_into().unwrap();
144 let idx = u64::from_le_bytes(idx_bytes);
145
146 Ok(PartialWallet {
147 sig,
148 v,
149 idx,
150 expiration_date,
151 t_type,
152 })
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Zeroize, Serialize, Deserialize)]
157pub struct Wallet {
158 signatures: WalletSignatures,
160
161 tickets_spent: u64,
163}
164
165impl Wallet {
166 pub fn new(signatures: WalletSignatures, tickets_spent: u64) -> Self {
167 Wallet {
168 signatures,
169 tickets_spent,
170 }
171 }
172
173 pub fn into_wallet_signatures(self) -> WalletSignatures {
174 self.into()
175 }
176
177 pub fn to_bytes(&self) -> [u8; WalletSignatures::SERIALISED_SIZE + 8] {
178 let mut bytes = [0u8; WalletSignatures::SERIALISED_SIZE + 8];
179 bytes[0..WalletSignatures::SERIALISED_SIZE].copy_from_slice(&self.signatures.to_bytes());
180 bytes[WalletSignatures::SERIALISED_SIZE..]
181 .copy_from_slice(&self.tickets_spent.to_be_bytes());
182 bytes
183 }
184
185 pub fn from_bytes(bytes: &[u8]) -> Result<Wallet> {
186 if bytes.len() != WalletSignatures::SERIALISED_SIZE + 8 {
187 return Err(CompactEcashError::DeserializationLengthMismatch {
188 type_name: "Wallet".into(),
189 expected: WalletSignatures::SERIALISED_SIZE + 8,
190 actual: bytes.len(),
191 });
192 }
193
194 #[allow(clippy::unwrap_used)]
196 #[allow(clippy::indexing_slicing)]
197 let tickets_bytes = bytes[WalletSignatures::SERIALISED_SIZE..]
198 .try_into()
199 .unwrap();
200
201 #[allow(clippy::indexing_slicing)]
202 let signatures = WalletSignatures::from_bytes(&bytes[..WalletSignatures::SERIALISED_SIZE])?;
203 let tickets_spent = u64::from_be_bytes(tickets_bytes);
204
205 Ok(Wallet {
206 signatures,
207 tickets_spent,
208 })
209 }
210
211 pub fn ensure_allowance(
212 params: &Parameters,
213 tickets_spent: u64,
214 spend_value: u64,
215 ) -> Result<()> {
216 if tickets_spent + spend_value > params.get_total_coins() {
217 Err(CompactEcashError::SpendExceedsAllowance {
218 spending: spend_value,
219 remaining: params.get_total_coins() - tickets_spent,
220 })
221 } else {
222 Ok(())
223 }
224 }
225
226 pub fn check_remaining_allowance(&self, params: &Parameters, spend_value: u64) -> Result<()> {
227 Self::ensure_allowance(params, self.tickets_spent, spend_value)
228 }
229
230 #[allow(clippy::too_many_arguments)]
231 pub fn spend(
232 &mut self,
233 params: &Parameters,
234 verification_key: &VerificationKeyAuth,
235 sk_user: &SecretKeyUser,
236 pay_info: &PayInfo,
237 spend_value: u64,
238 valid_dates_signatures: &[ExpirationDateSignature],
239 coin_indices_signatures: &[CoinIndexSignature],
240 spend_date_timestamp: EncodedDate,
241 ) -> Result<Payment> {
242 self.check_remaining_allowance(params, spend_value)?;
243
244 let payment = self.signatures.spend(
246 params,
247 verification_key,
248 sk_user,
249 pay_info,
250 self.tickets_spent,
251 spend_value,
252 valid_dates_signatures,
253 coin_indices_signatures,
254 spend_date_timestamp,
255 )?;
256
257 self.tickets_spent += spend_value;
259 Ok(payment)
260 }
261}
262
263impl From<Wallet> for WalletSignatures {
264 fn from(value: Wallet) -> Self {
265 value.signatures
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
277pub struct WalletSignatures {
278 #[zeroize(skip)]
279 sig: Signature,
280 v: Scalar,
281 expiration_date_timestamp: EncodedDate,
282 t_type: EncodedTicketType,
283}
284
285impl WalletSignatures {
286 pub fn with_tickets_spent(self, tickets_spent: u64) -> Wallet {
287 Wallet {
288 signatures: self,
289 tickets_spent,
290 }
291 }
292
293 pub fn new_wallet(self) -> Wallet {
294 self.with_tickets_spent(0)
295 }
296
297 pub fn encoded_expiration_date(&self) -> Scalar {
298 date_scalar(self.expiration_date_timestamp)
299 }
300}
301
302pub fn compute_pay_info_hash(pay_info: &PayInfo, k: u64) -> Scalar {
319 let mut bytes = Vec::new();
320 bytes.extend_from_slice(&pay_info.pay_info_bytes);
321 bytes.extend_from_slice(&k.to_le_bytes());
322 hash_to_scalar(bytes)
323}
324
325impl WalletSignatures {
326 pub const SERIALISED_SIZE: usize = 133;
328
329 pub fn signature(&self) -> &Signature {
330 &self.sig
331 }
332
333 pub fn to_bytes(&self) -> [u8; Self::SERIALISED_SIZE] {
344 let mut bytes = [0u8; Self::SERIALISED_SIZE];
345 bytes[0..96].copy_from_slice(&self.sig.to_bytes());
346 bytes[96..128].copy_from_slice(&self.v.to_bytes());
347 bytes[128..132].copy_from_slice(&self.expiration_date_timestamp.to_be_bytes());
348 bytes[132] = self.t_type;
349 bytes
350 }
351
352 pub fn from_bytes(bytes: &[u8]) -> Result<WalletSignatures> {
353 if bytes.len() != Self::SERIALISED_SIZE {
354 return Err(CompactEcashError::DeserializationLengthMismatch {
355 type_name: "WalletSignatures".into(),
356 expected: Self::SERIALISED_SIZE,
357 actual: bytes.len(),
358 });
359 }
360 #[allow(clippy::unwrap_used)]
362 #[allow(clippy::indexing_slicing)]
363 let sig_bytes: &[u8; 96] = &bytes[..96].try_into().unwrap();
364
365 #[allow(clippy::unwrap_used)]
366 #[allow(clippy::indexing_slicing)]
367 let v_bytes: &[u8; 32] = &bytes[96..128].try_into().unwrap();
368
369 #[allow(clippy::unwrap_used)]
370 #[allow(clippy::indexing_slicing)]
371 let expiration_date_bytes = bytes[128..132].try_into().unwrap();
372
373 let sig = Signature::try_from(sig_bytes.as_slice())?;
374 let v = Scalar::from_bytes(v_bytes).unwrap();
375 let expiration_date_timestamp = EncodedDate::from_be_bytes(expiration_date_bytes);
376 #[allow(clippy::indexing_slicing)]
377 let t_type = bytes[132];
378
379 Ok(WalletSignatures {
380 sig,
381 v,
382 expiration_date_timestamp,
383 t_type,
384 })
385 }
386
387 #[allow(clippy::too_many_arguments)]
404 pub fn spend<BI, BE>(
405 &self,
406 params: &Parameters,
407 verification_key: &VerificationKeyAuth,
408 sk_user: &SecretKeyUser,
409 pay_info: &PayInfo,
410 current_tickets_spent: u64,
411 spend_value: u64,
412 valid_dates_signatures: &[BE],
413 coin_indices_signatures: &[BI],
414 spend_date_timestamp: EncodedDate,
415 ) -> Result<Payment>
416 where
417 BI: Borrow<CoinIndexSignature>,
418 BE: Borrow<ExpirationDateSignature>,
419 {
420 let grp_params = params.grp();
422
423 if verification_key.beta_g2.is_empty() {
424 return Err(CompactEcashError::VerificationKeyTooShort);
425 }
426
427 if valid_dates_signatures.len() != constants::CRED_VALIDITY_PERIOD_DAYS as usize {
428 return Err(CompactEcashError::InsufficientNumberOfExpirationSignatures);
429 }
430
431 if coin_indices_signatures.len() != params.get_total_coins() as usize {
432 return Err(CompactEcashError::InsufficientNumberOfIndexSignatures);
433 }
434
435 Wallet::ensure_allowance(params, current_tickets_spent, spend_value)?;
436
437 let attributes = [&sk_user.sk, &self.v, &self.encoded_expiration_date()];
439
440 let (signature_prime, sign_blinding_factor) = self.signature().blind_and_randomise();
442
443 let kappa = compute_kappa(
445 grp_params,
446 verification_key,
447 &attributes,
448 sign_blinding_factor,
449 );
450
451 let date_signature_index =
454 find_index(spend_date_timestamp, self.expiration_date_timestamp)?;
455
456 #[allow(clippy::unwrap_used)]
458 let date_signature = valid_dates_signatures
459 .get(date_signature_index)
460 .unwrap()
461 .borrow();
462 let (date_signature_prime, date_sign_blinding_factor) =
463 date_signature.blind_and_randomise();
464 #[allow(clippy::unwrap_used)]
467 let kappa_e: G2Projective = grp_params.gen2() * date_sign_blinding_factor
468 + verification_key.alpha
469 + verification_key.beta_g2.first().unwrap() * self.encoded_expiration_date();
470
471 let o_c = grp_params.random_scalar();
473 #[allow(clippy::unwrap_used)]
475 let cc = grp_params.gen1() * o_c + grp_params.gamma_idx(1).unwrap() * self.v;
476
477 let mut aa: Vec<G1Projective> = Default::default();
478 let mut ss: Vec<G1Projective> = Default::default();
479 let mut tt: Vec<G1Projective> = Default::default();
480 let mut rr: Vec<Scalar> = Default::default();
481 let mut o_a: Vec<Scalar> = Default::default();
482 let mut o_mu: Vec<Scalar> = Default::default();
483 let mut mu: Vec<Scalar> = Default::default();
484 let r_k_vec: Vec<Scalar> = Default::default();
485 let mut kappa_k_vec: Vec<G2Projective> = Default::default();
486 let mut lk_vec: Vec<Scalar> = Default::default();
487
488 let mut coin_indices_signatures_prime: Vec<CoinIndexSignature> = Default::default();
489 for k in 0..spend_value {
490 let lk = current_tickets_spent + k;
491 lk_vec.push(Scalar::from(lk));
492
493 let rr_k = compute_pay_info_hash(pay_info, k);
495 rr.push(rr_k);
496
497 let o_a_k = grp_params.random_scalar();
498 o_a.push(o_a_k);
499 #[allow(clippy::unwrap_used)]
501 let aa_k =
502 grp_params.gen1() * o_a_k + grp_params.gamma_idx(1).unwrap() * Scalar::from(lk);
503 aa.push(aa_k);
504
505 let ss_k = pseudorandom_f_delta_v(grp_params, &self.v, lk)?;
507 ss.push(ss_k);
508 let tt_k = grp_params.gen1() * sk_user.sk
510 + pseudorandom_f_g_v(grp_params, &self.v, lk)? * rr_k;
511 tt.push(tt_k);
512
513 let maybe_mu_k: Option<Scalar> = (self.v + Scalar::from(lk) + Scalar::from(1))
515 .invert()
516 .into();
517 let mu_k = maybe_mu_k.ok_or(CompactEcashError::UnluckiestError)?;
518 mu.push(mu_k);
519
520 let o_mu_k = ((o_a_k + o_c) * mu_k).neg();
521 o_mu.push(o_mu_k);
522
523 #[allow(clippy::unwrap_used)]
527 let coin_sign = coin_indices_signatures.get(lk as usize).unwrap().borrow();
528 let (coin_sign_prime, coin_sign_blinding_factor) = coin_sign.blind_and_randomise();
529 coin_indices_signatures_prime.push(coin_sign_prime);
530 #[allow(clippy::unwrap_used)]
532 let kappa_k: G2Projective = grp_params.gen2() * coin_sign_blinding_factor
533 + verification_key.alpha
534 + verification_key.beta_g2.first().unwrap() * Scalar::from(lk);
535 kappa_k_vec.push(kappa_k);
536 }
537
538 let spend_instance = SpendInstance {
540 kappa,
541 cc,
542 aa: aa.clone(),
543 ss: ss.clone(),
544 tt: tt.clone(),
545 kappa_k: kappa_k_vec.clone(),
546 kappa_e,
547 };
548 let spend_witness = SpendWitness {
549 attributes: &attributes,
550 r: sign_blinding_factor,
551 o_c,
552 lk: lk_vec,
553 o_a,
554 mu,
555 o_mu,
556 r_k: r_k_vec,
557 r_e: date_sign_blinding_factor,
558 };
559
560 let zk_proof = SpendProof::construct(
561 &spend_instance,
562 &spend_witness,
563 verification_key,
564 &rr,
565 pay_info,
566 spend_value,
567 )?;
568
569 let pay = Payment {
571 kappa,
572 kappa_e,
573 sig: signature_prime,
574 sig_exp: date_signature_prime,
575 kappa_k: kappa_k_vec.clone(),
576 omega: coin_indices_signatures_prime,
577 ss: ss.clone(),
578 tt: tt.clone(),
579 aa: aa.clone(),
580 spend_value,
581 cc,
582 t_type: self.t_type,
583 zk_proof,
584 };
585
586 Ok(pay)
587 }
588}
589
590fn pseudorandom_f_delta_v(params: &GroupParameters, v: &Scalar, l: u64) -> Result<G1Projective> {
591 let maybe_pow: Option<Scalar> = (v + Scalar::from(l) + Scalar::from(1)).invert().into();
592 Ok(params.delta() * maybe_pow.ok_or(CompactEcashError::UnluckiestError)?)
593}
594
595fn pseudorandom_f_g_v(params: &GroupParameters, v: &Scalar, l: u64) -> Result<G1Projective> {
596 let maybe_pow: Option<Scalar> = (v + Scalar::from(l) + Scalar::from(1)).invert().into();
597 Ok(params.gen1() * maybe_pow.ok_or(CompactEcashError::UnluckiestError)?)
598}
599
600fn compute_kappa(
616 params: &GroupParameters,
617 verification_key: &VerificationKeyAuth,
618 attributes: &[&Scalar],
619 blinding_factor: Scalar,
620) -> G2Projective {
621 params.gen2() * blinding_factor
622 + verification_key.alpha
623 + attributes
624 .iter()
625 .zip(verification_key.beta_g2.iter())
626 .map(|(&priv_attr, beta_i)| beta_i * priv_attr)
627 .sum::<G2Projective>()
628}
629
630#[derive(PartialEq, Eq, Debug, Clone, Copy)]
640pub struct PayInfo {
641 pub pay_info_bytes: [u8; 72],
642}
643
644impl Serialize for PayInfo {
645 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
646 where
647 S: Serializer,
648 {
649 self.pay_info_bytes.to_vec().serialize(serializer)
650 }
651}
652
653impl<'de> Deserialize<'de> for PayInfo {
654 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
655 where
656 D: Deserializer<'de>,
657 {
658 let pay_info_bytes = <Vec<u8>>::deserialize(deserializer)?;
659 Ok(PayInfo {
660 pay_info_bytes: pay_info_bytes
661 .try_into()
662 .map_err(|_| serde::de::Error::custom("invalid pay info bytes"))?,
663 })
664 }
665}
666
667impl Bytable for PayInfo {
668 fn to_byte_vec(&self) -> Vec<u8> {
669 self.pay_info_bytes.to_vec()
670 }
671
672 fn try_from_byte_slice(slice: &[u8]) -> std::result::Result<Self, CompactEcashError> {
673 if slice.len() != 72 {
674 return Err(CompactEcashError::DeserializationLengthMismatch {
675 type_name: "PayInfo".into(),
676 expected: 72,
677 actual: slice.len(),
678 });
679 }
680 #[allow(clippy::unwrap_used)]
682 Ok(Self {
683 pay_info_bytes: slice.try_into().unwrap(),
684 })
685 }
686}
687
688impl Base58 for PayInfo {}
689
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
691pub struct Payment {
692 pub kappa: G2Projective,
693 pub kappa_e: G2Projective,
694 pub sig: Signature,
695 pub sig_exp: ExpirationDateSignature,
696 pub kappa_k: Vec<G2Projective>,
697 pub omega: Vec<CoinIndexSignature>,
698 pub ss: Vec<G1Projective>,
699 pub tt: Vec<G1Projective>,
700 pub aa: Vec<G1Projective>,
701 pub spend_value: u64,
702 pub cc: G1Projective,
703 pub t_type: EncodedTicketType,
704 pub zk_proof: SpendProof,
705}
706
707impl Payment {
708 pub fn check_signature_validity(&self, verification_key: &VerificationKeyAuth) -> Result<()> {
729 let params = ecash_group_parameters();
730 if bool::from(self.sig.h.is_identity()) {
731 return Err(CompactEcashError::SpendSignaturesValidity);
732 }
733
734 if verification_key.beta_g2.len() < 4 {
735 return Err(CompactEcashError::VerificationKeyTooShort);
736 }
737
738 for o in &self.omega {
739 if bool::from(o.is_at_infinity()) {
740 return Err(CompactEcashError::SpendSignaturesValidity);
741 }
742 }
743
744 #[allow(clippy::indexing_slicing)]
746 let kappa_type = self.kappa + verification_key.beta_g2[3] * type_scalar(self.t_type);
747 if !check_bilinear_pairing(
748 &self.sig.h.to_affine(),
749 &G2Prepared::from(kappa_type.to_affine()),
750 &self.sig.s.to_affine(),
751 params.prepared_miller_g2(),
752 ) {
753 return Err(CompactEcashError::SpendSignaturesValidity);
754 }
755 Ok(())
756 }
757
758 pub fn check_exp_signature_validity(
781 &self,
782 verification_key: &VerificationKeyAuth,
783 spend_date: Scalar,
784 ) -> Result<()> {
785 let grp_params = ecash_group_parameters();
786 if bool::from(self.sig_exp.h.is_identity()) {
788 return Err(CompactEcashError::ExpirationDateSignatureValidity);
789 }
790
791 if verification_key.beta_g2.len() < 3 {
792 return Err(CompactEcashError::VerificationKeyTooShort);
793 }
794
795 let m1: Scalar = spend_date;
797 let m2: Scalar = constants::TYPE_EXP;
798
799 #[allow(clippy::indexing_slicing)]
802 let combined_kappa_e =
803 self.kappa_e + verification_key.beta_g2[1] * m1 + verification_key.beta_g2[2] * m2;
804
805 if !check_bilinear_pairing(
806 &self.sig_exp.h.to_affine(),
807 &G2Prepared::from(combined_kappa_e.to_affine()),
808 &self.sig_exp.s.to_affine(),
809 grp_params.prepared_miller_g2(),
810 ) {
811 return Err(CompactEcashError::ExpirationDateSignatureValidity);
812 }
813
814 Ok(())
815 }
816
817 pub fn no_duplicate_serial_numbers(&self) -> Result<()> {
830 let mut seen_serial_numbers = Vec::new();
831
832 for serial_number in &self.ss {
833 if seen_serial_numbers.contains(serial_number) {
834 return Err(CompactEcashError::SpendDuplicateSerialNumber);
835 }
836 seen_serial_numbers.push(*serial_number);
837 }
838
839 Ok(())
840 }
841
842 pub fn batch_check_coin_index_signatures(
898 &self,
899 verification_key: &VerificationKeyAuth,
900 ) -> Result<()> {
901 if verification_key.beta_g2.len() < 3 {
902 return Err(CompactEcashError::VerificationKeyTooShort);
903 }
904
905 if self.omega.len() != self.kappa_k.len() {
906 return Err(CompactEcashError::SpendSignaturesVerification);
907 }
908
909 #[allow(clippy::indexing_slicing)]
911 let partially_signed = verification_key.beta_g2[1] * constants::TYPE_IDX
912 + verification_key.beta_g2[2] * constants::TYPE_IDX;
913
914 let mut pairing_terms = Vec::with_capacity(self.omega.len());
915 for (sig, kappa_k) in self.omega.iter().zip(self.kappa_k.iter()) {
916 pairing_terms.push((sig, partially_signed + kappa_k))
917 }
918
919 if !batch_verify_signatures(pairing_terms.iter()) {
920 return Err(CompactEcashError::SpendSignaturesVerification);
921 }
922 Ok(())
923 }
924
925 pub fn verify_spend_proof(
927 &self,
928 verification_key: &VerificationKeyAuth,
929 pay_info: &PayInfo,
930 ) -> Result<()> {
931 let mut rr = Vec::with_capacity(self.spend_value as usize);
933 for k in 0..self.spend_value {
934 let rr_k = compute_pay_info_hash(pay_info, k);
936 rr.push(rr_k);
937 }
938
939 let instance = SpendInstance {
941 kappa: self.kappa,
942 cc: self.cc,
943 aa: self.aa.clone(),
944 ss: self.ss.clone(),
945 tt: self.tt.clone(),
946 kappa_k: self.kappa_k.clone(),
947 kappa_e: self.kappa_e,
948 };
949
950 if !self
952 .zk_proof
953 .verify(&instance, verification_key, &rr, pay_info, self.spend_value)
954 {
955 return Err(CompactEcashError::SpendZKProofVerification);
956 }
957
958 Ok(())
959 }
960
961 pub fn spend_verify(
976 &self,
977 verification_key: &VerificationKeyAuth,
978 pay_info: &PayInfo,
979 spend_date: EncodedDate,
980 ) -> Result<()> {
981 self.no_duplicate_serial_numbers()?;
983 self.verify_spend_proof(verification_key, pay_info)?;
985 self.check_signature_validity(verification_key)?;
987 self.check_exp_signature_validity(verification_key, date_scalar(spend_date))?;
989 self.batch_check_coin_index_signatures(verification_key)?;
991
992 Ok(())
993 }
994
995 pub fn encoded_serial_number(&self) -> Vec<u8> {
996 SerialNumberRef { inner: &self.ss }.to_bytes()
997 }
998
999 pub fn serial_number_bs58(&self) -> String {
1000 SerialNumberRef { inner: &self.ss }.to_bs58()
1001 }
1002
1003 }
1009
1010pub struct SerialNumberRef<'a> {
1011 pub(crate) inner: &'a [G1Projective],
1012}
1013
1014impl SerialNumberRef<'_> {
1015 pub fn to_bytes(&self) -> Vec<u8> {
1016 let ss_len = self.inner.len();
1017 let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
1018 for s in self.inner {
1019 bytes.extend_from_slice(&s.to_affine().to_compressed());
1020 }
1021 bytes
1022 }
1023
1024 pub fn to_bs58(&self) -> String {
1025 bs58::encode(self.to_bytes()).into_string()
1026 }
1027}