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)]
224#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
225pub struct BlindedMessage {
226 pub amount: Amount,
230 #[serde(rename = "id")]
234 #[cfg_attr(feature = "swagger", schema(value_type = String))]
235 pub keyset_id: Id,
236 #[serde(rename = "B_")]
240 #[cfg_attr(feature = "swagger", schema(value_type = String))]
241 pub blinded_secret: PublicKey,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub witness: Option<Witness>,
247}
248
249impl BlindedMessage {
250 #[inline]
252 pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
253 Self {
254 amount,
255 keyset_id,
256 blinded_secret,
257 witness: None,
258 }
259 }
260
261 #[inline]
263 pub fn witness(&mut self, witness: Witness) {
264 self.witness = Some(witness);
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
271pub struct BlindSignature {
272 pub amount: Amount,
276 #[serde(rename = "id")]
280 #[cfg_attr(feature = "swagger", schema(value_type = String))]
281 pub keyset_id: Id,
282 #[serde(rename = "C_")]
286 #[cfg_attr(feature = "swagger", schema(value_type = String))]
287 pub c: PublicKey,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub dleq: Option<BlindSignatureDleq>,
293}
294
295impl Ord for BlindSignature {
296 fn cmp(&self, other: &Self) -> Ordering {
297 self.amount.cmp(&other.amount)
298 }
299}
300
301impl PartialOrd for BlindSignature {
302 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
303 Some(self.cmp(other))
304 }
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
309#[serde(untagged)]
310#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
311pub enum Witness {
312 #[serde(with = "serde_htlc_witness")]
314 HTLCWitness(HTLCWitness),
315 #[serde(with = "serde_p2pk_witness")]
317 P2PKWitness(P2PKWitness),
318}
319
320impl From<P2PKWitness> for Witness {
321 fn from(witness: P2PKWitness) -> Self {
322 Self::P2PKWitness(witness)
323 }
324}
325
326impl From<HTLCWitness> for Witness {
327 fn from(witness: HTLCWitness) -> Self {
328 Self::HTLCWitness(witness)
329 }
330}
331
332impl Witness {
333 pub fn add_signatures(&mut self, signatures: Vec<String>) {
335 match self {
336 Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatures),
337 Self::HTLCWitness(htlc_witness) => match &mut htlc_witness.signatures {
338 Some(sigs) => sigs.extend(signatures),
339 None => htlc_witness.signatures = Some(signatures),
340 },
341 }
342 }
343
344 pub fn signatures(&self) -> Option<Vec<String>> {
346 match self {
347 Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
348 Self::HTLCWitness(witness) => witness.signatures.clone(),
349 }
350 }
351
352 pub fn preimage(&self) -> Option<String> {
354 match self {
355 Self::P2PKWitness(_witness) => None,
356 Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
357 }
358 }
359}
360
361impl std::fmt::Display for Witness {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 write!(
364 f,
365 "{}",
366 serde_json::to_string(self).map_err(|_| std::fmt::Error)?
367 )
368 }
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
374pub struct Proof {
375 pub amount: Amount,
377 #[serde(rename = "id")]
379 #[cfg_attr(feature = "swagger", schema(value_type = String))]
380 pub keyset_id: Id,
381 #[cfg_attr(feature = "swagger", schema(value_type = String))]
383 pub secret: Secret,
384 #[serde(rename = "C")]
386 #[cfg_attr(feature = "swagger", schema(value_type = String))]
387 pub c: PublicKey,
388 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub witness: Option<Witness>,
391 #[serde(default, skip_serializing_if = "Option::is_none")]
393 pub dleq: Option<ProofDleq>,
394 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub p2pk_e: Option<PublicKey>,
398}
399
400impl Proof {
401 pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
403 Proof {
404 amount,
405 keyset_id,
406 secret,
407 c,
408 witness: None,
409 dleq: None,
410 p2pk_e: None,
411 }
412 }
413
414 pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
416 active_keyset_ids.contains(&self.keyset_id)
417 }
418
419 pub fn y(&self) -> Result<PublicKey, Error> {
423 Ok(hash_to_curve(self.secret.as_bytes())?)
424 }
425}
426
427impl Hash for Proof {
428 fn hash<H: Hasher>(&self, state: &mut H) {
429 self.secret.hash(state);
430 }
431}
432
433impl Ord for Proof {
434 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
435 self.amount.cmp(&other.amount)
436 }
437}
438
439impl PartialOrd for Proof {
440 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
441 Some(self.cmp(other))
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
447pub struct ProofV4 {
448 #[serde(rename = "a")]
450 pub amount: Amount,
451 #[serde(rename = "s")]
453 pub secret: Secret,
454 #[serde(
456 serialize_with = "serialize_v4_pubkey",
457 deserialize_with = "deserialize_v4_pubkey"
458 )]
459 pub c: PublicKey,
460 #[serde(default, skip_serializing_if = "Option::is_none")]
462 pub witness: Option<Witness>,
463 #[serde(rename = "d")]
465 pub dleq: Option<ProofDleq>,
466 #[serde(rename = "pe", default, skip_serializing_if = "Option::is_none")]
468 pub p2pk_e: Option<PublicKey>,
469}
470
471impl ProofV4 {
472 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
474 Proof {
475 amount: self.amount,
476 keyset_id: *keyset_id,
477 secret: self.secret.clone(),
478 c: self.c,
479 witness: self.witness.clone(),
480 dleq: self.dleq.clone(),
481 p2pk_e: self.p2pk_e,
482 }
483 }
484}
485
486impl Hash for ProofV4 {
487 fn hash<H: Hasher>(&self, state: &mut H) {
488 self.secret.hash(state);
489 }
490}
491
492impl From<Proof> for ProofV4 {
493 fn from(proof: Proof) -> ProofV4 {
494 let Proof {
495 amount,
496 secret,
497 c,
498 witness,
499 dleq,
500 p2pk_e,
501 ..
502 } = proof;
503 ProofV4 {
504 amount,
505 secret,
506 c,
507 witness,
508 dleq,
509 p2pk_e,
510 }
511 }
512}
513
514impl From<ProofV3> for ProofV4 {
515 fn from(proof: ProofV3) -> Self {
516 Self {
517 amount: proof.amount,
518 secret: proof.secret,
519 c: proof.c,
520 witness: proof.witness,
521 dleq: proof.dleq,
522 p2pk_e: None,
523 }
524 }
525}
526
527#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
529pub struct ProofV3 {
530 pub amount: Amount,
532 #[serde(rename = "id")]
534 pub keyset_id: ShortKeysetId,
535 pub secret: Secret,
537 #[serde(rename = "C")]
539 pub c: PublicKey,
540 #[serde(default, skip_serializing_if = "Option::is_none")]
542 pub witness: Option<Witness>,
543 #[serde(default, skip_serializing_if = "Option::is_none")]
545 pub dleq: Option<ProofDleq>,
546}
547
548impl ProofV3 {
549 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
551 Proof {
552 amount: self.amount,
553 keyset_id: *keyset_id,
554 secret: self.secret.clone(),
555 c: self.c,
556 witness: self.witness.clone(),
557 dleq: self.dleq.clone(),
558 p2pk_e: None,
559 }
560 }
561}
562
563impl From<Proof> for ProofV3 {
564 fn from(proof: Proof) -> ProofV3 {
565 let Proof {
566 amount,
567 keyset_id,
568 secret,
569 c,
570 witness,
571 dleq,
572 ..
573 } = proof;
574 ProofV3 {
575 amount,
576 secret,
577 c,
578 witness,
579 dleq,
580 keyset_id: keyset_id.into(),
581 }
582 }
583}
584
585impl Hash for ProofV3 {
586 fn hash<H: Hasher>(&self, state: &mut H) {
587 self.secret.hash(state);
588 }
589}
590
591fn serialize_v4_pubkey<S>(key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
592where
593 S: serde::Serializer,
594{
595 serializer.serialize_bytes(&key.to_bytes())
596}
597
598fn deserialize_v4_pubkey<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
599where
600 D: serde::Deserializer<'de>,
601{
602 let bytes = Vec::<u8>::deserialize(deserializer)?;
603 PublicKey::from_slice(&bytes).map_err(serde::de::Error::custom)
604}
605
606#[non_exhaustive]
608#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
609#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
610pub enum CurrencyUnit {
611 #[default]
613 Sat,
614 Msat,
616 Usd,
618 Eur,
620 Auth,
622 Custom(String),
624}
625
626#[cfg(feature = "mint")]
627impl CurrencyUnit {
628 #[deprecated(
630 since = "0.15.0",
631 note = "This function is outdated; use `hashed_derivation_index` instead."
632 )]
633 pub fn derivation_index(&self) -> Option<u32> {
634 match self {
635 Self::Sat => Some(0),
636 Self::Msat => Some(1),
637 Self::Usd => Some(2),
638 Self::Eur => Some(3),
639 Self::Auth => Some(4),
640 _ => None,
641 }
642 }
643
644 pub fn custom<S: AsRef<str>>(value: S) -> Self {
646 Self::Custom(normalize_custom_unit(value.as_ref()).to_uppercase())
647 }
648
649 pub fn hashed_derivation_index(&self) -> u32 {
651 use bitcoin::hashes::sha256;
652
653 let unit_str = self.to_string().to_uppercase();
655
656 let bytes = <sha256::Hash as BitcoinHash>::hash(unit_str.as_bytes());
657 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) & !(1 << 31)
659 }
660}
661
662fn normalize_custom_unit(value: &str) -> String {
663 let trimmed = value.trim_matches(|c: char| matches!(c, ' ' | '\t' | '\r' | '\n'));
664 trimmed.nfc().collect::<String>()
665}
666
667impl FromStr for CurrencyUnit {
668 type Err = Error;
669 fn from_str(value: &str) -> Result<Self, Self::Err> {
670 let upper_value = value.to_uppercase();
671 match upper_value.as_str() {
672 "SAT" => Ok(Self::Sat),
673 "MSAT" => Ok(Self::Msat),
674 "USD" => Ok(Self::Usd),
675 "EUR" => Ok(Self::Eur),
676 "AUTH" => Ok(Self::Auth),
677 _ => Ok(Self::Custom(normalize_custom_unit(value))),
678 }
679 }
680}
681
682impl fmt::Display for CurrencyUnit {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 let s = match self {
686 CurrencyUnit::Sat => "SAT",
687 CurrencyUnit::Msat => "MSAT",
688 CurrencyUnit::Usd => "USD",
689 CurrencyUnit::Eur => "EUR",
690 CurrencyUnit::Auth => "AUTH",
691 CurrencyUnit::Custom(unit) => unit,
692 };
693
694 if let Some(width) = f.width() {
695 write!(
696 f,
697 "{:width$}",
698 normalize_custom_unit(s).to_lowercase(),
699 width = width
700 )
701 } else {
702 write!(f, "{}", normalize_custom_unit(s).to_lowercase())
703 }
704 }
705}
706
707impl Serialize for CurrencyUnit {
708 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
709 where
710 S: serde::Serializer,
711 {
712 serializer.serialize_str(&self.to_string().to_lowercase())
713 }
714}
715
716impl<'de> Deserialize<'de> for CurrencyUnit {
717 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
718 where
719 D: Deserializer<'de>,
720 {
721 let currency: String = String::deserialize(deserializer)?;
722 Self::from_str(¤cy).map_err(|_| serde::de::Error::custom("Unsupported unit"))
723 }
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
728#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
729pub enum KnownMethod {
730 Bolt11,
732 Bolt12,
734}
735
736impl KnownMethod {
737 pub fn as_str(&self) -> &str {
739 match self {
740 Self::Bolt11 => "bolt11",
741 Self::Bolt12 => "bolt12",
742 }
743 }
744}
745
746impl fmt::Display for KnownMethod {
747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748 write!(f, "{}", self.as_str())
749 }
750}
751
752impl FromStr for KnownMethod {
753 type Err = Error;
754 fn from_str(value: &str) -> Result<Self, Self::Err> {
755 match value.to_lowercase().as_str() {
756 "bolt11" => Ok(Self::Bolt11),
757 "bolt12" => Ok(Self::Bolt12),
758 _ => Err(Error::UnsupportedPaymentMethod),
759 }
760 }
761}
762
763#[derive(Debug, Clone, PartialEq, Eq, Hash)]
767#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
768pub enum PaymentMethod {
769 Known(KnownMethod),
771 Custom(String),
773}
774
775impl PaymentMethod {
776 pub const BOLT11: Self = Self::Known(KnownMethod::Bolt11);
778 pub const BOLT12: Self = Self::Known(KnownMethod::Bolt12);
780
781 pub fn new(method: String) -> Self {
783 Self::from_str(&method).unwrap_or_else(|_| Self::Custom(method.to_lowercase()))
784 }
785
786 pub fn as_str(&self) -> &str {
788 match self {
789 Self::Known(known) => known.as_str(),
790 Self::Custom(custom) => custom.as_str(),
791 }
792 }
793
794 pub fn is_known(&self) -> bool {
796 matches!(self, Self::Known(_))
797 }
798
799 pub fn is_custom(&self) -> bool {
801 matches!(self, Self::Custom(_))
802 }
803
804 pub fn is_bolt11(&self) -> bool {
806 matches!(self, Self::Known(KnownMethod::Bolt11))
807 }
808
809 pub fn is_bolt12(&self) -> bool {
811 matches!(self, Self::Known(KnownMethod::Bolt12))
812 }
813}
814
815impl FromStr for PaymentMethod {
816 type Err = Error;
817 fn from_str(value: &str) -> Result<Self, Self::Err> {
818 match KnownMethod::from_str(value) {
819 Ok(known) => Ok(Self::Known(known)),
820 Err(_) => Ok(Self::Custom(value.to_lowercase())),
821 }
822 }
823}
824
825impl fmt::Display for PaymentMethod {
826 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827 write!(f, "{}", self.as_str())
828 }
829}
830
831impl From<String> for PaymentMethod {
832 fn from(s: String) -> Self {
833 Self::from_str(&s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
834 }
835}
836
837impl From<&str> for PaymentMethod {
838 fn from(s: &str) -> Self {
839 Self::from_str(s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
840 }
841}
842
843impl From<KnownMethod> for PaymentMethod {
844 fn from(known: KnownMethod) -> Self {
845 Self::Known(known)
846 }
847}
848
849impl PartialEq<&str> for PaymentMethod {
851 fn eq(&self, other: &&str) -> bool {
852 self.as_str() == *other
853 }
854}
855
856impl PartialEq<str> for PaymentMethod {
857 fn eq(&self, other: &str) -> bool {
858 self.as_str() == other
859 }
860}
861
862impl PartialEq<PaymentMethod> for &str {
863 fn eq(&self, other: &PaymentMethod) -> bool {
864 *self == other.as_str()
865 }
866}
867
868impl PartialEq<KnownMethod> for PaymentMethod {
869 fn eq(&self, other: &KnownMethod) -> bool {
870 matches!(self, Self::Known(k) if k == other)
871 }
872}
873
874impl PartialEq<PaymentMethod> for KnownMethod {
875 fn eq(&self, other: &PaymentMethod) -> bool {
876 matches!(other, PaymentMethod::Known(k) if k == self)
877 }
878}
879
880impl Serialize for PaymentMethod {
881 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
882 where
883 S: serde::Serializer,
884 {
885 serializer.serialize_str(self.as_str())
886 }
887}
888
889impl<'de> Deserialize<'de> for PaymentMethod {
890 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
891 where
892 D: Deserializer<'de>,
893 {
894 let payment_method: String = String::deserialize(deserializer)?;
895 Ok(Self::from_str(&payment_method).unwrap_or(Self::Custom(payment_method)))
896 }
897}
898
899#[cfg(feature = "wallet")]
901#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
902pub struct PreMint {
903 pub blinded_message: BlindedMessage,
905 pub secret: Secret,
907 pub r: SecretKey,
909 pub amount: Amount,
911}
912
913#[cfg(feature = "wallet")]
914impl Ord for PreMint {
915 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
916 self.amount.cmp(&other.amount)
917 }
918}
919
920#[cfg(feature = "wallet")]
921impl PartialOrd for PreMint {
922 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
923 Some(self.cmp(other))
924 }
925}
926
927#[cfg(feature = "wallet")]
929#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
930pub struct PreMintSecrets {
931 pub secrets: Vec<PreMint>,
933 pub keyset_id: Id,
935}
936
937#[cfg(feature = "wallet")]
938impl PreMintSecrets {
939 pub fn new(keyset_id: Id) -> Self {
941 Self {
942 secrets: Vec::new(),
943 keyset_id,
944 }
945 }
946
947 pub fn random(
949 keyset_id: Id,
950 amount: Amount,
951 amount_split_target: &SplitTarget,
952 fee_and_amounts: &FeeAndAmounts,
953 ) -> Result<Self, Error> {
954 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
955
956 let mut output = Vec::with_capacity(amount_split.len());
957
958 for amount in amount_split {
959 let secret = Secret::generate();
960 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
961
962 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
963
964 output.push(PreMint {
965 secret,
966 blinded_message,
967 r,
968 amount,
969 });
970 }
971
972 Ok(PreMintSecrets {
973 secrets: output,
974 keyset_id,
975 })
976 }
977
978 pub fn from_secrets(
980 keyset_id: Id,
981 amounts: Vec<Amount>,
982 secrets: Vec<Secret>,
983 ) -> Result<Self, Error> {
984 let mut output = Vec::with_capacity(secrets.len());
985
986 for (secret, amount) in secrets.into_iter().zip(amounts) {
987 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
988
989 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
990
991 output.push(PreMint {
992 secret,
993 blinded_message,
994 r,
995 amount,
996 });
997 }
998
999 Ok(PreMintSecrets {
1000 secrets: output,
1001 keyset_id,
1002 })
1003 }
1004
1005 pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
1007 let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
1008
1009 let mut output = Vec::with_capacity(count as usize);
1010
1011 for _i in 0..count {
1012 let secret = Secret::generate();
1013 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1014
1015 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
1016
1017 output.push(PreMint {
1018 secret,
1019 blinded_message,
1020 r,
1021 amount: Amount::ZERO,
1022 })
1023 }
1024
1025 Ok(PreMintSecrets {
1026 secrets: output,
1027 keyset_id,
1028 })
1029 }
1030
1031 #[cfg(feature = "wallet")]
1033 pub fn with_p2bk(
1034 keyset_id: Id,
1035 amount: Amount,
1036 amount_split_target: &SplitTarget,
1037 receiver_pubkey: PublicKey,
1038 conditions: Option<crate::nuts::nut10::Conditions>,
1039 ephemeral_keys: &[crate::nuts::nut01::SecretKey],
1040 fee_and_amounts: &FeeAndAmounts,
1041 ) -> Result<Self, Error> {
1042 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
1043
1044 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1045
1046 let mut output = Vec::with_capacity(amount_split.len());
1047
1048 let is_sig_all = conditions
1049 .as_ref()
1050 .is_some_and(|c| c.sig_flag == crate::nuts::nut11::SigFlag::SigAll);
1051 if !is_sig_all && ephemeral_keys.len() != amount_split.len() {
1052 return Err(Error::NUT28(
1053 crate::nuts::nut28::Error::InvalidCanonicalSlot(255),
1054 ));
1055 }
1056
1057 for (i, amount) in amount_split.into_iter().enumerate() {
1058 let ephemeral_key = if is_sig_all {
1059 &ephemeral_keys[0]
1060 } else {
1061 &ephemeral_keys[i]
1062 };
1063
1064 let r0 = ecdh_kdf(ephemeral_key, &receiver_pubkey, 0).map_err(Error::NUT28)?;
1066 let blinded_pubkey = blind_public_key(&receiver_pubkey, &r0).map_err(Error::NUT28)?;
1067
1068 let mut blinded_conditions = conditions.clone();
1069
1070 if let Some(ref mut cond) = blinded_conditions {
1072 let mut slot_idx = 1;
1073
1074 if let Some(ref mut pubkeys) = cond.pubkeys {
1075 for pk in pubkeys.iter_mut() {
1076 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1077 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1078 slot_idx += 1;
1079 }
1080 }
1081
1082 if let Some(ref mut refund_keys) = cond.refund_keys {
1083 for pk in refund_keys.iter_mut() {
1084 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1085 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1086 slot_idx += 1;
1087 }
1088 }
1089 }
1090
1091 let p2pk_conditions = crate::nuts::SpendingConditions::P2PKConditions {
1092 data: blinded_pubkey,
1093 conditions: blinded_conditions,
1094 };
1095
1096 let secret: crate::nuts::nut10::Secret = p2pk_conditions.into();
1097 let secret: Secret = secret.try_into()?;
1098 let (blinded, rs) = blind_message(&secret.to_bytes(), None)?;
1099
1100 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1101
1102 output.push(PreMint {
1103 secret,
1104 blinded_message,
1105 r: rs,
1106 amount,
1107 });
1108 }
1109
1110 Ok(PreMintSecrets {
1111 secrets: output,
1112 keyset_id,
1113 })
1114 }
1115
1116 pub fn with_conditions(
1118 keyset_id: Id,
1119 amount: Amount,
1120 amount_split_target: &SplitTarget,
1121 conditions: &nut10::SpendingConditions,
1122 fee_and_amounts: &FeeAndAmounts,
1123 ) -> Result<Self, Error> {
1124 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1125
1126 let mut output = Vec::with_capacity(amount_split.len());
1127
1128 for amount in amount_split {
1129 let secret: nut10::Secret = conditions.clone().into();
1130
1131 let secret: Secret = secret.try_into()?;
1132 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1133
1134 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1135
1136 output.push(PreMint {
1137 secret,
1138 blinded_message,
1139 r,
1140 amount,
1141 });
1142 }
1143
1144 Ok(PreMintSecrets {
1145 secrets: output,
1146 keyset_id,
1147 })
1148 }
1149
1150 #[inline]
1152 pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
1153 self.secrets.iter()
1154 }
1155
1156 #[inline]
1158 pub fn len(&self) -> usize {
1159 self.secrets.len()
1160 }
1161
1162 #[inline]
1164 pub fn is_empty(&self) -> bool {
1165 self.secrets.is_empty()
1166 }
1167
1168 pub fn total_amount(&self) -> Result<Amount, Error> {
1170 Ok(Amount::try_sum(
1171 self.secrets.iter().map(|PreMint { amount, .. }| *amount),
1172 )?)
1173 }
1174
1175 #[inline]
1177 pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
1178 self.iter().map(|pm| pm.blinded_message.clone()).collect()
1179 }
1180
1181 #[inline]
1183 pub fn secrets(&self) -> Vec<Secret> {
1184 self.iter().map(|pm| pm.secret.clone()).collect()
1185 }
1186
1187 #[inline]
1189 pub fn rs(&self) -> Vec<SecretKey> {
1190 self.iter().map(|pm| pm.r.clone()).collect()
1191 }
1192
1193 #[inline]
1195 pub fn amounts(&self) -> Vec<Amount> {
1196 self.iter().map(|pm| pm.amount).collect()
1197 }
1198
1199 #[inline]
1201 pub fn combine(&mut self, mut other: Self) {
1202 self.secrets.append(&mut other.secrets)
1203 }
1204
1205 #[inline]
1207 pub fn sort_secrets(&mut self) {
1208 self.secrets.sort();
1209 }
1210}
1211
1212#[cfg(feature = "wallet")]
1214impl Iterator for PreMintSecrets {
1215 type Item = PreMint;
1216
1217 fn next(&mut self) -> Option<Self::Item> {
1218 if self.secrets.is_empty() {
1220 return None;
1221 }
1222 Some(self.secrets.remove(0))
1223 }
1224}
1225
1226#[cfg(feature = "wallet")]
1227impl Ord for PreMintSecrets {
1228 fn cmp(&self, other: &Self) -> Ordering {
1229 self.secrets.cmp(&other.secrets)
1230 }
1231}
1232
1233#[cfg(feature = "wallet")]
1234impl PartialOrd for PreMintSecrets {
1235 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1236 Some(self.cmp(other))
1237 }
1238}
1239
1240#[cfg(test)]
1241mod tests {
1242 use std::collections::hash_map::DefaultHasher;
1243 use std::collections::HashSet;
1244 use std::hash::{Hash, Hasher};
1245 use std::str::FromStr;
1246
1247 use super::*;
1248
1249 #[test]
1250 fn test_proof_serialize() {
1251 let proof = "[{\"id\":\"009a1f293253e41e\",\"amount\":2,\"secret\":\"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\"C\":\"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"},{\"id\":\"009a1f293253e41e\",\"amount\":8,\"secret\":\"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\"C\":\"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"}]";
1252 let proof: Proofs = serde_json::from_str(proof).unwrap();
1253
1254 assert_eq!(
1255 proof[0].clone().keyset_id,
1256 Id::from_str("009a1f293253e41e").unwrap()
1257 );
1258
1259 assert_eq!(proof.len(), 2);
1260 }
1261
1262 #[test]
1263 #[cfg(feature = "wallet")]
1264 fn test_blank_blinded_messages() {
1265 let b = PreMintSecrets::blank(
1266 Id::from_str("009a1f293253e41e").unwrap(),
1267 Amount::from(1000),
1268 )
1269 .unwrap();
1270 assert_eq!(b.len(), 10);
1271
1272 let b = PreMintSecrets::blank(Id::from_str("009a1f293253e41e").unwrap(), Amount::from(1))
1273 .unwrap();
1274 assert_eq!(b.len(), 1);
1275 }
1276
1277 #[test]
1278 #[cfg(feature = "wallet")]
1279 fn test_premint_secrets_accessors_and_total_amount() {
1280 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1281 let amounts = vec![
1282 Amount::from(1_u64),
1283 Amount::from(2_u64),
1284 Amount::from(4_u64),
1285 ];
1286 let secrets = vec![Secret::generate(), Secret::generate(), Secret::generate()];
1287
1288 let premint_secrets =
1289 PreMintSecrets::from_secrets(keyset_id, amounts.clone(), secrets.clone()).unwrap();
1290
1291 assert_eq!(premint_secrets.total_amount().unwrap(), Amount::from(7_u64));
1292 assert_eq!(premint_secrets.amounts(), amounts);
1293 assert_eq!(premint_secrets.secrets(), secrets);
1294
1295 let blinded_messages = premint_secrets.blinded_messages();
1296 assert_eq!(blinded_messages.len(), 3);
1297 assert_eq!(blinded_messages[0].amount, Amount::from(1_u64));
1298 assert_eq!(blinded_messages[1].amount, Amount::from(2_u64));
1299 assert_eq!(blinded_messages[2].amount, Amount::from(4_u64));
1300 assert!(blinded_messages
1301 .iter()
1302 .all(|message| message.keyset_id == keyset_id));
1303
1304 let rs = premint_secrets.rs();
1305 assert_eq!(rs.len(), 3);
1306 assert_eq!(rs[0], premint_secrets.secrets[0].r);
1307 assert_eq!(rs[1], premint_secrets.secrets[1].r);
1308 assert_eq!(rs[2], premint_secrets.secrets[2].r);
1309 }
1310
1311 #[test]
1312 #[cfg(feature = "wallet")]
1313 fn test_premint_secrets_combine_and_sort() {
1314 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1315 let mut combined = PreMintSecrets::from_secrets(
1316 keyset_id,
1317 vec![Amount::from(8_u64), Amount::from(2_u64)],
1318 vec![Secret::generate(), Secret::generate()],
1319 )
1320 .unwrap();
1321 let other = PreMintSecrets::from_secrets(
1322 keyset_id,
1323 vec![Amount::from(4_u64), Amount::from(1_u64)],
1324 vec![Secret::generate(), Secret::generate()],
1325 )
1326 .unwrap();
1327
1328 combined.combine(other);
1329
1330 assert_eq!(combined.len(), 4);
1331 assert_eq!(
1332 combined.amounts(),
1333 vec![
1334 Amount::from(8_u64),
1335 Amount::from(2_u64),
1336 Amount::from(4_u64),
1337 Amount::from(1_u64)
1338 ]
1339 );
1340
1341 combined.sort_secrets();
1342
1343 assert_eq!(
1344 combined.amounts(),
1345 vec![
1346 Amount::from(1_u64),
1347 Amount::from(2_u64),
1348 Amount::from(4_u64),
1349 Amount::from(8_u64)
1350 ]
1351 );
1352 }
1353
1354 #[test]
1355 #[cfg(feature = "wallet")]
1356 fn test_premint_secrets_iterator_next_yields_all_items() {
1357 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1358 let premint_secrets = PreMintSecrets::from_secrets(
1359 keyset_id,
1360 vec![
1361 Amount::from(1_u64),
1362 Amount::from(2_u64),
1363 Amount::from(4_u64),
1364 ],
1365 vec![Secret::generate(), Secret::generate(), Secret::generate()],
1366 )
1367 .unwrap();
1368 let expected = premint_secrets.secrets.clone();
1369 let mut iterated = premint_secrets.clone();
1370
1371 assert_eq!(iterated.next(), Some(expected[0].clone()));
1372 assert_eq!(iterated.next(), Some(expected[1].clone()));
1373 assert_eq!(iterated.next(), Some(expected[2].clone()));
1374 assert_eq!(iterated.next(), None);
1375 assert!(iterated.is_empty());
1376 }
1377
1378 #[test]
1379 fn custom_unit_ser_der() {
1380 let unit = CurrencyUnit::Custom(String::from("test"));
1381 let serialized = serde_json::to_string(&unit).unwrap();
1382 let deserialized: CurrencyUnit = serde_json::from_str(&serialized).unwrap();
1383 assert_eq!(unit, deserialized)
1384 }
1385
1386 #[test]
1387 #[cfg(feature = "mint")]
1388 fn test_currency_unit_custom_normalizes_and_stays_custom() {
1389 let unit = CurrencyUnit::custom(" usd\n");
1390
1391 assert_eq!(unit, CurrencyUnit::Custom("USD".to_string()));
1392 assert_ne!(unit, CurrencyUnit::default());
1393 assert_eq!(unit.to_string(), "usd");
1394 }
1395
1396 #[test]
1397 fn test_currency_unit_parsing() {
1398 assert_eq!(CurrencyUnit::from_str("sat").unwrap(), CurrencyUnit::Sat);
1399 assert_eq!(CurrencyUnit::from_str("SAT").unwrap(), CurrencyUnit::Sat);
1400 assert_eq!(CurrencyUnit::from_str("msat").unwrap(), CurrencyUnit::Msat);
1401 assert_eq!(CurrencyUnit::from_str("MSAT").unwrap(), CurrencyUnit::Msat);
1402 assert_eq!(CurrencyUnit::from_str("usd").unwrap(), CurrencyUnit::Usd);
1403 assert_eq!(CurrencyUnit::from_str("USD").unwrap(), CurrencyUnit::Usd);
1404 assert_eq!(CurrencyUnit::from_str("eur").unwrap(), CurrencyUnit::Eur);
1405 assert_eq!(CurrencyUnit::from_str("EUR").unwrap(), CurrencyUnit::Eur);
1406 assert_eq!(CurrencyUnit::from_str("auth").unwrap(), CurrencyUnit::Auth);
1407 assert_eq!(CurrencyUnit::from_str("AUTH").unwrap(), CurrencyUnit::Auth);
1408
1409 assert_eq!(
1411 CurrencyUnit::from_str("custom").unwrap(),
1412 CurrencyUnit::Custom("custom".to_string())
1413 );
1414 }
1415
1416 #[test]
1417 #[cfg(feature = "mint")]
1418 fn four_bytes_hash_currency_unit() {
1419 let unit = CurrencyUnit::Sat;
1420 let index = unit.hashed_derivation_index();
1421 assert_eq!(index, 1967237907);
1422
1423 let unit = CurrencyUnit::Msat;
1424 let index = unit.hashed_derivation_index();
1425 assert_eq!(index, 142929756);
1426
1427 let unit = CurrencyUnit::Eur;
1428 let index = unit.hashed_derivation_index();
1429 assert_eq!(index, 1473545324);
1430
1431 let unit = CurrencyUnit::Usd;
1432 let index = unit.hashed_derivation_index();
1433 assert_eq!(index, 577560378);
1434
1435 let unit = CurrencyUnit::Auth;
1436 let index = unit.hashed_derivation_index();
1437
1438 assert_eq!(index, 1222349093)
1439 }
1440
1441 #[test]
1442 fn test_payment_method_parsing() {
1443 assert_eq!(
1445 PaymentMethod::from_str("bolt11").unwrap(),
1446 PaymentMethod::BOLT11
1447 );
1448 assert_eq!(
1449 PaymentMethod::from_str("BOLT11").unwrap(),
1450 PaymentMethod::BOLT11
1451 );
1452 assert_eq!(
1453 PaymentMethod::from_str("Bolt11").unwrap(),
1454 PaymentMethod::Known(KnownMethod::Bolt11)
1455 );
1456
1457 assert_eq!(
1458 PaymentMethod::from_str("bolt12").unwrap(),
1459 PaymentMethod::BOLT12
1460 );
1461 assert_eq!(
1462 PaymentMethod::from_str("BOLT12").unwrap(),
1463 PaymentMethod::Known(KnownMethod::Bolt12)
1464 );
1465
1466 assert_eq!(
1468 PaymentMethod::from_str("custom").unwrap(),
1469 PaymentMethod::Custom("custom".to_string())
1470 );
1471 assert_eq!(
1472 PaymentMethod::from_str("PAYPAL").unwrap(),
1473 PaymentMethod::Custom("paypal".to_string())
1474 );
1475
1476 assert_eq!(PaymentMethod::BOLT11.as_str(), "bolt11");
1478 assert_eq!(PaymentMethod::BOLT12.as_str(), "bolt12");
1479 assert_eq!(PaymentMethod::from("paypal").as_str(), "paypal");
1480
1481 assert!(PaymentMethod::BOLT11 == "bolt11");
1483 assert!(PaymentMethod::BOLT12 == "bolt12");
1484 assert!(PaymentMethod::Custom("paypal".to_string()) == "paypal");
1485
1486 assert!(PaymentMethod::BOLT11 == KnownMethod::Bolt11);
1488 assert!(PaymentMethod::BOLT12 == KnownMethod::Bolt12);
1489
1490 let methods = vec![
1492 PaymentMethod::BOLT11,
1493 PaymentMethod::BOLT12,
1494 PaymentMethod::Custom("test".to_string()),
1495 ];
1496
1497 for method in methods {
1498 let serialized = serde_json::to_string(&method).unwrap();
1499 let deserialized: PaymentMethod = serde_json::from_str(&serialized).unwrap();
1500 assert_eq!(method, deserialized);
1501 }
1502 }
1503
1504 #[test]
1513 fn test_is_bolt12_with_bolt12() {
1514 let method = PaymentMethod::BOLT12;
1516 assert!(method.is_bolt12());
1517
1518 let method = PaymentMethod::Known(KnownMethod::Bolt12);
1520 assert!(method.is_bolt12());
1521 }
1522
1523 #[test]
1524 fn test_is_bolt12_with_non_bolt12() {
1525 let method = PaymentMethod::BOLT11;
1527 assert!(!method.is_bolt12());
1528
1529 let method = PaymentMethod::Known(KnownMethod::Bolt11);
1531 assert!(!method.is_bolt12());
1532
1533 let method = PaymentMethod::Custom("paypal".to_string());
1535 assert!(!method.is_bolt12());
1536
1537 let method = PaymentMethod::Custom("bolt12".to_string());
1538 assert!(!method.is_bolt12()); }
1540
1541 #[test]
1543 fn test_is_bolt12_comprehensive() {
1544 assert!(PaymentMethod::BOLT12.is_bolt12());
1546 assert!(PaymentMethod::Known(KnownMethod::Bolt12).is_bolt12());
1547
1548 assert!(!PaymentMethod::BOLT11.is_bolt12());
1549 assert!(!PaymentMethod::Known(KnownMethod::Bolt11).is_bolt12());
1550 assert!(!PaymentMethod::Custom("anything".to_string()).is_bolt12());
1551 assert!(!PaymentMethod::Custom("bolt12".to_string()).is_bolt12());
1552 }
1553
1554 #[test]
1555 fn test_witness_serialization() {
1556 let htlc_witness = HTLCWitness {
1557 preimage: "preimage".to_string(),
1558 signatures: Some(vec!["sig1".to_string()]),
1559 };
1560 let witness = Witness::HTLCWitness(htlc_witness);
1561
1562 let serialized = serde_json::to_string(&witness).unwrap();
1563 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1564
1565 assert!(matches!(deserialized, Witness::HTLCWitness(_)));
1566
1567 let p2pk_witness = P2PKWitness {
1568 signatures: vec!["sig1".to_string(), "sig2".to_string()],
1569 };
1570 let witness = Witness::P2PKWitness(p2pk_witness);
1571
1572 let serialized = serde_json::to_string(&witness).unwrap();
1573 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1574
1575 assert!(matches!(deserialized, Witness::P2PKWitness(_)));
1576 }
1577
1578 #[test]
1579 fn test_proofs_methods_count_by_keyset() {
1580 let proofs: Proofs = serde_json::from_str(
1581 r#"[
1582 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1583 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1584 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1585 ]"#,
1586 ).unwrap();
1587
1588 let counts = proofs.count_by_keyset();
1589 assert_eq!(counts.len(), 2);
1590 assert_eq!(counts[&Id::from_str("009a1f293253e41e").unwrap()], 2);
1591 assert_eq!(counts[&Id::from_str("00ad268c4d1f5826").unwrap()], 1);
1592 }
1593
1594 #[test]
1595 fn test_proofs_methods_sum_by_keyset() {
1596 let proofs: Proofs = serde_json::from_str(
1597 r#"[
1598 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1599 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1600 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1601 ]"#,
1602 ).unwrap();
1603
1604 let sums = proofs.sum_by_keyset();
1605 assert_eq!(sums.len(), 2);
1606 assert_eq!(
1607 sums[&Id::from_str("009a1f293253e41e").unwrap()],
1608 Amount::from(10)
1609 );
1610 assert_eq!(
1611 sums[&Id::from_str("00ad268c4d1f5826").unwrap()],
1612 Amount::from(4)
1613 );
1614 }
1615
1616 #[test]
1617 fn test_proofs_methods_total_amount() {
1618 let proofs: Proofs = serde_json::from_str(
1619 r#"[
1620 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1621 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1622 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1623 ]"#,
1624 ).unwrap();
1625
1626 let total = proofs.total_amount().unwrap();
1627 assert_eq!(total, Amount::from(14));
1628 }
1629
1630 #[test]
1631 fn test_proofs_methods_ys() {
1632 let proofs: Proofs = serde_json::from_str(
1633 r#"[
1634 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1635 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1636 ]"#,
1637 ).unwrap();
1638
1639 let ys = proofs.ys().unwrap();
1640 assert_eq!(ys.len(), 2);
1641 assert_ne!(ys[0], ys[1]);
1643 }
1644
1645 #[test]
1646 fn test_proofs_methods_hashset() {
1647 let proofs: Proofs = serde_json::from_str(
1648 r#"[
1649 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1650 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1651 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1652 ]"#,
1653 ).unwrap();
1654
1655 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1656
1657 let counts = proof_set.count_by_keyset();
1659 assert_eq!(counts.len(), 2);
1660
1661 let sums = proof_set.sum_by_keyset();
1662 assert_eq!(sums.len(), 2);
1663 let total: u64 = sums.values().map(|a| u64::from(*a)).sum();
1665 assert_eq!(total, 14);
1666 }
1667
1668 #[test]
1669 fn test_hashset_total_amount() {
1670 let proofs: Proofs = serde_json::from_str(
1671 r#"[
1672 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1673 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1674 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1675 ]"#,
1676 )
1677 .unwrap();
1678
1679 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1680
1681 let total = proof_set.total_amount().unwrap();
1683 assert_eq!(total, Amount::from(14));
1684 }
1685
1686 #[test]
1687 fn test_hashset_ys() {
1688 let proofs: Proofs = serde_json::from_str(
1689 r#"[
1690 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1691 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1692 ]"#,
1693 )
1694 .unwrap();
1695
1696 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1697
1698 let ys = proof_set.ys().unwrap();
1700 assert_eq!(ys.len(), 2);
1701 assert_ne!(ys[0], ys[1]);
1703 }
1704
1705 #[test]
1706 fn test_hashset_without_dleqs() {
1707 let proofs: Proofs = serde_json::from_str(
1708 r#"[
1709 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1710 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1711 ]"#,
1712 )
1713 .unwrap();
1714
1715 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1716
1717 let proofs_without_dleqs = proof_set.without_dleqs();
1719 assert_eq!(proofs_without_dleqs.len(), 2);
1720 for proof in &proofs_without_dleqs {
1722 assert!(proof.dleq.is_none());
1723 }
1724 }
1725
1726 #[test]
1727 fn test_proofs_without_p2pk_e_preserves_other_fields() {
1728 let p2pk_e = PublicKey::from_str(
1729 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1730 )
1731 .unwrap();
1732 let mut proofs: Proofs = serde_json::from_str(
1733 r#"[
1734 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1735 {"id":"00ad268c4d1f5826","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1736 ]"#,
1737 )
1738 .unwrap();
1739 proofs[0].p2pk_e = Some(p2pk_e);
1740 proofs[1].p2pk_e = Some(p2pk_e);
1741
1742 let stripped = proofs.without_p2pk_e();
1743
1744 assert_eq!(stripped.len(), proofs.len());
1745 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1746 assert_eq!(stripped[0].amount, proofs[0].amount);
1747 assert_eq!(stripped[0].keyset_id, proofs[0].keyset_id);
1748 assert_eq!(stripped[0].secret, proofs[0].secret);
1749 assert_eq!(stripped[0].c, proofs[0].c);
1750 assert_eq!(proofs[0].p2pk_e, Some(p2pk_e));
1751 assert_eq!(proofs[1].p2pk_e, Some(p2pk_e));
1752 }
1753
1754 #[test]
1755 fn test_hashset_without_p2pk_e_preserves_all_proofs() {
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 proof_set: HashSet<Proof> = proofs.clone().into_iter().collect();
1771 let stripped = proof_set.without_p2pk_e();
1772
1773 assert_eq!(stripped.len(), proof_set.len());
1774 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1775
1776 let stripped_keys: HashSet<_> = stripped.iter().map(|proof| proof.secret.clone()).collect();
1777 let original_keys: HashSet<_> = proofs.iter().map(|proof| proof.secret.clone()).collect();
1778 assert_eq!(stripped_keys, original_keys);
1779 }
1780
1781 #[test]
1782 #[cfg(feature = "wallet")]
1783 fn test_with_p2bk_rejects_mismatched_ephemeral_keys_when_not_sig_all() {
1784 use crate::amount::{FeeAndAmounts, SplitTarget};
1785 use crate::nuts::nut11::SigFlag;
1786 use crate::Conditions;
1787
1788 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1789 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1790 let receiver_pubkey = receiver_secret_key.public_key();
1791 let conditions =
1792 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1793 let ephemeral_keys = vec![crate::nuts::nut01::SecretKey::generate()];
1794 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1795
1796 let result = PreMintSecrets::with_p2bk(
1797 keyset_id,
1798 Amount::from(3_u64),
1799 &SplitTarget::default(),
1800 receiver_pubkey,
1801 Some(conditions),
1802 &ephemeral_keys,
1803 &fee_and_amounts,
1804 );
1805
1806 assert!(result.is_err());
1807 }
1808
1809 #[test]
1810 #[cfg(feature = "wallet")]
1811 fn test_with_p2bk_allows_single_ephemeral_key_for_sig_all() {
1812 use crate::amount::{FeeAndAmounts, SplitTarget};
1813 use crate::nuts::nut11::SigFlag;
1814 use crate::{Conditions, SpendingConditions};
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::SigAll), None).unwrap();
1821 let ephemeral_key = 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_key],
1831 &fee_and_amounts,
1832 )
1833 .unwrap();
1834
1835 assert_eq!(result.len(), 2);
1836 for premint in result.iter() {
1837 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1838 match spending_conditions {
1839 SpendingConditions::P2PKConditions { data, conditions } => {
1840 assert_ne!(data, receiver_pubkey);
1841 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigAll);
1842 }
1843 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1844 }
1845 }
1846 }
1847
1848 #[test]
1849 #[cfg(feature = "wallet")]
1850 fn test_with_p2bk_allows_one_ephemeral_key_per_output_when_not_sig_all() {
1851 use crate::amount::{FeeAndAmounts, SplitTarget};
1852 use crate::nuts::nut11::SigFlag;
1853 use crate::{Conditions, SpendingConditions};
1854
1855 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1856 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1857 let receiver_pubkey = receiver_secret_key.public_key();
1858 let conditions =
1859 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1860 let ephemeral_keys = vec![
1861 crate::nuts::nut01::SecretKey::generate(),
1862 crate::nuts::nut01::SecretKey::generate(),
1863 ];
1864 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1865
1866 let result = PreMintSecrets::with_p2bk(
1867 keyset_id,
1868 Amount::from(3_u64),
1869 &SplitTarget::default(),
1870 receiver_pubkey,
1871 Some(conditions),
1872 &ephemeral_keys,
1873 &fee_and_amounts,
1874 )
1875 .unwrap();
1876
1877 assert_eq!(result.len(), 2);
1878 for premint in result.iter() {
1879 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1880 match spending_conditions {
1881 SpendingConditions::P2PKConditions { data, conditions } => {
1882 assert_ne!(data, receiver_pubkey);
1883 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigInputs);
1884 }
1885 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1886 }
1887 }
1888 }
1889
1890 #[test]
1891 #[cfg(feature = "wallet")]
1892 fn test_with_p2bk_uses_canonical_slots_for_pubkeys_and_refund_keys() {
1893 use crate::amount::{FeeAndAmounts, SplitTarget};
1894 use crate::nuts::nut11::SigFlag;
1895 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
1896 use crate::{Conditions, SpendingConditions};
1897
1898 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1899 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1900 let receiver_pubkey = receiver_secret_key.public_key();
1901 let additional_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
1902 let additional_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
1903 let refund_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
1904 let refund_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
1905 let conditions = Conditions::new(
1906 None,
1907 Some(vec![additional_key_1, additional_key_2]),
1908 Some(vec![refund_key_1, refund_key_2]),
1909 Some(1),
1910 Some(SigFlag::SigAll),
1911 Some(1),
1912 )
1913 .unwrap();
1914 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
1915 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1916
1917 let premint_secrets = PreMintSecrets::with_p2bk(
1918 keyset_id,
1919 Amount::from(1_u64),
1920 &SplitTarget::default(),
1921 receiver_pubkey,
1922 Some(conditions.clone()),
1923 std::slice::from_ref(&ephemeral_key),
1924 &fee_and_amounts,
1925 )
1926 .unwrap();
1927
1928 let premint = premint_secrets.iter().next().unwrap();
1929 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1930
1931 match spending_conditions {
1932 SpendingConditions::P2PKConditions { data, conditions } => {
1933 let blinded_conditions = conditions.unwrap();
1934
1935 let expected_primary = blind_public_key(
1936 &receiver_pubkey,
1937 &ecdh_kdf(&ephemeral_key, &receiver_pubkey, 0).unwrap(),
1938 )
1939 .unwrap();
1940 let expected_additional = vec![
1941 blind_public_key(
1942 &additional_key_1,
1943 &ecdh_kdf(&ephemeral_key, &additional_key_1, 1).unwrap(),
1944 )
1945 .unwrap(),
1946 blind_public_key(
1947 &additional_key_2,
1948 &ecdh_kdf(&ephemeral_key, &additional_key_2, 2).unwrap(),
1949 )
1950 .unwrap(),
1951 ];
1952 let expected_refund = vec![
1953 blind_public_key(
1954 &refund_key_1,
1955 &ecdh_kdf(&ephemeral_key, &refund_key_1, 3).unwrap(),
1956 )
1957 .unwrap(),
1958 blind_public_key(
1959 &refund_key_2,
1960 &ecdh_kdf(&ephemeral_key, &refund_key_2, 4).unwrap(),
1961 )
1962 .unwrap(),
1963 ];
1964
1965 assert_eq!(data, expected_primary);
1966 assert_eq!(blinded_conditions.pubkeys.unwrap(), expected_additional);
1967 assert_eq!(blinded_conditions.refund_keys.unwrap(), expected_refund);
1968 assert_eq!(blinded_conditions.sig_flag, SigFlag::SigAll);
1969 }
1970 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1971 }
1972 }
1973
1974 #[test]
1975 fn test_blind_signature_partial_cmp() {
1976 let sig1 = BlindSignature {
1977 amount: Amount::from(10),
1978 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
1979 c: PublicKey::from_str(
1980 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1981 )
1982 .unwrap(),
1983 dleq: None,
1984 };
1985 let sig2 = BlindSignature {
1986 amount: Amount::from(20),
1987 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
1988 c: PublicKey::from_str(
1989 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1990 )
1991 .unwrap(),
1992 dleq: None,
1993 };
1994 let sig3 = BlindSignature {
1995 amount: Amount::from(10),
1996 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
1997 c: PublicKey::from_str(
1998 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1999 )
2000 .unwrap(),
2001 dleq: None,
2002 };
2003
2004 assert_eq!(sig1.partial_cmp(&sig2), Some(Ordering::Less));
2006 assert_eq!(sig2.partial_cmp(&sig1), Some(Ordering::Greater));
2007 assert_eq!(sig1.partial_cmp(&sig3), Some(Ordering::Equal));
2008
2009 let mut sigs = [sig2.clone(), sig1.clone(), sig3.clone()];
2011 sigs.sort();
2012 assert_eq!(sigs[0].amount, Amount::from(10));
2013 assert_eq!(sigs[2].amount, Amount::from(20));
2014 }
2015
2016 #[test]
2017 fn test_witness_preimage() {
2018 let htlc_witness = HTLCWitness {
2020 preimage: "test_preimage".to_string(),
2021 signatures: Some(vec!["sig1".to_string()]),
2022 };
2023 let witness = Witness::HTLCWitness(htlc_witness);
2024 assert_eq!(witness.preimage(), Some("test_preimage".to_string()));
2025
2026 let p2pk_witness = P2PKWitness {
2028 signatures: vec!["sig1".to_string()],
2029 };
2030 let witness = Witness::P2PKWitness(p2pk_witness);
2031 assert_eq!(witness.preimage(), None);
2032 }
2033
2034 #[test]
2035 fn test_proof_is_active() {
2036 let proof: Proof = serde_json::from_str(
2037 r#"{"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2038 ).unwrap();
2039
2040 let active_keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2041 let inactive_keyset_id = Id::from_str("00ad268c4d1f5826").unwrap();
2042
2043 assert!(proof.is_active(&[active_keyset_id]));
2045
2046 assert!(!proof.is_active(&[inactive_keyset_id]));
2048
2049 assert!(!proof.is_active(&[]));
2051
2052 assert!(proof.is_active(&[inactive_keyset_id, active_keyset_id]));
2054 }
2055
2056 fn compute_hash<T: Hash>(value: &T) -> u64 {
2058 let mut hasher = DefaultHasher::new();
2059 value.hash(&mut hasher);
2060 hasher.finish()
2061 }
2062
2063 #[test]
2064 fn test_proof_hash_uses_secret() {
2065 let proof1: Proof = serde_json::from_str(
2067 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2068 ).unwrap();
2069
2070 let proof2: Proof = serde_json::from_str(
2071 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2072 ).unwrap();
2073
2074 assert_eq!(compute_hash(&proof1), compute_hash(&proof2));
2076
2077 let proof3: Proof = serde_json::from_str(
2079 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2080 ).unwrap();
2081
2082 assert_ne!(compute_hash(&proof1), compute_hash(&proof3));
2083 }
2084
2085 #[test]
2086 fn test_proof_v4_hash_uses_secret() {
2087 let proof1: Proof = serde_json::from_str(
2088 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2089 ).unwrap();
2090
2091 let proof2: Proof = serde_json::from_str(
2092 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2093 ).unwrap();
2094
2095 let proof_v4_1: ProofV4 = proof1.into();
2096 let proof_v4_2: ProofV4 = proof2.into();
2097
2098 assert_eq!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_2));
2100
2101 let proof3: Proof = serde_json::from_str(
2103 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2104 ).unwrap();
2105 let proof_v4_3: ProofV4 = proof3.into();
2106
2107 assert_ne!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_3));
2108 }
2109
2110 #[test]
2111 fn test_proof_v3_hash_uses_secret() {
2112 let proof1: Proof = serde_json::from_str(
2113 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2114 ).unwrap();
2115
2116 let proof2: Proof = serde_json::from_str(
2117 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2118 ).unwrap();
2119
2120 let proof_v3_1: ProofV3 = proof1.into();
2121 let proof_v3_2: ProofV3 = proof2.into();
2122
2123 assert_eq!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_2));
2125
2126 let proof3: Proof = serde_json::from_str(
2128 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2129 ).unwrap();
2130 let proof_v3_3: ProofV3 = proof3.into();
2131
2132 assert_ne!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_3));
2133 }
2134
2135 #[test]
2136 #[cfg(feature = "mint")]
2137 #[allow(deprecated)]
2138 fn test_currency_unit_derivation_index() {
2139 assert_eq!(CurrencyUnit::Sat.derivation_index(), Some(0));
2142 assert_eq!(CurrencyUnit::Msat.derivation_index(), Some(1));
2143 assert_eq!(CurrencyUnit::Usd.derivation_index(), Some(2));
2144 assert_eq!(CurrencyUnit::Eur.derivation_index(), Some(3));
2145 assert_eq!(CurrencyUnit::Auth.derivation_index(), Some(4));
2146
2147 assert_eq!(
2149 CurrencyUnit::Custom("btc".to_string()).derivation_index(),
2150 None
2151 );
2152 }
2153}