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