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