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
12#[cfg(feature = "mint")]
13use bitcoin::hashes::Hash as BitcoinHash;
14use serde::{Deserialize, Deserializer, Serialize};
15use thiserror::Error;
16use unicode_normalization::UnicodeNormalization;
17
18use super::nut02::ShortKeysetId;
19#[cfg(feature = "wallet")]
20use super::nut10;
21#[cfg(feature = "wallet")]
22use crate::amount::FeeAndAmounts;
23#[cfg(feature = "wallet")]
24use crate::amount::SplitTarget;
25#[cfg(feature = "wallet")]
26use crate::dhke::blind_message;
27use crate::dhke::hash_to_curve;
28use crate::nuts::nut01::PublicKey;
29#[cfg(feature = "wallet")]
30use crate::nuts::nut01::SecretKey;
31use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness};
32use crate::nuts::nut12::BlindSignatureDleq;
33use crate::nuts::nut14::{serde_htlc_witness, HTLCWitness};
34use crate::nuts::{Id, ProofDleq};
35use crate::secret::Secret;
36use crate::Amount;
37
38pub mod token;
39pub use token::{Token, TokenV3, TokenV4};
40
41pub type Proofs = Vec<Proof>;
43
44pub trait ProofsMethods {
46 fn count_by_keyset(&self) -> HashMap<Id, u64>;
48
49 fn sum_by_keyset(&self) -> HashMap<Id, Amount>;
51
52 fn total_amount(&self) -> Result<Amount, Error>;
54
55 fn ys(&self) -> Result<Vec<PublicKey>, Error>;
57
58 fn without_dleqs(&self) -> Proofs;
60
61 fn without_p2pk_e(&self) -> Proofs;
63}
64
65impl ProofsMethods for Proofs {
66 fn count_by_keyset(&self) -> HashMap<Id, u64> {
67 count_by_keyset(self.iter())
68 }
69
70 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
71 sum_by_keyset(self.iter())
72 }
73
74 fn total_amount(&self) -> Result<Amount, Error> {
75 total_amount(self.iter())
76 }
77
78 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
79 ys(self.iter())
80 }
81
82 fn without_dleqs(&self) -> Proofs {
83 self.iter()
84 .map(|p| {
85 let mut p = p.clone();
86 p.dleq = None;
87 p
88 })
89 .collect()
90 }
91
92 fn without_p2pk_e(&self) -> Proofs {
93 self.iter()
94 .map(|p| {
95 let mut p = p.clone();
96 p.p2pk_e = None;
97 p
98 })
99 .collect()
100 }
101}
102
103impl ProofsMethods for HashSet<Proof> {
104 fn count_by_keyset(&self) -> HashMap<Id, u64> {
105 count_by_keyset(self.iter())
106 }
107
108 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
109 sum_by_keyset(self.iter())
110 }
111
112 fn total_amount(&self) -> Result<Amount, Error> {
113 total_amount(self.iter())
114 }
115
116 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
117 ys(self.iter())
118 }
119
120 fn without_dleqs(&self) -> Proofs {
121 self.iter()
122 .map(|p| {
123 let mut p = p.clone();
124 p.dleq = None;
125 p
126 })
127 .collect()
128 }
129
130 fn without_p2pk_e(&self) -> Proofs {
131 self.iter()
132 .map(|p| {
133 let mut p = p.clone();
134 p.p2pk_e = None;
135 p
136 })
137 .collect()
138 }
139}
140
141fn count_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, u64> {
142 let mut counts = HashMap::new();
143 for proof in proofs {
144 *counts.entry(proof.keyset_id).or_insert(0) += 1;
145 }
146 counts
147}
148
149fn sum_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, Amount> {
150 let mut sums = HashMap::new();
151 for proof in proofs {
152 *sums.entry(proof.keyset_id).or_insert(Amount::ZERO) += proof.amount;
153 }
154 sums
155}
156
157fn total_amount<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Amount, Error> {
158 Amount::try_sum(proofs.map(|p| p.amount)).map_err(Into::into)
159}
160
161fn ys<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Vec<PublicKey>, Error> {
162 proofs.map(Proof::y).collect::<Result<Vec<PublicKey>, _>>()
163}
164
165#[derive(Debug, Error)]
167pub enum Error {
168 #[error("Proofs required in token")]
170 ProofsRequired,
171 #[error("Unsupported token")]
173 UnsupportedToken,
174 #[error("Unsupported unit")]
176 UnsupportedUnit,
177 #[error("Unsupported payment method")]
179 UnsupportedPaymentMethod,
180 #[error("Duplicate proofs in token")]
182 DuplicateProofs,
183 #[error(transparent)]
185 SerdeJsonError(#[from] serde_json::Error),
186 #[error(transparent)]
188 Utf8ParseError(#[from] FromUtf8Error),
189 #[error(transparent)]
191 Base64Error(#[from] bitcoin::base64::DecodeError),
192 #[error(transparent)]
194 CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
195 #[error(transparent)]
197 CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
198 #[error(transparent)]
200 Amount(#[from] crate::amount::Error),
201 #[error(transparent)]
203 Secret(#[from] crate::secret::Error),
204 #[error(transparent)]
206 DHKE(#[from] crate::dhke::Error),
207 #[error(transparent)]
209 NUT10(#[from] crate::nuts::nut10::Error),
210 #[error(transparent)]
212 NUT11(#[from] crate::nuts::nut11::Error),
213 #[error(transparent)]
215 NUT02(#[from] crate::nuts::nut02::Error),
216 #[cfg(feature = "wallet")]
217 #[error(transparent)]
218 NUT28(#[from] crate::nuts::nut28::Error),
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
224pub struct BlindedMessage {
225 pub amount: Amount,
229 #[serde(rename = "id")]
233 pub keyset_id: Id,
234 #[serde(rename = "B_")]
238 pub blinded_secret: PublicKey,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub witness: Option<Witness>,
244}
245
246impl BlindedMessage {
247 #[inline]
249 pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
250 Self {
251 amount,
252 keyset_id,
253 blinded_secret,
254 witness: None,
255 }
256 }
257
258 #[inline]
260 pub fn witness(&mut self, witness: Witness) {
261 self.witness = Some(witness);
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267pub struct BlindSignature {
268 pub amount: Amount,
272 #[serde(rename = "id")]
276 pub keyset_id: Id,
277 #[serde(rename = "C_")]
281 pub c: PublicKey,
282 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub dleq: Option<BlindSignatureDleq>,
287}
288
289impl Ord for BlindSignature {
290 fn cmp(&self, other: &Self) -> Ordering {
291 self.amount.cmp(&other.amount)
292 }
293}
294
295impl PartialOrd for BlindSignature {
296 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
297 Some(self.cmp(other))
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
303#[serde(untagged)]
304pub enum Witness {
305 #[serde(with = "serde_htlc_witness")]
307 HTLCWitness(HTLCWitness),
308 #[serde(with = "serde_p2pk_witness")]
310 P2PKWitness(P2PKWitness),
311}
312
313impl From<P2PKWitness> for Witness {
314 fn from(witness: P2PKWitness) -> Self {
315 Self::P2PKWitness(witness)
316 }
317}
318
319impl From<HTLCWitness> for Witness {
320 fn from(witness: HTLCWitness) -> Self {
321 Self::HTLCWitness(witness)
322 }
323}
324
325impl Witness {
326 pub fn add_signatures(&mut self, signatures: Vec<String>) {
328 match self {
329 Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatures),
330 Self::HTLCWitness(htlc_witness) => match &mut htlc_witness.signatures {
331 Some(sigs) => sigs.extend(signatures),
332 None => htlc_witness.signatures = Some(signatures),
333 },
334 }
335 }
336
337 pub fn signatures(&self) -> Option<Vec<String>> {
339 match self {
340 Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
341 Self::HTLCWitness(witness) => witness.signatures.clone(),
342 }
343 }
344
345 pub fn preimage(&self) -> Option<String> {
347 match self {
348 Self::P2PKWitness(_witness) => None,
349 Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
350 }
351 }
352}
353
354impl std::fmt::Display for Witness {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 write!(
357 f,
358 "{}",
359 serde_json::to_string(self).map_err(|_| std::fmt::Error)?
360 )
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub struct Proof {
367 pub amount: Amount,
369 #[serde(rename = "id")]
371 pub keyset_id: Id,
372 pub secret: Secret,
374 #[serde(rename = "C")]
376 pub c: PublicKey,
377 #[serde(default, skip_serializing_if = "Option::is_none")]
379 pub witness: Option<Witness>,
380 #[serde(default, skip_serializing_if = "Option::is_none")]
382 pub dleq: Option<ProofDleq>,
383 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub p2pk_e: Option<PublicKey>,
387}
388
389impl Proof {
390 pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
392 Proof {
393 amount,
394 keyset_id,
395 secret,
396 c,
397 witness: None,
398 dleq: None,
399 p2pk_e: None,
400 }
401 }
402
403 pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
405 active_keyset_ids.contains(&self.keyset_id)
406 }
407
408 pub fn y(&self) -> Result<PublicKey, Error> {
412 Ok(hash_to_curve(self.secret.as_bytes())?)
413 }
414}
415
416impl Hash for Proof {
417 fn hash<H: Hasher>(&self, state: &mut H) {
418 self.secret.hash(state);
419 }
420}
421
422impl Ord for Proof {
423 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
424 self.amount.cmp(&other.amount)
425 }
426}
427
428impl PartialOrd for Proof {
429 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
430 Some(self.cmp(other))
431 }
432}
433
434#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
436pub struct ProofV4 {
437 #[serde(rename = "a")]
439 pub amount: Amount,
440 #[serde(rename = "s")]
442 pub secret: Secret,
443 #[serde(
445 serialize_with = "serialize_v4_pubkey",
446 deserialize_with = "deserialize_v4_pubkey"
447 )]
448 pub c: PublicKey,
449 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub witness: Option<Witness>,
452 #[serde(rename = "d")]
454 pub dleq: Option<ProofDleq>,
455 #[serde(rename = "pe", default, skip_serializing_if = "Option::is_none")]
457 pub p2pk_e: Option<PublicKey>,
458}
459
460impl ProofV4 {
461 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
463 Proof {
464 amount: self.amount,
465 keyset_id: *keyset_id,
466 secret: self.secret.clone(),
467 c: self.c,
468 witness: self.witness.clone(),
469 dleq: self.dleq.clone(),
470 p2pk_e: self.p2pk_e,
471 }
472 }
473}
474
475impl Hash for ProofV4 {
476 fn hash<H: Hasher>(&self, state: &mut H) {
477 self.secret.hash(state);
478 }
479}
480
481impl From<Proof> for ProofV4 {
482 fn from(proof: Proof) -> ProofV4 {
483 let Proof {
484 amount,
485 secret,
486 c,
487 witness,
488 dleq,
489 p2pk_e,
490 ..
491 } = proof;
492 ProofV4 {
493 amount,
494 secret,
495 c,
496 witness,
497 dleq,
498 p2pk_e,
499 }
500 }
501}
502
503impl From<ProofV3> for ProofV4 {
504 fn from(proof: ProofV3) -> Self {
505 Self {
506 amount: proof.amount,
507 secret: proof.secret,
508 c: proof.c,
509 witness: proof.witness,
510 dleq: proof.dleq,
511 p2pk_e: None,
512 }
513 }
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
518pub struct ProofV3 {
519 pub amount: Amount,
521 #[serde(rename = "id")]
523 pub keyset_id: ShortKeysetId,
524 pub secret: Secret,
526 #[serde(rename = "C")]
528 pub c: PublicKey,
529 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub witness: Option<Witness>,
532 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub dleq: Option<ProofDleq>,
535}
536
537impl ProofV3 {
538 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
540 Proof {
541 amount: self.amount,
542 keyset_id: *keyset_id,
543 secret: self.secret.clone(),
544 c: self.c,
545 witness: self.witness.clone(),
546 dleq: self.dleq.clone(),
547 p2pk_e: None,
548 }
549 }
550}
551
552impl From<Proof> for ProofV3 {
553 fn from(proof: Proof) -> ProofV3 {
554 let Proof {
555 amount,
556 keyset_id,
557 secret,
558 c,
559 witness,
560 dleq,
561 ..
562 } = proof;
563 ProofV3 {
564 amount,
565 secret,
566 c,
567 witness,
568 dleq,
569 keyset_id: keyset_id.into(),
570 }
571 }
572}
573
574impl Hash for ProofV3 {
575 fn hash<H: Hasher>(&self, state: &mut H) {
576 self.secret.hash(state);
577 }
578}
579
580fn serialize_v4_pubkey<S>(key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
581where
582 S: serde::Serializer,
583{
584 serializer.serialize_bytes(&key.to_bytes())
585}
586
587fn deserialize_v4_pubkey<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
588where
589 D: serde::Deserializer<'de>,
590{
591 let bytes = Vec::<u8>::deserialize(deserializer)?;
592 PublicKey::from_slice(&bytes).map_err(serde::de::Error::custom)
593}
594
595#[non_exhaustive]
597#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
598pub enum CurrencyUnit {
599 #[default]
601 Sat,
602 Msat,
604 Usd,
606 Eur,
608 Auth,
610 Custom(String),
612}
613
614#[cfg(feature = "mint")]
615impl CurrencyUnit {
616 #[deprecated(
618 since = "0.15.0",
619 note = "This function is outdated; use `hashed_derivation_index` instead."
620 )]
621 pub fn derivation_index(&self) -> Option<u32> {
622 match self {
623 Self::Sat => Some(0),
624 Self::Msat => Some(1),
625 Self::Usd => Some(2),
626 Self::Eur => Some(3),
627 Self::Auth => Some(4),
628 _ => None,
629 }
630 }
631
632 pub fn custom<S: AsRef<str>>(value: S) -> Self {
634 Self::Custom(normalize_custom_unit(value.as_ref()).to_uppercase())
635 }
636
637 pub fn hashed_derivation_index(&self) -> u32 {
639 use bitcoin::hashes::sha256;
640
641 let unit_str = self.to_string().to_uppercase();
643
644 let bytes = <sha256::Hash as BitcoinHash>::hash(unit_str.as_bytes());
645 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) & !(1 << 31)
647 }
648}
649
650fn normalize_custom_unit(value: &str) -> String {
651 let trimmed = value.trim_matches(|c: char| matches!(c, ' ' | '\t' | '\r' | '\n'));
652 trimmed.nfc().collect::<String>()
653}
654
655impl FromStr for CurrencyUnit {
656 type Err = Error;
657 fn from_str(value: &str) -> Result<Self, Self::Err> {
658 let upper_value = value.to_uppercase();
659 match upper_value.as_str() {
660 "SAT" => Ok(Self::Sat),
661 "MSAT" => Ok(Self::Msat),
662 "USD" => Ok(Self::Usd),
663 "EUR" => Ok(Self::Eur),
664 "AUTH" => Ok(Self::Auth),
665 _ => Ok(Self::Custom(normalize_custom_unit(value))),
666 }
667 }
668}
669
670impl fmt::Display for CurrencyUnit {
671 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672 let s = match self {
674 CurrencyUnit::Sat => "SAT",
675 CurrencyUnit::Msat => "MSAT",
676 CurrencyUnit::Usd => "USD",
677 CurrencyUnit::Eur => "EUR",
678 CurrencyUnit::Auth => "AUTH",
679 CurrencyUnit::Custom(unit) => unit,
680 };
681
682 if let Some(width) = f.width() {
683 write!(
684 f,
685 "{:width$}",
686 normalize_custom_unit(s).to_lowercase(),
687 width = width
688 )
689 } else {
690 write!(f, "{}", normalize_custom_unit(s).to_lowercase())
691 }
692 }
693}
694
695impl Serialize for CurrencyUnit {
696 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
697 where
698 S: serde::Serializer,
699 {
700 serializer.serialize_str(&self.to_string().to_lowercase())
701 }
702}
703
704impl<'de> Deserialize<'de> for CurrencyUnit {
705 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
706 where
707 D: Deserializer<'de>,
708 {
709 let currency: String = String::deserialize(deserializer)?;
710 Self::from_str(¤cy).map_err(|_| serde::de::Error::custom("Unsupported unit"))
711 }
712}
713
714#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
716pub enum KnownMethod {
717 Bolt11,
719 Bolt12,
721 Onchain,
723}
724
725impl KnownMethod {
726 pub fn as_str(&self) -> &str {
728 match self {
729 Self::Bolt11 => "bolt11",
730 Self::Bolt12 => "bolt12",
731 Self::Onchain => "onchain",
732 }
733 }
734}
735
736impl fmt::Display for KnownMethod {
737 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738 write!(f, "{}", self.as_str())
739 }
740}
741
742impl FromStr for KnownMethod {
743 type Err = Error;
744 fn from_str(value: &str) -> Result<Self, Self::Err> {
745 match value.to_lowercase().as_str() {
746 "bolt11" => Ok(Self::Bolt11),
747 "bolt12" => Ok(Self::Bolt12),
748 "onchain" => Ok(Self::Onchain),
749 _ => Err(Error::UnsupportedPaymentMethod),
750 }
751 }
752}
753
754#[derive(Debug, Clone, PartialEq, Eq, Hash)]
758pub enum PaymentMethod {
759 Known(KnownMethod),
761 Custom(String),
763}
764
765impl PaymentMethod {
766 pub const BOLT11: Self = Self::Known(KnownMethod::Bolt11);
768 pub const BOLT12: Self = Self::Known(KnownMethod::Bolt12);
770
771 pub fn new(method: String) -> Self {
773 Self::from_str(&method).unwrap_or_else(|_| Self::Custom(method.to_lowercase()))
774 }
775
776 pub fn is_valid_custom_method_name(method: &str) -> bool {
778 !method.is_empty()
779 && method
780 .chars()
781 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
782 }
783
784 pub fn is_valid_method_name(&self) -> bool {
786 match self {
787 Self::Known(_) => true,
788 Self::Custom(method) => Self::is_valid_custom_method_name(method),
789 }
790 }
791
792 pub fn as_str(&self) -> &str {
794 match self {
795 Self::Known(known) => known.as_str(),
796 Self::Custom(custom) => custom.as_str(),
797 }
798 }
799
800 pub fn is_known(&self) -> bool {
802 matches!(self, Self::Known(_))
803 }
804
805 pub fn is_custom(&self) -> bool {
807 matches!(self, Self::Custom(_))
808 }
809
810 pub fn is_bolt11(&self) -> bool {
812 matches!(self, Self::Known(KnownMethod::Bolt11))
813 }
814
815 pub fn is_bolt12(&self) -> bool {
817 matches!(self, Self::Known(KnownMethod::Bolt12))
818 }
819
820 pub fn is_onchain(&self) -> bool {
822 matches!(self, Self::Known(KnownMethod::Onchain))
823 }
824}
825
826impl FromStr for PaymentMethod {
827 type Err = Error;
828 fn from_str(value: &str) -> Result<Self, Self::Err> {
829 match KnownMethod::from_str(value) {
830 Ok(known) => Ok(Self::Known(known)),
831 Err(_) => Ok(Self::Custom(value.to_lowercase())),
832 }
833 }
834}
835
836impl fmt::Display for PaymentMethod {
837 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
838 write!(f, "{}", self.as_str())
839 }
840}
841
842impl From<String> for PaymentMethod {
843 fn from(s: String) -> Self {
844 Self::from_str(&s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
845 }
846}
847
848impl From<&str> for PaymentMethod {
849 fn from(s: &str) -> Self {
850 Self::from_str(s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
851 }
852}
853
854impl From<KnownMethod> for PaymentMethod {
855 fn from(known: KnownMethod) -> Self {
856 Self::Known(known)
857 }
858}
859
860impl PartialEq<&str> for PaymentMethod {
862 fn eq(&self, other: &&str) -> bool {
863 self.as_str() == *other
864 }
865}
866
867impl PartialEq<str> for PaymentMethod {
868 fn eq(&self, other: &str) -> bool {
869 self.as_str() == other
870 }
871}
872
873impl PartialEq<PaymentMethod> for &str {
874 fn eq(&self, other: &PaymentMethod) -> bool {
875 *self == other.as_str()
876 }
877}
878
879impl PartialEq<KnownMethod> for PaymentMethod {
880 fn eq(&self, other: &KnownMethod) -> bool {
881 matches!(self, Self::Known(k) if k == other)
882 }
883}
884
885impl PartialEq<PaymentMethod> for KnownMethod {
886 fn eq(&self, other: &PaymentMethod) -> bool {
887 matches!(other, PaymentMethod::Known(k) if k == self)
888 }
889}
890
891impl Serialize for PaymentMethod {
892 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
893 where
894 S: serde::Serializer,
895 {
896 serializer.serialize_str(self.as_str())
897 }
898}
899
900impl<'de> Deserialize<'de> for PaymentMethod {
901 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
902 where
903 D: Deserializer<'de>,
904 {
905 let payment_method: String = String::deserialize(deserializer)?;
906 Ok(Self::from_str(&payment_method).unwrap_or(Self::Custom(payment_method)))
907 }
908}
909
910#[cfg(feature = "wallet")]
912#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
913pub struct PreMint {
914 pub blinded_message: BlindedMessage,
916 pub secret: Secret,
918 pub r: SecretKey,
920 pub amount: Amount,
922}
923
924#[cfg(feature = "wallet")]
925impl Ord for PreMint {
926 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
927 self.amount.cmp(&other.amount)
928 }
929}
930
931#[cfg(feature = "wallet")]
932impl PartialOrd for PreMint {
933 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
934 Some(self.cmp(other))
935 }
936}
937
938#[cfg(feature = "wallet")]
940#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
941pub struct PreMintSecrets {
942 pub secrets: Vec<PreMint>,
944 pub keyset_id: Id,
946}
947
948#[cfg(feature = "wallet")]
949impl PreMintSecrets {
950 pub fn new(keyset_id: Id) -> Self {
952 Self {
953 secrets: Vec::new(),
954 keyset_id,
955 }
956 }
957
958 pub fn random(
960 keyset_id: Id,
961 amount: Amount,
962 amount_split_target: &SplitTarget,
963 fee_and_amounts: &FeeAndAmounts,
964 ) -> Result<Self, Error> {
965 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
966
967 let mut output = Vec::with_capacity(amount_split.len());
968
969 for amount in amount_split {
970 let secret = Secret::generate();
971 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
972
973 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
974
975 output.push(PreMint {
976 secret,
977 blinded_message,
978 r,
979 amount,
980 });
981 }
982
983 Ok(PreMintSecrets {
984 secrets: output,
985 keyset_id,
986 })
987 }
988
989 pub fn from_secrets(
991 keyset_id: Id,
992 amounts: Vec<Amount>,
993 secrets: Vec<Secret>,
994 ) -> Result<Self, Error> {
995 let mut output = Vec::with_capacity(secrets.len());
996
997 for (secret, amount) in secrets.into_iter().zip(amounts) {
998 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
999
1000 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1001
1002 output.push(PreMint {
1003 secret,
1004 blinded_message,
1005 r,
1006 amount,
1007 });
1008 }
1009
1010 Ok(PreMintSecrets {
1011 secrets: output,
1012 keyset_id,
1013 })
1014 }
1015
1016 pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
1018 let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
1019
1020 let mut output = Vec::with_capacity(count as usize);
1021
1022 for _i in 0..count {
1023 let secret = Secret::generate();
1024 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1025
1026 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
1027
1028 output.push(PreMint {
1029 secret,
1030 blinded_message,
1031 r,
1032 amount: Amount::ZERO,
1033 })
1034 }
1035
1036 Ok(PreMintSecrets {
1037 secrets: output,
1038 keyset_id,
1039 })
1040 }
1041
1042 #[cfg(feature = "wallet")]
1044 pub fn with_p2bk(
1045 keyset_id: Id,
1046 amount: Amount,
1047 amount_split_target: &SplitTarget,
1048 receiver_pubkey: PublicKey,
1049 conditions: Option<crate::nuts::nut10::Conditions>,
1050 ephemeral_keys: &[crate::nuts::nut01::SecretKey],
1051 fee_and_amounts: &FeeAndAmounts,
1052 ) -> Result<Self, Error> {
1053 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
1054
1055 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1056
1057 let mut output = Vec::with_capacity(amount_split.len());
1058
1059 let is_sig_all = conditions
1060 .as_ref()
1061 .is_some_and(|c| c.sig_flag == crate::nuts::nut11::SigFlag::SigAll);
1062 if !is_sig_all && ephemeral_keys.len() != amount_split.len() {
1063 return Err(Error::NUT28(
1064 crate::nuts::nut28::Error::InvalidCanonicalSlot(255),
1065 ));
1066 }
1067
1068 for (i, amount) in amount_split.into_iter().enumerate() {
1069 let ephemeral_key = if is_sig_all {
1070 &ephemeral_keys[0]
1071 } else {
1072 &ephemeral_keys[i]
1073 };
1074
1075 let r0 = ecdh_kdf(ephemeral_key, &receiver_pubkey, 0).map_err(Error::NUT28)?;
1077 let blinded_pubkey = blind_public_key(&receiver_pubkey, &r0).map_err(Error::NUT28)?;
1078
1079 let mut blinded_conditions = conditions.clone();
1080
1081 if let Some(ref mut cond) = blinded_conditions {
1083 let mut slot_idx = 1;
1084
1085 if let Some(ref mut pubkeys) = cond.pubkeys {
1086 for pk in pubkeys.iter_mut() {
1087 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1088 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1089 slot_idx += 1;
1090 }
1091 }
1092
1093 if let Some(ref mut refund_keys) = cond.refund_keys {
1094 for pk in refund_keys.iter_mut() {
1095 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1096 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1097 slot_idx += 1;
1098 }
1099 }
1100 }
1101
1102 let p2pk_conditions = crate::nuts::SpendingConditions::P2PKConditions {
1103 data: blinded_pubkey,
1104 conditions: blinded_conditions,
1105 };
1106
1107 let secret: crate::nuts::nut10::Secret = p2pk_conditions.into();
1108 let secret: Secret = secret.try_into()?;
1109 let (blinded, rs) = blind_message(&secret.to_bytes(), None)?;
1110
1111 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1112
1113 output.push(PreMint {
1114 secret,
1115 blinded_message,
1116 r: rs,
1117 amount,
1118 });
1119 }
1120
1121 Ok(PreMintSecrets {
1122 secrets: output,
1123 keyset_id,
1124 })
1125 }
1126
1127 pub fn with_conditions(
1129 keyset_id: Id,
1130 amount: Amount,
1131 amount_split_target: &SplitTarget,
1132 conditions: &nut10::SpendingConditions,
1133 fee_and_amounts: &FeeAndAmounts,
1134 ) -> Result<Self, Error> {
1135 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1136
1137 let mut output = Vec::with_capacity(amount_split.len());
1138
1139 for amount in amount_split {
1140 let secret: nut10::Secret = conditions.clone().into();
1141
1142 let secret: Secret = secret.try_into()?;
1143 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1144
1145 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1146
1147 output.push(PreMint {
1148 secret,
1149 blinded_message,
1150 r,
1151 amount,
1152 });
1153 }
1154
1155 Ok(PreMintSecrets {
1156 secrets: output,
1157 keyset_id,
1158 })
1159 }
1160
1161 #[inline]
1163 pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
1164 self.secrets.iter()
1165 }
1166
1167 #[inline]
1169 pub fn len(&self) -> usize {
1170 self.secrets.len()
1171 }
1172
1173 #[inline]
1175 pub fn is_empty(&self) -> bool {
1176 self.secrets.is_empty()
1177 }
1178
1179 pub fn total_amount(&self) -> Result<Amount, Error> {
1181 Ok(Amount::try_sum(
1182 self.secrets.iter().map(|PreMint { amount, .. }| *amount),
1183 )?)
1184 }
1185
1186 #[inline]
1188 pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
1189 self.iter().map(|pm| pm.blinded_message.clone()).collect()
1190 }
1191
1192 #[inline]
1194 pub fn secrets(&self) -> Vec<Secret> {
1195 self.iter().map(|pm| pm.secret.clone()).collect()
1196 }
1197
1198 #[inline]
1200 pub fn rs(&self) -> Vec<SecretKey> {
1201 self.iter().map(|pm| pm.r.clone()).collect()
1202 }
1203
1204 #[inline]
1206 pub fn amounts(&self) -> Vec<Amount> {
1207 self.iter().map(|pm| pm.amount).collect()
1208 }
1209
1210 #[inline]
1212 pub fn combine(&mut self, mut other: Self) {
1213 self.secrets.append(&mut other.secrets)
1214 }
1215
1216 #[inline]
1218 pub fn sort_secrets(&mut self) {
1219 self.secrets.sort();
1220 }
1221}
1222
1223#[cfg(feature = "wallet")]
1225impl Iterator for PreMintSecrets {
1226 type Item = PreMint;
1227
1228 fn next(&mut self) -> Option<Self::Item> {
1229 if self.secrets.is_empty() {
1231 return None;
1232 }
1233 Some(self.secrets.remove(0))
1234 }
1235}
1236
1237#[cfg(feature = "wallet")]
1238impl Ord for PreMintSecrets {
1239 fn cmp(&self, other: &Self) -> Ordering {
1240 self.secrets.cmp(&other.secrets)
1241 }
1242}
1243
1244#[cfg(feature = "wallet")]
1245impl PartialOrd for PreMintSecrets {
1246 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1247 Some(self.cmp(other))
1248 }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use std::collections::hash_map::DefaultHasher;
1254 use std::collections::HashSet;
1255 use std::hash::{Hash, Hasher};
1256 use std::str::FromStr;
1257
1258 use super::*;
1259
1260 #[test]
1261 fn test_proof_serialize() {
1262 let proof = "[{\"id\":\"009a1f293253e41e\",\"amount\":2,\"secret\":\"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\"C\":\"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"},{\"id\":\"009a1f293253e41e\",\"amount\":8,\"secret\":\"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\"C\":\"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"}]";
1263 let proof: Proofs = serde_json::from_str(proof).unwrap();
1264
1265 assert_eq!(
1266 proof[0].clone().keyset_id,
1267 Id::from_str("009a1f293253e41e").unwrap()
1268 );
1269
1270 assert_eq!(proof.len(), 2);
1271 }
1272
1273 #[test]
1274 #[cfg(feature = "wallet")]
1275 fn test_blank_blinded_messages() {
1276 let b = PreMintSecrets::blank(
1277 Id::from_str("009a1f293253e41e").unwrap(),
1278 Amount::from(1000),
1279 )
1280 .unwrap();
1281 assert_eq!(b.len(), 10);
1282
1283 let b = PreMintSecrets::blank(Id::from_str("009a1f293253e41e").unwrap(), Amount::from(1))
1284 .unwrap();
1285 assert_eq!(b.len(), 1);
1286 }
1287
1288 #[test]
1289 #[cfg(feature = "wallet")]
1290 fn test_premint_secrets_accessors_and_total_amount() {
1291 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1292 let amounts = vec![
1293 Amount::from(1_u64),
1294 Amount::from(2_u64),
1295 Amount::from(4_u64),
1296 ];
1297 let secrets = vec![Secret::generate(), Secret::generate(), Secret::generate()];
1298
1299 let premint_secrets =
1300 PreMintSecrets::from_secrets(keyset_id, amounts.clone(), secrets.clone()).unwrap();
1301
1302 assert_eq!(premint_secrets.total_amount().unwrap(), Amount::from(7_u64));
1303 assert_eq!(premint_secrets.amounts(), amounts);
1304 assert_eq!(premint_secrets.secrets(), secrets);
1305
1306 let blinded_messages = premint_secrets.blinded_messages();
1307 assert_eq!(blinded_messages.len(), 3);
1308 assert_eq!(blinded_messages[0].amount, Amount::from(1_u64));
1309 assert_eq!(blinded_messages[1].amount, Amount::from(2_u64));
1310 assert_eq!(blinded_messages[2].amount, Amount::from(4_u64));
1311 assert!(blinded_messages
1312 .iter()
1313 .all(|message| message.keyset_id == keyset_id));
1314
1315 let rs = premint_secrets.rs();
1316 assert_eq!(rs.len(), 3);
1317 assert_eq!(rs[0], premint_secrets.secrets[0].r);
1318 assert_eq!(rs[1], premint_secrets.secrets[1].r);
1319 assert_eq!(rs[2], premint_secrets.secrets[2].r);
1320 }
1321
1322 #[test]
1323 #[cfg(feature = "wallet")]
1324 fn test_premint_secrets_combine_and_sort() {
1325 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1326 let mut combined = PreMintSecrets::from_secrets(
1327 keyset_id,
1328 vec![Amount::from(8_u64), Amount::from(2_u64)],
1329 vec![Secret::generate(), Secret::generate()],
1330 )
1331 .unwrap();
1332 let other = PreMintSecrets::from_secrets(
1333 keyset_id,
1334 vec![Amount::from(4_u64), Amount::from(1_u64)],
1335 vec![Secret::generate(), Secret::generate()],
1336 )
1337 .unwrap();
1338
1339 combined.combine(other);
1340
1341 assert_eq!(combined.len(), 4);
1342 assert_eq!(
1343 combined.amounts(),
1344 vec![
1345 Amount::from(8_u64),
1346 Amount::from(2_u64),
1347 Amount::from(4_u64),
1348 Amount::from(1_u64)
1349 ]
1350 );
1351
1352 combined.sort_secrets();
1353
1354 assert_eq!(
1355 combined.amounts(),
1356 vec![
1357 Amount::from(1_u64),
1358 Amount::from(2_u64),
1359 Amount::from(4_u64),
1360 Amount::from(8_u64)
1361 ]
1362 );
1363 }
1364
1365 #[test]
1366 #[cfg(feature = "wallet")]
1367 fn test_premint_secrets_iterator_next_yields_all_items() {
1368 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1369 let premint_secrets = PreMintSecrets::from_secrets(
1370 keyset_id,
1371 vec![
1372 Amount::from(1_u64),
1373 Amount::from(2_u64),
1374 Amount::from(4_u64),
1375 ],
1376 vec![Secret::generate(), Secret::generate(), Secret::generate()],
1377 )
1378 .unwrap();
1379 let expected = premint_secrets.secrets.clone();
1380 let mut iterated = premint_secrets.clone();
1381
1382 assert_eq!(iterated.next(), Some(expected[0].clone()));
1383 assert_eq!(iterated.next(), Some(expected[1].clone()));
1384 assert_eq!(iterated.next(), Some(expected[2].clone()));
1385 assert_eq!(iterated.next(), None);
1386 assert!(iterated.is_empty());
1387 }
1388
1389 #[test]
1390 fn custom_unit_ser_der() {
1391 let unit = CurrencyUnit::Custom(String::from("test"));
1392 let serialized = serde_json::to_string(&unit).unwrap();
1393 let deserialized: CurrencyUnit = serde_json::from_str(&serialized).unwrap();
1394 assert_eq!(unit, deserialized)
1395 }
1396
1397 #[test]
1398 #[cfg(feature = "mint")]
1399 fn test_currency_unit_custom_normalizes_and_stays_custom() {
1400 let unit = CurrencyUnit::custom(" usd\n");
1401
1402 assert_eq!(unit, CurrencyUnit::Custom("USD".to_string()));
1403 assert_ne!(unit, CurrencyUnit::default());
1404 assert_eq!(unit.to_string(), "usd");
1405 }
1406
1407 #[test]
1408 fn test_currency_unit_parsing() {
1409 assert_eq!(CurrencyUnit::from_str("sat").unwrap(), CurrencyUnit::Sat);
1410 assert_eq!(CurrencyUnit::from_str("SAT").unwrap(), CurrencyUnit::Sat);
1411 assert_eq!(CurrencyUnit::from_str("msat").unwrap(), CurrencyUnit::Msat);
1412 assert_eq!(CurrencyUnit::from_str("MSAT").unwrap(), CurrencyUnit::Msat);
1413 assert_eq!(CurrencyUnit::from_str("usd").unwrap(), CurrencyUnit::Usd);
1414 assert_eq!(CurrencyUnit::from_str("USD").unwrap(), CurrencyUnit::Usd);
1415 assert_eq!(CurrencyUnit::from_str("eur").unwrap(), CurrencyUnit::Eur);
1416 assert_eq!(CurrencyUnit::from_str("EUR").unwrap(), CurrencyUnit::Eur);
1417 assert_eq!(CurrencyUnit::from_str("auth").unwrap(), CurrencyUnit::Auth);
1418 assert_eq!(CurrencyUnit::from_str("AUTH").unwrap(), CurrencyUnit::Auth);
1419
1420 assert_eq!(
1422 CurrencyUnit::from_str("custom").unwrap(),
1423 CurrencyUnit::Custom("custom".to_string())
1424 );
1425 }
1426
1427 #[test]
1428 #[cfg(feature = "mint")]
1429 fn four_bytes_hash_currency_unit() {
1430 let unit = CurrencyUnit::Sat;
1431 let index = unit.hashed_derivation_index();
1432 assert_eq!(index, 1967237907);
1433
1434 let unit = CurrencyUnit::Msat;
1435 let index = unit.hashed_derivation_index();
1436 assert_eq!(index, 142929756);
1437
1438 let unit = CurrencyUnit::Eur;
1439 let index = unit.hashed_derivation_index();
1440 assert_eq!(index, 1473545324);
1441
1442 let unit = CurrencyUnit::Usd;
1443 let index = unit.hashed_derivation_index();
1444 assert_eq!(index, 577560378);
1445
1446 let unit = CurrencyUnit::Auth;
1447 let index = unit.hashed_derivation_index();
1448
1449 assert_eq!(index, 1222349093)
1450 }
1451
1452 #[test]
1453 fn test_payment_method_parsing() {
1454 assert_eq!(
1456 PaymentMethod::from_str("bolt11").unwrap(),
1457 PaymentMethod::BOLT11
1458 );
1459 assert_eq!(
1460 PaymentMethod::from_str("BOLT11").unwrap(),
1461 PaymentMethod::BOLT11
1462 );
1463 assert_eq!(
1464 PaymentMethod::from_str("Bolt11").unwrap(),
1465 PaymentMethod::Known(KnownMethod::Bolt11)
1466 );
1467
1468 assert_eq!(
1469 PaymentMethod::from_str("bolt12").unwrap(),
1470 PaymentMethod::BOLT12
1471 );
1472 assert_eq!(
1473 PaymentMethod::from_str("BOLT12").unwrap(),
1474 PaymentMethod::Known(KnownMethod::Bolt12)
1475 );
1476
1477 assert_eq!(
1479 PaymentMethod::from_str("custom").unwrap(),
1480 PaymentMethod::Custom("custom".to_string())
1481 );
1482 assert_eq!(
1483 PaymentMethod::from_str("PAYPAL").unwrap(),
1484 PaymentMethod::Custom("paypal".to_string())
1485 );
1486
1487 assert_eq!(PaymentMethod::BOLT11.as_str(), "bolt11");
1489 assert_eq!(PaymentMethod::BOLT12.as_str(), "bolt12");
1490 assert_eq!(PaymentMethod::from("paypal").as_str(), "paypal");
1491
1492 assert!(PaymentMethod::BOLT11 == "bolt11");
1494 assert!(PaymentMethod::BOLT12 == "bolt12");
1495 assert!(PaymentMethod::Custom("paypal".to_string()) == "paypal");
1496
1497 assert!(PaymentMethod::BOLT11 == KnownMethod::Bolt11);
1499 assert!(PaymentMethod::BOLT12 == KnownMethod::Bolt12);
1500
1501 let methods = vec![
1503 PaymentMethod::BOLT11,
1504 PaymentMethod::BOLT12,
1505 PaymentMethod::Custom("test".to_string()),
1506 ];
1507
1508 for method in methods {
1509 let serialized = serde_json::to_string(&method).unwrap();
1510 let deserialized: PaymentMethod = serde_json::from_str(&serialized).unwrap();
1511 assert_eq!(method, deserialized);
1512 }
1513 }
1514
1515 #[test]
1524 fn test_is_bolt12_with_bolt12() {
1525 let method = PaymentMethod::BOLT12;
1527 assert!(method.is_bolt12());
1528
1529 let method = PaymentMethod::Known(KnownMethod::Bolt12);
1531 assert!(method.is_bolt12());
1532 }
1533
1534 #[test]
1535 fn test_is_bolt12_with_non_bolt12() {
1536 let method = PaymentMethod::BOLT11;
1538 assert!(!method.is_bolt12());
1539
1540 let method = PaymentMethod::Known(KnownMethod::Bolt11);
1542 assert!(!method.is_bolt12());
1543
1544 let method = PaymentMethod::Custom("paypal".to_string());
1546 assert!(!method.is_bolt12());
1547
1548 let method = PaymentMethod::Custom("bolt12".to_string());
1549 assert!(!method.is_bolt12()); }
1551
1552 #[test]
1554 fn test_is_bolt12_comprehensive() {
1555 assert!(PaymentMethod::BOLT12.is_bolt12());
1557 assert!(PaymentMethod::Known(KnownMethod::Bolt12).is_bolt12());
1558
1559 assert!(!PaymentMethod::BOLT11.is_bolt12());
1560 assert!(!PaymentMethod::Known(KnownMethod::Bolt11).is_bolt12());
1561 assert!(!PaymentMethod::Custom("anything".to_string()).is_bolt12());
1562 assert!(!PaymentMethod::Custom("bolt12".to_string()).is_bolt12());
1563 }
1564
1565 #[test]
1571 fn test_is_onchain_comprehensive() {
1572 assert!(PaymentMethod::Known(KnownMethod::Onchain).is_onchain());
1573
1574 assert!(!PaymentMethod::BOLT11.is_onchain());
1575 assert!(!PaymentMethod::BOLT12.is_onchain());
1576 assert!(!PaymentMethod::Known(KnownMethod::Bolt11).is_onchain());
1577 assert!(!PaymentMethod::Known(KnownMethod::Bolt12).is_onchain());
1578 assert!(!PaymentMethod::Custom("anything".to_string()).is_onchain());
1579 assert!(!PaymentMethod::Custom("onchain".to_string()).is_onchain());
1580 }
1581
1582 #[test]
1583 fn test_witness_serialization() {
1584 let htlc_witness = HTLCWitness {
1585 preimage: "preimage".to_string(),
1586 signatures: Some(vec!["sig1".to_string()]),
1587 };
1588 let witness = Witness::HTLCWitness(htlc_witness);
1589
1590 let serialized = serde_json::to_string(&witness).unwrap();
1591 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1592
1593 assert!(matches!(deserialized, Witness::HTLCWitness(_)));
1594
1595 let p2pk_witness = P2PKWitness {
1596 signatures: vec!["sig1".to_string(), "sig2".to_string()],
1597 };
1598 let witness = Witness::P2PKWitness(p2pk_witness);
1599
1600 let serialized = serde_json::to_string(&witness).unwrap();
1601 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1602
1603 assert!(matches!(deserialized, Witness::P2PKWitness(_)));
1604 }
1605
1606 #[test]
1607 fn test_proofs_methods_count_by_keyset() {
1608 let proofs: Proofs = serde_json::from_str(
1609 r#"[
1610 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1611 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1612 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1613 ]"#,
1614 ).unwrap();
1615
1616 let counts = proofs.count_by_keyset();
1617 assert_eq!(counts.len(), 2);
1618 assert_eq!(counts[&Id::from_str("009a1f293253e41e").unwrap()], 2);
1619 assert_eq!(counts[&Id::from_str("00ad268c4d1f5826").unwrap()], 1);
1620 }
1621
1622 #[test]
1623 fn test_proofs_methods_sum_by_keyset() {
1624 let proofs: Proofs = serde_json::from_str(
1625 r#"[
1626 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1627 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1628 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1629 ]"#,
1630 ).unwrap();
1631
1632 let sums = proofs.sum_by_keyset();
1633 assert_eq!(sums.len(), 2);
1634 assert_eq!(
1635 sums[&Id::from_str("009a1f293253e41e").unwrap()],
1636 Amount::from(10)
1637 );
1638 assert_eq!(
1639 sums[&Id::from_str("00ad268c4d1f5826").unwrap()],
1640 Amount::from(4)
1641 );
1642 }
1643
1644 #[test]
1645 fn test_proofs_methods_total_amount() {
1646 let proofs: Proofs = serde_json::from_str(
1647 r#"[
1648 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1649 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1650 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1651 ]"#,
1652 ).unwrap();
1653
1654 let total = proofs.total_amount().unwrap();
1655 assert_eq!(total, Amount::from(14));
1656 }
1657
1658 #[test]
1659 fn test_proofs_methods_ys() {
1660 let proofs: Proofs = serde_json::from_str(
1661 r#"[
1662 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1663 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1664 ]"#,
1665 ).unwrap();
1666
1667 let ys = proofs.ys().unwrap();
1668 assert_eq!(ys.len(), 2);
1669 assert_ne!(ys[0], ys[1]);
1671 }
1672
1673 #[test]
1674 fn test_proofs_methods_hashset() {
1675 let proofs: Proofs = serde_json::from_str(
1676 r#"[
1677 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1678 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1679 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1680 ]"#,
1681 ).unwrap();
1682
1683 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1684
1685 let counts = proof_set.count_by_keyset();
1687 assert_eq!(counts.len(), 2);
1688
1689 let sums = proof_set.sum_by_keyset();
1690 assert_eq!(sums.len(), 2);
1691 let total: u64 = sums.values().map(|a| u64::from(*a)).sum();
1693 assert_eq!(total, 14);
1694 }
1695
1696 #[test]
1697 fn test_hashset_total_amount() {
1698 let proofs: Proofs = serde_json::from_str(
1699 r#"[
1700 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1701 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1702 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1703 ]"#,
1704 )
1705 .unwrap();
1706
1707 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1708
1709 let total = proof_set.total_amount().unwrap();
1711 assert_eq!(total, Amount::from(14));
1712 }
1713
1714 #[test]
1715 fn test_hashset_ys() {
1716 let proofs: Proofs = serde_json::from_str(
1717 r#"[
1718 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1719 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1720 ]"#,
1721 )
1722 .unwrap();
1723
1724 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1725
1726 let ys = proof_set.ys().unwrap();
1728 assert_eq!(ys.len(), 2);
1729 assert_ne!(ys[0], ys[1]);
1731 }
1732
1733 #[test]
1734 fn test_hashset_without_dleqs() {
1735 let proofs: Proofs = serde_json::from_str(
1736 r#"[
1737 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1738 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1739 ]"#,
1740 )
1741 .unwrap();
1742
1743 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1744
1745 let proofs_without_dleqs = proof_set.without_dleqs();
1747 assert_eq!(proofs_without_dleqs.len(), 2);
1748 for proof in &proofs_without_dleqs {
1750 assert!(proof.dleq.is_none());
1751 }
1752 }
1753
1754 #[test]
1755 fn test_proofs_without_p2pk_e_preserves_other_fields() {
1756 let p2pk_e = PublicKey::from_str(
1757 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1758 )
1759 .unwrap();
1760 let mut proofs: Proofs = serde_json::from_str(
1761 r#"[
1762 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1763 {"id":"00ad268c4d1f5826","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1764 ]"#,
1765 )
1766 .unwrap();
1767 proofs[0].p2pk_e = Some(p2pk_e);
1768 proofs[1].p2pk_e = Some(p2pk_e);
1769
1770 let stripped = proofs.without_p2pk_e();
1771
1772 assert_eq!(stripped.len(), proofs.len());
1773 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1774 assert_eq!(stripped[0].amount, proofs[0].amount);
1775 assert_eq!(stripped[0].keyset_id, proofs[0].keyset_id);
1776 assert_eq!(stripped[0].secret, proofs[0].secret);
1777 assert_eq!(stripped[0].c, proofs[0].c);
1778 assert_eq!(proofs[0].p2pk_e, Some(p2pk_e));
1779 assert_eq!(proofs[1].p2pk_e, Some(p2pk_e));
1780 }
1781
1782 #[test]
1783 fn test_hashset_without_p2pk_e_preserves_all_proofs() {
1784 let p2pk_e = PublicKey::from_str(
1785 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1786 )
1787 .unwrap();
1788 let mut proofs: Proofs = serde_json::from_str(
1789 r#"[
1790 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1791 {"id":"00ad268c4d1f5826","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1792 ]"#,
1793 )
1794 .unwrap();
1795 proofs[0].p2pk_e = Some(p2pk_e);
1796 proofs[1].p2pk_e = Some(p2pk_e);
1797
1798 let proof_set: HashSet<Proof> = proofs.clone().into_iter().collect();
1799 let stripped = proof_set.without_p2pk_e();
1800
1801 assert_eq!(stripped.len(), proof_set.len());
1802 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1803
1804 let stripped_keys: HashSet<_> = stripped.iter().map(|proof| proof.secret.clone()).collect();
1805 let original_keys: HashSet<_> = proofs.iter().map(|proof| proof.secret.clone()).collect();
1806 assert_eq!(stripped_keys, original_keys);
1807 }
1808
1809 #[test]
1810 #[cfg(feature = "wallet")]
1811 fn test_with_p2bk_rejects_mismatched_ephemeral_keys_when_not_sig_all() {
1812 use crate::amount::{FeeAndAmounts, SplitTarget};
1813 use crate::nuts::nut11::SigFlag;
1814 use crate::Conditions;
1815
1816 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1817 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1818 let receiver_pubkey = receiver_secret_key.public_key();
1819 let conditions =
1820 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1821 let ephemeral_keys = vec![crate::nuts::nut01::SecretKey::generate()];
1822 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1823
1824 let result = PreMintSecrets::with_p2bk(
1825 keyset_id,
1826 Amount::from(3_u64),
1827 &SplitTarget::default(),
1828 receiver_pubkey,
1829 Some(conditions),
1830 &ephemeral_keys,
1831 &fee_and_amounts,
1832 );
1833
1834 assert!(result.is_err());
1835 }
1836
1837 #[test]
1838 #[cfg(feature = "wallet")]
1839 fn test_with_p2bk_allows_single_ephemeral_key_for_sig_all() {
1840 use crate::amount::{FeeAndAmounts, SplitTarget};
1841 use crate::nuts::nut11::SigFlag;
1842 use crate::{Conditions, SpendingConditions};
1843
1844 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1845 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1846 let receiver_pubkey = receiver_secret_key.public_key();
1847 let conditions =
1848 Conditions::new(None, None, None, None, Some(SigFlag::SigAll), None).unwrap();
1849 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
1850 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1851
1852 let result = PreMintSecrets::with_p2bk(
1853 keyset_id,
1854 Amount::from(3_u64),
1855 &SplitTarget::default(),
1856 receiver_pubkey,
1857 Some(conditions),
1858 &[ephemeral_key],
1859 &fee_and_amounts,
1860 )
1861 .unwrap();
1862
1863 assert_eq!(result.len(), 2);
1864 for premint in result.iter() {
1865 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1866 match spending_conditions {
1867 SpendingConditions::P2PKConditions { data, conditions } => {
1868 assert_ne!(data, receiver_pubkey);
1869 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigAll);
1870 }
1871 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1872 }
1873 }
1874 }
1875
1876 #[test]
1877 #[cfg(feature = "wallet")]
1878 fn test_with_p2bk_allows_one_ephemeral_key_per_output_when_not_sig_all() {
1879 use crate::amount::{FeeAndAmounts, SplitTarget};
1880 use crate::nuts::nut11::SigFlag;
1881 use crate::{Conditions, SpendingConditions};
1882
1883 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1884 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1885 let receiver_pubkey = receiver_secret_key.public_key();
1886 let conditions =
1887 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1888 let ephemeral_keys = vec![
1889 crate::nuts::nut01::SecretKey::generate(),
1890 crate::nuts::nut01::SecretKey::generate(),
1891 ];
1892 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1893
1894 let result = PreMintSecrets::with_p2bk(
1895 keyset_id,
1896 Amount::from(3_u64),
1897 &SplitTarget::default(),
1898 receiver_pubkey,
1899 Some(conditions),
1900 &ephemeral_keys,
1901 &fee_and_amounts,
1902 )
1903 .unwrap();
1904
1905 assert_eq!(result.len(), 2);
1906 for premint in result.iter() {
1907 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1908 match spending_conditions {
1909 SpendingConditions::P2PKConditions { data, conditions } => {
1910 assert_ne!(data, receiver_pubkey);
1911 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigInputs);
1912 }
1913 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1914 }
1915 }
1916 }
1917
1918 #[test]
1919 #[cfg(feature = "wallet")]
1920 fn test_with_p2bk_uses_canonical_slots_for_pubkeys_and_refund_keys() {
1921 use crate::amount::{FeeAndAmounts, SplitTarget};
1922 use crate::nuts::nut11::SigFlag;
1923 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
1924 use crate::{Conditions, SpendingConditions};
1925
1926 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1927 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1928 let receiver_pubkey = receiver_secret_key.public_key();
1929 let additional_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
1930 let additional_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
1931 let refund_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
1932 let refund_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
1933 let conditions = Conditions::new(
1934 None,
1935 Some(vec![additional_key_1, additional_key_2]),
1936 Some(vec![refund_key_1, refund_key_2]),
1937 Some(1),
1938 Some(SigFlag::SigAll),
1939 Some(1),
1940 )
1941 .unwrap();
1942 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
1943 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1944
1945 let premint_secrets = PreMintSecrets::with_p2bk(
1946 keyset_id,
1947 Amount::from(1_u64),
1948 &SplitTarget::default(),
1949 receiver_pubkey,
1950 Some(conditions.clone()),
1951 std::slice::from_ref(&ephemeral_key),
1952 &fee_and_amounts,
1953 )
1954 .unwrap();
1955
1956 let premint = premint_secrets.iter().next().unwrap();
1957 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1958
1959 match spending_conditions {
1960 SpendingConditions::P2PKConditions { data, conditions } => {
1961 let blinded_conditions = conditions.unwrap();
1962
1963 let expected_primary = blind_public_key(
1964 &receiver_pubkey,
1965 &ecdh_kdf(&ephemeral_key, &receiver_pubkey, 0).unwrap(),
1966 )
1967 .unwrap();
1968 let expected_additional = vec![
1969 blind_public_key(
1970 &additional_key_1,
1971 &ecdh_kdf(&ephemeral_key, &additional_key_1, 1).unwrap(),
1972 )
1973 .unwrap(),
1974 blind_public_key(
1975 &additional_key_2,
1976 &ecdh_kdf(&ephemeral_key, &additional_key_2, 2).unwrap(),
1977 )
1978 .unwrap(),
1979 ];
1980 let expected_refund = vec![
1981 blind_public_key(
1982 &refund_key_1,
1983 &ecdh_kdf(&ephemeral_key, &refund_key_1, 3).unwrap(),
1984 )
1985 .unwrap(),
1986 blind_public_key(
1987 &refund_key_2,
1988 &ecdh_kdf(&ephemeral_key, &refund_key_2, 4).unwrap(),
1989 )
1990 .unwrap(),
1991 ];
1992
1993 assert_eq!(data, expected_primary);
1994 assert_eq!(blinded_conditions.pubkeys.unwrap(), expected_additional);
1995 assert_eq!(blinded_conditions.refund_keys.unwrap(), expected_refund);
1996 assert_eq!(blinded_conditions.sig_flag, SigFlag::SigAll);
1997 }
1998 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1999 }
2000 }
2001
2002 #[test]
2003 fn test_blind_signature_partial_cmp() {
2004 let sig1 = BlindSignature {
2005 amount: Amount::from(10),
2006 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2007 c: PublicKey::from_str(
2008 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2009 )
2010 .unwrap(),
2011 dleq: None,
2012 };
2013 let sig2 = BlindSignature {
2014 amount: Amount::from(20),
2015 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2016 c: PublicKey::from_str(
2017 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2018 )
2019 .unwrap(),
2020 dleq: None,
2021 };
2022 let sig3 = BlindSignature {
2023 amount: Amount::from(10),
2024 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2025 c: PublicKey::from_str(
2026 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2027 )
2028 .unwrap(),
2029 dleq: None,
2030 };
2031
2032 assert_eq!(sig1.partial_cmp(&sig2), Some(Ordering::Less));
2034 assert_eq!(sig2.partial_cmp(&sig1), Some(Ordering::Greater));
2035 assert_eq!(sig1.partial_cmp(&sig3), Some(Ordering::Equal));
2036
2037 let mut sigs = [sig2.clone(), sig1.clone(), sig3.clone()];
2039 sigs.sort();
2040 assert_eq!(sigs[0].amount, Amount::from(10));
2041 assert_eq!(sigs[2].amount, Amount::from(20));
2042 }
2043
2044 #[test]
2045 fn test_witness_preimage() {
2046 let htlc_witness = HTLCWitness {
2048 preimage: "test_preimage".to_string(),
2049 signatures: Some(vec!["sig1".to_string()]),
2050 };
2051 let witness = Witness::HTLCWitness(htlc_witness);
2052 assert_eq!(witness.preimage(), Some("test_preimage".to_string()));
2053
2054 let p2pk_witness = P2PKWitness {
2056 signatures: vec!["sig1".to_string()],
2057 };
2058 let witness = Witness::P2PKWitness(p2pk_witness);
2059 assert_eq!(witness.preimage(), None);
2060 }
2061
2062 #[test]
2063 fn test_proof_is_active() {
2064 let proof: Proof = serde_json::from_str(
2065 r#"{"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2066 ).unwrap();
2067
2068 let active_keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2069 let inactive_keyset_id = Id::from_str("00ad268c4d1f5826").unwrap();
2070
2071 assert!(proof.is_active(&[active_keyset_id]));
2073
2074 assert!(!proof.is_active(&[inactive_keyset_id]));
2076
2077 assert!(!proof.is_active(&[]));
2079
2080 assert!(proof.is_active(&[inactive_keyset_id, active_keyset_id]));
2082 }
2083
2084 fn compute_hash<T: Hash>(value: &T) -> u64 {
2086 let mut hasher = DefaultHasher::new();
2087 value.hash(&mut hasher);
2088 hasher.finish()
2089 }
2090
2091 #[test]
2092 fn test_proof_hash_uses_secret() {
2093 let proof1: Proof = serde_json::from_str(
2095 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2096 ).unwrap();
2097
2098 let proof2: Proof = serde_json::from_str(
2099 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2100 ).unwrap();
2101
2102 assert_eq!(compute_hash(&proof1), compute_hash(&proof2));
2104
2105 let proof3: Proof = serde_json::from_str(
2107 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2108 ).unwrap();
2109
2110 assert_ne!(compute_hash(&proof1), compute_hash(&proof3));
2111 }
2112
2113 #[test]
2114 fn test_proof_v4_hash_uses_secret() {
2115 let proof1: Proof = serde_json::from_str(
2116 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2117 ).unwrap();
2118
2119 let proof2: Proof = serde_json::from_str(
2120 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2121 ).unwrap();
2122
2123 let proof_v4_1: ProofV4 = proof1.into();
2124 let proof_v4_2: ProofV4 = proof2.into();
2125
2126 assert_eq!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_2));
2128
2129 let proof3: Proof = serde_json::from_str(
2131 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2132 ).unwrap();
2133 let proof_v4_3: ProofV4 = proof3.into();
2134
2135 assert_ne!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_3));
2136 }
2137
2138 #[test]
2139 fn test_proof_v3_hash_uses_secret() {
2140 let proof1: Proof = serde_json::from_str(
2141 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2142 ).unwrap();
2143
2144 let proof2: Proof = serde_json::from_str(
2145 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2146 ).unwrap();
2147
2148 let proof_v3_1: ProofV3 = proof1.into();
2149 let proof_v3_2: ProofV3 = proof2.into();
2150
2151 assert_eq!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_2));
2153
2154 let proof3: Proof = serde_json::from_str(
2156 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2157 ).unwrap();
2158 let proof_v3_3: ProofV3 = proof3.into();
2159
2160 assert_ne!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_3));
2161 }
2162
2163 #[test]
2164 #[cfg(feature = "mint")]
2165 #[allow(deprecated)]
2166 fn test_currency_unit_derivation_index() {
2167 assert_eq!(CurrencyUnit::Sat.derivation_index(), Some(0));
2170 assert_eq!(CurrencyUnit::Msat.derivation_index(), Some(1));
2171 assert_eq!(CurrencyUnit::Usd.derivation_index(), Some(2));
2172 assert_eq!(CurrencyUnit::Eur.derivation_index(), Some(3));
2173 assert_eq!(CurrencyUnit::Auth.derivation_index(), Some(4));
2174
2175 assert_eq!(
2177 CurrencyUnit::Custom("btc".to_string()).derivation_index(),
2178 None
2179 );
2180 }
2181}