Skip to main content

cashu/nuts/nut00/
mod.rs

1//! NUT-00: Notation and Models
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/00.md>
4
5use 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
42/// List of [Proof]
43pub type Proofs = Vec<Proof>;
44
45/// Utility methods for [Proofs]
46pub trait ProofsMethods {
47    /// Count proofs by keyset
48    fn count_by_keyset(&self) -> HashMap<Id, u64>;
49
50    /// Sum proofs by keyset
51    fn sum_by_keyset(&self) -> HashMap<Id, Amount>;
52
53    /// Try to sum up the amounts of all [Proof]s
54    fn total_amount(&self) -> Result<Amount, Error>;
55
56    /// Try to fetch the pubkeys of all [Proof]s
57    fn ys(&self) -> Result<Vec<PublicKey>, Error>;
58
59    /// Create a copy of proofs without dleqs
60    fn without_dleqs(&self) -> Proofs;
61
62    /// Create a copy of proofs without P2BK nonce
63    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/// NUT00 Error
167#[derive(Debug, Error)]
168pub enum Error {
169    /// Proofs required
170    #[error("Proofs required in token")]
171    ProofsRequired,
172    /// Unsupported token
173    #[error("Unsupported token")]
174    UnsupportedToken,
175    /// Unsupported token
176    #[error("Unsupported unit")]
177    UnsupportedUnit,
178    /// Unsupported token
179    #[error("Unsupported payment method")]
180    UnsupportedPaymentMethod,
181    /// Duplicate proofs in token
182    #[error("Duplicate proofs in token")]
183    DuplicateProofs,
184    /// Serde Json error
185    #[error(transparent)]
186    SerdeJsonError(#[from] serde_json::Error),
187    /// Utf8 parse error
188    #[error(transparent)]
189    Utf8ParseError(#[from] FromUtf8Error),
190    /// Base64 error
191    #[error(transparent)]
192    Base64Error(#[from] bitcoin::base64::DecodeError),
193    /// Ciborium deserialization error
194    #[error(transparent)]
195    CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
196    /// Ciborium serialization error
197    #[error(transparent)]
198    CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
199    /// Amount Error
200    #[error(transparent)]
201    Amount(#[from] crate::amount::Error),
202    /// Secret error
203    #[error(transparent)]
204    Secret(#[from] crate::secret::Error),
205    /// DHKE error
206    #[error(transparent)]
207    DHKE(#[from] crate::dhke::Error),
208    /// NUT10 error
209    #[error(transparent)]
210    NUT10(#[from] crate::nuts::nut10::Error),
211    /// NUT11 error
212    #[error(transparent)]
213    NUT11(#[from] crate::nuts::nut11::Error),
214    /// Short keyset id -> id error
215    #[error(transparent)]
216    NUT02(#[from] crate::nuts::nut02::Error),
217    #[cfg(feature = "wallet")]
218    #[error(transparent)]
219    /// NUT28 P2BK error
220    NUT28(#[from] crate::nuts::nut28::Error),
221}
222
223/// Blinded Message (also called `output`)
224#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
225pub struct BlindedMessage {
226    /// Amount
227    ///
228    /// The value for the requested [BlindSignature]
229    pub amount: Amount,
230    /// Keyset ID
231    ///
232    /// ID from which we expect a signature.
233    #[serde(rename = "id")]
234    pub keyset_id: Id,
235    /// Blinded secret message (B_)
236    ///
237    /// The blinded secret message generated by the sender.
238    #[serde(rename = "B_")]
239    pub blinded_secret: PublicKey,
240    /// Witness
241    ///
242    /// <https://github.com/cashubtc/nuts/blob/main/11.md>
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub witness: Option<Witness>,
245}
246
247impl BlindedMessage {
248    /// Compose new blinded message
249    #[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    /// Add witness
260    #[inline]
261    pub fn witness(&mut self, witness: Witness) {
262        self.witness = Some(witness);
263    }
264}
265
266/// Blind Signature (also called `promise`)
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268pub struct BlindSignature {
269    /// Amount
270    ///
271    /// The value of the blinded token.
272    pub amount: Amount,
273    /// Keyset ID
274    ///
275    /// ID of the mint keys that signed the token.
276    #[serde(rename = "id")]
277    pub keyset_id: Id,
278    /// Blinded signature (C_)
279    ///
280    /// The blinded signature on the secret message `B_` of [BlindedMessage].
281    #[serde(rename = "C_")]
282    pub c: PublicKey,
283    /// DLEQ Proof
284    ///
285    /// <https://github.com/cashubtc/nuts/blob/main/12.md>
286    #[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/// Witness
303#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
304#[serde(untagged)]
305pub enum Witness {
306    /// HTLC Witness
307    #[serde(with = "serde_htlc_witness")]
308    HTLCWitness(HTLCWitness),
309    /// P2PK Witness
310    #[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    /// Add signatures to [`Witness`]
328    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    /// Get signatures on [`Witness`]
339    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    /// Get preimage from [`Witness`]
347    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/// Proofs
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
367pub struct Proof {
368    /// Amount
369    pub amount: Amount,
370    /// `Keyset id`
371    #[serde(rename = "id")]
372    pub keyset_id: Id,
373    /// Secret message
374    pub secret: Secret,
375    /// Unblinded signature
376    #[serde(rename = "C")]
377    pub c: PublicKey,
378    /// Witness
379    #[serde(default, skip_serializing_if = "Option::is_none")]
380    pub witness: Option<Witness>,
381    /// DLEQ Proof
382    #[serde(default, skip_serializing_if = "Option::is_none")]
383    pub dleq: Option<ProofDleq>,
384    /// P2BK Ephemeral Public Key (NUT-28)
385    /// Used for Pay-to-Blinded-Key privacy feature
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub p2pk_e: Option<PublicKey>,
388}
389
390impl Proof {
391    /// Create new [`Proof`]
392    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    /// Check if proof is in active keyset `Id`s
405    pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
406        active_keyset_ids.contains(&self.keyset_id)
407    }
408
409    /// Get y from proof
410    ///
411    /// Where y is `hash_to_curve(secret)`
412    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/// Proof V4
436#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
437pub struct ProofV4 {
438    /// Amount in satoshi
439    #[serde(rename = "a")]
440    pub amount: Amount,
441    /// Secret message
442    #[serde(rename = "s")]
443    pub secret: Secret,
444    /// Unblinded signature
445    #[serde(
446        serialize_with = "serialize_v4_pubkey",
447        deserialize_with = "deserialize_v4_pubkey"
448    )]
449    pub c: PublicKey,
450    /// Witness
451    #[serde(default, skip_serializing_if = "Option::is_none")]
452    pub witness: Option<Witness>,
453    /// DLEQ Proof
454    #[serde(rename = "d")]
455    pub dleq: Option<ProofDleq>,
456    /// P2BK Ephemeral Public Key (NUT-28)
457    #[serde(rename = "pe", default, skip_serializing_if = "Option::is_none")]
458    pub p2pk_e: Option<PublicKey>,
459}
460
461impl ProofV4 {
462    /// [`ProofV4`] into [`Proof`]
463    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/// Proof v3 with short keyset id
518#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
519pub struct ProofV3 {
520    /// Amount
521    pub amount: Amount,
522    /// Short keyset id
523    #[serde(rename = "id")]
524    pub keyset_id: ShortKeysetId,
525    /// Secret message
526    pub secret: Secret,
527    /// Unblinded signature
528    #[serde(rename = "C")]
529    pub c: PublicKey,
530    /// Witness
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub witness: Option<Witness>,
533    /// DLEQ Proof
534    #[serde(default, skip_serializing_if = "Option::is_none")]
535    pub dleq: Option<ProofDleq>,
536}
537
538impl ProofV3 {
539    /// [`ProofV3`] into [`Proof`]
540    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/// Currency Unit
597#[non_exhaustive]
598#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
599pub enum CurrencyUnit {
600    /// Sat
601    #[default]
602    Sat,
603    /// Msat
604    Msat,
605    /// Usd
606    Usd,
607    /// Euro
608    Eur,
609    /// Auth
610    Auth,
611    /// Custom currency unit
612    Custom(Arc<str>),
613}
614
615impl CurrencyUnit {
616    /// Construct a custom unit, normalizing to lowercase and trimming whitespace.
617    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    /// Derivation index mint will use for unit
625    #[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    ///  Big endian encoded integer of the first 4 bytes of the sha256 hash of the unit string.
641    pub fn hashed_derivation_index(&self) -> u32 {
642        use bitcoin::hashes::sha256;
643
644        // transform to uppercase
645        let unit_str = self.to_string().to_uppercase();
646
647        let bytes = <sha256::Hash as BitcoinHash>::hash(unit_str.as_bytes());
648        // Take the first 4 bytes and convert to u32 (big endian) make sure the integer
649        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(&currency).map_err(|_| serde::de::Error::custom("Unsupported unit"))
712    }
713}
714
715/// Known payment methods
716#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
717pub enum KnownMethod {
718    /// Lightning BOLT11
719    Bolt11,
720    /// Lightning BOLT12
721    Bolt12,
722    /// Onchain Bitcoin
723    Onchain,
724}
725
726impl KnownMethod {
727    /// Get the method name as a string
728    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/// Payment Method
756///
757/// Represents either a known payment method (bolt11, bolt12) or a custom payment method.
758#[derive(Debug, Clone, PartialEq, Eq, Hash)]
759pub enum PaymentMethod {
760    /// Known payment method (bolt11, bolt12)
761    Known(KnownMethod),
762    /// Custom payment method (e.g., "paypal", "stripe")
763    Custom(String),
764}
765
766impl PaymentMethod {
767    /// BOLT11 payment method
768    pub const BOLT11: Self = Self::Known(KnownMethod::Bolt11);
769    /// BOLT12 payment method
770    pub const BOLT12: Self = Self::Known(KnownMethod::Bolt12);
771
772    /// Create a new PaymentMethod from a string
773    pub fn new(method: String) -> Self {
774        Self::from_str(&method).unwrap_or_else(|_| Self::Custom(method.to_lowercase()))
775    }
776
777    /// Returns true when a custom payment method name is safe to use as a URL path segment.
778    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    /// Returns true when this payment method is safe to use in wallet HTTP routes.
786    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    /// Get the method name as a string
794    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    /// Derive a human-readable payment method name.
802    ///
803    /// This is the fallback wallets should use when NUT-04 or NUT-05
804    /// `method_name` is omitted or null.
805    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    /// Check if this is a known method
825    pub fn is_known(&self) -> bool {
826        matches!(self, Self::Known(_))
827    }
828
829    /// Check if this is a custom method
830    pub fn is_custom(&self) -> bool {
831        matches!(self, Self::Custom(_))
832    }
833
834    /// Check if this is bolt11
835    pub fn is_bolt11(&self) -> bool {
836        matches!(self, Self::Known(KnownMethod::Bolt11))
837    }
838
839    /// Check if this is bolt12
840    pub fn is_bolt12(&self) -> bool {
841        matches!(self, Self::Known(KnownMethod::Bolt12))
842    }
843
844    /// Check if this is onchain
845    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
884// Implement PartialEq with &str for ergonomic comparisons
885impl 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/// PreMint
935#[cfg(feature = "wallet")]
936#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
937pub struct PreMint {
938    /// Blinded message
939    pub blinded_message: BlindedMessage,
940    /// Secret
941    pub secret: Secret,
942    /// R
943    pub r: SecretKey,
944    /// Amount
945    pub amount: Amount,
946    /// NUT-13 derivation index for deterministic secrets.
947    #[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/// Premint Secrets
966#[cfg(feature = "wallet")]
967#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
968pub struct PreMintSecrets {
969    /// Secrets
970    pub secrets: Vec<PreMint>,
971    /// Keyset Id
972    pub keyset_id: Id,
973}
974
975#[cfg(feature = "wallet")]
976impl PreMintSecrets {
977    /// Create new [`PreMintSecrets`]
978    pub fn new(keyset_id: Id) -> Self {
979        Self {
980            secrets: Vec::new(),
981            keyset_id,
982        }
983    }
984
985    /// Outputs for speceifed amount with random secret
986    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    /// Outputs from pre defined secrets
1018    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    /// Blank Outputs used for NUT-08 change
1046    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    /// Outputs with P2BK spending conditions
1073    #[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            // Blind data pubkey (slot 0)
1116            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            // Blind tags pubkeys
1122            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    /// Outputs with specific spending conditions
1169    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    /// Iterate over secrets
1204    #[inline]
1205    pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
1206        self.secrets.iter()
1207    }
1208
1209    /// Length of secrets
1210    #[inline]
1211    pub fn len(&self) -> usize {
1212        self.secrets.len()
1213    }
1214
1215    /// If secrets is empty
1216    #[inline]
1217    pub fn is_empty(&self) -> bool {
1218        self.secrets.is_empty()
1219    }
1220
1221    /// Totoal amount of secrets
1222    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    /// [`BlindedMessage`]s from [`PreMintSecrets`]
1229    #[inline]
1230    pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
1231        self.iter().map(|pm| pm.blinded_message.clone()).collect()
1232    }
1233
1234    /// [`Secret`]s from [`PreMintSecrets`]
1235    #[inline]
1236    pub fn secrets(&self) -> Vec<Secret> {
1237        self.iter().map(|pm| pm.secret.clone()).collect()
1238    }
1239
1240    /// Blinding factor from [`PreMintSecrets`]
1241    #[inline]
1242    pub fn rs(&self) -> Vec<SecretKey> {
1243        self.iter().map(|pm| pm.r.clone()).collect()
1244    }
1245
1246    /// Amounts from [`PreMintSecrets`]
1247    #[inline]
1248    pub fn amounts(&self) -> Vec<Amount> {
1249        self.iter().map(|pm| pm.amount).collect()
1250    }
1251
1252    /// Combine [`PreMintSecrets`]
1253    #[inline]
1254    pub fn combine(&mut self, mut other: Self) {
1255        self.secrets.append(&mut other.secrets)
1256    }
1257
1258    /// Sort [`PreMintSecrets`] by [`Amount`]
1259    #[inline]
1260    pub fn sort_secrets(&mut self) {
1261        self.secrets.sort();
1262    }
1263}
1264
1265// Implement Iterator for PreMintSecrets
1266#[cfg(feature = "wallet")]
1267impl Iterator for PreMintSecrets {
1268    type Item = PreMint;
1269
1270    fn next(&mut self) -> Option<Self::Item> {
1271        // Use the iterator of the vector
1272        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        // Custom
1470        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        // Test known methods (case insensitive)
1508        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        // Test custom methods
1531        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        // Test string conversion
1541        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        // Test ergonomic comparisons with strings
1546        assert!(PaymentMethod::BOLT11 == "bolt11");
1547        assert!(PaymentMethod::BOLT12 == "bolt12");
1548        assert!(PaymentMethod::Custom("paypal".to_string()) == "paypal");
1549
1550        // Test comparison with KnownMethod
1551        assert!(PaymentMethod::BOLT11 == KnownMethod::Bolt11);
1552        assert!(PaymentMethod::BOLT12 == KnownMethod::Bolt12);
1553
1554        // Test serialization/deserialization consistency
1555        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    /// Tests that is_bolt12 correctly identifies BOLT12 payment methods.
1614    ///
1615    /// This is critical for code that needs to distinguish between BOLT11 and BOLT12.
1616    /// If is_bolt12 always returns true or false, the wrong payment flow may be used.
1617    ///
1618    /// Mutant testing: Kills mutations that:
1619    /// - Replace is_bolt12 with true
1620    /// - Replace is_bolt12 with false
1621    #[test]
1622    fn test_is_bolt12_with_bolt12() {
1623        // BOLT12 should return true
1624        let method = PaymentMethod::BOLT12;
1625        assert!(method.is_bolt12());
1626
1627        // Known BOLT12 should also return true
1628        let method = PaymentMethod::Known(KnownMethod::Bolt12);
1629        assert!(method.is_bolt12());
1630    }
1631
1632    #[test]
1633    fn test_is_bolt12_with_non_bolt12() {
1634        // BOLT11 should return false
1635        let method = PaymentMethod::BOLT11;
1636        assert!(!method.is_bolt12());
1637
1638        // Known BOLT11 should return false
1639        let method = PaymentMethod::Known(KnownMethod::Bolt11);
1640        assert!(!method.is_bolt12());
1641
1642        // Custom methods should return false
1643        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()); // String match is not the same as actual BOLT12
1648    }
1649
1650    /// Tests that is_bolt12 correctly distinguishes between all payment method variants.
1651    #[test]
1652    fn test_is_bolt12_comprehensive() {
1653        // Test all variants
1654        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    /// Tests that is_onchain correctly identifies onchain payment methods.
1664    ///
1665    /// Mutant testing: Kills mutations that:
1666    /// - Replace is_onchain with true
1667    /// - Replace is_onchain with false
1668    #[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        // Each Y is hash_to_curve of the secret, verify they're different
1768        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        // Test HashSet implementation of ProofsMethods
1784        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        // Total should be 14 (2 + 8 + 4)
1790        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        // Test total_amount directly on HashSet
1808        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        // Test ys() directly on HashSet - should return 2 public keys
1825        let ys = proof_set.ys().unwrap();
1826        assert_eq!(ys.len(), 2);
1827        // Each Y is hash_to_curve of the secret, verify they're different
1828        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        // Test without_dleqs() directly on HashSet
1844        let proofs_without_dleqs = proof_set.without_dleqs();
1845        assert_eq!(proofs_without_dleqs.len(), 2);
1846        // Verify all dleqs are None
1847        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    /// Each key is blinded under its own slot, so a repeated key would blind
2101    /// into two different keys and hide the duplicate from verification. The
2102    /// rejection has to land before any output is built.
2103    #[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    /// Every output in the batch carries the same conditions, so the batch has
2152    /// to be rejected before the first secret is built.
2153    #[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    /// Twelve slots have to fail as TooManyPubkeys, not as the
2197    /// InvalidCanonicalSlot the key derivation would raise on its own.
2198    #[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        // Test partial_cmp
2275        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        // Verify sorting works
2280        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        // Test HTLCWitness returns Some(preimage)
2289        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        // Test P2PKWitness returns None
2297        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        // Test is_active returns true when keyset is in active list
2314        assert!(proof.is_active(&[active_keyset_id]));
2315
2316        // Test is_active returns false when keyset is not in active list
2317        assert!(!proof.is_active(&[inactive_keyset_id]));
2318
2319        // Test with empty list
2320        assert!(!proof.is_active(&[]));
2321
2322        // Test with multiple active keysets
2323        assert!(proof.is_active(&[inactive_keyset_id, active_keyset_id]));
2324    }
2325
2326    /// Helper function to compute hash of a value
2327    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        // Two proofs with same secret should hash to the same value
2336        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        // Same secret = same hash (even with different keyset_id and amount)
2345        assert_eq!(compute_hash(&proof1), compute_hash(&proof2));
2346
2347        // Different secret = different hash
2348        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        // Same secret = same hash
2369        assert_eq!(compute_hash(&proof_v4_1), compute_hash(&proof_v4_2));
2370
2371        // Different secret = different hash
2372        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        // Same secret = same hash
2394        assert_eq!(compute_hash(&proof_v3_1), compute_hash(&proof_v3_2));
2395
2396        // Different secret = different hash
2397        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        // Each currency unit should have a specific derivation index
2410        // These values are important for key derivation compatibility
2411        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        // Custom units should return None
2418        assert_eq!(CurrencyUnit::Custom("btc".into()).derivation_index(), None);
2419    }
2420}