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;
11use std::sync::Arc;
12
13#[cfg(feature = "mint")]
14use bitcoin::hashes::Hash as BitcoinHash;
15use serde::{Deserialize, Deserializer, Serialize};
16use thiserror::Error;
17use unicode_normalization::UnicodeNormalization;
18
19use super::nut02::ShortKeysetId;
20#[cfg(feature = "wallet")]
21use super::nut10;
22#[cfg(feature = "wallet")]
23use crate::amount::FeeAndAmounts;
24#[cfg(feature = "wallet")]
25use crate::amount::SplitTarget;
26#[cfg(feature = "wallet")]
27use crate::dhke::blind_message;
28use crate::dhke::hash_to_curve;
29use crate::nuts::nut01::PublicKey;
30#[cfg(feature = "wallet")]
31use crate::nuts::nut01::SecretKey;
32use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness};
33use crate::nuts::nut12::BlindSignatureDleq;
34use crate::nuts::nut14::{serde_htlc_witness, HTLCWitness};
35use crate::nuts::{Id, ProofDleq};
36use crate::secret::Secret;
37use crate::Amount;
38
39pub mod token;
40pub use token::{Token, TokenV3, TokenV4};
41
42pub type Proofs = Vec<Proof>;
44
45pub trait ProofsMethods {
47 fn count_by_keyset(&self) -> HashMap<Id, u64>;
49
50 fn sum_by_keyset(&self) -> HashMap<Id, Amount>;
52
53 fn total_amount(&self) -> Result<Amount, Error>;
55
56 fn ys(&self) -> Result<Vec<PublicKey>, Error>;
58
59 fn without_dleqs(&self) -> Proofs;
61
62 fn without_p2pk_e(&self) -> Proofs;
64}
65
66impl ProofsMethods for Proofs {
67 fn count_by_keyset(&self) -> HashMap<Id, u64> {
68 count_by_keyset(self.iter())
69 }
70
71 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
72 sum_by_keyset(self.iter())
73 }
74
75 fn total_amount(&self) -> Result<Amount, Error> {
76 total_amount(self.iter())
77 }
78
79 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
80 ys(self.iter())
81 }
82
83 fn without_dleqs(&self) -> Proofs {
84 self.iter()
85 .map(|p| {
86 let mut p = p.clone();
87 p.dleq = None;
88 p
89 })
90 .collect()
91 }
92
93 fn without_p2pk_e(&self) -> Proofs {
94 self.iter()
95 .map(|p| {
96 let mut p = p.clone();
97 p.p2pk_e = None;
98 p
99 })
100 .collect()
101 }
102}
103
104impl ProofsMethods for HashSet<Proof> {
105 fn count_by_keyset(&self) -> HashMap<Id, u64> {
106 count_by_keyset(self.iter())
107 }
108
109 fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
110 sum_by_keyset(self.iter())
111 }
112
113 fn total_amount(&self) -> Result<Amount, Error> {
114 total_amount(self.iter())
115 }
116
117 fn ys(&self) -> Result<Vec<PublicKey>, Error> {
118 ys(self.iter())
119 }
120
121 fn without_dleqs(&self) -> Proofs {
122 self.iter()
123 .map(|p| {
124 let mut p = p.clone();
125 p.dleq = None;
126 p
127 })
128 .collect()
129 }
130
131 fn without_p2pk_e(&self) -> Proofs {
132 self.iter()
133 .map(|p| {
134 let mut p = p.clone();
135 p.p2pk_e = None;
136 p
137 })
138 .collect()
139 }
140}
141
142fn count_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, u64> {
143 let mut counts = HashMap::new();
144 for proof in proofs {
145 *counts.entry(proof.keyset_id).or_insert(0) += 1;
146 }
147 counts
148}
149
150fn sum_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, Amount> {
151 let mut sums = HashMap::new();
152 for proof in proofs {
153 *sums.entry(proof.keyset_id).or_insert(Amount::ZERO) += proof.amount;
154 }
155 sums
156}
157
158fn total_amount<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Amount, Error> {
159 Amount::try_sum(proofs.map(|p| p.amount)).map_err(Into::into)
160}
161
162fn ys<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Vec<PublicKey>, Error> {
163 proofs.map(Proof::y).collect::<Result<Vec<PublicKey>, _>>()
164}
165
166#[derive(Debug, Error)]
168pub enum Error {
169 #[error("Proofs required in token")]
171 ProofsRequired,
172 #[error("Unsupported token")]
174 UnsupportedToken,
175 #[error("Unsupported unit")]
177 UnsupportedUnit,
178 #[error("Unsupported payment method")]
180 UnsupportedPaymentMethod,
181 #[error("Duplicate proofs in token")]
183 DuplicateProofs,
184 #[error(transparent)]
186 SerdeJsonError(#[from] serde_json::Error),
187 #[error(transparent)]
189 Utf8ParseError(#[from] FromUtf8Error),
190 #[error(transparent)]
192 Base64Error(#[from] bitcoin::base64::DecodeError),
193 #[error(transparent)]
195 CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
196 #[error(transparent)]
198 CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
199 #[error(transparent)]
201 Amount(#[from] crate::amount::Error),
202 #[error(transparent)]
204 Secret(#[from] crate::secret::Error),
205 #[error(transparent)]
207 DHKE(#[from] crate::dhke::Error),
208 #[error(transparent)]
210 NUT10(#[from] crate::nuts::nut10::Error),
211 #[error(transparent)]
213 NUT11(#[from] crate::nuts::nut11::Error),
214 #[error(transparent)]
216 NUT02(#[from] crate::nuts::nut02::Error),
217 #[cfg(feature = "wallet")]
218 #[error(transparent)]
219 NUT28(#[from] crate::nuts::nut28::Error),
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
225pub struct BlindedMessage {
226 pub amount: Amount,
230 #[serde(rename = "id")]
234 pub keyset_id: Id,
235 #[serde(rename = "B_")]
239 pub blinded_secret: PublicKey,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub witness: Option<Witness>,
245}
246
247impl BlindedMessage {
248 #[inline]
250 pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
251 Self {
252 amount,
253 keyset_id,
254 blinded_secret,
255 witness: None,
256 }
257 }
258
259 #[inline]
261 pub fn witness(&mut self, witness: Witness) {
262 self.witness = Some(witness);
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268pub struct BlindSignature {
269 pub amount: Amount,
273 #[serde(rename = "id")]
277 pub keyset_id: Id,
278 #[serde(rename = "C_")]
282 pub c: PublicKey,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
287 pub dleq: Option<BlindSignatureDleq>,
288}
289
290impl Ord for BlindSignature {
291 fn cmp(&self, other: &Self) -> Ordering {
292 self.amount.cmp(&other.amount)
293 }
294}
295
296impl PartialOrd for BlindSignature {
297 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
298 Some(self.cmp(other))
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
304#[serde(untagged)]
305pub enum Witness {
306 #[serde(with = "serde_htlc_witness")]
308 HTLCWitness(HTLCWitness),
309 #[serde(with = "serde_p2pk_witness")]
311 P2PKWitness(P2PKWitness),
312}
313
314impl From<P2PKWitness> for Witness {
315 fn from(witness: P2PKWitness) -> Self {
316 Self::P2PKWitness(witness)
317 }
318}
319
320impl From<HTLCWitness> for Witness {
321 fn from(witness: HTLCWitness) -> Self {
322 Self::HTLCWitness(witness)
323 }
324}
325
326impl Witness {
327 pub fn add_signatures(&mut self, signatures: Vec<String>) {
329 match self {
330 Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatures),
331 Self::HTLCWitness(htlc_witness) => match &mut htlc_witness.signatures {
332 Some(sigs) => sigs.extend(signatures),
333 None => htlc_witness.signatures = Some(signatures),
334 },
335 }
336 }
337
338 pub fn signatures(&self) -> Option<Vec<String>> {
340 match self {
341 Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
342 Self::HTLCWitness(witness) => witness.signatures.clone(),
343 }
344 }
345
346 pub fn preimage(&self) -> Option<String> {
348 match self {
349 Self::P2PKWitness(_witness) => None,
350 Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
351 }
352 }
353}
354
355impl std::fmt::Display for Witness {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 write!(
358 f,
359 "{}",
360 serde_json::to_string(self).map_err(|_| std::fmt::Error)?
361 )
362 }
363}
364
365#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
367pub struct Proof {
368 pub amount: Amount,
370 #[serde(rename = "id")]
372 pub keyset_id: Id,
373 pub secret: Secret,
375 #[serde(rename = "C")]
377 pub c: PublicKey,
378 #[serde(default, skip_serializing_if = "Option::is_none")]
380 pub witness: Option<Witness>,
381 #[serde(default, skip_serializing_if = "Option::is_none")]
383 pub dleq: Option<ProofDleq>,
384 #[serde(default, skip_serializing_if = "Option::is_none")]
387 pub p2pk_e: Option<PublicKey>,
388}
389
390impl Proof {
391 pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
393 Proof {
394 amount,
395 keyset_id,
396 secret,
397 c,
398 witness: None,
399 dleq: None,
400 p2pk_e: None,
401 }
402 }
403
404 pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
406 active_keyset_ids.contains(&self.keyset_id)
407 }
408
409 pub fn y(&self) -> Result<PublicKey, Error> {
413 Ok(hash_to_curve(self.secret.as_bytes())?)
414 }
415}
416
417impl Hash for Proof {
418 fn hash<H: Hasher>(&self, state: &mut H) {
419 self.secret.hash(state);
420 }
421}
422
423impl Ord for Proof {
424 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
425 self.amount.cmp(&other.amount)
426 }
427}
428
429impl PartialOrd for Proof {
430 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
431 Some(self.cmp(other))
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
437pub struct ProofV4 {
438 #[serde(rename = "a")]
440 pub amount: Amount,
441 #[serde(rename = "s")]
443 pub secret: Secret,
444 #[serde(
446 serialize_with = "serialize_v4_pubkey",
447 deserialize_with = "deserialize_v4_pubkey"
448 )]
449 pub c: PublicKey,
450 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub witness: Option<Witness>,
453 #[serde(rename = "d")]
455 pub dleq: Option<ProofDleq>,
456 #[serde(rename = "pe", default, skip_serializing_if = "Option::is_none")]
458 pub p2pk_e: Option<PublicKey>,
459}
460
461impl ProofV4 {
462 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
464 Proof {
465 amount: self.amount,
466 keyset_id: *keyset_id,
467 secret: self.secret.clone(),
468 c: self.c,
469 witness: self.witness.clone(),
470 dleq: self.dleq.clone(),
471 p2pk_e: self.p2pk_e,
472 }
473 }
474}
475
476impl Hash for ProofV4 {
477 fn hash<H: Hasher>(&self, state: &mut H) {
478 self.secret.hash(state);
479 }
480}
481
482impl From<Proof> for ProofV4 {
483 fn from(proof: Proof) -> ProofV4 {
484 let Proof {
485 amount,
486 secret,
487 c,
488 witness,
489 dleq,
490 p2pk_e,
491 ..
492 } = proof;
493 ProofV4 {
494 amount,
495 secret,
496 c,
497 witness,
498 dleq,
499 p2pk_e,
500 }
501 }
502}
503
504impl From<ProofV3> for ProofV4 {
505 fn from(proof: ProofV3) -> Self {
506 Self {
507 amount: proof.amount,
508 secret: proof.secret,
509 c: proof.c,
510 witness: proof.witness,
511 dleq: proof.dleq,
512 p2pk_e: None,
513 }
514 }
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
519pub struct ProofV3 {
520 pub amount: Amount,
522 #[serde(rename = "id")]
524 pub keyset_id: ShortKeysetId,
525 pub secret: Secret,
527 #[serde(rename = "C")]
529 pub c: PublicKey,
530 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub witness: Option<Witness>,
533 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub dleq: Option<ProofDleq>,
536}
537
538impl ProofV3 {
539 pub fn into_proof(&self, keyset_id: &Id) -> Proof {
541 Proof {
542 amount: self.amount,
543 keyset_id: *keyset_id,
544 secret: self.secret.clone(),
545 c: self.c,
546 witness: self.witness.clone(),
547 dleq: self.dleq.clone(),
548 p2pk_e: None,
549 }
550 }
551}
552
553impl From<Proof> for ProofV3 {
554 fn from(proof: Proof) -> ProofV3 {
555 let Proof {
556 amount,
557 keyset_id,
558 secret,
559 c,
560 witness,
561 dleq,
562 ..
563 } = proof;
564 ProofV3 {
565 amount,
566 secret,
567 c,
568 witness,
569 dleq,
570 keyset_id: keyset_id.into(),
571 }
572 }
573}
574
575impl Hash for ProofV3 {
576 fn hash<H: Hasher>(&self, state: &mut H) {
577 self.secret.hash(state);
578 }
579}
580
581fn serialize_v4_pubkey<S>(key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
582where
583 S: serde::Serializer,
584{
585 serializer.serialize_bytes(&key.to_bytes())
586}
587
588fn deserialize_v4_pubkey<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
589where
590 D: serde::Deserializer<'de>,
591{
592 let bytes = Vec::<u8>::deserialize(deserializer)?;
593 PublicKey::from_slice(&bytes).map_err(serde::de::Error::custom)
594}
595
596#[non_exhaustive]
598#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
599pub enum CurrencyUnit {
600 #[default]
602 Sat,
603 Msat,
605 Usd,
607 Eur,
609 Auth,
611 Custom(Arc<str>),
613}
614
615impl CurrencyUnit {
616 pub fn custom<S: AsRef<str>>(value: S) -> Self {
618 Self::Custom(normalize_custom_unit(value.as_ref()).into())
619 }
620}
621
622#[cfg(feature = "mint")]
623impl CurrencyUnit {
624 #[deprecated(
626 since = "0.15.0",
627 note = "This function is outdated; use `hashed_derivation_index` instead."
628 )]
629 pub fn derivation_index(&self) -> Option<u32> {
630 match self {
631 Self::Sat => Some(0),
632 Self::Msat => Some(1),
633 Self::Usd => Some(2),
634 Self::Eur => Some(3),
635 Self::Auth => Some(4),
636 _ => None,
637 }
638 }
639
640 pub fn hashed_derivation_index(&self) -> u32 {
642 use bitcoin::hashes::sha256;
643
644 let unit_str = self.to_string().to_uppercase();
646
647 let bytes = <sha256::Hash as BitcoinHash>::hash(unit_str.as_bytes());
648 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) & !(1 << 31)
650 }
651}
652
653fn normalize_custom_unit(value: &str) -> String {
654 let trimmed = value.trim_matches(|c: char| matches!(c, ' ' | '\t' | '\r' | '\n'));
655 trimmed
656 .chars()
657 .flat_map(char::to_lowercase)
658 .nfc()
659 .collect::<String>()
660}
661
662impl FromStr for CurrencyUnit {
663 type Err = Error;
664 fn from_str(value: &str) -> Result<Self, Self::Err> {
665 let upper_value = value.to_uppercase();
666 match upper_value.as_str() {
667 "SAT" => Ok(Self::Sat),
668 "MSAT" => Ok(Self::Msat),
669 "USD" => Ok(Self::Usd),
670 "EUR" => Ok(Self::Eur),
671 "AUTH" => Ok(Self::Auth),
672 _ => Ok(Self::custom(value)),
673 }
674 }
675}
676
677impl fmt::Display for CurrencyUnit {
678 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679 let s = match self {
680 CurrencyUnit::Sat => "SAT",
681 CurrencyUnit::Msat => "MSAT",
682 CurrencyUnit::Usd => "USD",
683 CurrencyUnit::Eur => "EUR",
684 CurrencyUnit::Auth => "AUTH",
685 CurrencyUnit::Custom(unit) => unit.as_ref(),
686 };
687
688 if let Some(width) = f.width() {
689 write!(f, "{:width$}", normalize_custom_unit(s), width = width)
690 } else {
691 write!(f, "{}", normalize_custom_unit(s))
692 }
693 }
694}
695
696impl Serialize for CurrencyUnit {
697 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
698 where
699 S: serde::Serializer,
700 {
701 serializer.serialize_str(&self.to_string())
702 }
703}
704
705impl<'de> Deserialize<'de> for CurrencyUnit {
706 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
707 where
708 D: Deserializer<'de>,
709 {
710 let currency: String = String::deserialize(deserializer)?;
711 Self::from_str(¤cy).map_err(|_| serde::de::Error::custom("Unsupported unit"))
712 }
713}
714
715#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
717pub enum KnownMethod {
718 Bolt11,
720 Bolt12,
722 Onchain,
724}
725
726impl KnownMethod {
727 pub fn as_str(&self) -> &str {
729 match self {
730 Self::Bolt11 => "bolt11",
731 Self::Bolt12 => "bolt12",
732 Self::Onchain => "onchain",
733 }
734 }
735}
736
737impl fmt::Display for KnownMethod {
738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739 write!(f, "{}", self.as_str())
740 }
741}
742
743impl FromStr for KnownMethod {
744 type Err = Error;
745 fn from_str(value: &str) -> Result<Self, Self::Err> {
746 match value.to_lowercase().as_str() {
747 "bolt11" => Ok(Self::Bolt11),
748 "bolt12" => Ok(Self::Bolt12),
749 "onchain" => Ok(Self::Onchain),
750 _ => Err(Error::UnsupportedPaymentMethod),
751 }
752 }
753}
754
755#[derive(Debug, Clone, PartialEq, Eq, Hash)]
759pub enum PaymentMethod {
760 Known(KnownMethod),
762 Custom(String),
764}
765
766impl PaymentMethod {
767 pub const BOLT11: Self = Self::Known(KnownMethod::Bolt11);
769 pub const BOLT12: Self = Self::Known(KnownMethod::Bolt12);
771
772 pub fn new(method: String) -> Self {
774 Self::from_str(&method).unwrap_or_else(|_| Self::Custom(method.to_lowercase()))
775 }
776
777 pub fn is_valid_custom_method_name(method: &str) -> bool {
779 !method.is_empty()
780 && method
781 .chars()
782 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
783 }
784
785 pub fn is_valid_method_name(&self) -> bool {
787 match self {
788 Self::Known(_) => true,
789 Self::Custom(method) => Self::is_valid_custom_method_name(method),
790 }
791 }
792
793 pub fn as_str(&self) -> &str {
795 match self {
796 Self::Known(known) => known.as_str(),
797 Self::Custom(custom) => custom.as_str(),
798 }
799 }
800
801 pub fn derived_method_name(&self) -> String {
806 self.as_str()
807 .split(['_', '-'])
808 .map(|word| {
809 let mut chars = word.chars();
810 match chars.next() {
811 Some(first) => {
812 let mut title = String::new();
813 title.extend(first.to_uppercase());
814 title.extend(chars.flat_map(char::to_lowercase));
815 title
816 }
817 None => String::new(),
818 }
819 })
820 .collect::<Vec<_>>()
821 .join(" ")
822 }
823
824 pub fn is_known(&self) -> bool {
826 matches!(self, Self::Known(_))
827 }
828
829 pub fn is_custom(&self) -> bool {
831 matches!(self, Self::Custom(_))
832 }
833
834 pub fn is_bolt11(&self) -> bool {
836 matches!(self, Self::Known(KnownMethod::Bolt11))
837 }
838
839 pub fn is_bolt12(&self) -> bool {
841 matches!(self, Self::Known(KnownMethod::Bolt12))
842 }
843
844 pub fn is_onchain(&self) -> bool {
846 matches!(self, Self::Known(KnownMethod::Onchain))
847 }
848}
849
850impl FromStr for PaymentMethod {
851 type Err = Error;
852 fn from_str(value: &str) -> Result<Self, Self::Err> {
853 match KnownMethod::from_str(value) {
854 Ok(known) => Ok(Self::Known(known)),
855 Err(_) => Ok(Self::Custom(value.to_lowercase())),
856 }
857 }
858}
859
860impl fmt::Display for PaymentMethod {
861 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
862 write!(f, "{}", self.as_str())
863 }
864}
865
866impl From<String> for PaymentMethod {
867 fn from(s: String) -> Self {
868 Self::from_str(&s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
869 }
870}
871
872impl From<&str> for PaymentMethod {
873 fn from(s: &str) -> Self {
874 Self::from_str(s).unwrap_or_else(|_| Self::Custom(s.to_lowercase()))
875 }
876}
877
878impl From<KnownMethod> for PaymentMethod {
879 fn from(known: KnownMethod) -> Self {
880 Self::Known(known)
881 }
882}
883
884impl PartialEq<&str> for PaymentMethod {
886 fn eq(&self, other: &&str) -> bool {
887 self.as_str() == *other
888 }
889}
890
891impl PartialEq<str> for PaymentMethod {
892 fn eq(&self, other: &str) -> bool {
893 self.as_str() == other
894 }
895}
896
897impl PartialEq<PaymentMethod> for &str {
898 fn eq(&self, other: &PaymentMethod) -> bool {
899 *self == other.as_str()
900 }
901}
902
903impl PartialEq<KnownMethod> for PaymentMethod {
904 fn eq(&self, other: &KnownMethod) -> bool {
905 matches!(self, Self::Known(k) if k == other)
906 }
907}
908
909impl PartialEq<PaymentMethod> for KnownMethod {
910 fn eq(&self, other: &PaymentMethod) -> bool {
911 matches!(other, PaymentMethod::Known(k) if k == self)
912 }
913}
914
915impl Serialize for PaymentMethod {
916 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
917 where
918 S: serde::Serializer,
919 {
920 serializer.serialize_str(self.as_str())
921 }
922}
923
924impl<'de> Deserialize<'de> for PaymentMethod {
925 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
926 where
927 D: Deserializer<'de>,
928 {
929 let payment_method: String = String::deserialize(deserializer)?;
930 Ok(Self::from_str(&payment_method).unwrap_or(Self::Custom(payment_method)))
931 }
932}
933
934#[cfg(feature = "wallet")]
936#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
937pub struct PreMint {
938 pub blinded_message: BlindedMessage,
940 pub secret: Secret,
942 pub r: SecretKey,
944 pub amount: Amount,
946 #[serde(skip)]
948 pub derivation_index: Option<u32>,
949}
950
951#[cfg(feature = "wallet")]
952impl Ord for PreMint {
953 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
954 self.amount.cmp(&other.amount)
955 }
956}
957
958#[cfg(feature = "wallet")]
959impl PartialOrd for PreMint {
960 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
961 Some(self.cmp(other))
962 }
963}
964
965#[cfg(feature = "wallet")]
967#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
968pub struct PreMintSecrets {
969 pub secrets: Vec<PreMint>,
971 pub keyset_id: Id,
973}
974
975#[cfg(feature = "wallet")]
976impl PreMintSecrets {
977 pub fn new(keyset_id: Id) -> Self {
979 Self {
980 secrets: Vec::new(),
981 keyset_id,
982 }
983 }
984
985 pub fn random(
987 keyset_id: Id,
988 amount: Amount,
989 amount_split_target: &SplitTarget,
990 fee_and_amounts: &FeeAndAmounts,
991 ) -> Result<Self, Error> {
992 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
993
994 let mut output = Vec::with_capacity(amount_split.len());
995
996 for amount in amount_split {
997 let secret = Secret::generate();
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 derivation_index: None,
1008 });
1009 }
1010
1011 Ok(PreMintSecrets {
1012 secrets: output,
1013 keyset_id,
1014 })
1015 }
1016
1017 pub fn from_secrets(
1019 keyset_id: Id,
1020 amounts: Vec<Amount>,
1021 secrets: Vec<Secret>,
1022 ) -> Result<Self, Error> {
1023 let mut output = Vec::with_capacity(secrets.len());
1024
1025 for (secret, amount) in secrets.into_iter().zip(amounts) {
1026 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1027
1028 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1029
1030 output.push(PreMint {
1031 secret,
1032 blinded_message,
1033 r,
1034 amount,
1035 derivation_index: None,
1036 });
1037 }
1038
1039 Ok(PreMintSecrets {
1040 secrets: output,
1041 keyset_id,
1042 })
1043 }
1044
1045 pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
1047 let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
1048
1049 let mut output = Vec::with_capacity(count as usize);
1050
1051 for _i in 0..count {
1052 let secret = Secret::generate();
1053 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1054
1055 let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
1056
1057 output.push(PreMint {
1058 secret,
1059 blinded_message,
1060 r,
1061 amount: Amount::ZERO,
1062 derivation_index: None,
1063 })
1064 }
1065
1066 Ok(PreMintSecrets {
1067 secrets: output,
1068 keyset_id,
1069 })
1070 }
1071
1072 #[cfg(feature = "wallet")]
1074 pub fn with_p2bk(
1075 keyset_id: Id,
1076 amount: Amount,
1077 amount_split_target: &SplitTarget,
1078 receiver_pubkey: PublicKey,
1079 conditions: Option<crate::nuts::nut10::Conditions>,
1080 ephemeral_keys: &[crate::nuts::nut01::SecretKey],
1081 fee_and_amounts: &FeeAndAmounts,
1082 ) -> Result<Self, Error> {
1083 use crate::nuts::nut10::spending_conditions::{check_locking_slots, validate_p2pk};
1084 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
1085
1086 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1087
1088 validate_p2pk(receiver_pubkey, conditions.as_ref())?;
1089
1090 if let Some(conditions) = conditions.as_ref() {
1091 check_locking_slots(
1092 conditions.pubkeys.as_ref().map(Vec::len).unwrap_or(0),
1093 conditions.refund_keys.as_deref(),
1094 )?;
1095 }
1096
1097 let mut output = Vec::with_capacity(amount_split.len());
1098
1099 let is_sig_all = conditions
1100 .as_ref()
1101 .is_some_and(|c| c.sig_flag == crate::nuts::nut11::SigFlag::SigAll);
1102 if !is_sig_all && ephemeral_keys.len() != amount_split.len() {
1103 return Err(Error::NUT28(
1104 crate::nuts::nut28::Error::InvalidCanonicalSlot(255),
1105 ));
1106 }
1107
1108 for (i, amount) in amount_split.into_iter().enumerate() {
1109 let ephemeral_key = if is_sig_all {
1110 &ephemeral_keys[0]
1111 } else {
1112 &ephemeral_keys[i]
1113 };
1114
1115 let r0 = ecdh_kdf(ephemeral_key, &receiver_pubkey, 0).map_err(Error::NUT28)?;
1117 let blinded_pubkey = blind_public_key(&receiver_pubkey, &r0).map_err(Error::NUT28)?;
1118
1119 let mut blinded_conditions = conditions.clone();
1120
1121 if let Some(ref mut cond) = blinded_conditions {
1123 let mut slot_idx = 1;
1124
1125 if let Some(ref mut pubkeys) = cond.pubkeys {
1126 for pk in pubkeys.iter_mut() {
1127 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1128 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1129 slot_idx += 1;
1130 }
1131 }
1132
1133 if let Some(ref mut refund_keys) = cond.refund_keys {
1134 for pk in refund_keys.iter_mut() {
1135 let r = ecdh_kdf(ephemeral_key, pk, slot_idx).map_err(Error::NUT28)?;
1136 *pk = blind_public_key(pk, &r).map_err(Error::NUT28)?;
1137 slot_idx += 1;
1138 }
1139 }
1140 }
1141
1142 let p2pk_conditions = crate::nuts::SpendingConditions::P2PKConditions {
1143 data: blinded_pubkey,
1144 conditions: blinded_conditions,
1145 };
1146
1147 let secret: crate::nuts::nut10::Secret = p2pk_conditions.try_into()?;
1148 let secret: Secret = secret.try_into()?;
1149 let (blinded, rs) = blind_message(&secret.to_bytes(), None)?;
1150
1151 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1152
1153 output.push(PreMint {
1154 secret,
1155 blinded_message,
1156 r: rs,
1157 amount,
1158 derivation_index: None,
1159 });
1160 }
1161
1162 Ok(PreMintSecrets {
1163 secrets: output,
1164 keyset_id,
1165 })
1166 }
1167
1168 pub fn with_conditions(
1170 keyset_id: Id,
1171 amount: Amount,
1172 amount_split_target: &SplitTarget,
1173 conditions: &nut10::SpendingConditions,
1174 fee_and_amounts: &FeeAndAmounts,
1175 ) -> Result<Self, Error> {
1176 let amount_split = amount.split_targeted(amount_split_target, fee_and_amounts)?;
1177
1178 let mut output = Vec::with_capacity(amount_split.len());
1179
1180 for amount in amount_split {
1181 let secret: nut10::Secret = conditions.clone().try_into()?;
1182
1183 let secret: Secret = secret.try_into()?;
1184 let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
1185
1186 let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
1187
1188 output.push(PreMint {
1189 secret,
1190 blinded_message,
1191 r,
1192 amount,
1193 derivation_index: None,
1194 });
1195 }
1196
1197 Ok(PreMintSecrets {
1198 secrets: output,
1199 keyset_id,
1200 })
1201 }
1202
1203 #[inline]
1205 pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
1206 self.secrets.iter()
1207 }
1208
1209 #[inline]
1211 pub fn len(&self) -> usize {
1212 self.secrets.len()
1213 }
1214
1215 #[inline]
1217 pub fn is_empty(&self) -> bool {
1218 self.secrets.is_empty()
1219 }
1220
1221 pub fn total_amount(&self) -> Result<Amount, Error> {
1223 Ok(Amount::try_sum(
1224 self.secrets.iter().map(|PreMint { amount, .. }| *amount),
1225 )?)
1226 }
1227
1228 #[inline]
1230 pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
1231 self.iter().map(|pm| pm.blinded_message.clone()).collect()
1232 }
1233
1234 #[inline]
1236 pub fn secrets(&self) -> Vec<Secret> {
1237 self.iter().map(|pm| pm.secret.clone()).collect()
1238 }
1239
1240 #[inline]
1242 pub fn rs(&self) -> Vec<SecretKey> {
1243 self.iter().map(|pm| pm.r.clone()).collect()
1244 }
1245
1246 #[inline]
1248 pub fn amounts(&self) -> Vec<Amount> {
1249 self.iter().map(|pm| pm.amount).collect()
1250 }
1251
1252 #[inline]
1254 pub fn combine(&mut self, mut other: Self) {
1255 self.secrets.append(&mut other.secrets)
1256 }
1257
1258 #[inline]
1260 pub fn sort_secrets(&mut self) {
1261 self.secrets.sort();
1262 }
1263}
1264
1265#[cfg(feature = "wallet")]
1267impl Iterator for PreMintSecrets {
1268 type Item = PreMint;
1269
1270 fn next(&mut self) -> Option<Self::Item> {
1271 if self.secrets.is_empty() {
1273 return None;
1274 }
1275 Some(self.secrets.remove(0))
1276 }
1277}
1278
1279#[cfg(feature = "wallet")]
1280impl Ord for PreMintSecrets {
1281 fn cmp(&self, other: &Self) -> Ordering {
1282 self.secrets.cmp(&other.secrets)
1283 }
1284}
1285
1286#[cfg(feature = "wallet")]
1287impl PartialOrd for PreMintSecrets {
1288 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1289 Some(self.cmp(other))
1290 }
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295 use std::collections::hash_map::DefaultHasher;
1296 use std::collections::HashSet;
1297 use std::hash::{Hash, Hasher};
1298 use std::str::FromStr;
1299
1300 use super::*;
1301
1302 #[test]
1303 fn test_proof_serialize() {
1304 let proof = "[{\"id\":\"009a1f293253e41e\",\"amount\":2,\"secret\":\"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\"C\":\"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"},{\"id\":\"009a1f293253e41e\",\"amount\":8,\"secret\":\"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\"C\":\"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"}]";
1305 let proof: Proofs = serde_json::from_str(proof).unwrap();
1306
1307 assert_eq!(
1308 proof[0].clone().keyset_id,
1309 Id::from_str("009a1f293253e41e").unwrap()
1310 );
1311
1312 assert_eq!(proof.len(), 2);
1313 }
1314
1315 #[test]
1316 #[cfg(feature = "wallet")]
1317 fn test_blank_blinded_messages() {
1318 let b = PreMintSecrets::blank(
1319 Id::from_str("009a1f293253e41e").unwrap(),
1320 Amount::from(1000),
1321 )
1322 .unwrap();
1323 assert_eq!(b.len(), 10);
1324
1325 let b = PreMintSecrets::blank(Id::from_str("009a1f293253e41e").unwrap(), Amount::from(1))
1326 .unwrap();
1327 assert_eq!(b.len(), 1);
1328 }
1329
1330 #[test]
1331 #[cfg(feature = "wallet")]
1332 fn test_premint_secrets_accessors_and_total_amount() {
1333 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1334 let amounts = vec![
1335 Amount::from(1_u64),
1336 Amount::from(2_u64),
1337 Amount::from(4_u64),
1338 ];
1339 let secrets = vec![Secret::generate(), Secret::generate(), Secret::generate()];
1340
1341 let premint_secrets =
1342 PreMintSecrets::from_secrets(keyset_id, amounts.clone(), secrets.clone()).unwrap();
1343
1344 assert_eq!(premint_secrets.total_amount().unwrap(), Amount::from(7_u64));
1345 assert_eq!(premint_secrets.amounts(), amounts);
1346 assert_eq!(premint_secrets.secrets(), secrets);
1347
1348 let blinded_messages = premint_secrets.blinded_messages();
1349 assert_eq!(blinded_messages.len(), 3);
1350 assert_eq!(blinded_messages[0].amount, Amount::from(1_u64));
1351 assert_eq!(blinded_messages[1].amount, Amount::from(2_u64));
1352 assert_eq!(blinded_messages[2].amount, Amount::from(4_u64));
1353 assert!(blinded_messages
1354 .iter()
1355 .all(|message| message.keyset_id == keyset_id));
1356
1357 let rs = premint_secrets.rs();
1358 assert_eq!(rs.len(), 3);
1359 assert_eq!(rs[0], premint_secrets.secrets[0].r);
1360 assert_eq!(rs[1], premint_secrets.secrets[1].r);
1361 assert_eq!(rs[2], premint_secrets.secrets[2].r);
1362 }
1363
1364 #[test]
1365 #[cfg(feature = "wallet")]
1366 fn test_premint_secrets_combine_and_sort() {
1367 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1368 let mut combined = PreMintSecrets::from_secrets(
1369 keyset_id,
1370 vec![Amount::from(8_u64), Amount::from(2_u64)],
1371 vec![Secret::generate(), Secret::generate()],
1372 )
1373 .unwrap();
1374 let other = PreMintSecrets::from_secrets(
1375 keyset_id,
1376 vec![Amount::from(4_u64), Amount::from(1_u64)],
1377 vec![Secret::generate(), Secret::generate()],
1378 )
1379 .unwrap();
1380
1381 combined.combine(other);
1382
1383 assert_eq!(combined.len(), 4);
1384 assert_eq!(
1385 combined.amounts(),
1386 vec![
1387 Amount::from(8_u64),
1388 Amount::from(2_u64),
1389 Amount::from(4_u64),
1390 Amount::from(1_u64)
1391 ]
1392 );
1393
1394 combined.sort_secrets();
1395
1396 assert_eq!(
1397 combined.amounts(),
1398 vec![
1399 Amount::from(1_u64),
1400 Amount::from(2_u64),
1401 Amount::from(4_u64),
1402 Amount::from(8_u64)
1403 ]
1404 );
1405 }
1406
1407 #[test]
1408 #[cfg(feature = "wallet")]
1409 fn test_premint_secrets_iterator_next_yields_all_items() {
1410 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1411 let premint_secrets = PreMintSecrets::from_secrets(
1412 keyset_id,
1413 vec![
1414 Amount::from(1_u64),
1415 Amount::from(2_u64),
1416 Amount::from(4_u64),
1417 ],
1418 vec![Secret::generate(), Secret::generate(), Secret::generate()],
1419 )
1420 .unwrap();
1421 let expected = premint_secrets.secrets.clone();
1422 let mut iterated = premint_secrets.clone();
1423
1424 assert_eq!(iterated.next(), Some(expected[0].clone()));
1425 assert_eq!(iterated.next(), Some(expected[1].clone()));
1426 assert_eq!(iterated.next(), Some(expected[2].clone()));
1427 assert_eq!(iterated.next(), None);
1428 assert!(iterated.is_empty());
1429 }
1430
1431 #[test]
1432 fn custom_unit_ser_der() {
1433 let unit = CurrencyUnit::from_str("BADCOIN").unwrap();
1434 let serialized = serde_json::to_string(&unit).unwrap();
1435 let deserialized: CurrencyUnit = serde_json::from_str(&serialized).unwrap();
1436 let deserialized_uppercase: CurrencyUnit = serde_json::from_str(r#""BADCOIN""#).unwrap();
1437
1438 assert_eq!(unit, CurrencyUnit::Custom("badcoin".into()));
1439 assert_eq!(unit, CurrencyUnit::from_str("badcoin").unwrap());
1440 assert_eq!(unit, CurrencyUnit::custom("BADCOIN"));
1441 assert_eq!(unit.to_string(), "badcoin");
1442 assert_eq!(serialized, r#""badcoin""#);
1443 assert_eq!(unit, deserialized);
1444 assert_eq!(unit, deserialized_uppercase);
1445 }
1446
1447 #[test]
1448 fn test_currency_unit_custom_normalizes_and_stays_custom() {
1449 let unit = CurrencyUnit::custom(" usd\n");
1450
1451 assert_eq!(unit, CurrencyUnit::Custom("usd".into()));
1452 assert_ne!(unit, CurrencyUnit::default());
1453 assert_eq!(unit.to_string(), "usd");
1454 }
1455
1456 #[test]
1457 fn test_currency_unit_parsing() {
1458 assert_eq!(CurrencyUnit::from_str("sat").unwrap(), CurrencyUnit::Sat);
1459 assert_eq!(CurrencyUnit::from_str("SAT").unwrap(), CurrencyUnit::Sat);
1460 assert_eq!(CurrencyUnit::from_str("msat").unwrap(), CurrencyUnit::Msat);
1461 assert_eq!(CurrencyUnit::from_str("MSAT").unwrap(), CurrencyUnit::Msat);
1462 assert_eq!(CurrencyUnit::from_str("usd").unwrap(), CurrencyUnit::Usd);
1463 assert_eq!(CurrencyUnit::from_str("USD").unwrap(), CurrencyUnit::Usd);
1464 assert_eq!(CurrencyUnit::from_str("eur").unwrap(), CurrencyUnit::Eur);
1465 assert_eq!(CurrencyUnit::from_str("EUR").unwrap(), CurrencyUnit::Eur);
1466 assert_eq!(CurrencyUnit::from_str("auth").unwrap(), CurrencyUnit::Auth);
1467 assert_eq!(CurrencyUnit::from_str("AUTH").unwrap(), CurrencyUnit::Auth);
1468
1469 assert_eq!(
1471 CurrencyUnit::from_str("custom").unwrap(),
1472 CurrencyUnit::Custom("custom".into())
1473 );
1474 assert_eq!(
1475 CurrencyUnit::from_str("CuStOm").unwrap(),
1476 CurrencyUnit::Custom("custom".into())
1477 );
1478 }
1479
1480 #[test]
1481 #[cfg(feature = "mint")]
1482 fn four_bytes_hash_currency_unit() {
1483 let unit = CurrencyUnit::Sat;
1484 let index = unit.hashed_derivation_index();
1485 assert_eq!(index, 1967237907);
1486
1487 let unit = CurrencyUnit::Msat;
1488 let index = unit.hashed_derivation_index();
1489 assert_eq!(index, 142929756);
1490
1491 let unit = CurrencyUnit::Eur;
1492 let index = unit.hashed_derivation_index();
1493 assert_eq!(index, 1473545324);
1494
1495 let unit = CurrencyUnit::Usd;
1496 let index = unit.hashed_derivation_index();
1497 assert_eq!(index, 577560378);
1498
1499 let unit = CurrencyUnit::Auth;
1500 let index = unit.hashed_derivation_index();
1501
1502 assert_eq!(index, 1222349093)
1503 }
1504
1505 #[test]
1506 fn test_payment_method_parsing() {
1507 assert_eq!(
1509 PaymentMethod::from_str("bolt11").unwrap(),
1510 PaymentMethod::BOLT11
1511 );
1512 assert_eq!(
1513 PaymentMethod::from_str("BOLT11").unwrap(),
1514 PaymentMethod::BOLT11
1515 );
1516 assert_eq!(
1517 PaymentMethod::from_str("Bolt11").unwrap(),
1518 PaymentMethod::Known(KnownMethod::Bolt11)
1519 );
1520
1521 assert_eq!(
1522 PaymentMethod::from_str("bolt12").unwrap(),
1523 PaymentMethod::BOLT12
1524 );
1525 assert_eq!(
1526 PaymentMethod::from_str("BOLT12").unwrap(),
1527 PaymentMethod::Known(KnownMethod::Bolt12)
1528 );
1529
1530 assert_eq!(
1532 PaymentMethod::from_str("custom").unwrap(),
1533 PaymentMethod::Custom("custom".to_string())
1534 );
1535 assert_eq!(
1536 PaymentMethod::from_str("PAYPAL").unwrap(),
1537 PaymentMethod::Custom("paypal".to_string())
1538 );
1539
1540 assert_eq!(PaymentMethod::BOLT11.as_str(), "bolt11");
1542 assert_eq!(PaymentMethod::BOLT12.as_str(), "bolt12");
1543 assert_eq!(PaymentMethod::from("paypal").as_str(), "paypal");
1544
1545 assert!(PaymentMethod::BOLT11 == "bolt11");
1547 assert!(PaymentMethod::BOLT12 == "bolt12");
1548 assert!(PaymentMethod::Custom("paypal".to_string()) == "paypal");
1549
1550 assert!(PaymentMethod::BOLT11 == KnownMethod::Bolt11);
1552 assert!(PaymentMethod::BOLT12 == KnownMethod::Bolt12);
1553
1554 let methods = vec![
1556 PaymentMethod::BOLT11,
1557 PaymentMethod::BOLT12,
1558 PaymentMethod::Custom("test".to_string()),
1559 ];
1560
1561 for method in methods {
1562 let serialized = serde_json::to_string(&method).unwrap();
1563 let deserialized: PaymentMethod = serde_json::from_str(&serialized).unwrap();
1564 assert_eq!(method, deserialized);
1565 }
1566 }
1567
1568 #[test]
1569 fn test_payment_method_custom_name_validation() {
1570 for method in ["paypal", "pay-pal", "pay_pal", "pay123", "Pay-123_USD"] {
1571 assert!(
1572 PaymentMethod::is_valid_custom_method_name(method),
1573 "{method} should be valid"
1574 );
1575 }
1576
1577 for method in [
1578 "",
1579 "pay pal",
1580 "pay/pal",
1581 "pay.pal",
1582 "pay:pal",
1583 "\u{00fc}mlaut",
1584 ] {
1585 assert!(
1586 !PaymentMethod::is_valid_custom_method_name(method),
1587 "{method} should be invalid"
1588 );
1589 }
1590 }
1591
1592 #[test]
1593 fn test_payment_method_name_validation() {
1594 assert!(PaymentMethod::BOLT11.is_valid_method_name());
1595 assert!(PaymentMethod::Known(KnownMethod::Onchain).is_valid_method_name());
1596 assert!(PaymentMethod::Custom("pay-pal_123".to_string()).is_valid_method_name());
1597 assert!(!PaymentMethod::Custom("pay/pal".to_string()).is_valid_method_name());
1598 }
1599
1600 #[test]
1601 fn test_payment_method_derived_method_name() {
1602 assert_eq!(PaymentMethod::BOLT11.derived_method_name(), "Bolt11");
1603 assert_eq!(
1604 PaymentMethod::Custom("apple-pay".to_string()).derived_method_name(),
1605 "Apple Pay"
1606 );
1607 assert_eq!(
1608 PaymentMethod::Custom("pay_pal".to_string()).derived_method_name(),
1609 "Pay Pal"
1610 );
1611 }
1612
1613 #[test]
1622 fn test_is_bolt12_with_bolt12() {
1623 let method = PaymentMethod::BOLT12;
1625 assert!(method.is_bolt12());
1626
1627 let method = PaymentMethod::Known(KnownMethod::Bolt12);
1629 assert!(method.is_bolt12());
1630 }
1631
1632 #[test]
1633 fn test_is_bolt12_with_non_bolt12() {
1634 let method = PaymentMethod::BOLT11;
1636 assert!(!method.is_bolt12());
1637
1638 let method = PaymentMethod::Known(KnownMethod::Bolt11);
1640 assert!(!method.is_bolt12());
1641
1642 let method = PaymentMethod::Custom("paypal".to_string());
1644 assert!(!method.is_bolt12());
1645
1646 let method = PaymentMethod::Custom("bolt12".to_string());
1647 assert!(!method.is_bolt12()); }
1649
1650 #[test]
1652 fn test_is_bolt12_comprehensive() {
1653 assert!(PaymentMethod::BOLT12.is_bolt12());
1655 assert!(PaymentMethod::Known(KnownMethod::Bolt12).is_bolt12());
1656
1657 assert!(!PaymentMethod::BOLT11.is_bolt12());
1658 assert!(!PaymentMethod::Known(KnownMethod::Bolt11).is_bolt12());
1659 assert!(!PaymentMethod::Custom("anything".to_string()).is_bolt12());
1660 assert!(!PaymentMethod::Custom("bolt12".to_string()).is_bolt12());
1661 }
1662
1663 #[test]
1669 fn test_is_onchain_comprehensive() {
1670 assert!(PaymentMethod::Known(KnownMethod::Onchain).is_onchain());
1671
1672 assert!(!PaymentMethod::BOLT11.is_onchain());
1673 assert!(!PaymentMethod::BOLT12.is_onchain());
1674 assert!(!PaymentMethod::Known(KnownMethod::Bolt11).is_onchain());
1675 assert!(!PaymentMethod::Known(KnownMethod::Bolt12).is_onchain());
1676 assert!(!PaymentMethod::Custom("anything".to_string()).is_onchain());
1677 assert!(!PaymentMethod::Custom("onchain".to_string()).is_onchain());
1678 }
1679
1680 #[test]
1681 fn test_witness_serialization() {
1682 let htlc_witness = HTLCWitness {
1683 preimage: "preimage".to_string(),
1684 signatures: Some(vec!["sig1".to_string()]),
1685 };
1686 let witness = Witness::HTLCWitness(htlc_witness);
1687
1688 let serialized = serde_json::to_string(&witness).unwrap();
1689 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1690
1691 assert!(matches!(deserialized, Witness::HTLCWitness(_)));
1692
1693 let p2pk_witness = P2PKWitness {
1694 signatures: vec!["sig1".to_string(), "sig2".to_string()],
1695 };
1696 let witness = Witness::P2PKWitness(p2pk_witness);
1697
1698 let serialized = serde_json::to_string(&witness).unwrap();
1699 let deserialized: Witness = serde_json::from_str(&serialized).unwrap();
1700
1701 assert!(matches!(deserialized, Witness::P2PKWitness(_)));
1702 }
1703
1704 #[test]
1705 fn test_proofs_methods_count_by_keyset() {
1706 let proofs: Proofs = serde_json::from_str(
1707 r#"[
1708 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1709 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1710 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1711 ]"#,
1712 ).unwrap();
1713
1714 let counts = proofs.count_by_keyset();
1715 assert_eq!(counts.len(), 2);
1716 assert_eq!(counts[&Id::from_str("009a1f293253e41e").unwrap()], 2);
1717 assert_eq!(counts[&Id::from_str("00ad268c4d1f5826").unwrap()], 1);
1718 }
1719
1720 #[test]
1721 fn test_proofs_methods_sum_by_keyset() {
1722 let proofs: Proofs = serde_json::from_str(
1723 r#"[
1724 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1725 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1726 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1727 ]"#,
1728 ).unwrap();
1729
1730 let sums = proofs.sum_by_keyset();
1731 assert_eq!(sums.len(), 2);
1732 assert_eq!(
1733 sums[&Id::from_str("009a1f293253e41e").unwrap()],
1734 Amount::from(10)
1735 );
1736 assert_eq!(
1737 sums[&Id::from_str("00ad268c4d1f5826").unwrap()],
1738 Amount::from(4)
1739 );
1740 }
1741
1742 #[test]
1743 fn test_proofs_methods_total_amount() {
1744 let proofs: Proofs = serde_json::from_str(
1745 r#"[
1746 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1747 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1748 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1749 ]"#,
1750 ).unwrap();
1751
1752 let total = proofs.total_amount().unwrap();
1753 assert_eq!(total, Amount::from(14));
1754 }
1755
1756 #[test]
1757 fn test_proofs_methods_ys() {
1758 let proofs: Proofs = serde_json::from_str(
1759 r#"[
1760 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1761 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1762 ]"#,
1763 ).unwrap();
1764
1765 let ys = proofs.ys().unwrap();
1766 assert_eq!(ys.len(), 2);
1767 assert_ne!(ys[0], ys[1]);
1769 }
1770
1771 #[test]
1772 fn test_proofs_methods_hashset() {
1773 let proofs: Proofs = serde_json::from_str(
1774 r#"[
1775 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1776 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1777 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1778 ]"#,
1779 ).unwrap();
1780
1781 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1782
1783 let counts = proof_set.count_by_keyset();
1785 assert_eq!(counts.len(), 2);
1786
1787 let sums = proof_set.sum_by_keyset();
1788 assert_eq!(sums.len(), 2);
1789 let total: u64 = sums.values().map(|a| u64::from(*a)).sum();
1791 assert_eq!(total, 14);
1792 }
1793
1794 #[test]
1795 fn test_hashset_total_amount() {
1796 let proofs: Proofs = serde_json::from_str(
1797 r#"[
1798 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1799 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"},
1800 {"id":"00ad268c4d1f5826","amount":4,"secret":"secret3","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}
1801 ]"#,
1802 )
1803 .unwrap();
1804
1805 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1806
1807 let total = proof_set.total_amount().unwrap();
1809 assert_eq!(total, Amount::from(14));
1810 }
1811
1812 #[test]
1813 fn test_hashset_ys() {
1814 let proofs: Proofs = serde_json::from_str(
1815 r#"[
1816 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1817 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1818 ]"#,
1819 )
1820 .unwrap();
1821
1822 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1823
1824 let ys = proof_set.ys().unwrap();
1826 assert_eq!(ys.len(), 2);
1827 assert_ne!(ys[0], ys[1]);
1829 }
1830
1831 #[test]
1832 fn test_hashset_without_dleqs() {
1833 let proofs: Proofs = serde_json::from_str(
1834 r#"[
1835 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1836 {"id":"009a1f293253e41e","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1837 ]"#,
1838 )
1839 .unwrap();
1840
1841 let proof_set: HashSet<Proof> = proofs.into_iter().collect();
1842
1843 let proofs_without_dleqs = proof_set.without_dleqs();
1845 assert_eq!(proofs_without_dleqs.len(), 2);
1846 for proof in &proofs_without_dleqs {
1848 assert!(proof.dleq.is_none());
1849 }
1850 }
1851
1852 #[test]
1853 fn test_proofs_without_p2pk_e_preserves_other_fields() {
1854 let p2pk_e = PublicKey::from_str(
1855 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1856 )
1857 .unwrap();
1858 let mut proofs: Proofs = serde_json::from_str(
1859 r#"[
1860 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1861 {"id":"00ad268c4d1f5826","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1862 ]"#,
1863 )
1864 .unwrap();
1865 proofs[0].p2pk_e = Some(p2pk_e);
1866 proofs[1].p2pk_e = Some(p2pk_e);
1867
1868 let stripped = proofs.without_p2pk_e();
1869
1870 assert_eq!(stripped.len(), proofs.len());
1871 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1872 assert_eq!(stripped[0].amount, proofs[0].amount);
1873 assert_eq!(stripped[0].keyset_id, proofs[0].keyset_id);
1874 assert_eq!(stripped[0].secret, proofs[0].secret);
1875 assert_eq!(stripped[0].c, proofs[0].c);
1876 assert_eq!(proofs[0].p2pk_e, Some(p2pk_e));
1877 assert_eq!(proofs[1].p2pk_e, Some(p2pk_e));
1878 }
1879
1880 #[test]
1881 fn test_hashset_without_p2pk_e_preserves_all_proofs() {
1882 let p2pk_e = PublicKey::from_str(
1883 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
1884 )
1885 .unwrap();
1886 let mut proofs: Proofs = serde_json::from_str(
1887 r#"[
1888 {"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"},
1889 {"id":"00ad268c4d1f5826","amount":8,"secret":"secret2","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}
1890 ]"#,
1891 )
1892 .unwrap();
1893 proofs[0].p2pk_e = Some(p2pk_e);
1894 proofs[1].p2pk_e = Some(p2pk_e);
1895
1896 let proof_set: HashSet<Proof> = proofs.clone().into_iter().collect();
1897 let stripped = proof_set.without_p2pk_e();
1898
1899 assert_eq!(stripped.len(), proof_set.len());
1900 assert!(stripped.iter().all(|proof| proof.p2pk_e.is_none()));
1901
1902 let stripped_keys: HashSet<_> = stripped.iter().map(|proof| proof.secret.clone()).collect();
1903 let original_keys: HashSet<_> = proofs.iter().map(|proof| proof.secret.clone()).collect();
1904 assert_eq!(stripped_keys, original_keys);
1905 }
1906
1907 #[test]
1908 #[cfg(feature = "wallet")]
1909 fn test_with_p2bk_rejects_mismatched_ephemeral_keys_when_not_sig_all() {
1910 use crate::amount::{FeeAndAmounts, SplitTarget};
1911 use crate::nuts::nut11::SigFlag;
1912 use crate::Conditions;
1913
1914 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1915 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1916 let receiver_pubkey = receiver_secret_key.public_key();
1917 let conditions =
1918 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1919 let ephemeral_keys = vec![crate::nuts::nut01::SecretKey::generate()];
1920 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1921
1922 let result = PreMintSecrets::with_p2bk(
1923 keyset_id,
1924 Amount::from(3_u64),
1925 &SplitTarget::default(),
1926 receiver_pubkey,
1927 Some(conditions),
1928 &ephemeral_keys,
1929 &fee_and_amounts,
1930 );
1931
1932 assert!(result.is_err());
1933 }
1934
1935 #[test]
1936 #[cfg(feature = "wallet")]
1937 fn test_with_p2bk_allows_single_ephemeral_key_for_sig_all() {
1938 use crate::amount::{FeeAndAmounts, SplitTarget};
1939 use crate::nuts::nut11::SigFlag;
1940 use crate::{Conditions, SpendingConditions};
1941
1942 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1943 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1944 let receiver_pubkey = receiver_secret_key.public_key();
1945 let conditions =
1946 Conditions::new(None, None, None, None, Some(SigFlag::SigAll), None).unwrap();
1947 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
1948 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1949
1950 let result = PreMintSecrets::with_p2bk(
1951 keyset_id,
1952 Amount::from(3_u64),
1953 &SplitTarget::default(),
1954 receiver_pubkey,
1955 Some(conditions),
1956 &[ephemeral_key],
1957 &fee_and_amounts,
1958 )
1959 .unwrap();
1960
1961 assert_eq!(result.len(), 2);
1962 for premint in result.iter() {
1963 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
1964 match spending_conditions {
1965 SpendingConditions::P2PKConditions { data, conditions } => {
1966 assert_ne!(data, receiver_pubkey);
1967 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigAll);
1968 }
1969 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
1970 }
1971 }
1972 }
1973
1974 #[test]
1975 #[cfg(feature = "wallet")]
1976 fn test_with_p2bk_allows_one_ephemeral_key_per_output_when_not_sig_all() {
1977 use crate::amount::{FeeAndAmounts, SplitTarget};
1978 use crate::nuts::nut11::SigFlag;
1979 use crate::{Conditions, SpendingConditions};
1980
1981 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
1982 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
1983 let receiver_pubkey = receiver_secret_key.public_key();
1984 let conditions =
1985 Conditions::new(None, None, None, None, Some(SigFlag::SigInputs), None).unwrap();
1986 let ephemeral_keys = vec![
1987 crate::nuts::nut01::SecretKey::generate(),
1988 crate::nuts::nut01::SecretKey::generate(),
1989 ];
1990 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
1991
1992 let result = PreMintSecrets::with_p2bk(
1993 keyset_id,
1994 Amount::from(3_u64),
1995 &SplitTarget::default(),
1996 receiver_pubkey,
1997 Some(conditions),
1998 &ephemeral_keys,
1999 &fee_and_amounts,
2000 )
2001 .unwrap();
2002
2003 assert_eq!(result.len(), 2);
2004 for premint in result.iter() {
2005 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
2006 match spending_conditions {
2007 SpendingConditions::P2PKConditions { data, conditions } => {
2008 assert_ne!(data, receiver_pubkey);
2009 assert_eq!(conditions.unwrap().sig_flag, SigFlag::SigInputs);
2010 }
2011 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
2012 }
2013 }
2014 }
2015
2016 #[test]
2017 #[cfg(feature = "wallet")]
2018 fn test_with_p2bk_uses_canonical_slots_for_pubkeys_and_refund_keys() {
2019 use crate::amount::{FeeAndAmounts, SplitTarget};
2020 use crate::nuts::nut11::SigFlag;
2021 use crate::nuts::nut28::{blind_public_key, ecdh_kdf};
2022 use crate::{Conditions, SpendingConditions};
2023
2024 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2025 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
2026 let receiver_pubkey = receiver_secret_key.public_key();
2027 let additional_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
2028 let additional_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
2029 let refund_key_1 = crate::nuts::nut01::SecretKey::generate().public_key();
2030 let refund_key_2 = crate::nuts::nut01::SecretKey::generate().public_key();
2031 let conditions = Conditions::new(
2032 Some(crate::util::unix_time() + 3600),
2033 Some(vec![additional_key_1, additional_key_2]),
2034 Some(vec![refund_key_1, refund_key_2]),
2035 Some(1),
2036 Some(SigFlag::SigAll),
2037 Some(1),
2038 )
2039 .unwrap();
2040 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
2041 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
2042
2043 let premint_secrets = PreMintSecrets::with_p2bk(
2044 keyset_id,
2045 Amount::from(1_u64),
2046 &SplitTarget::default(),
2047 receiver_pubkey,
2048 Some(conditions.clone()),
2049 std::slice::from_ref(&ephemeral_key),
2050 &fee_and_amounts,
2051 )
2052 .unwrap();
2053
2054 let premint = premint_secrets.iter().next().unwrap();
2055 let spending_conditions = SpendingConditions::try_from(&premint.secret).unwrap();
2056
2057 match spending_conditions {
2058 SpendingConditions::P2PKConditions { data, conditions } => {
2059 let blinded_conditions = conditions.unwrap();
2060
2061 let expected_primary = blind_public_key(
2062 &receiver_pubkey,
2063 &ecdh_kdf(&ephemeral_key, &receiver_pubkey, 0).unwrap(),
2064 )
2065 .unwrap();
2066 let expected_additional = vec![
2067 blind_public_key(
2068 &additional_key_1,
2069 &ecdh_kdf(&ephemeral_key, &additional_key_1, 1).unwrap(),
2070 )
2071 .unwrap(),
2072 blind_public_key(
2073 &additional_key_2,
2074 &ecdh_kdf(&ephemeral_key, &additional_key_2, 2).unwrap(),
2075 )
2076 .unwrap(),
2077 ];
2078 let expected_refund = vec![
2079 blind_public_key(
2080 &refund_key_1,
2081 &ecdh_kdf(&ephemeral_key, &refund_key_1, 3).unwrap(),
2082 )
2083 .unwrap(),
2084 blind_public_key(
2085 &refund_key_2,
2086 &ecdh_kdf(&ephemeral_key, &refund_key_2, 4).unwrap(),
2087 )
2088 .unwrap(),
2089 ];
2090
2091 assert_eq!(data, expected_primary);
2092 assert_eq!(blinded_conditions.pubkeys.unwrap(), expected_additional);
2093 assert_eq!(blinded_conditions.refund_keys.unwrap(), expected_refund);
2094 assert_eq!(blinded_conditions.sig_flag, SigFlag::SigAll);
2095 }
2096 SpendingConditions::HTLCConditions { .. } => panic!("expected P2PK conditions"),
2097 }
2098 }
2099
2100 #[test]
2104 #[cfg(feature = "wallet")]
2105 fn test_with_p2bk_rejects_duplicate_pubkeys_before_blinding() {
2106 use crate::amount::{FeeAndAmounts, SplitTarget};
2107 use crate::nuts::nut11::SigFlag;
2108 use crate::Conditions;
2109
2110 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2111 let receiver_secret_key = crate::nuts::nut01::SecretKey::generate();
2112 let mut bytes = receiver_secret_key.public_key().to_bytes();
2113 bytes[0] = 0x02;
2114 let pk_02 = PublicKey::from_slice(&bytes).unwrap();
2115 bytes[0] = 0x03;
2116 let pk_03 = PublicKey::from_slice(&bytes).unwrap();
2117
2118 let conditions = Conditions {
2119 locktime: None,
2120 pubkeys: Some(vec![pk_03]),
2121 refund_keys: None,
2122 num_sigs: None,
2123 sig_flag: SigFlag::SigAll,
2124 num_sigs_refund: None,
2125 };
2126 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
2127 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
2128
2129 let err = PreMintSecrets::with_p2bk(
2130 keyset_id,
2131 Amount::from(1_u64),
2132 &SplitTarget::default(),
2133 pk_02,
2134 Some(conditions),
2135 std::slice::from_ref(&ephemeral_key),
2136 &fee_and_amounts,
2137 )
2138 .expect_err("duplicate key should be rejected before blinding");
2139
2140 assert!(
2141 matches!(
2142 err,
2143 Error::NUT10(crate::nuts::nut10::Error::NUT11(
2144 crate::nuts::nut11::Error::DuplicatePubkey
2145 ))
2146 ),
2147 "Expected DuplicatePubkey, got: {err:?}"
2148 );
2149 }
2150
2151 #[test]
2154 #[cfg(feature = "wallet")]
2155 fn test_with_conditions_rejects_duplicate_pubkeys() {
2156 use crate::amount::{FeeAndAmounts, SplitTarget};
2157 use crate::nuts::nut11::SigFlag;
2158 use crate::{Conditions, SpendingConditions};
2159
2160 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2161 let pubkey = crate::nuts::nut01::SecretKey::generate().public_key();
2162
2163 let conditions = Conditions {
2164 locktime: None,
2165 pubkeys: Some(vec![pubkey]),
2166 refund_keys: None,
2167 num_sigs: None,
2168 sig_flag: SigFlag::SigInputs,
2169 num_sigs_refund: None,
2170 };
2171 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
2172
2173 let err = PreMintSecrets::with_conditions(
2174 keyset_id,
2175 Amount::from(2_u64),
2176 &SplitTarget::default(),
2177 &SpendingConditions::P2PKConditions {
2178 data: pubkey,
2179 conditions: Some(conditions),
2180 },
2181 &fee_and_amounts,
2182 )
2183 .expect_err("duplicate key should be rejected for the whole batch");
2184
2185 assert!(
2186 matches!(
2187 err,
2188 Error::NUT10(crate::nuts::nut10::Error::NUT11(
2189 crate::nuts::nut11::Error::DuplicatePubkey
2190 ))
2191 ),
2192 "Expected DuplicatePubkey, got: {err:?}"
2193 );
2194 }
2195
2196 #[test]
2199 #[cfg(feature = "wallet")]
2200 fn test_with_p2bk_rejects_more_slots_than_nut28_allows() {
2201 use crate::amount::{FeeAndAmounts, SplitTarget};
2202 use crate::nuts::nut11::SigFlag;
2203 use crate::Conditions;
2204
2205 let keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2206 let receiver_pubkey = crate::nuts::nut01::SecretKey::generate().public_key();
2207 let pubkeys: Vec<PublicKey> = (0..11)
2208 .map(|_| crate::nuts::nut01::SecretKey::generate().public_key())
2209 .collect();
2210
2211 let conditions = Conditions {
2212 locktime: None,
2213 pubkeys: Some(pubkeys),
2214 refund_keys: None,
2215 num_sigs: Some(1),
2216 sig_flag: SigFlag::SigAll,
2217 num_sigs_refund: None,
2218 };
2219 let ephemeral_key = crate::nuts::nut01::SecretKey::generate();
2220 let fee_and_amounts = FeeAndAmounts::from((0, (0..32).map(|x| 2u64.pow(x)).collect()));
2221
2222 let err = PreMintSecrets::with_p2bk(
2223 keyset_id,
2224 Amount::from(1_u64),
2225 &SplitTarget::default(),
2226 receiver_pubkey,
2227 Some(conditions),
2228 std::slice::from_ref(&ephemeral_key),
2229 &fee_and_amounts,
2230 )
2231 .expect_err("twelve slots should be rejected");
2232
2233 assert!(
2234 matches!(
2235 err,
2236 Error::NUT10(crate::nuts::nut10::Error::NUT11(
2237 crate::nuts::nut11::Error::TooManyPubkeys { slots: 12 }
2238 ))
2239 ),
2240 "Expected TooManyPubkeys, got: {err:?}"
2241 );
2242 }
2243
2244 #[test]
2245 fn test_blind_signature_partial_cmp() {
2246 let sig1 = BlindSignature {
2247 amount: Amount::from(10),
2248 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2249 c: PublicKey::from_str(
2250 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2251 )
2252 .unwrap(),
2253 dleq: None,
2254 };
2255 let sig2 = BlindSignature {
2256 amount: Amount::from(20),
2257 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2258 c: PublicKey::from_str(
2259 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2260 )
2261 .unwrap(),
2262 dleq: None,
2263 };
2264 let sig3 = BlindSignature {
2265 amount: Amount::from(10),
2266 keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
2267 c: PublicKey::from_str(
2268 "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea",
2269 )
2270 .unwrap(),
2271 dleq: None,
2272 };
2273
2274 assert_eq!(sig1.partial_cmp(&sig2), Some(Ordering::Less));
2276 assert_eq!(sig2.partial_cmp(&sig1), Some(Ordering::Greater));
2277 assert_eq!(sig1.partial_cmp(&sig3), Some(Ordering::Equal));
2278
2279 let mut sigs = [sig2.clone(), sig1.clone(), sig3.clone()];
2281 sigs.sort();
2282 assert_eq!(sigs[0].amount, Amount::from(10));
2283 assert_eq!(sigs[2].amount, Amount::from(20));
2284 }
2285
2286 #[test]
2287 fn test_witness_preimage() {
2288 let htlc_witness = HTLCWitness {
2290 preimage: "test_preimage".to_string(),
2291 signatures: Some(vec!["sig1".to_string()]),
2292 };
2293 let witness = Witness::HTLCWitness(htlc_witness);
2294 assert_eq!(witness.preimage(), Some("test_preimage".to_string()));
2295
2296 let p2pk_witness = P2PKWitness {
2298 signatures: vec!["sig1".to_string()],
2299 };
2300 let witness = Witness::P2PKWitness(p2pk_witness);
2301 assert_eq!(witness.preimage(), None);
2302 }
2303
2304 #[test]
2305 fn test_proof_is_active() {
2306 let proof: Proof = serde_json::from_str(
2307 r#"{"id":"009a1f293253e41e","amount":2,"secret":"secret1","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2308 ).unwrap();
2309
2310 let active_keyset_id = Id::from_str("009a1f293253e41e").unwrap();
2311 let inactive_keyset_id = Id::from_str("00ad268c4d1f5826").unwrap();
2312
2313 assert!(proof.is_active(&[active_keyset_id]));
2315
2316 assert!(!proof.is_active(&[inactive_keyset_id]));
2318
2319 assert!(!proof.is_active(&[]));
2321
2322 assert!(proof.is_active(&[inactive_keyset_id, active_keyset_id]));
2324 }
2325
2326 fn compute_hash<T: Hash>(value: &T) -> u64 {
2328 let mut hasher = DefaultHasher::new();
2329 value.hash(&mut hasher);
2330 hasher.finish()
2331 }
2332
2333 #[test]
2334 fn test_proof_hash_uses_secret() {
2335 let proof1: Proof = serde_json::from_str(
2337 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2338 ).unwrap();
2339
2340 let proof2: Proof = serde_json::from_str(
2341 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2342 ).unwrap();
2343
2344 assert_eq!(compute_hash(&proof1), compute_hash(&proof2));
2346
2347 let proof3: Proof = serde_json::from_str(
2349 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2350 ).unwrap();
2351
2352 assert_ne!(compute_hash(&proof1), compute_hash(&proof3));
2353 }
2354
2355 #[test]
2356 fn test_proof_v4_hash_uses_secret() {
2357 let proof1: Proof = serde_json::from_str(
2358 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2359 ).unwrap();
2360
2361 let proof2: Proof = serde_json::from_str(
2362 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2363 ).unwrap();
2364
2365 let proof_v4_1: ProofV4 = proof1.into();
2366 let proof_v4_2: ProofV4 = proof2.into();
2367
2368 assert_eq!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_2));
2370
2371 let proof3: Proof = serde_json::from_str(
2373 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2374 ).unwrap();
2375 let proof_v4_3: ProofV4 = proof3.into();
2376
2377 assert_ne!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_3));
2378 }
2379
2380 #[test]
2381 fn test_proof_v3_hash_uses_secret() {
2382 let proof1: Proof = serde_json::from_str(
2383 r#"{"id":"009a1f293253e41e","amount":2,"secret":"same_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2384 ).unwrap();
2385
2386 let proof2: Proof = serde_json::from_str(
2387 r#"{"id":"00ad268c4d1f5826","amount":8,"secret":"same_secret","C":"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059"}"#,
2388 ).unwrap();
2389
2390 let proof_v3_1: ProofV3 = proof1.into();
2391 let proof_v3_2: ProofV3 = proof2.into();
2392
2393 assert_eq!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_2));
2395
2396 let proof3: Proof = serde_json::from_str(
2398 r#"{"id":"009a1f293253e41e","amount":2,"secret":"different_secret","C":"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea"}"#,
2399 ).unwrap();
2400 let proof_v3_3: ProofV3 = proof3.into();
2401
2402 assert_ne!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_3));
2403 }
2404
2405 #[test]
2406 #[cfg(feature = "mint")]
2407 #[allow(deprecated)]
2408 fn test_currency_unit_derivation_index() {
2409 assert_eq!(CurrencyUnit::Sat.derivation_index(), Some(0));
2412 assert_eq!(CurrencyUnit::Msat.derivation_index(), Some(1));
2413 assert_eq!(CurrencyUnit::Usd.derivation_index(), Some(2));
2414 assert_eq!(CurrencyUnit::Eur.derivation_index(), Some(3));
2415 assert_eq!(CurrencyUnit::Auth.derivation_index(), Some(4));
2416
2417 assert_eq!(CurrencyUnit::Custom("btc".into()).derivation_index(), None);
2419 }
2420}