1use std::cmp::Ordering;
6use std::collections::{HashMap, HashSet};
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::str::FromStr;
10use std::string::FromUtf8Error;
11
12use serde::{de, Deserialize, Deserializer, Serialize};
13use thiserror::Error;
14
15use super::nut02::ShortKeysetId;
16#[cfg(feature = "wallet")]
17use super::nut10;
18#[cfg(feature = "wallet")]
19use super::nut11::SpendingConditions;
20#[cfg(feature = "wallet")]
21use crate::amount::SplitTarget;
22#[cfg(feature = "wallet")]
23use crate::dhke::blind_message;
24use crate::dhke::hash_to_curve;
25use crate::nuts::nut01::PublicKey;
26#[cfg(feature = "wallet")]
27use crate::nuts::nut01::SecretKey;
28use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness};
29use crate::nuts::nut12::BlindSignatureDleq;
30use crate::nuts::nut14::{serde_htlc_witness, HTLCWitness};
31use crate::nuts::{Id, ProofDleq};
32use crate::secret::Secret;
33use crate::Amount;
34
35pub mod token;
36pub use token::{Token, TokenV3, TokenV4};
37
38pub type Proofs = Vec<Proof>;
40
41pub trait ProofsMethods {
43 fn count_by_keyset(&self) -> HashMap<Id, u64>;
45
46 fn sum_by_keyset(&self) -> HashMap<Id, Amount>;
48
49 fn total_amount(&self) -> Result<Amount, Error>;
51
52 fn ys(&self) -> Result<Vec<PublicKey>, Error>;
54
55 fn without_dleqs(&self) -> Proofs;
57}
58
59impl ProofsMethods for Proofs {
60 fn count_by_keyset(&self) -> HashMap<Id, u64> {
61 count_by_keyset(self.iter())
62 }
63
64 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
65 sum_by_keyset(self.iter())
66 }
67
68 fn total_amount(&self) -> Result<Amount, Error> {
69 total_amount(self.iter())
70 }
71
72 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
73 ys(self.iter())
74 }
75
76 fn without_dleqs(&self) -> Proofs {
77 self.iter()
78 .map(|p| {
79 let mut p = p.clone();
80 p.dleq = None;
81 p
82 })
83 .collect()
84 }
85}
86
87impl ProofsMethods for HashSet<Proof> {
88 fn count_by_keyset(&self) -> HashMap<Id, u64> {
89 count_by_keyset(self.iter())
90 }
91
92 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
93 sum_by_keyset(self.iter())
94 }
95
96 fn total_amount(&self) -> Result<Amount, Error> {
97 total_amount(self.iter())
98 }
99
100 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
101 ys(self.iter())
102 }
103
104 fn without_dleqs(&self) -> Proofs {
105 self.iter()
106 .map(|p| {
107 let mut p = p.clone();
108 p.dleq = None;
109 p
110 })
111 .collect()
112 }
113}
114
115fn count_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, u64> {
116 let mut counts = HashMap::new();
117 for proof in proofs {
118 *counts.entry(proof.keyset_id).or_insert(0) += 1;
119 }
120 counts
121}
122
123fn sum_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, Amount> {
124 let mut sums = HashMap::new();
125 for proof in proofs {
126 *sums.entry(proof.keyset_id).or_insert(Amount::ZERO) += proof.amount;
127 }
128 sums
129}
130
131fn total_amount<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Amount, Error> {
132 Amount::try_sum(proofs.map(|p| p.amount)).map_err(Into::into)
133}
134
135fn ys<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Vec<PublicKey>, Error> {
136 proofs.map(|p| p.y()).collect::<Result<Vec<PublicKey>, _>>()
137}
138
139#[derive(Debug, Error)]
141pub enum Error {
142 #[error("Proofs required in token")]
144 ProofsRequired,
145 #[error("Unsupported token")]
147 UnsupportedToken,
148 #[error("Unsupported unit")]
150 UnsupportedUnit,
151 #[error("Unsupported payment method")]
153 UnsupportedPaymentMethod,
154 #[error("Duplicate proofs in token")]
156 DuplicateProofs,
157 #[error(transparent)]
159 SerdeJsonError(#[from] serde_json::Error),
160 #[error(transparent)]
162 Utf8ParseError(#[from] FromUtf8Error),
163 #[error(transparent)]
165 Base64Error(#[from] bitcoin::base64::DecodeError),
166 #[error(transparent)]
168 CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
169 #[error(transparent)]
171 CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
172 #[error(transparent)]
174 Amount(#[from] crate::amount::Error),
175 #[error(transparent)]
177 Secret(#[from] crate::secret::Error),
178 #[error(transparent)]
180 DHKE(#[from] crate::dhke::Error),
181 #[error(transparent)]
183 NUT10(#[from] crate::nuts::nut10::Error),
184 #[error(transparent)]
186 NUT11(#[from] crate::nuts::nut11::Error),
187 #[error(transparent)]
189 NUT02(#[from] crate::nuts::nut02::Error),
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
194#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
195pub struct BlindedMessage {
196 pub amount: Amount,
200 #[serde(rename = "id")]
204 #[cfg_attr(feature = "swagger", schema(value_type = String))]
205 pub keyset_id: Id,
206 #[serde(rename = "B_")]
210 #[cfg_attr(feature = "swagger", schema(value_type = String))]
211 pub blinded_secret: PublicKey,
212 #[serde(skip_serializing_if = "Option::is_none")]
216 pub witness: Option<Witness>,
217}
218
219impl BlindedMessage {
220 #[inline]
222 pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
223 Self {
224 amount,
225 keyset_id,
226 blinded_secret,
227 witness: None,
228 }
229 }
230
231 #[inline]
233 pub fn witness(&mut self, witness: Witness) {
234 self.witness = Some(witness);
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
241pub struct BlindSignature {
242 pub amount: Amount,
246 #[serde(rename = "id")]
250 #[cfg_attr(feature = "swagger", schema(value_type = String))]
251 pub keyset_id: Id,
252 #[serde(rename = "C_")]
256 #[cfg_attr(feature = "swagger", schema(value_type = String))]
257 pub c: PublicKey,
258 #[serde(skip_serializing_if = "Option::is_none")]
262 pub dleq: Option<BlindSignatureDleq>,
263}
264
265impl Ord for BlindSignature {
266 fn cmp(&self, other: &Self) -> Ordering {
267 self.amount.cmp(&other.amount)
268 }
269}
270
271impl PartialOrd for BlindSignature {
272 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
273 Some(self.cmp(other))
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
279#[serde(untagged)]
280#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
281pub enum Witness {
282 #[serde(with = "serde_p2pk_witness")]
284 P2PKWitness(P2PKWitness),
285 #[serde(with = "serde_htlc_witness")]
287 HTLCWitness(HTLCWitness),
288}
289
290impl From<P2PKWitness> for Witness {
291 fn from(witness: P2PKWitness) -> Self {
292 Self::P2PKWitness(witness)
293 }
294}
295
296impl From<HTLCWitness> for Witness {
297 fn from(witness: HTLCWitness) -> Self {
298 Self::HTLCWitness(witness)
299 }
300}
301
302impl Witness {
303 pub fn add_signatures(&mut self, signatues: Vec<String>) {
305 match self {
306 Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatues),
307 Self::HTLCWitness(htlc_witness) => {
308 htlc_witness.signatures = htlc_witness.signatures.clone().map(|sigs| {
309 let mut sigs = sigs;
310 sigs.extend(signatues);
311 sigs
312 });
313 }
314 }
315 }
316
317 pub fn signatures(&self) -> Option<Vec<String>> {
319 match self {
320 Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
321 Self::HTLCWitness(witness) => witness.signatures.clone(),
322 }
323 }
324
325 pub fn preimage(&self) -> Option<String> {
327 match self {
328 Self::P2PKWitness(_witness) => None,
329 Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
330 }
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
336#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
337pub struct Proof {
338 pub amount: Amount,
340 #[serde(rename = "id")]
342 #[cfg_attr(feature = "swagger", schema(value_type = String))]
343 pub keyset_id: Id,
344 #[cfg_attr(feature = "swagger", schema(value_type = String))]
346 pub secret: Secret,
347 #[serde(rename = "C")]
349 #[cfg_attr(feature = "swagger", schema(value_type = String))]
350 pub c: PublicKey,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub witness: Option<Witness>,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub dleq: Option<ProofDleq>,
357}
358
359impl Proof {
360 pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
362 Proof {
363 amount,
364 keyset_id,
365 secret,
366 c,
367 witness: None,
368 dleq: None,
369 }
370 }
371
372 pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
374 active_keyset_ids.contains(&self.keyset_id)
375 }
376
377 pub fn y(&self) -> Result<PublicKey, Error> {
381 Ok(hash_to_curve(self.secret.as_bytes())?)
382 }
383}
384
385impl Hash for Proof {
386 fn hash<H: Hasher>(&self, state: &mut H) {
387 self.secret.hash(state);
388 }
389}
390
391impl Ord for Proof {
392 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
393 self.amount.cmp(&other.amount)
394 }
395}
396
397impl PartialOrd for Proof {
398 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
399 Some(self.cmp(other))
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub struct ProofV4 {
406 #[serde(rename = "a")]
408 pub amount: Amount,
409 #[serde(rename = "s")]
411 pub secret: Secret,
412 #[serde(
414 serialize_with = "serialize_v4_pubkey",
415 deserialize_with = "deserialize_v4_pubkey"
416 )]
417 pub c: PublicKey,
418 #[serde(default)]
420 #[serde(skip_serializing_if = "Option::is_none")]
421 pub witness: Option<Witness>,
422 #[serde(rename = "d")]
424 pub dleq: Option<ProofDleq>,
425}
426
427impl ProofV4 {
428 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
430 Proof {
431 amount: self.amount,
432 keyset_id: *keyset_id,
433 secret: self.secret.clone(),
434 c: self.c,
435 witness: self.witness.clone(),
436 dleq: self.dleq.clone(),
437 }
438 }
439}
440
441impl Hash for ProofV4 {
442 fn hash<H: Hasher>(&self, state: &mut H) {
443 self.secret.hash(state);
444 }
445}
446
447impl From<Proof> for ProofV4 {
448 fn from(proof: Proof) -> ProofV4 {
449 let Proof {
450 amount,
451 keyset_id: _,
452 secret,
453 c,
454 witness,
455 dleq,
456 } = proof;
457 ProofV4 {
458 amount,
459 secret,
460 c,
461 witness,
462 dleq,
463 }
464 }
465}
466
467impl From<ProofV3> for ProofV4 {
468 fn from(proof: ProofV3) -> Self {
469 Self {
470 amount: proof.amount,
471 secret: proof.secret,
472 c: proof.c,
473 witness: proof.witness,
474 dleq: proof.dleq,
475 }
476 }
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
481pub struct ProofV3 {
482 pub amount: Amount,
484 #[serde(rename = "id")]
486 pub keyset_id: ShortKeysetId,
487 pub secret: Secret,
489 #[serde(rename = "C")]
491 pub c: PublicKey,
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub witness: Option<Witness>,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub dleq: Option<ProofDleq>,
498}
499
500impl ProofV3 {
501 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
503 Proof {
504 amount: self.amount,
505 keyset_id: *keyset_id,
506 secret: self.secret.clone(),
507 c: self.c,
508 witness: self.witness.clone(),
509 dleq: self.dleq.clone(),
510 }
511 }
512}
513
514impl From<Proof> for ProofV3 {
515 fn from(proof: Proof) -> ProofV3 {
516 let Proof {
517 amount,
518 keyset_id,
519 secret,
520 c,
521 witness,
522 dleq,
523 } = proof;
524 ProofV3 {
525 amount,
526 secret,
527 c,
528 witness,
529 dleq,
530 keyset_id: keyset_id.into(),
531 }
532 }
533}
534
535impl Hash for ProofV3 {
536 fn hash<H: Hasher>(&self, state: &mut H) {
537 self.secret.hash(state);
538 }
539}
540
541fn serialize_v4_pubkey<S>(key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
542where
543 S: serde::Serializer,
544{
545 serializer.serialize_bytes(&key.to_bytes())
546}
547
548fn deserialize_v4_pubkey<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
549where
550 D: serde::Deserializer<'de>,
551{
552 let bytes = Vec::<u8>::deserialize(deserializer)?;
553 PublicKey::from_slice(&bytes).map_err(serde::de::Error::custom)
554}
555
556#[non_exhaustive]
558#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
559#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
560pub enum CurrencyUnit {
561 #[default]
563 Sat,
564 Msat,
566 Usd,
568 Eur,
570 Auth,
572 Custom(String),
574}
575
576#[cfg(feature = "mint")]
577impl CurrencyUnit {
578 pub fn derivation_index(&self) -> Option<u32> {
580 match self {
581 Self::Sat => Some(0),
582 Self::Msat => Some(1),
583 Self::Usd => Some(2),
584 Self::Eur => Some(3),
585 Self::Auth => Some(4),
586 _ => None,
587 }
588 }
589}
590
591impl FromStr for CurrencyUnit {
592 type Err = Error;
593 fn from_str(value: &str) -> Result<Self, Self::Err> {
594 let value = &value.to_uppercase();
595 match value.as_str() {
596 "SAT" => Ok(Self::Sat),
597 "MSAT" => Ok(Self::Msat),
598 "USD" => Ok(Self::Usd),
599 "EUR" => Ok(Self::Eur),
600 "AUTH" => Ok(Self::Auth),
601 c => Ok(Self::Custom(c.to_string())),
602 }
603 }
604}
605
606impl fmt::Display for CurrencyUnit {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 let s = match self {
609 CurrencyUnit::Sat => "SAT",
610 CurrencyUnit::Msat => "MSAT",
611 CurrencyUnit::Usd => "USD",
612 CurrencyUnit::Eur => "EUR",
613 CurrencyUnit::Auth => "AUTH",
614 CurrencyUnit::Custom(unit) => unit,
615 };
616 if let Some(width) = f.width() {
617 write!(f, "{:width$}", s.to_lowercase(), width = width)
618 } else {
619 write!(f, "{}", s.to_lowercase())
620 }
621 }
622}
623
624impl Serialize for CurrencyUnit {
625 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
626 where
627 S: serde::Serializer,
628 {
629 serializer.serialize_str(&self.to_string())
630 }
631}
632
633impl<'de> Deserialize<'de> for CurrencyUnit {
634 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
635 where
636 D: Deserializer<'de>,
637 {
638 let currency: String = String::deserialize(deserializer)?;
639 Self::from_str(¤cy).map_err(|_| serde::de::Error::custom("Unsupported unit"))
640 }
641}
642
643#[non_exhaustive]
645#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
646#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
647pub enum PaymentMethod {
648 #[default]
650 Bolt11,
651 Custom(String),
653}
654
655impl FromStr for PaymentMethod {
656 type Err = Error;
657 fn from_str(value: &str) -> Result<Self, Self::Err> {
658 match value.to_lowercase().as_str() {
659 "bolt11" => Ok(Self::Bolt11),
660 c => Ok(Self::Custom(c.to_string())),
661 }
662 }
663}
664
665impl fmt::Display for PaymentMethod {
666 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667 match self {
668 PaymentMethod::Bolt11 => write!(f, "bolt11"),
669 PaymentMethod::Custom(p) => write!(f, "{p}"),
670 }
671 }
672}
673
674impl Serialize for PaymentMethod {
675 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
676 where
677 S: serde::Serializer,
678 {
679 serializer.serialize_str(&self.to_string())
680 }
681}
682
683impl<'de> Deserialize<'de> for PaymentMethod {
684 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
685 where
686 D: Deserializer<'de>,
687 {
688 let payment_method: String = String::deserialize(deserializer)?;
689 Self::from_str(&payment_method).map_err(|_| de::Error::custom("Unsupported payment method"))
690 }
691}
692
693#[cfg(feature = "wallet")]
695#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
696pub struct PreMint {
697 pub blinded_message: BlindedMessage,
699 pub secret: Secret,
701 pub r: SecretKey,
703 pub amount: Amount,
705}
706
707#[cfg(feature = "wallet")]
708impl Ord for PreMint {
709 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
710 self.amount.cmp(&other.amount)
711 }
712}
713
714#[cfg(feature = "wallet")]
715impl PartialOrd for PreMint {
716 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
717 Some(self.cmp(other))
718 }
719}
720
721#[cfg(feature = "wallet")]
723#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
724pub struct PreMintSecrets {
725 pub secrets: Vec<PreMint>,
727 pub keyset_id: Id,
729}
730
731#[cfg(feature = "wallet")]
732impl PreMintSecrets {
733 pub fn new(keyset_id: Id) -> Self {
735 Self {
736 secrets: Vec::new(),
737 keyset_id,
738 }
739 }
740
741 pub fn random(
743 keyset_id: Id,
744 amount: Amount,
745 amount_split_target: &SplitTarget,
746 ) -> Result<Self, Error> {
747 let amount_split = amount.split_targeted(amount_split_target)?;
748
749 let mut output = Vec::with_capacity(amount_split.len());
750
751 for amount in amount_split {
752 let secret = Secret::generate();
753 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
754
755 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
756
757 output.push(PreMint {
758 secret,
759 blinded_message,
760 r,
761 amount,
762 });
763 }
764
765 Ok(PreMintSecrets {
766 secrets: output,
767 keyset_id,
768 })
769 }
770
771 pub fn from_secrets(
773 keyset_id: Id,
774 amounts: Vec<Amount>,
775 secrets: Vec<Secret>,
776 ) -> Result<Self, Error> {
777 let mut output = Vec::with_capacity(secrets.len());
778
779 for (secret, amount) in secrets.into_iter().zip(amounts) {
780 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
781
782 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
783
784 output.push(PreMint {
785 secret,
786 blinded_message,
787 r,
788 amount,
789 });
790 }
791
792 Ok(PreMintSecrets {
793 secrets: output,
794 keyset_id,
795 })
796 }
797
798 pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
800 let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
801
802 let mut output = Vec::with_capacity(count as usize);
803
804 for _i in 0..count {
805 let secret = Secret::generate();
806 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
807
808 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
809
810 output.push(PreMint {
811 secret,
812 blinded_message,
813 r,
814 amount: Amount::ZERO,
815 })
816 }
817
818 Ok(PreMintSecrets {
819 secrets: output,
820 keyset_id,
821 })
822 }
823
824 pub fn with_conditions(
826 keyset_id: Id,
827 amount: Amount,
828 amount_split_target: &SplitTarget,
829 conditions: &SpendingConditions,
830 ) -> Result<Self, Error> {
831 let amount_split = amount.split_targeted(amount_split_target)?;
832
833 let mut output = Vec::with_capacity(amount_split.len());
834
835 for amount in amount_split {
836 let secret: nut10::Secret = conditions.clone().into();
837
838 let secret: Secret = secret.try_into()?;
839 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
840
841 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
842
843 output.push(PreMint {
844 secret,
845 blinded_message,
846 r,
847 amount,
848 });
849 }
850
851 Ok(PreMintSecrets {
852 secrets: output,
853 keyset_id,
854 })
855 }
856
857 #[inline]
859 pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
860 self.secrets.iter()
861 }
862
863 #[inline]
865 pub fn len(&self) -> usize {
866 self.secrets.len()
867 }
868
869 #[inline]
871 pub fn is_empty(&self) -> bool {
872 self.secrets.is_empty()
873 }
874
875 pub fn total_amount(&self) -> Result<Amount, Error> {
877 Ok(Amount::try_sum(
878 self.secrets.iter().map(|PreMint { amount, .. }| *amount),
879 )?)
880 }
881
882 #[inline]
884 pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
885 self.iter().map(|pm| pm.blinded_message.clone()).collect()
886 }
887
888 #[inline]
890 pub fn secrets(&self) -> Vec<Secret> {
891 self.iter().map(|pm| pm.secret.clone()).collect()
892 }
893
894 #[inline]
896 pub fn rs(&self) -> Vec<SecretKey> {
897 self.iter().map(|pm| pm.r.clone()).collect()
898 }
899
900 #[inline]
902 pub fn amounts(&self) -> Vec<Amount> {
903 self.iter().map(|pm| pm.amount).collect()
904 }
905
906 #[inline]
908 pub fn combine(&mut self, mut other: Self) {
909 self.secrets.append(&mut other.secrets)
910 }
911
912 #[inline]
914 pub fn sort_secrets(&mut self) {
915 self.secrets.sort();
916 }
917}
918
919#[cfg(feature = "wallet")]
921impl Iterator for PreMintSecrets {
922 type Item = PreMint;
923
924 fn next(&mut self) -> Option<Self::Item> {
925 self.secrets.pop()
927 }
928}
929
930#[cfg(feature = "wallet")]
931impl Ord for PreMintSecrets {
932 fn cmp(&self, other: &Self) -> Ordering {
933 self.secrets.cmp(&other.secrets)
934 }
935}
936
937#[cfg(feature = "wallet")]
938impl PartialOrd for PreMintSecrets {
939 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
940 Some(self.cmp(other))
941 }
942}
943
944#[cfg(test)]
945mod tests {
946 use std::str::FromStr;
947
948 use super::*;
949
950 #[test]
951 fn test_proof_serialize() {
952 let proof = "[{\"id\":\"009a1f293253e41e\",\"amount\":2,\"secret\":\"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\"C\":\"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"},{\"id\":\"009a1f293253e41e\",\"amount\":8,\"secret\":\"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\"C\":\"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"}]";
953 let proof: Proofs = serde_json::from_str(proof).unwrap();
954
955 assert_eq!(
956 proof[0].clone().keyset_id,
957 Id::from_str("009a1f293253e41e").unwrap()
958 );
959
960 assert_eq!(proof.len(), 2);
961 }
962
963 #[test]
964 #[cfg(feature = "wallet")]
965 fn test_blank_blinded_messages() {
966 let b = PreMintSecrets::blank(
967 Id::from_str("009a1f293253e41e").unwrap(),
968 Amount::from(1000),
969 )
970 .unwrap();
971 assert_eq!(b.len(), 10);
972
973 let b = PreMintSecrets::blank(Id::from_str("009a1f293253e41e").unwrap(), Amount::from(1))
974 .unwrap();
975 assert_eq!(b.len(), 1);
976 }
977}