Skip to main content

cashu/nuts/nut11/
mod.rs

1//! NUT-11: Pay to Public Key (P2PK)
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/11.md>
4
5use std::collections::HashSet;
6use std::str::FromStr;
7use std::{fmt, vec};
8
9use bitcoin::secp256k1::schnorr::Signature;
10use serde::de::DeserializeOwned;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14use super::nut00::Witness;
15use super::nut01::PublicKey;
16use super::nut05::MeltRequest;
17use super::{Kind, Nut10Secret, Proof, Proofs, SecretKey};
18use crate::nut10::{
19    get_pubkeys_and_required_sigs, Conditions, SpendingConditionVerification, MAX_LOCKING_SLOTS,
20};
21use crate::nuts::nut00::BlindedMessage;
22use crate::util::unix_time;
23use crate::{SpendingConditions, SwapRequest};
24
25pub mod serde_p2pk_witness;
26
27/// Nut11 Error
28#[derive(Debug, Error)]
29pub enum Error {
30    /// Incorrect secret kind
31    #[error("Secret is not a p2pk secret")]
32    IncorrectSecretKind,
33    /// Incorrect secret kind
34    #[error("Witness is not a p2pk witness")]
35    IncorrectWitnessKind,
36    /// P2PK locktime has already passed
37    #[error("Locktime in past")]
38    LocktimeInPast,
39    /// Witness signature is not valid
40    #[error("Invalid signature")]
41    InvalidSignature,
42    /// Unknown tag in P2PK secret
43    #[error("Unknown tag P2PK secret")]
44    UnknownTag,
45    /// Unknown Sigflag
46    #[error("Unknown sigflag")]
47    UnknownSigFlag,
48    /// P2PK Spend conditions not meet
49    #[error("P2PK spend conditions are not met")]
50    SpendConditionsNotMet,
51    /// Pubkey must be in data field of P2PK
52    #[error("P2PK required in secret data")]
53    P2PKPubkeyRequired,
54    /// Witness Signatures not provided
55    #[error("Witness signatures not provided")]
56    SignaturesNotProvided,
57    /// Duplicate signature from same pubkey
58    #[error("Duplicate signature from the same pubkey detected")]
59    DuplicateSignature,
60    /// Duplicate public key in multisig (same x-coordinate)
61    #[error("Duplicate public key in multisig (same x-coordinate)")]
62    DuplicatePubkey,
63    /// More locking slots requested than NUT-28 allows
64    #[error(
65        "Too many pubkeys, {slots} slots provided, maximum allowed is {MAX_LOCKING_SLOTS} in total"
66    )]
67    TooManyPubkeys {
68        /// Slots the conditions would occupy
69        slots: usize,
70    },
71    /// Refund keys given without a locktime, so the refund path is unreachable
72    #[error("refund keys require a locktime")]
73    RefundKeysRequireLocktime,
74    /// Impossible multisig configuration: num_sigs exceeds available pubkeys
75    #[error(
76        "Impossible multisig: required {required} signatures but only {available} keys available"
77    )]
78    ImpossibleMultisigConfiguration {
79        /// Number of signatures required
80        required: u64,
81        /// Number of available keys
82        available: u64,
83    },
84    /// Impossible refund multisig configuration: num_sigs_refund exceeds refund keys
85    #[error("Impossible refund multisig: required {required} signatures but only {available} refund keys available")]
86    ImpossibleRefundMultisigConfiguration {
87        /// Number of refund signatures required
88        required: u64,
89        /// Number of available refund keys
90        available: u64,
91    },
92    /// Preimage not supported in P2PK
93    #[error("P2PK does not support preimage requirements")]
94    PreimageNotSupportedInP2PK,
95    /// SIG_ALL not supported in this context
96    #[error("SIG_ALL proofs must be verified using a different method")]
97    SigAllNotSupportedHere,
98    /// Number of required signatures cannot be zero
99    #[error("Number of required signatures must be 1 or greater")]
100    ZeroSignaturesRequired,
101    /// Serde Json error
102    #[error(transparent)]
103    SerdeJsonError(#[from] serde_json::Error),
104    /// Secp256k1 error
105    #[error(transparent)]
106    Secp256k1(#[from] bitcoin::secp256k1::Error),
107    /// NUT01 Error
108    #[error(transparent)]
109    NUT01(#[from] crate::nuts::nut01::Error),
110}
111
112/// P2Pk Witness
113#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
114pub struct P2PKWitness {
115    /// Signatures
116    pub signatures: Vec<String>,
117}
118
119impl P2PKWitness {
120    #[inline]
121    /// Check id Witness is empty
122    pub fn is_empty(&self) -> bool {
123        self.signatures.is_empty()
124    }
125}
126
127impl Proof {
128    /// Sign [Proof]
129    pub fn sign_p2pk(&mut self, secret_key: SecretKey) -> Result<(), Error> {
130        let msg: Vec<u8> = self.secret.to_bytes();
131        let signature: Signature = secret_key.sign(&msg)?;
132
133        let signatures = vec![signature.to_string()];
134
135        match self.witness.as_mut() {
136            Some(witness) => {
137                witness.add_signatures(signatures);
138            }
139            None => {
140                let mut p2pk_witness = Witness::P2PKWitness(P2PKWitness::default());
141                p2pk_witness.add_signatures(signatures);
142                self.witness = Some(p2pk_witness);
143            }
144        };
145
146        Ok(())
147    }
148
149    /// Verify P2PK signature on [Proof]
150    ///
151    /// Per NUT-11, there are two spending pathways after locktime:
152    /// 1. Primary path (data + pubkeys): ALWAYS available
153    /// 2. Refund path (refund keys): available AFTER locktime
154    ///
155    /// The verification tries both paths - if either succeeds, the proof is valid.
156    pub fn verify_p2pk(&self) -> Result<(), Error> {
157        let secret: Nut10Secret = self.secret.clone().try_into()?;
158        let spending_conditions: Conditions = secret
159            .secret_data()
160            .tags()
161            .cloned()
162            .unwrap_or_default()
163            .try_into()
164            .map_err(|_| Error::SpendConditionsNotMet)?;
165
166        if spending_conditions.sig_flag == SigFlag::SigAll {
167            return Err(Error::SigAllNotSupportedHere);
168        }
169
170        if secret.kind() != Kind::P2PK {
171            return Err(Error::IncorrectSecretKind);
172        }
173
174        // Get spending requirements (includes both primary and refund paths)
175        let now = unix_time();
176        let requirements =
177            super::nut10::get_pubkeys_and_required_sigs(&secret, now).map_err(|err| match err {
178                super::nut10::Error::NUT11(nut11_err) => nut11_err,
179                _ => Error::SpendConditionsNotMet,
180            })?;
181
182        if requirements.preimage_needed {
183            return Err(Error::PreimageNotSupportedInP2PK);
184        }
185
186        // Extract witness signatures
187        let witness_signatures = match &self.witness {
188            Some(witness) => witness.signatures(),
189            None => None,
190        };
191
192        let msg: &[u8] = self.secret.as_bytes();
193
194        // Try primary path first (data + pubkeys)
195        // Per NUT-11: "Locktime Multisig conditions continue to apply"
196        {
197            let primary_valid = witness_signatures
198                .as_ref()
199                .and_then(|sigs| {
200                    sigs.iter()
201                        .map(|s| Signature::from_str(s))
202                        .collect::<Result<Vec<_>, _>>()
203                        .ok()
204                })
205                .and_then(|sigs| valid_signatures(msg, &requirements.pubkeys, &sigs).ok())
206                .is_some_and(|count| count >= requirements.required_sigs);
207
208            if primary_valid {
209                return Ok(());
210            }
211        }
212
213        // Primary path failed or no signatures - try refund path if available
214        {
215            if let Some(refund_path) = &requirements.refund_path {
216                // Anyone can spend (locktime passed, no refund keys)
217                if refund_path.required_sigs == 0 {
218                    return Ok(());
219                }
220
221                // Need signatures for refund path
222                let refund_valid = witness_signatures
223                    .as_ref()
224                    .and_then(|sigs| {
225                        sigs.iter()
226                            .map(|s| Signature::from_str(s))
227                            .collect::<Result<Vec<_>, _>>()
228                            .ok()
229                    })
230                    .and_then(|sigs| valid_signatures(msg, &refund_path.pubkeys, &sigs).ok())
231                    .is_some_and(|count| count >= refund_path.required_sigs);
232
233                if refund_valid {
234                    return Ok(());
235                }
236            }
237        }
238
239        // Neither path succeeded
240        if witness_signatures.is_none() {
241            Err(Error::SignaturesNotProvided)
242        } else {
243            Err(Error::SpendConditionsNotMet)
244        }
245    }
246}
247
248impl SpendingConditions {
249    /// New P2PK [SpendingConditions]
250    pub fn new_p2pk(pubkey: PublicKey, conditions: Option<Conditions>) -> Self {
251        Self::P2PKConditions {
252            data: pubkey,
253            conditions,
254        }
255    }
256}
257
258/// Extract and parse Schnorr signatures from a witness
259///
260/// This helper function extracts signature strings from a witness and parses them
261/// into bitcoin secp256k1 Schnorr signatures.
262pub(crate) fn extract_signatures_from_witness(
263    witness: &super::Witness,
264) -> Result<Vec<bitcoin::secp256k1::schnorr::Signature>, Error> {
265    use std::str::FromStr;
266
267    let witness_sigs = witness.signatures().ok_or(Error::SignaturesNotProvided)?;
268
269    witness_sigs
270        .iter()
271        .map(|s| bitcoin::secp256k1::schnorr::Signature::from_str(s))
272        .collect::<Result<Vec<_>, _>>()
273        .map_err(|_| Error::InvalidSignature)
274}
275
276/// Verify P2PK SIG_ALL signatures
277///
278/// Do NOT call this directly. This is called only from 'verify_full_sig_all_check',
279/// which has already done many important SIG_ALL checks. This performs the final
280/// signature verification for SIG_ALL+P2PK transactions.
281///
282/// Per NUT-11, there are two spending pathways after locktime:
283/// 1. Primary path (data + pubkeys): ALWAYS available
284/// 2. Refund path (refund keys): available AFTER locktime
285pub(crate) fn verify_sig_all_p2pk(first_input: &Proof, msg_to_sign: String) -> Result<(), Error> {
286    // Get the first input, as it's the one with the signatures
287    let first_secret =
288        Nut10Secret::try_from(&first_input.secret).map_err(|_| Error::IncorrectSecretKind)?;
289
290    // Record current time for locktime evaluation
291    let current_time = crate::util::unix_time();
292
293    // Get spending requirements (includes both primary and refund paths)
294    let requirements = get_pubkeys_and_required_sigs(&first_secret, current_time)
295        .map_err(|_| Error::SpendConditionsNotMet)?;
296
297    debug_assert!(
298        !requirements.preimage_needed,
299        "P2PK should never require preimage"
300    );
301
302    // Check for "anyone can spend" case first (locktime passed, no refund keys)
303    // This doesn't require any signatures
304    if let Some(refund_path) = &requirements.refund_path {
305        if refund_path.required_sigs == 0 {
306            return Ok(());
307        }
308    }
309
310    // Get the witness (needed for signature extraction)
311    let first_witness = first_input
312        .witness
313        .as_ref()
314        .ok_or(Error::SignaturesNotProvided)?;
315
316    // Try primary path first (data + pubkeys)
317    // Per NUT-11: "Locktime Multisig conditions continue to apply"
318    {
319        let primary_valid = extract_signatures_from_witness(first_witness)
320            .ok()
321            .and_then(|sigs| {
322                valid_signatures(msg_to_sign.as_bytes(), &requirements.pubkeys, &sigs).ok()
323            })
324            .is_some_and(|count| count >= requirements.required_sigs);
325
326        if primary_valid {
327            return Ok(());
328        }
329    }
330
331    // Primary path failed - try refund path if available
332    {
333        if let Some(refund_path) = &requirements.refund_path {
334            let signatures = extract_signatures_from_witness(first_witness)?;
335            let valid_sig_count =
336                valid_signatures(msg_to_sign.as_bytes(), &refund_path.pubkeys, &signatures)
337                    .map_err(|_| Error::InvalidSignature)?;
338
339            if valid_sig_count >= refund_path.required_sigs {
340                return Ok(());
341            }
342        }
343    }
344
345    // Neither path succeeded
346    Err(Error::SpendConditionsNotMet)
347}
348
349/// Returns count of valid signatures (each public key is only counted once)
350/// Returns error if the same pubkey has multiple valid signatures
351pub(crate) fn valid_signatures(
352    msg: &[u8],
353    pubkeys: &[PublicKey],
354    signatures: &[Signature],
355) -> Result<u64, Error> {
356    let mut verified_pubkeys = HashSet::new();
357
358    for pubkey in pubkeys {
359        for signature in signatures {
360            if pubkey.verify(msg, signature).is_ok() {
361                // Insert the x-only coordinate to prevent double counting
362                let x_only = pubkey.x_only_public_key();
363                // If the pubkey's x-coordinate is already verified, return a duplicate signature error
364                if !verified_pubkeys.insert(x_only) {
365                    return Err(Error::DuplicateSignature);
366                }
367            }
368        }
369    }
370
371    Ok(verified_pubkeys.len() as u64)
372}
373
374impl BlindedMessage {
375    /// Sign [BlindedMessage]
376    pub fn sign_p2pk(&mut self, secret_key: SecretKey) -> Result<(), Error> {
377        let msg: [u8; 33] = self.blinded_secret.to_bytes();
378        let signature: Signature = secret_key.sign(&msg)?;
379
380        let signatures = vec![signature.to_string()];
381
382        match self.witness.as_mut() {
383            Some(witness) => {
384                witness.add_signatures(signatures);
385            }
386            None => {
387                let mut p2pk_witness = Witness::P2PKWitness(P2PKWitness::default());
388                p2pk_witness.add_signatures(signatures);
389                self.witness = Some(p2pk_witness);
390            }
391        };
392
393        Ok(())
394    }
395
396    /// Verify P2PK conditions on [BlindedMessage]
397    pub fn verify_p2pk(&self, pubkeys: &Vec<PublicKey>, required_sigs: u64) -> Result<(), Error> {
398        let mut verified_pubkeys = HashSet::new();
399        if let Some(witness) = &self.witness {
400            for signature in witness
401                .signatures()
402                .ok_or(Error::SignaturesNotProvided)?
403                .iter()
404            {
405                for v in pubkeys {
406                    let msg = &self.blinded_secret.to_bytes();
407                    let sig = Signature::from_str(signature)?;
408
409                    if v.verify(msg, &sig).is_ok() {
410                        let x_only = v.x_only_public_key();
411                        if !verified_pubkeys.insert(x_only) {
412                            return Err(Error::DuplicateSignature);
413                        }
414                    } else {
415                        tracing::debug!(
416                            "Could not verify signature: {sig} on message: {}",
417                            self.blinded_secret
418                        )
419                    }
420                }
421            }
422        }
423
424        let valid_sigs = verified_pubkeys.len() as u64;
425
426        if valid_sigs.ge(&required_sigs) {
427            Ok(())
428        } else {
429            Err(Error::SpendConditionsNotMet)
430        }
431    }
432}
433
434/// Signature flag
435///
436/// Defined in [NUT11](https://github.com/cashubtc/nuts/blob/main/11.md)
437#[derive(
438    Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash,
439)]
440pub enum SigFlag {
441    #[default]
442    /// Requires valid signatures on all inputs.
443    /// It is the default signature flag and will be applied even if the
444    /// `sigflag` tag is absent.
445    SigInputs,
446    /// Requires valid signatures on all inputs and on all outputs.
447    SigAll,
448}
449
450impl fmt::Display for SigFlag {
451    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
452        match self {
453            Self::SigAll => write!(f, "SIG_ALL"),
454            Self::SigInputs => write!(f, "SIG_INPUTS"),
455        }
456    }
457}
458
459impl FromStr for SigFlag {
460    type Err = Error;
461    fn from_str(tag: &str) -> Result<Self, Self::Err> {
462        match tag {
463            "SIG_ALL" => Ok(Self::SigAll),
464            "SIG_INPUTS" => Ok(Self::SigInputs),
465            _ => Err(Error::UnknownSigFlag),
466        }
467    }
468}
469
470/// Get the signature flag that should be enforced for a set of proofs and the
471/// public keys that signatures are valid for
472pub fn enforce_sig_flag(proofs: Proofs) -> EnforceSigFlag {
473    let mut sig_flag = SigFlag::SigInputs;
474    let mut pubkeys = HashSet::new();
475    let mut sigs_required = 1;
476    for proof in proofs {
477        if let Ok(secret) = Nut10Secret::try_from(proof.secret) {
478            if secret.kind().eq(&Kind::P2PK) {
479                if let Ok(verifying_key) = PublicKey::from_str(secret.secret_data().data()) {
480                    pubkeys.insert(verifying_key);
481                }
482            }
483
484            if let Some(tags) = secret.secret_data().tags() {
485                if let Ok(conditions) = Conditions::try_from(tags.clone()) {
486                    if conditions.sig_flag.eq(&SigFlag::SigAll) {
487                        sig_flag = SigFlag::SigAll;
488                    }
489
490                    if let Some(sigs) = conditions.num_sigs {
491                        if sigs > sigs_required {
492                            sigs_required = sigs;
493                        }
494                    }
495
496                    if let Some(pubs) = conditions.pubkeys {
497                        pubkeys.extend(pubs);
498                    }
499                }
500            }
501        }
502    }
503
504    EnforceSigFlag {
505        sig_flag,
506        pubkeys,
507        sigs_required,
508    }
509}
510
511/// Enforce Sigflag info
512#[derive(Debug, Clone, PartialEq, Eq)]
513pub struct EnforceSigFlag {
514    /// Sigflag required for proofs
515    pub sig_flag: SigFlag,
516    /// Pubkeys that can sign for proofs
517    pub pubkeys: HashSet<PublicKey>,
518    /// Number of sigs required for proofs
519    pub sigs_required: u64,
520}
521
522impl SwapRequest {
523    /// Sign swap request with SIG_ALL
524    pub fn sign_sig_all(&mut self, secret_key: SecretKey) -> Result<(), Error> {
525        // Get message to sign
526        let msg = self.sig_all_msg_to_sign();
527        let signature = secret_key.sign(msg.as_bytes())?;
528
529        // Add signature to first input witness
530        let first_input = self
531            .inputs_mut()
532            .first_mut()
533            .ok_or(Error::IncorrectSecretKind)?;
534
535        match first_input.witness.as_mut() {
536            Some(witness) => {
537                witness.add_signatures(vec![signature.to_string()]);
538            }
539            None => {
540                let mut p2pk_witness = Witness::P2PKWitness(P2PKWitness::default());
541                p2pk_witness.add_signatures(vec![signature.to_string()]);
542                first_input.witness = Some(p2pk_witness);
543            }
544        };
545
546        Ok(())
547    }
548}
549
550impl<Q> MeltRequest<Q>
551where
552    Q: std::fmt::Display + Serialize + DeserializeOwned,
553{
554    /// Sign melt request with SIG_ALL
555    pub fn sign_sig_all(&mut self, secret_key: SecretKey) -> Result<(), Error> {
556        // Get message to sign
557        let msg = self.sig_all_msg_to_sign();
558        let signature = secret_key.sign(msg.as_bytes())?;
559
560        // Add signature to first input witness
561        let first_input = self
562            .inputs_mut()
563            .first_mut()
564            .ok_or(Error::SpendConditionsNotMet)?;
565
566        match first_input.witness.as_mut() {
567            Some(witness) => {
568                witness.add_signatures(vec![signature.to_string()]);
569            }
570            None => {
571                let mut p2pk_witness = Witness::P2PKWitness(P2PKWitness::default());
572                p2pk_witness.add_signatures(vec![signature.to_string()]);
573                first_input.witness = Some(p2pk_witness);
574            }
575        };
576
577        Ok(())
578    }
579}
580
581#[cfg(feature = "mint")]
582#[cfg(test)]
583mod tests {
584    use std::str::FromStr;
585
586    use uuid::Uuid;
587
588    use super::*;
589    use crate::nuts::Id;
590    use crate::quote_id::QuoteId;
591    use crate::secret::Secret;
592    use crate::{Amount, BlindedMessage};
593
594    fn proof_for_enforce_sig_flag(num_sigs: u64) -> Proof {
595        let primary = SecretKey::generate().public_key();
596        let signer_two = SecretKey::generate().public_key();
597        let signer_three = SecretKey::generate().public_key();
598        let conditions = Conditions {
599            pubkeys: Some(vec![signer_two, signer_three]),
600            num_sigs: Some(num_sigs),
601            sig_flag: SigFlag::SigAll,
602            ..Default::default()
603        };
604        let secret: Secret = SpendingConditions::new_p2pk(primary, Some(conditions))
605            .try_into()
606            .unwrap();
607
608        Proof {
609            amount: Amount::ONE,
610            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
611            secret,
612            c: primary,
613            witness: None,
614            dleq: None,
615            p2pk_e: None,
616        }
617    }
618
619    #[test]
620    fn test_secret_ser() {
621        let data = PublicKey::from_str(
622            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
623        )
624        .unwrap();
625
626        let conditions = Conditions {
627            locktime: Some(99999),
628            pubkeys: Some(vec![
629                PublicKey::from_str(
630                    "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
631                )
632                .unwrap(),
633                PublicKey::from_str(
634                    "023192200a0cfd3867e48eb63b03ff599c7e46c8f4e41146b2d281173ca6c50c54",
635                )
636                .unwrap(),
637            ]),
638            refund_keys: Some(vec![PublicKey::from_str(
639                "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
640            )
641            .unwrap()]),
642            num_sigs: Some(2),
643            sig_flag: SigFlag::SigAll,
644            num_sigs_refund: None,
645        };
646
647        let secret: Nut10Secret = SpendingConditions::new_p2pk(data, Some(conditions))
648            .try_into()
649            .unwrap();
650
651        let secret_str = serde_json::to_string(&secret).unwrap();
652
653        let secret_der: Nut10Secret = serde_json::from_str(&secret_str).unwrap();
654
655        assert_eq!(secret_der, secret);
656    }
657
658    #[test]
659    fn sign_proof() {
660        let secret_key =
661            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
662                .unwrap();
663
664        let signing_key_two =
665            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
666                .unwrap();
667
668        let signing_key_three =
669            SecretKey::from_str("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
670                .unwrap();
671        let v_key: PublicKey = secret_key.public_key();
672        let v_key_two: PublicKey = signing_key_two.public_key();
673        let v_key_three: PublicKey = signing_key_three.public_key();
674
675        let conditions = Conditions {
676            locktime: Some(21000000000),
677            pubkeys: Some(vec![v_key_two, v_key_three]),
678            refund_keys: Some(vec![v_key]),
679            num_sigs: Some(2),
680            sig_flag: SigFlag::SigInputs,
681            num_sigs_refund: None,
682        };
683
684        let secret: Secret = SpendingConditions::new_p2pk(v_key, Some(conditions))
685            .try_into()
686            .unwrap();
687
688        let mut proof = Proof {
689            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
690            amount: Amount::ZERO,
691            secret,
692            c: PublicKey::from_str(
693                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
694            )
695            .unwrap(),
696            witness: Some(Witness::P2PKWitness(P2PKWitness { signatures: vec![] })),
697            dleq: None,
698            p2pk_e: None,
699        };
700
701        proof.sign_p2pk(secret_key).unwrap();
702        proof.sign_p2pk(signing_key_two).unwrap();
703
704        assert!(proof.verify_p2pk().is_ok());
705    }
706
707    #[test]
708    fn test_p2pk_witness_is_empty_tracks_signatures() {
709        assert!(P2PKWitness::default().is_empty());
710        assert!(!P2PKWitness {
711            signatures: vec!["signature".to_string()],
712        }
713        .is_empty());
714    }
715
716    #[test]
717    fn test_enforce_sig_flag_keeps_highest_required_signature_count() {
718        let proofs = vec![proof_for_enforce_sig_flag(2), proof_for_enforce_sig_flag(3)]
719            .into_iter()
720            .collect();
721
722        let enforced = enforce_sig_flag(proofs);
723
724        assert_eq!(enforced.sig_flag, SigFlag::SigAll);
725        assert_eq!(enforced.sigs_required, 3);
726        assert_eq!(enforced.pubkeys.len(), 6);
727    }
728
729    #[test]
730    fn test_verify() {
731        // Proof with a valid signature
732        let json: &str = r#"{
733            "amount":1,
734            "secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]",
735            "C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
736            "id":"009a1f293253e41e",
737            "witness":"{\"signatures\":[\"60f3c9b766770b46caac1d27e1ae6b77c8866ebaeba0b9489fe6a15a837eaa6fcd6eaa825499c72ac342983983fd3ba3a8a41f56677cc99ffd73da68b59e1383\"]}"
738        }"#;
739        let valid_proof: Proof = serde_json::from_str(json).unwrap();
740
741        valid_proof.verify_p2pk().unwrap();
742        assert!(valid_proof.verify_p2pk().is_ok());
743
744        // Proof with a signature that is in a different secret
745        let invalid_proof = r#"{"amount":1,"secret":"[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id":"009a1f293253e41e","witness":"{\"signatures\":[\"3426df9730d365a9d18d79bed2f3e78e9172d7107c55306ac5ddd1b2d065893366cfa24ff3c874ebf1fc22360ba5888ddf6ff5dbcb9e5f2f5a1368f7afc64f15\"]}"}"#;
746
747        let invalid_proof: Proof = serde_json::from_str(invalid_proof).unwrap();
748
749        assert!(invalid_proof.verify_p2pk().is_err());
750    }
751
752    #[test]
753    fn verify_multi_sig() {
754        // Proof with 2 valid signatures to satifiy the condition
755        let valid_proof = r#"{"amount":0,"secret":"[\"P2PK\",{\"nonce\":\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id":"009a1f293253e41e","witness":"{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\",\"9a72ca2d4d5075be5b511ee48dbc5e45f259bcf4a4e8bf18587f433098a9cd61ff9737dc6e8022de57c76560214c4568377792d4c2c6432886cc7050487a1f22\"]}"}"#;
756
757        let valid_proof: Proof = serde_json::from_str(valid_proof).unwrap();
758
759        assert!(valid_proof.verify_p2pk().is_ok());
760
761        // Proof with only one of the required signatures
762        let invalid_proof = r#"{"amount":0,"secret":"[\"P2PK\",{\"nonce\":\"0ed3fcb22c649dd7bbbdcca36e0c52d4f0187dd3b6a19efcc2bfbebb5f85b2a1\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id":"009a1f293253e41e","witness":"{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\"]}"}"#;
763
764        let invalid_proof: Proof = serde_json::from_str(invalid_proof).unwrap();
765
766        // Verification should fail without the requires signatures
767        assert!(invalid_proof.verify_p2pk().is_err());
768    }
769
770    #[test]
771    fn test_valid_signatures_duplicate() {
772        let secret_key = SecretKey::generate();
773        let pubkey = secret_key.public_key();
774        let msg = b"test message";
775        let sig = secret_key.sign(msg).unwrap();
776
777        // One signature, one pubkey - OK
778        assert_eq!(valid_signatures(msg, &[pubkey], &[sig]).unwrap(), 1);
779
780        // Two identical signatures, one pubkey - Error
781        let res = valid_signatures(msg, &[pubkey], &[sig, sig]);
782        assert!(res.is_err());
783        assert!(matches!(res.unwrap_err(), Error::DuplicateSignature));
784    }
785
786    #[test]
787    fn test_blinded_message_verify_p2pk_duplicate_sig() {
788        let secret_key = SecretKey::generate();
789        let pubkey = secret_key.public_key();
790
791        let mut blinded_message = BlindedMessage {
792            amount: Amount::ZERO,
793            blinded_secret: pubkey,
794            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
795            witness: None,
796        };
797
798        // Sign once
799        blinded_message.sign_p2pk(secret_key).unwrap();
800
801        // Add the same signature again to the witness
802        if let Some(Witness::P2PKWitness(p2pk_witness)) = &mut blinded_message.witness {
803            let sig = p2pk_witness.signatures[0].clone();
804            p2pk_witness.signatures.push(sig);
805        }
806
807        // Verification should fail with DuplicateSignature error
808        let res = blinded_message.verify_p2pk(&vec![pubkey], 1);
809        assert!(res.is_err());
810        assert!(
811            matches!(res.unwrap_err(), Error::DuplicateSignature),
812            "Expected DuplicateSignature error"
813        );
814    }
815
816    #[test]
817    fn test_blinded_message_verify_p2pk_accepts_single_valid_signature() {
818        let secret_key = SecretKey::generate();
819        let pubkey = secret_key.public_key();
820
821        let mut blinded_message = BlindedMessage {
822            amount: Amount::ZERO,
823            blinded_secret: pubkey,
824            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
825            witness: None,
826        };
827
828        blinded_message.sign_p2pk(secret_key).unwrap();
829
830        assert!(blinded_message.verify_p2pk(&vec![pubkey], 1).is_ok());
831    }
832
833    #[test]
834    fn test_blinded_message_verify_p2pk_duplicate_xonly() {
835        let secret_key = SecretKey::generate();
836        let pubkey = secret_key.public_key();
837
838        // Create another pubkey with same x-coordinate but different parity
839        let mut bytes = pubkey.to_bytes();
840        bytes[0] = if bytes[0] == 0x02 { 0x03 } else { 0x02 };
841        let pubkey_alt = PublicKey::from_slice(&bytes).unwrap();
842
843        let mut blinded_message = BlindedMessage {
844            amount: Amount::ZERO,
845            blinded_secret: pubkey,
846            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
847            witness: None,
848        };
849
850        // Sign once
851        blinded_message.sign_p2pk(secret_key).unwrap();
852
853        // Verification should fail if both pubkeys are provided
854        let res = blinded_message.verify_p2pk(&vec![pubkey, pubkey_alt], 1);
855        assert!(res.is_err());
856        assert!(
857            matches!(res.unwrap_err(), Error::DuplicateSignature),
858            "Expected DuplicateSignature error for same x-only pubkeys"
859        );
860    }
861
862    /// The secret is built directly rather than through `SpendingConditions`,
863    /// which now rejects the duplicate: a hostile token can still carry both
864    /// keys, and verification has to reject it.
865    #[test]
866    fn test_duplicate_key_in_main_pathway() {
867        let secret_key = SecretKey::generate();
868        let pubkey = secret_key.public_key();
869        let mut bytes = pubkey.to_bytes();
870        bytes[0] = 0x02;
871        let pk_02 = PublicKey::from_slice(&bytes).unwrap();
872        bytes[0] = 0x03;
873        let pk_03 = PublicKey::from_slice(&bytes).unwrap();
874
875        // data is pk_02, pubkeys contains pk_03. They are duplicates in the primary path.
876        let conditions = Conditions {
877            locktime: None,
878            pubkeys: Some(vec![pk_03]),
879            refund_keys: None,
880            num_sigs: Some(2),
881            sig_flag: SigFlag::SigInputs,
882            num_sigs_refund: None,
883        };
884
885        let secret: Secret = Nut10Secret::new(
886            Kind::P2PK,
887            crate::nuts::SecretData::new(pk_02.to_hex(), Some(conditions)),
888        )
889        .try_into()
890        .unwrap();
891
892        let mut proof = Proof {
893            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
894            amount: Amount::ZERO,
895            secret,
896            c: PublicKey::from_str(
897                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
898            )
899            .unwrap(),
900            witness: Some(Witness::P2PKWitness(P2PKWitness { signatures: vec![] })),
901            dleq: None,
902            p2pk_e: None,
903        };
904
905        // We sign twice (since we need 2 sigs). It can even be the same signature or different (if we had different messages, but here it's the same message).
906        proof.sign_p2pk(secret_key.clone()).unwrap();
907        // Just duplicate the signature in the witness
908        if let Some(Witness::P2PKWitness(w)) = &mut proof.witness {
909            w.signatures.push(w.signatures[0].clone());
910        }
911
912        let res = proof.verify_p2pk();
913        assert!(res.is_err(), "Expected an error but got {:?}", res);
914        let err = res.unwrap_err();
915        assert!(
916            matches!(err, Error::DuplicatePubkey),
917            "Expected DuplicatePubkey, got {:?}",
918            err
919        );
920    }
921
922    #[test]
923    fn test_duplicate_key_in_both_pathways() {
924        let secret_key = SecretKey::generate();
925        let pubkey = secret_key.public_key();
926        let mut bytes = pubkey.to_bytes();
927        bytes[0] = 0x02;
928        let pk_02 = PublicKey::from_slice(&bytes).unwrap();
929        bytes[0] = 0x03;
930        let pk_03 = PublicKey::from_slice(&bytes).unwrap();
931
932        // Let's modify the test to put a duplicate key inside the refund path.
933        let conditions = Conditions {
934            locktime: Some(0),
935            pubkeys: None,
936            refund_keys: Some(vec![pk_03, pk_02]), // Duplicate x-coord in refund path
937            num_sigs: None,
938            sig_flag: SigFlag::SigInputs,
939            num_sigs_refund: Some(2),
940        };
941
942        let secret: Secret = Nut10Secret::new(
943            Kind::P2PK,
944            crate::nuts::SecretData::new(secret_key.public_key().to_hex(), Some(conditions)),
945        )
946        .try_into()
947        .unwrap();
948
949        let proof = Proof {
950            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
951            amount: Amount::ZERO,
952            secret,
953            c: PublicKey::from_str(
954                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
955            )
956            .unwrap(),
957            witness: Some(Witness::P2PKWitness(P2PKWitness { signatures: vec![] })),
958            dleq: None,
959            p2pk_e: None,
960        };
961
962        let res = proof.verify_p2pk();
963        assert!(res.is_err(), "Expected an error but got {:?}", res);
964        let err = res.unwrap_err();
965        assert!(
966            matches!(err, Error::DuplicatePubkey),
967            "Expected DuplicatePubkey, got {:?}",
968            err
969        );
970    }
971
972    #[test]
973    fn verify_refund() {
974        let valid_proof = r#"{"amount":1,"id":"009a1f293253e41e","secret":"[\"P2PK\",{\"nonce\":\"902685f492ef3bb2ca35a47ddbba484a3365d143b9776d453947dcbf1ddf9689\",\"data\":\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"locktime\",\"21\"],[\"n_sigs\",\"2\"],[\"refund\",\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","witness":"{\"signatures\":[\"710507b4bc202355c91ea3c147c0d0189c75e179d995e566336afd759cb342bcad9a593345f559d9b9e108ac2c9b5bd9f0b4b6a295028a98606a0a2e95eb54f7\"]}"}"#;
975
976        let valid_proof: Proof = serde_json::from_str(valid_proof).unwrap();
977        assert!(valid_proof.verify_p2pk().is_ok());
978
979        let invalid_proof = r#"{"amount":1,"id":"009a1f293253e41e","secret":"[\"P2PK\",{\"nonce\":\"64c46e5d30df27286166814b71b5d69801704f23a7ad626b05688fbdb48dcc98\",\"data\":\"026f6a2b1d709dbca78124a9f30a742985f7eddd894e72f637f7085bf69b997b9a\",\"tags\":[[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"03142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"locktime\",\"21\"],[\"n_sigs\",\"2\"],[\"refund\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"],[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","witness":"{\"signatures\":[\"f661d3dc046d636d47cb3d06586da42c498f0300373d1c2a4f417a44252cdf3809bce207c8888f934dba0d2b1671f1b8622d526840f2d5883e571b462630c1ff\"]}"}"#;
980
981        let invalid_proof: Proof = serde_json::from_str(invalid_proof).unwrap();
982
983        assert!(invalid_proof.verify_p2pk().is_err());
984    }
985
986    #[test]
987    fn sig_with_non_refund_keys_after_locktime() {
988        let secret_key =
989            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
990                .unwrap();
991
992        let signing_key_two =
993            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
994                .unwrap();
995
996        let signing_key_three =
997            SecretKey::from_str("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
998                .unwrap();
999        let v_key: PublicKey = secret_key.public_key();
1000        let v_key_two: PublicKey = signing_key_two.public_key();
1001        let v_key_three: PublicKey = signing_key_three.public_key();
1002
1003        let conditions = Conditions {
1004            locktime: Some(21),
1005            pubkeys: Some(vec![v_key_three]),
1006            refund_keys: Some(vec![v_key, v_key_two]),
1007            num_sigs: None,
1008            sig_flag: SigFlag::SigInputs,
1009            num_sigs_refund: Some(2),
1010        };
1011
1012        let secret: Secret = SpendingConditions::new_p2pk(v_key, Some(conditions))
1013            .try_into()
1014            .unwrap();
1015
1016        let mut proof = Proof {
1017            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
1018            amount: Amount::ZERO,
1019            secret,
1020            c: PublicKey::from_str(
1021                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
1022            )
1023            .unwrap(),
1024            witness: Some(Witness::P2PKWitness(P2PKWitness { signatures: vec![] })),
1025            dleq: None,
1026            p2pk_e: None,
1027        };
1028
1029        proof.sign_p2pk(signing_key_three.clone()).unwrap();
1030
1031        // Per NUT-11: primary path (pubkeys) is ALWAYS available, even after locktime
1032        // Signing with a key from pubkeys should succeed
1033        assert!(proof.verify_p2pk().is_ok());
1034
1035        proof.witness = None;
1036
1037        // Sign with secret_key (pubkey = v_key, which is the data key and part of primary path)
1038        // Per NUT-11: primary path is always available, and data key is part of primary path
1039        proof.sign_p2pk(secret_key).unwrap();
1040        assert!(
1041            proof.verify_p2pk().is_ok(),
1042            "Data key signature should satisfy primary path"
1043        );
1044
1045        // Adding more signatures still works (but wasn't needed for primary path)
1046        proof.sign_p2pk(signing_key_two).unwrap();
1047        assert!(proof.verify_p2pk().is_ok());
1048    }
1049
1050    // Helper functions for melt request tests
1051    fn create_test_proof(secret: Secret, pubkey: PublicKey, id: &str) -> Proof {
1052        Proof {
1053            keyset_id: Id::from_str(id).unwrap(),
1054            amount: Amount::ZERO,
1055            secret,
1056            c: pubkey,
1057            witness: None,
1058            dleq: None,
1059            p2pk_e: None,
1060        }
1061    }
1062
1063    fn create_test_secret(pubkey: PublicKey, conditions: Conditions) -> Secret {
1064        SpendingConditions::new_p2pk(pubkey, Some(conditions))
1065            .try_into()
1066            .unwrap()
1067    }
1068
1069    fn create_test_blinded_msg(pubkey: PublicKey) -> BlindedMessage {
1070        BlindedMessage {
1071            amount: Amount::ZERO,
1072            blinded_secret: pubkey,
1073            keyset_id: Id::from_str("009a1f293253e41e").unwrap(),
1074            witness: None,
1075        }
1076    }
1077
1078    #[test]
1079    fn test_melt_sig_all_basic_signing() {
1080        let secret_key =
1081            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1082                .unwrap();
1083        let pubkey = secret_key.public_key();
1084
1085        // Create conditions with SIG_ALL
1086        let conditions = Conditions {
1087            sig_flag: SigFlag::SigAll,
1088            ..Default::default()
1089        };
1090
1091        let secret = create_test_secret(pubkey, conditions);
1092        let proof = create_test_proof(secret, pubkey, "009a1f293253e41e");
1093        let blinded_msg = create_test_blinded_msg(pubkey);
1094
1095        // Create melt request
1096        let mut melt = MeltRequest::new(
1097            QuoteId::UUID(Uuid::new_v4()),
1098            vec![proof],
1099            Some(vec![blinded_msg]),
1100        );
1101
1102        // Before signing, should fail verification
1103        assert!(
1104            melt.verify_spending_conditions().is_err(),
1105            "Unsigned melt request should fail verification"
1106        );
1107
1108        // Sign the request
1109        assert!(
1110            melt.sign_sig_all(secret_key).is_ok(),
1111            "Signing should succeed"
1112        );
1113
1114        // After signing, should pass verification
1115        assert!(
1116            melt.verify_spending_conditions().is_ok(),
1117            "Signed melt request should pass verification"
1118        );
1119    }
1120
1121    #[test]
1122    fn test_melt_sig_all_unauthorized_key() {
1123        let secret_key =
1124            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1125                .unwrap();
1126        let pubkey = secret_key.public_key();
1127
1128        // The data key is the authorized key; repeating it in the tag would be a duplicate
1129        let conditions = Conditions {
1130            sig_flag: SigFlag::SigAll,
1131            pubkeys: None,
1132            ..Default::default()
1133        };
1134
1135        let secret = create_test_secret(pubkey, conditions);
1136        let proof = create_test_proof(secret, pubkey, "009a1f293253e41e");
1137
1138        let mut melt = MeltRequest::new(Uuid::new_v4(), vec![proof], None);
1139
1140        // Sign with unauthorized key
1141        let unauthorized_key =
1142            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1143                .unwrap();
1144        melt.sign_sig_all(unauthorized_key).unwrap();
1145
1146        // Verification should fail (unauthorized signature)
1147        assert!(
1148            melt.verify_spending_conditions().is_err(),
1149            "Verification should fail with unauthorized key signature"
1150        );
1151    }
1152
1153    #[test]
1154    fn test_melt_sig_all_wrong_flag() {
1155        let secret_key =
1156            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1157                .unwrap();
1158        let pubkey = secret_key.public_key();
1159
1160        // Create conditions with SIG_INPUTS instead of SIG_ALL
1161        let conditions = Conditions {
1162            sig_flag: SigFlag::SigInputs,
1163            pubkeys: None,
1164            ..Default::default()
1165        };
1166
1167        let secret = create_test_secret(pubkey, conditions);
1168        let proof = create_test_proof(secret, pubkey, "009a1f293253e41e");
1169
1170        let mut melt = MeltRequest::new(Uuid::new_v4(), vec![proof], None);
1171
1172        // Signing
1173        melt.sign_sig_all(secret_key).unwrap();
1174
1175        // Verification should fail (wrong flag - expected SIG_ALL)
1176        assert!(
1177            melt.verify_spending_conditions().is_err(),
1178            "Verification should fail with SIG_INPUTS flag when expecting SIG_ALL"
1179        );
1180    }
1181
1182    #[test]
1183    fn test_melt_sig_all_multiple_inputs() {
1184        let secret_key =
1185            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1186                .unwrap();
1187        let pubkey = secret_key.public_key();
1188
1189        // Create conditions
1190        let conditions = Conditions {
1191            sig_flag: SigFlag::SigAll,
1192            ..Default::default()
1193        };
1194
1195        let secret = create_test_secret(pubkey, conditions);
1196
1197        // Create two proofs with same secret
1198        let proof1 = create_test_proof(secret.clone(), pubkey, "009a1f293253e41e");
1199        let proof2 = create_test_proof(secret, pubkey, "009a1f293253e41f");
1200
1201        let mut melt = MeltRequest::new(Uuid::new_v4(), vec![proof1, proof2], None);
1202
1203        // Signing should work with multiple matching inputs
1204        assert!(
1205            melt.sign_sig_all(secret_key).is_ok(),
1206            "Signing with multiple matching inputs should succeed"
1207        );
1208        assert!(
1209            melt.verify_spending_conditions().is_ok(),
1210            "Verification should succeed with multiple matching inputs"
1211        );
1212    }
1213
1214    #[test]
1215    fn test_melt_sig_all_mismatched_inputs() {
1216        let secret_key =
1217            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1218                .unwrap();
1219        let pubkey = secret_key.public_key();
1220
1221        // Create first secret and proof
1222        let conditions1 = Conditions {
1223            sig_flag: SigFlag::SigAll,
1224            ..Default::default()
1225        };
1226        let secret1 = create_test_secret(pubkey, conditions1.clone());
1227        let proof1 = create_test_proof(secret1, pubkey, "009a1f293253e41e");
1228
1229        // Create second secret with different data
1230        let conditions2 = conditions1.clone();
1231        let secret2: Secret = SpendingConditions::new_p2pk(
1232            PublicKey::from_str(
1233                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
1234            )
1235            .unwrap(),
1236            Some(conditions2),
1237        )
1238        .try_into()
1239        .unwrap();
1240        let proof2 = create_test_proof(secret2, pubkey, "009a1f293253e41f");
1241
1242        let mut melt = MeltRequest::new(Uuid::new_v4(), vec![proof1, proof2], None);
1243
1244        // Signing should succeed (no validation during signing)
1245        melt.sign_sig_all(secret_key).unwrap();
1246
1247        // Verification should fail (catches mismatched inputs)
1248        assert!(
1249            melt.verify_spending_conditions().is_err(),
1250            "Verification should fail with mismatched input secrets"
1251        );
1252    }
1253
1254    #[test]
1255    fn test_melt_sig_all_multiple_signatures() {
1256        let secret_key1 =
1257            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1258                .unwrap();
1259        let pubkey1 = secret_key1.public_key();
1260
1261        let secret_key2 =
1262            SecretKey::from_str("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
1263                .unwrap();
1264        let pubkey2 = secret_key2.public_key();
1265
1266        // Create conditions requiring 2 signatures
1267        let conditions = Conditions {
1268            sig_flag: SigFlag::SigAll,
1269            num_sigs: Some(2),
1270            pubkeys: Some(vec![pubkey2]),
1271            ..Default::default()
1272        };
1273
1274        let secret = create_test_secret(pubkey1, conditions);
1275        let proof = create_test_proof(secret, pubkey1, "009a1f293253e41e");
1276
1277        let mut melt = MeltRequest::new(
1278            Uuid::new_v4(),
1279            vec![proof],
1280            Some(vec![create_test_blinded_msg(
1281                SecretKey::generate().public_key(),
1282            )]),
1283        );
1284
1285        // First signature
1286        assert!(
1287            melt.sign_sig_all(secret_key1).is_ok(),
1288            "First signature should succeed"
1289        );
1290        assert!(
1291            melt.verify_spending_conditions().is_err(),
1292            "Single signature should not verify when two required"
1293        );
1294
1295        // Second signature
1296        assert!(
1297            melt.sign_sig_all(secret_key2).is_ok(),
1298            "Second signature should succeed"
1299        );
1300
1301        assert!(
1302            melt.verify_spending_conditions().is_ok(),
1303            "Both signatures should verify successfully"
1304        );
1305    }
1306
1307    #[test]
1308    fn test_melt_sig_all_message_components() {
1309        let secret_key =
1310            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1311                .unwrap();
1312        let pubkey = secret_key.public_key();
1313
1314        let conditions = Conditions {
1315            sig_flag: SigFlag::SigAll,
1316            pubkeys: None,
1317            ..Default::default()
1318        };
1319
1320        let secret = create_test_secret(pubkey, conditions);
1321        let proof = create_test_proof(secret.clone(), pubkey, "009a1f293253e41e");
1322        let blinded_msg = create_test_blinded_msg(pubkey);
1323        let quote_id = Uuid::new_v4();
1324
1325        let melt = MeltRequest::new(quote_id, vec![proof], Some(vec![blinded_msg.clone()]));
1326
1327        // Get message to sign
1328        let msg = melt.sig_all_msg_to_sign();
1329
1330        // Verify all components are present in the message
1331        assert!(
1332            msg.contains(&secret.to_string()),
1333            "Message should contain secret"
1334        );
1335        assert!(
1336            msg.contains(&blinded_msg.blinded_secret.to_hex()),
1337            "Message should contain blinded message in hex format"
1338        );
1339        assert!(
1340            msg.contains(&quote_id.to_string()),
1341            "Message should contain quote ID"
1342        );
1343    }
1344
1345    // "SIG_ALL Test Vectors", starting with swaps : https://github.com/cashubtc/nuts/blob/5b050c7960607cca0481c28517cab5cc091b5d2e/tests/11-test.md#sig_all-test-vectors
1346    #[test]
1347    fn test_sig_all_swap_single_sig() {
1348        // Valid SwapRequest with SIG_ALL signature
1349        let valid_swap = r#"{
1350          "inputs": [
1351            {
1352              "amount": 2,
1353              "id": "00bfa73302d12ffd",
1354              "secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1355              "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd",
1356              "witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}"
1357            }
1358          ],
1359          "outputs": [
1360            {
1361              "amount": 2,
1362              "id": "00bfa73302d12ffd",
1363              "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1364            }
1365          ]
1366}"#;
1367
1368        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1369
1370        // Verify the message format
1371        let msg_to_sign = valid_swap.sig_all_msg_to_sign();
1372        assert_eq!(
1373            msg_to_sign,
1374            "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd2038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1375        );
1376
1377        // Verify the SHA256 hash of the message
1378        use bitcoin::hashes::{sha256, Hash};
1379        let msg_hash = sha256::Hash::hash(msg_to_sign.as_bytes());
1380        assert_eq!(
1381            msg_hash.to_string(),
1382            "de7f9e3ca0fcc5ed3258fcf83dbf1be7fa78a5ed6da7bf2aa60d61e9dc6eb09a"
1383        );
1384
1385        assert!(
1386            valid_swap.verify_spending_conditions().is_ok(),
1387            "Valid SIG_ALL swap request should verify"
1388        );
1389    }
1390
1391    #[test]
1392    fn test_sig_all_swap_single_sig_2() {
1393        // The following is a SwapRequest with a valid sig_all signature.
1394        let valid_swap = r#"{
1395              "inputs": [
1396                {
1397                  "amount": 2,
1398                  "id": "00bfa73302d12ffd",
1399                  "secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1400                  "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd",
1401                  "witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}"
1402                }
1403              ],
1404              "outputs": [
1405                {
1406                  "amount": 2,
1407                  "id": "00bfa73302d12ffd",
1408                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1409                }
1410              ]
1411}"#;
1412
1413        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1414
1415        assert!(
1416            valid_swap.verify_spending_conditions().is_ok(),
1417            "Valid SIG_ALL swap request should verify"
1418        );
1419    }
1420
1421    #[test]
1422    fn test_sig_all_multiple_secrets() {
1423        // The following is a SwapRequest that is invalid as there are multiple secrets.
1424        let invalid_swap = r#"{
1425                  "inputs": [
1426                {
1427                  "amount": 1,
1428                  "id": "00bfa73302d12ffd",
1429                  "secret": "[\"P2PK\",{\"nonce\":\"fa6dd3fac9086c153878dec90b9e37163d38ff2ecf8b37db6470e9d185abbbae\",\"data\":\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1430                  "C": "024d232312bab25af2e73f41d56864d378edca9109ae8f76e1030e02e585847786",
1431                  "witness": "{\"signatures\":[\"27b4d260a1186e3b62a26c0d14ffeab3b9f7c3889e78707b8fd3836b473a00601afbd53a2288ad20a624a8bbe3344453215ea075fc0ce479dd8666fd3d9162cc\"]}"
1432                },
1433                {
1434                  "amount": 2,
1435                  "id": "00bfa73302d12ffd",
1436                  "secret": "[\"P2PK\",{\"nonce\":\"4007b21fc5f5b1d4920bc0a08b158d98fd0fb2b0b0262b57ff53c6c5d6c2ae8c\",\"data\":\"033b42b04e659fed13b669f8b16cdaffc3ee5738608810cf97a7631d09bd01399d\",\"tags\":[[\"locktime\",\"122222222222222\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1437                  "C": "02417400f2af09772219c831501afcbab4efb3b2e75175635d5474069608deb641"
1438                }
1439              ],
1440              "outputs": [
1441                {
1442                  "amount": 1,
1443                  "id": "00bfa73302d12ffd",
1444                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1445                },
1446                {
1447                  "amount": 1,
1448                  "id": "00bfa73302d12ffd",
1449                  "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5"
1450                },
1451                {
1452                  "amount": 1,
1453                  "id": "00bfa73302d12ffd",
1454                  "B_": "02c0d4fce02a7a0f09e3f1bca952db910b17e81a7ebcbce62cd8dcfb127d21e37b"
1455                }
1456              ]
1457}"#;
1458
1459        let invalid_swap: SwapRequest = serde_json::from_str(invalid_swap).unwrap();
1460
1461        assert!(
1462            invalid_swap.verify_spending_conditions().is_err(),
1463            "Invalid swap with multiple secrets shouldn't be accepted"
1464        );
1465    }
1466
1467    #[test]
1468    fn test_sig_all_multiple_signatures_provided() {
1469        // The following is a SwapRequest multiple valid signatures are provided and required.
1470        let valid_swap = r#"{
1471              "inputs": [
1472                {
1473                  "amount": 2,
1474                  "id": "00bfa73302d12ffd",
1475                  "secret": "[\"P2PK\",{\"nonce\":\"04bfd885fc982d553711092d037fdceb7320fd8f96b0d4fd6d31a65b83b94272\",\"data\":\"0275e78025b558dbe6cb8fdd032a2e7613ca14fda5c1f4c4e3427f5077a7bd90e4\",\"tags\":[[\"pubkeys\",\"035163650bbd5ed4be7693f40f340346ba548b941074e9138b67ef6c42755f3449\",\"02817d22a8edc44c4141e192995a7976647c335092199f9e076a170c7336e2f5cc\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1476                  "C": "03866a09946562482c576ca989d06371e412b221890804c7da8887d321380755be",
1477                  "witness": "{\"signatures\":[\"be1d72c5ca16a93c5a34f25ec63ce632ddc3176787dac363321af3fd0f55d1927e07451bc451ffe5c682d76688ea9925d7977dffbb15bd79763b527f474734b0\",\"669d6d10d7ed35395009f222f6c7bdc28a378a1ebb72ee43117be5754648501da3bedf2fd6ff0c7849ac92683538c60af0af504102e40f2d8daca8e08b1ca16b\"]}"
1478                }
1479              ],
1480              "outputs": [
1481                {
1482                  "amount": 2,
1483                  "id": "00bfa73302d12ffd",
1484                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1485                }
1486              ]
1487}"#;
1488
1489        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1490
1491        assert!(
1492            valid_swap.verify_spending_conditions().is_ok(),
1493            "Valid swap with multiple signatures should be accepted"
1494        );
1495    }
1496
1497    #[test]
1498    fn test_sig_all_mixed_pubkeys_and_refund() {
1499        // The following is an invalid SwapRequest with pubkeys and refund mixed.
1500        let invalid_swap = r#"{
1501                      "inputs": [
1502                {
1503                  "amount": 2,
1504                  "id": "00bfa73302d12ffd",
1505                  "secret": "[\"P2PK\",{\"nonce\":\"3e9253419a11f0a541dd6baeddecf8356fc864b5d061f12f05632bc3aee6b5c4\",\"data\":\"0343cca0e48ce9e3fdcddba4637ff8cdbf6f5ed9cfdf1873e63827e760f0ed4db5\",\"tags\":[[\"pubkeys\",\"0235e0a719f8b046cee90f55a59b1cdd6ca75ce23e49cbcd82c9e5b7310e21ebcd\",\"020443f98b356e021bae82bdfc05ff433cab21e27fca9ab7b0995aedb2e7aabc43\"],[\"locktime\",\"100\"],[\"n_sigs\",\"2\"],[\"refund\",\"026b432e62b041bf9cdae534203739c73fa506c9a2d6aa58a52bc601a1dec421e1\",\"02e3494a2e07e7f6e7d4567e0da7a563592bff1e121df2383667f15b83e9168a9e\"],[\"n_sigs_refund\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1506                  "C": "026c12ee3bffa5c617debcf823bf1af6a9b47145b699f2737bba3394f0893eb869",
1507                  "witness": "{\"signatures\":[\"bfe884145ce6512331324321c3946dfd812428a53656b108b59d26559a186ba2ab45e5be9ce94e2dff0d09078e25ccb82d06a8b3a63cd3dc67065b8f77292776\",\"236e5cc9c30f85a893a29a4302e41e6f2015caef4229f28fa65e2f5c9d55515cc9a1852093a81a5095055d85fd55bf4da124e55354b56e0a39e83b58b0afc197\"]}"
1508                }
1509              ],
1510              "outputs": [
1511                {
1512                  "amount": 1,
1513                  "id": "00bfa73302d12ffd",
1514                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1515                },
1516                {
1517                  "amount": 1,
1518                  "id": "00bfa73302d12ffd",
1519                  "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5"
1520                }
1521              ]
1522}"#;
1523
1524        let invalid_swap: SwapRequest = serde_json::from_str(invalid_swap).unwrap();
1525
1526        assert!(
1527            invalid_swap.verify_spending_conditions().is_err(),
1528            "Invalid swap with mixed refunds and pubkeys shouldn't be accepted"
1529        );
1530    }
1531
1532    #[test]
1533    fn test_sig_all_locktime_passed_with_valid_refund_key_sigs() {
1534        // The following is a SwapRequest with locktime passed and refund keys signatures are valid
1535        let valid_swap = r#"{
1536                      "inputs": [
1537                {
1538                  "amount": 2,
1539                  "id": "00bfa73302d12ffd",
1540                  "secret": "[\"P2PK\",{\"nonce\":\"9ea35553beb18d553d0a53120d0175a0991ca6109370338406eed007b26eacd1\",\"data\":\"02af21e09300af92e7b48c48afdb12e22933738cfb9bba67b27c00c679aae3ec25\",\"tags\":[[\"locktime\",\"1\"],[\"refund\",\"02637c19143c58b2c58bd378400a7b82bdc91d6dedaeb803b28640ef7d28a887ac\",\"0345c7fdf7ec7c8e746cca264bf27509eb4edb9ac421f8fbfab1dec64945a4d797\"],[\"n_sigs_refund\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1541                  "C": "03dd83536fbbcbb74ccb3c87147df26753fd499cc2c095f74367fff0fb459c312e",
1542                  "witness": "{\"signatures\":[\"23b58ef28cd22f3dff421121240ddd621deee83a3bc229fd67019c2e338d91e2c61577e081e1375dbab369307bba265e887857110ca3b4bd949211a0a298805f\",\"7e75948ef1513564fdcecfcbd389deac67c730f7004f8631ba90c0844d3e8c0cf470b656306877df5141f65fd3b7e85445a8452c3323ab273e6d0d44843817ed\"]}"
1543                }
1544              ],
1545              "outputs": [
1546                {
1547                  "amount": 2,
1548                  "id": "00bfa73302d12ffd",
1549                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1550                }
1551              ]
1552}"#;
1553
1554        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1555
1556        assert!(
1557            valid_swap.verify_spending_conditions().is_ok(),
1558            "Valid post-locktime swap with refund keys should be accepted"
1559        );
1560    }
1561
1562    #[test]
1563    fn test_sig_all_htlc_and_pubkey() {
1564        // The following is a valid `SwapRequest` with an HTLC also locked to a public key
1565        let valid_swap = r#"{
1566                      "inputs": [
1567                {
1568                  "amount": 2,
1569                  "id": "00bfa73302d12ffd",
1570                  "secret": "[\"HTLC\",{\"nonce\":\"d730dd70cd7ec6e687829857de8e70aab2b970712f4dbe288343eca20e63c28c\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0350cda8a1d5257dbd6ba8401a9a27384b9ab699e636e986101172167799469b14\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1571                  "C": "03ff6567e2e6c31db5cb7189dab2b5121930086791c93899e4eff3dda61cb57273",
1572                  "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"a4c00a9ad07f9936e404494fda99a9b935c82d7c053173b304b8663124c81d4b00f64a225f5acf41043ca52b06382722bd04ded0fbeb0fcc404eed3b24778b88\"]}"
1573                }
1574              ],
1575              "outputs": [
1576                {
1577                  "amount": 2,
1578                  "id": "00bfa73302d12ffd",
1579                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1580                }
1581              ]
1582}"#;
1583
1584        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1585
1586        assert!(
1587            valid_swap.verify_spending_conditions().is_ok(),
1588            "Valid swap with htlc and pubkey should be accepted"
1589        );
1590    }
1591
1592    #[test]
1593    fn test_sig_all_enforce_locktime_with_only_refund_signed() {
1594        // The following is an invalid SwapRequest with an HTLC also locked to a public key, locktime and refund key. locktime is not expired but proof is signed with refund key.
1595        let invalid_swap = r#"{
1596              "inputs": [
1597                {
1598                  "amount": 2,
1599                  "id": "00bfa73302d12ffd",
1600                  "secret": "[\"HTLC\",{\"nonce\":\"512c4045f12fdfd6f55059669c189e040c37c1ce2f8be104ed6aec296acce4e9\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"03ba83defd31c63f8841d188f0d41b5bb3af1bb3c08d0ba46f8f1d26a4d45e8cad\"],[\"locktime\",\"4854185133\"],[\"refund\",\"032f1008a79c722e93a1b4b853f85f38283f9ef74ee4c5c91293eb1cc3c5e46e34\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1601                  "C": "02207abeff828146f1fc3909c74613d5605bd057f16791994b3c91f045b39a6939",
1602                  "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"7816d57871bde5be2e4281065dbe5b15f641d8f1ed9437a3ae556464d6f9b8a0a2e6660337a915f2c26dce1453a416daf682b8fb593b67a0750fce071e0759b9\"]}"
1603                }
1604              ],
1605              "outputs": [
1606                {
1607                  "amount": 1,
1608                  "id": "00bfa73302d12ffd",
1609                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1610                },
1611                {
1612                  "amount": 1,
1613                  "id": "00bfa73302d12ffd",
1614                  "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5"
1615                }
1616              ]
1617}"#;
1618
1619        let invalid_swap: SwapRequest = serde_json::from_str(invalid_swap).unwrap();
1620
1621        assert!(
1622            invalid_swap.verify_spending_conditions().is_err(),
1623            "Invalid swap with pre-locktime conditions not met shouldn't be accepted"
1624        );
1625    }
1626
1627    #[test]
1628    fn test_sig_all_htlc_post_locktime() {
1629        // The following is a valid SwapRequest with a multisig HTLC using the refund path.
1630        // Per NUT-14: After locktime, the refund path is available when preimage is invalid/missing.
1631        // The preimage is intentionally invalid (all zeros) to test the refund path.
1632        let valid_swap = r#"{
1633              "inputs": [
1634                {
1635                  "amount": 2,
1636                  "id": "00bfa73302d12ffd",
1637                  "secret": "[\"HTLC\",{\"nonce\":\"c9b0fabb8007c0db4bef64d5d128cdcf3c79e8bb780c3294adf4c88e96c32647\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"039e6ec7e922abb4162235b3a42965eb11510b07b7461f6b1a17478b1c9c64d100\"],[\"locktime\",\"1\"],[\"refund\",\"02ce1bbd2c9a4be8029c9a6435ad601c45677f5cde81f8a7f0ed535e0039d0eb6c\",\"03c43c00ff57f63cfa9e732f0520c342123e21331d0121139f1b636921eeec095f\"],[\"n_sigs_refund\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1638                  "C": "0344b6f1471cf18a8cbae0e624018c816be5e3a9b04dcb7689f64173c1ae90a3a5",
1639                  "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"signatures\":[\"98e21672d409cc782c720f203d8284f0af0c8713f18167499f9f101b7050c3e657fb0e57478ebd8bd561c31aa6c30f4cd20ec38c73f5755b7b4ddee693bca5a5\",\"693f40129dbf905ed9c8008081c694f72a36de354f9f4fa7a61b389cf781f62a0ae0586612fb2eb504faaf897fefb6742309186117f4743bcebcb8e350e975e2\"]}"
1640                }
1641              ],
1642              "outputs": [
1643                {
1644                  "amount": 2,
1645                  "id": "00bfa73302d12ffd",
1646                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1647                }
1648              ]
1649}"#;
1650
1651        let valid_swap: SwapRequest = serde_json::from_str(valid_swap).unwrap();
1652
1653        assert!(
1654            valid_swap.verify_spending_conditions().is_ok(),
1655            "Valid post-locktime swap with htlc should be accepted"
1656        );
1657    }
1658
1659    #[test]
1660    fn test_sig_all_swap_mismatched_inputs() {
1661        // Invalid SwapRequest - mismatched inputs with SIG_ALL
1662        let invalid_swap = r#"{
1663            "inputs": [
1664        {
1665          "amount": 1,
1666          "id": "00bfa73302d12ffd",
1667          "secret": "[\"P2PK\",{\"nonce\":\"e2a221fe361f19d95c5c3312ccff3ffa075b4fe37beec99de85a6ee70568385b\",\"data\":\"03dad7f9c588f4cbb55c2e1b7b802fa2bbc63a614d9e9ecdf56a8e7ee8ca65be86\",\"tags\":[[\"pubkeys\",\"025f2af63fd65ca97c3bde4070549683e72769d28def2f1cd3d63576cd9c2ffa6c\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1668          "C": "02a79c09b0605f4e7a21976b511cc7be01cdaeac54b29645258c84f2e74bff13f6",
1669          "witness": "{\"signatures\":[\"b42c7af7e98ca4e3bba8b73702120970286196340b340c21299676dbc7b10cafaa7baeb243affc01afce3218616cf8b3f6b4baaf4414fedb31b0c6653912f769\",\"17781910e2d806cae464f8a692929ee31124c0cd7eaf1e0d94292c6cbc122da09076b649080b8de9201f87d83b99fe04e33d701817eb287d1cdd9c4d0410e625\"]}"
1670        },
1671        {
1672          "amount": 2,
1673          "id": "00bfa73302d12ffd",
1674          "secret": "[\"P2PK\",{\"nonce\":\"973c78b5e84c0986209dc14ba57682baf38fa4c1ea60c4c5f6834779a1a13e6d\",\"data\":\"02685df03c777837bc7155bd2d0d8e98eede7e956a4cd8a9edac84532584e68e0f\",\"tags\":[[\"pubkeys\",\"025f2af63fd65ca97c3bde4070549683e72769d28def2f1cd3d63576cd9c2ffa6c\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1675          "C": "02be48c564cf6a7b4d09fbaf3a78a153a79f687ac4623e48ce1788effc3fb1e024"
1676        }
1677      ],
1678      "outputs": [
1679        {
1680          "amount": 1,
1681          "id": "00bfa73302d12ffd",
1682          "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1683        },
1684        {
1685          "amount": 1,
1686          "id": "00bfa73302d12ffd",
1687          "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5"
1688        },
1689        {
1690          "amount": 1,
1691          "id": "00bfa73302d12ffd",
1692          "B_": "02c0d4fce02a7a0f09e3f1bca952db910b17e81a7ebcbce62cd8dcfb127d21e37b"
1693        }
1694      ]
1695    }"#;
1696
1697        let invalid_swap: SwapRequest = serde_json::from_str(invalid_swap).unwrap();
1698        assert!(
1699            invalid_swap.verify_spending_conditions().is_err(),
1700            "Invalid SIG_ALL swap request should fail verification"
1701        );
1702    }
1703
1704    #[test]
1705    fn test_sig_all_mixed_pubkeys_and_refund_pubkeys() {
1706        // SwapRequest with mixed up signatures from pubkey and refund_pubkeys
1707        let invalidsig_all_swap = r#"{
1708  "inputs": [
1709    {
1710      "amount": 2,
1711      "id": "00bfa73302d12ffd",
1712      "secret": "[\"P2PK\",{\"nonce\":\"cc93775c74df53d7c97eb37f72018d166a45ce4f4c65f11c4014b19acd02bd2f\",\"data\":\"02f515ab63e973e0dadfc284bf2ef330b01aa99c3ff775d88272f9c17afa25568c\",\"tags\":[[\"pubkeys\",\"026925e5bb547a3ec6b2d9b8934e23b882f54f89b2a9f45300bf81fd1b311d9c97\"],[\"n_sigs\",\"2\"],[\"refund\",\"03c8cd46b7e6592c41df38bc54dce2555586e7adbb15cc80a02d1a05829677286d\"],[\"n_sigs_refund\",\"1\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1713      "C": "03f6d40d0ab11f4082ee7e977534a6fcd151394d647cde4ab122157e6d755410fd",
1714      "witness": "{\"signatures\":[\"a9f61c2b7161a50839bf7f3e2e1cb9bd7bdacd2ce62c0d458a5969db44646dad409a282241b412e8b191cc7432bcfebf16ad72339a9fb966ca71c8bd971662cc\",\"aa778ec15fe9408e1989c712c823e833f33d45780b9a25555ea76004b05d495e99fd326914484f92e7e91f919ee575e79add26e9d4bbe4349d7333d7e0021af7\"]}"
1715    }
1716  ],
1717  "outputs": [
1718    {
1719      "amount": 1,
1720      "id": "00bfa73302d12ffd",
1721      "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1722    },
1723    {
1724      "amount": 1,
1725      "id": "00bfa73302d12ffd",
1726      "B_": "03afe7c87e32d436f0957f1d70a2bca025822a84a8623e3a33aed0a167016e0ca5"
1727    }
1728  ]
1729}"#;
1730
1731        let invalid_swap: SwapRequest = serde_json::from_str(invalidsig_all_swap).unwrap();
1732        assert!(
1733            invalid_swap.verify_spending_conditions().is_err(),
1734            "Invalid SIG_ALL swap request should fail verification"
1735        );
1736    }
1737
1738    #[test]
1739    fn test_sig_all_htlc_unexpired_timelock_refund_signature() {
1740        // SwapRequest signed with refund_pubkey without expiration
1741        let invalidsig_all_swap = r#"{
1742  "inputs": [
1743    {
1744      "amount": 2,
1745      "id": "00bfa73302d12ffd",
1746      "secret": "[\"HTLC\",{\"nonce\":\"b6f0c59ea4084369d4196e1318477121c2451d59ae767060e083cb6846e6bbe0\",\"data\":\"ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5\",\"tags\":[[\"pubkeys\",\"0329fdfde4becf9ff871129653ff6464bb2c922fbcba442e6166a8b5849599604f\"],[\"locktime\",\"4854185133\"],[\"refund\",\"035fcf4a5393e4bdef0567aa0b8a9555edba36e5fcb283f3bbce52d86a687817d3\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1747      "C": "024fbbee3f3cc306a48841ba327435b64de20b8b172b98296a3e573c673d52562b",
1748      "witness": "{\"preimage\":\"0000000000000000000000000000000000000000000000000000000000000001\",\"signatures\":[\"7526819070a291f731e77acfbe9da71ddc0f748fd2a3e6c2510bc83c61daaa656df345afa3832fe7cb94352c8835a4794ad499760729c0be29417387d1fc3cd1\"]}"
1749    }
1750  ],
1751  "outputs": [
1752    {
1753      "amount": 2,
1754      "id": "00bfa73302d12ffd",
1755      "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1756    }
1757  ]
1758}"#;
1759
1760        let invalid_swap: SwapRequest = serde_json::from_str(invalidsig_all_swap).unwrap();
1761        assert!(
1762            invalid_swap.verify_spending_conditions().is_err(),
1763            "Invalid SIG_ALL swap request should fail verification"
1764        );
1765    }
1766
1767    // Now the Melt examples at the end of 11-test.md
1768    #[test]
1769    fn test_sig_all_melt() {
1770        // Valid MeltRequest with SIG_ALL signature
1771        // Example MeltRequest:
1772        let valid_melt = r#"{
1773              "quote": "cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0",
1774              "inputs": [
1775                {
1776                  "amount": 2,
1777                  "id": "00bfa73302d12ffd",
1778                  "secret": "[\"P2PK\",{\"nonce\":\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\",\"data\":\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1779                  "C": "02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b",
1780                  "witness": "{\"signatures\":[\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\"]}"
1781                }
1782              ],
1783              "outputs": [
1784                {
1785                  "amount": 0,
1786                  "id": "00bfa73302d12ffd",
1787                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1788                }
1789              ]
1790}"#;
1791
1792        let valid_melt: MeltRequest<String> = serde_json::from_str(valid_melt).unwrap();
1793
1794        // Verify the message format
1795        let msg_to_sign = valid_melt.sig_all_msg_to_sign();
1796        assert_eq!(
1797            msg_to_sign,
1798            r#"["P2PK",{"nonce":"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de","data":"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835","tags":[["sigflag","SIG_ALL"]]}]02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b0038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0"#
1799        );
1800
1801        // Verify the SHA256 hash of the message
1802        use bitcoin::hashes::{sha256, Hash};
1803        let msg_hash = sha256::Hash::hash(msg_to_sign.as_bytes());
1804        assert_eq!(
1805            msg_hash.to_string(),
1806            "9efa1067cc7dc870f4074f695115829c3cd817a6866c3b84e9814adf3c3cf262"
1807        );
1808
1809        assert!(
1810            valid_melt.verify_spending_conditions().is_ok(),
1811            "Valid SIG_ALL melt request should verify"
1812        );
1813    }
1814
1815    #[test]
1816    fn test_sig_all_valid_melt() {
1817        // The following is a valid SIG_ALL MeltRequest.
1818        let valid_melt = r#"{
1819              "quote": "cF8911fzT88aEi1d-6boZZkq5lYxbUSVs-HbJxK0",
1820              "inputs": [
1821                {
1822                  "amount": 2,
1823                  "id": "00bfa73302d12ffd",
1824                  "secret": "[\"P2PK\",{\"nonce\":\"bbf9edf441d17097e39f5095a3313ba24d3055ab8a32f758ff41c10d45c4f3de\",\"data\":\"029116d32e7da635c8feeb9f1f4559eb3d9b42d400f9d22a64834d89cde0eb6835\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1825                  "C": "02a9d461ff36448469dccf828fa143833ae71c689886ac51b62c8d61ddaa10028b",
1826                  "witness": "{\"signatures\":[\"478224fbe715e34f78cb33451db6fcf8ab948afb8bd04ff1a952c92e562ac0f7c1cb5e61809410635be0aa94d0448f7f7959bd5762cc3802b0a00ff58b2da747\"]}"
1827                }
1828              ],
1829              "outputs": [
1830                {
1831                  "amount": 0,
1832                  "id": "00bfa73302d12ffd",
1833                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1834                }
1835              ]
1836}"#;
1837
1838        let valid_melt: MeltRequest<String> = serde_json::from_str(valid_melt).unwrap();
1839
1840        assert!(
1841            valid_melt.verify_spending_conditions().is_ok(),
1842            "Valid SIG_ALL melt request should verify"
1843        );
1844    }
1845
1846    #[test]
1847    fn test_sig_all_valid_multisig_melt() {
1848        // The following is a valid multi-sig SIG_ALL MeltRequest.
1849        let valid_melt = r#"{
1850              "quote": "Db3qEMVwFN2tf_1JxbZp29aL5cVXpSMIwpYfyOVF",
1851              "inputs": [
1852                {
1853                  "amount": 2,
1854                  "id": "00bfa73302d12ffd",
1855                  "secret": "[\"P2PK\",{\"nonce\":\"68d7822538740e4f9c9ebf5183ef6c4501c7a9bca4e509ce2e41e1d62e7b8a99\",\"data\":\"0394e841bd59aeadce16380df6174cb29c9fea83b0b65b226575e6d73cc5a1bd59\",\"tags\":[[\"pubkeys\",\"033d892d7ad2a7d53708b7a5a2af101cbcef69522bd368eacf55fcb4f1b0494058\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1856                  "C": "03a70c42ec9d7192422c7f7a3ad017deda309fb4a2453fcf9357795ea706cc87a9",
1857                  "witness": "{\"signatures\":[\"ed739970d003f703da2f101a51767b63858f4894468cc334be04aa3befab1617a81e3eef093441afb499974152d279e59d9582a31dc68adbc17ffc22a2516086\",\"f9efe1c70eb61e7ad8bd615c50ff850410a4135ea73ba5fd8e12a734743ad045e575e9e76ea5c52c8e7908d3ad5c0eaae93337e5c11109e52848dc328d6757a2\"]}"
1858                }
1859              ],
1860              "outputs": [
1861                {
1862                  "amount": 0,
1863                  "id": "00bfa73302d12ffd",
1864                  "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1865                }
1866              ]
1867}"#;
1868
1869        let valid_melt: MeltRequest<String> = serde_json::from_str(valid_melt).unwrap();
1870
1871        assert!(
1872            valid_melt.verify_spending_conditions().is_ok(),
1873            "Valid SIG_ALL melt request should verify"
1874        );
1875    }
1876
1877    #[test]
1878    fn test_sig_all_melt_wrong_sig() {
1879        // Invalid MeltRequest - wrong signature for SIG_ALL
1880        let invalid_melt = r#"{
1881            "inputs": [{
1882                "amount": 1,
1883                "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
1884                "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
1885                "id": "009a1f293253e41e",
1886                "witness": "{\"signatures\":[\"3426df9730d365a9d18d79bed2f3e78e9172d7107c55306ac5ddd1b2d065893366cfa24ff3c874ebf1fc22360ba5888ddf6ff5dbcb9e5f2f5a1368f7afc64f15\"]}"
1887            }],
1888            "quote": "test_quote_123",
1889            "outputs": null
1890        }"#;
1891
1892        let invalid_melt: MeltRequest<String> = serde_json::from_str(invalid_melt).unwrap();
1893        assert!(
1894            invalid_melt.verify_spending_conditions().is_err(),
1895            "Invalid SIG_ALL melt request should fail verification"
1896        );
1897    }
1898
1899    #[test]
1900    #[ignore]
1901    fn test_sig_all_melt_msg_to_sign() {
1902        let multisig_melt = r#"{
1903  "quote": "uHwJ-f6HFAC-lU2dMw0KOu6gd5S571FXQQHioYMD",
1904  "inputs": [
1905    {
1906      "amount": 4,
1907      "id": "00bfa73302d12ffd",
1908      "secret": "[\"P2PK\",{\"nonce\":\"f5c26c928fb4433131780105eac330338bb9c0af2b2fd29fad9e4f18c4a96d84\",\"data\":\"03c4840e19277822bfeecf104dcd3f38d95b33249983ac6fed755869f23484fb2a\",\"tags\":[[\"pubkeys\",\"0256dcc53d9330e0bc6e9b3d47c26287695aba9fe55cafdde6f46ef56e09582bfb\"],[\"n_sigs\",\"1\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1909      "C": "02174667f98114abeb741f4964bdc88a3b86efde0afa38f791094c1e07e5df3beb",
1910      "witness": "{\"signatures\":[\"abeeceba92bc7d1c514844ddb354d1e88a9776dfb55d3cdc5c289240386e401c3d983b68371ce5530e86c8fc4ff90195982a262f83fa8a5335b43e75af5f5fc7\"]}"
1911    }
1912  ],
1913  "outputs": [
1914    {
1915      "amount": 0,
1916      "id": "00bfa73302d12ffd",
1917      "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1918    }
1919  ]
1920}"#;
1921
1922        let multisig_melt: MeltRequest<String> = serde_json::from_str(multisig_melt).unwrap();
1923
1924        assert!(
1925            multisig_melt.verify_spending_conditions().is_ok(),
1926            "melt request with SIG_ALL should succeed"
1927        );
1928
1929        let msg_to_sign = multisig_melt.sig_all_msg_to_sign();
1930
1931        assert_eq!(
1932            msg_to_sign,
1933            r#"["P2PK",{"nonce":"f5c26c928fb4433131780105eac330338bb9c0af2b2fd29fad9e4f18c4a96d84","data":"03c4840e19277822bfeecf104dcd3f38d95b33249983ac6fed755869f23484fb2a","tags":[["pubkeys","0256dcc53d9330e0bc6e9b3d47c26287695aba9fe55cafdde6f46ef56e09582bfb"],["n_sigs","1"],["sigflag","SIG_ALL"]]}]02174667f98114abeb741f4964bdc88a3b86efde0afa38f791094c1e07e5df3beb000bfa73302d12ffd038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39uHwJ-f6HFAC-lU2dMw0KOu6gd5S571FXQQHioYMD"#
1934        );
1935    }
1936
1937    #[test]
1938    #[ignore]
1939    fn test_sig_all_melt_multi_sig() {
1940        // MeltRequest with multi-sig SIG_ALL requiring 2 signatures
1941        let multisig_melt = r#"{
1942  "quote": "wYHbJm5S1GTL28tDHoUAwcvb-31vu5kfDhnLxV9D",
1943  "inputs": [
1944    {
1945      "amount": 4,
1946      "id": "00bfa73302d12ffd",
1947      "secret": "[\"P2PK\",{\"nonce\":\"1705e988054354b703bc9103472cc5646ec76ed557517410186fa827c19c444d\",\"data\":\"024c8b5ec0e560f1fc77d7872ab75dd10a00af73a8ba715b81093b800849cb21fb\",\"tags\":[[\"pubkeys\",\"028d32bc906b3724724244812c450f688c548020f5d5a8c1d6cd1075650933d1a3\"],[\"n_sigs\",\"2\"],[\"sigflag\",\"SIG_ALL\"]]}]",
1948      "C": "02f2a0ff12c4dd95f2476662f1df49e5126f09a5ea1f3ce13b985db57661953072",
1949      "witness": "{\"signatures\":[\"a98a2616716d7813394a54ddc82234e5c47f0ddbddb98ccd1cad25236758fa235c8ae64d9fccd15efbe0ad5eba52a3df8433e9f1c05bc50defcb9161a5bd4bc4\",\"dd418cbbb23276dab8d72632ee77de730b932a3c6e8e15bc8802cef13db0b346915fe6e04e7fae03c3b5af026e25f71a24dc05b28135f0a9b69bc6c7289b6b8d\"]}"
1950    }
1951  ],
1952  "outputs": [
1953    {
1954      "amount": 0,
1955      "id": "00bfa73302d12ffd",
1956      "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
1957    }
1958  ]
1959}"#;
1960
1961        let multisig_melt: MeltRequest<String> = serde_json::from_str(multisig_melt).unwrap();
1962        assert!(
1963            multisig_melt.verify_spending_conditions().is_ok(),
1964            "Multi-sig SIG_ALL melt request should verify with both signatures"
1965        );
1966
1967        // MeltRequest with insufficient signatures for multi-sig SIG_ALL
1968        let insufficient_sigs_melt = r#"{
1969            "inputs": [{
1970                "amount": 1,
1971                "secret": "[\"P2PK\",{\"nonce\":\"859d4935c4907062a6297cf4e663e2835d90d97ecdd510745d32f6816323a41f\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_ALL\"],[\"pubkeys\",\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"02142715675faf8da1ecc4d51e0b9e539fa0d52fdd96ed60dbe99adb15d6b05ad9\"],[\"n_sigs\",\"2\"]]}]",
1972                "C": "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
1973                "id": "009a1f293253e41e",
1974                "witness": "{\"signatures\":[\"83564aca48c668f50d022a426ce0ed19d3a9bdcffeeaee0dc1e7ea7e98e9eff1840fcc821724f623468c94f72a8b0a7280fa9ef5a54a1b130ef3055217f467b3\"]}"
1975            }],
1976            "quote": "test_quote_123",
1977            "outputs": null
1978        }"#;
1979
1980        let insufficient_sigs_melt: MeltRequest<String> =
1981            serde_json::from_str(insufficient_sigs_melt).unwrap();
1982        assert!(
1983            insufficient_sigs_melt.verify_spending_conditions().is_err(),
1984            "Multi-sig SIG_ALL melt request should fail with insufficient signatures"
1985        );
1986    }
1987
1988    // Helper functions for tests
1989    fn create_test_keys() -> (SecretKey, PublicKey) {
1990        let secret_key =
1991            SecretKey::from_str("99590802251e78ee1051648439eedb003dc539093a48a44e7b8f2642c909ea37")
1992                .unwrap();
1993        let pubkey = secret_key.public_key();
1994        (secret_key, pubkey)
1995    }
1996
1997    #[test]
1998    fn test_sig_all_basic_signing_verification() {
1999        let (secret_key, pubkey) = create_test_keys();
2000
2001        // Create basic SIG_ALL conditions
2002        let conditions = Conditions {
2003            sig_flag: SigFlag::SigAll,
2004            ..Default::default()
2005        };
2006
2007        let secret = create_test_secret(pubkey, conditions);
2008        let proof1 = create_test_proof(secret.clone(), pubkey, "009a1f293253e41e");
2009        let proof2 = create_test_proof(secret, pubkey, "009a1f293253e41f");
2010        let blinded_msg = create_test_blinded_msg(pubkey);
2011
2012        // Test basic signing flow
2013        let mut swap = SwapRequest::new(vec![proof1, proof2], vec![blinded_msg]);
2014        assert!(
2015            swap.verify_spending_conditions().is_err(),
2016            "Unsigned swap should fail verification"
2017        );
2018
2019        assert!(
2020            swap.sign_sig_all(secret_key).is_ok(),
2021            "Signing should succeed"
2022        );
2023
2024        println!("{}", serde_json::to_string(&swap).unwrap());
2025
2026        assert!(
2027            swap.verify_spending_conditions().is_ok(),
2028            "Signed swap should pass verification"
2029        );
2030    }
2031
2032    #[test]
2033    fn test_sig_all_unauthorized_key() {
2034        let (_secret_key, pubkey) = create_test_keys();
2035
2036        let conditions = Conditions {
2037            sig_flag: SigFlag::SigAll,
2038            ..Default::default()
2039        };
2040
2041        let secret = create_test_secret(pubkey, conditions);
2042        let proof = create_test_proof(secret, pubkey, "009a1f293253e41e");
2043        let blinded_msg = create_test_blinded_msg(pubkey);
2044
2045        // Create unauthorized key
2046        let unauthorized_key =
2047            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
2048                .unwrap();
2049
2050        let mut swap = SwapRequest::new(vec![proof], vec![blinded_msg]);
2051
2052        // Signing should succeed (no validation)
2053        swap.sign_sig_all(unauthorized_key).unwrap();
2054
2055        // Verification should fail (unauthorized signature)
2056        assert!(
2057            swap.verify_spending_conditions().is_err(),
2058            "Verification should fail with unauthorized key signature"
2059        );
2060    }
2061
2062    #[test]
2063    fn test_sig_all_mismatched_secrets() {
2064        let (secret_key, pubkey) = create_test_keys();
2065
2066        let conditions = Conditions {
2067            sig_flag: SigFlag::SigAll,
2068            ..Default::default()
2069        };
2070
2071        // Create first proof with original secret
2072        let secret1 = create_test_secret(pubkey, conditions.clone());
2073
2074        // Create second proof with different secret data
2075        let different_secret: Secret = SpendingConditions::new_p2pk(
2076            PublicKey::from_str(
2077                "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
2078            )
2079            .unwrap(),
2080            Some(conditions),
2081        )
2082        .try_into()
2083        .unwrap();
2084
2085        let proof1 = create_test_proof(secret1, pubkey, "009a1f293253e41e");
2086        let proof2 = create_test_proof(different_secret, pubkey, "009a1f293253e41f");
2087        let blinded_msg = create_test_blinded_msg(pubkey);
2088
2089        let mut swap = SwapRequest::new(vec![proof1, proof2], vec![blinded_msg]);
2090
2091        // Signing should succeed (no validation)
2092        swap.sign_sig_all(secret_key).unwrap();
2093
2094        // Verification should fail (mismatched secrets)
2095        assert!(
2096            swap.verify_spending_conditions().is_err(),
2097            "Verification should fail with mismatched secrets"
2098        );
2099    }
2100
2101    #[test]
2102    fn test_sig_all_wrong_flag() {
2103        let (secret_key, pubkey) = create_test_keys();
2104
2105        // Create conditions with SIG_INPUTS instead of SIG_ALL
2106        let sig_inputs_conditions = Conditions {
2107            sig_flag: SigFlag::SigInputs,
2108            ..Default::default()
2109        };
2110
2111        let secret = create_test_secret(pubkey, sig_inputs_conditions);
2112        let proof = create_test_proof(secret, pubkey, "009a1f293253e41e");
2113        let blinded_msg = create_test_blinded_msg(pubkey);
2114
2115        let mut swap = SwapRequest::new(vec![proof], vec![blinded_msg]);
2116
2117        // Signing should succeed (no validation)
2118        swap.sign_sig_all(secret_key).unwrap();
2119
2120        // Verification should fail (wrong flag - has SIG_INPUTS but sign_sig_all expects SIG_ALL)
2121        assert!(
2122            swap.verify_spending_conditions().is_err(),
2123            "Verification should fail with SIG_INPUTS flag when sign_sig_all was used"
2124        );
2125    }
2126
2127    #[test]
2128    fn test_sig_all_multiple_signatures() {
2129        let (secret_key1, pubkey1) = create_test_keys();
2130        let secret_key2 =
2131            SecretKey::from_str("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
2132                .unwrap();
2133        let pubkey2 = secret_key2.public_key();
2134
2135        // Create conditions requiring 2 signatures
2136        let conditions = Conditions {
2137            num_sigs: Some(2),
2138            sig_flag: SigFlag::SigAll,
2139            pubkeys: Some(vec![pubkey2]),
2140            ..Default::default()
2141        };
2142
2143        let secret = create_test_secret(pubkey1, conditions);
2144        let proof = create_test_proof(secret, pubkey1, "009a1f293253e41e");
2145        let blinded_msg = create_test_blinded_msg(pubkey1);
2146
2147        let mut swap = SwapRequest::new(vec![proof], vec![blinded_msg]);
2148
2149        // Sign with first key
2150        assert!(
2151            swap.sign_sig_all(secret_key1).is_ok(),
2152            "First signature should succeed"
2153        );
2154        assert!(
2155            swap.verify_spending_conditions().is_err(),
2156            "Single signature should not verify when two required"
2157        );
2158
2159        // Sign with second key
2160        assert!(
2161            swap.sign_sig_all(secret_key2).is_ok(),
2162            "Second signature should succeed"
2163        );
2164
2165        assert!(
2166            swap.verify_spending_conditions().is_ok(),
2167            "Both signatures should verify"
2168        );
2169    }
2170
2171    #[test]
2172    fn test_conditions_valid_multisig() {
2173        let pubkey1 = PublicKey::from_str(
2174            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2175        )
2176        .unwrap();
2177        let pubkey2 = PublicKey::from_str(
2178            "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
2179        )
2180        .unwrap();
2181
2182        // 2 additional pubkeys + 1 data key = 3 available, requiring 2 should succeed
2183        let result = Conditions::new(
2184            None,
2185            Some(vec![pubkey1, pubkey2]),
2186            None,
2187            Some(2),
2188            None,
2189            None,
2190        );
2191        assert!(result.is_ok(), "2-of-3 multisig should be valid");
2192    }
2193
2194    #[test]
2195    fn test_conditions_impossible_multisig() {
2196        let pubkey1 = PublicKey::from_str(
2197            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2198        )
2199        .unwrap();
2200        let pubkey2 = PublicKey::from_str(
2201            "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
2202        )
2203        .unwrap();
2204
2205        // 2 additional pubkeys + 1 data key = 3 available, requiring 5 is impossible
2206        let result = Conditions::new(
2207            None,
2208            Some(vec![pubkey1, pubkey2]),
2209            None,
2210            Some(5),
2211            None,
2212            None,
2213        );
2214        assert!(result.is_err(), "5-of-3 multisig should be impossible");
2215        let err = result.unwrap_err();
2216        assert!(
2217            matches!(
2218                err,
2219                crate::nut10::Error::NUT11(Error::ImpossibleMultisigConfiguration {
2220                    required: 5,
2221                    available: 3,
2222                })
2223            ),
2224            "Expected ImpossibleMultisigConfiguration, got: {err:?}"
2225        );
2226    }
2227
2228    #[test]
2229    fn test_conditions_valid_single_sig_no_pubkeys() {
2230        // No additional pubkeys, only data key (1 available), requiring 1 should succeed
2231        let result = Conditions::new(None, None, None, Some(1), None, None);
2232        assert!(result.is_ok(), "1-of-1 (data key only) should be valid");
2233    }
2234
2235    #[test]
2236    fn test_conditions_impossible_no_pubkeys() {
2237        // No additional pubkeys, only data key (1 available), requiring 2 is impossible
2238        let result = Conditions::new(None, None, None, Some(2), None, None);
2239        assert!(result.is_err(), "2-of-1 multisig should be impossible");
2240        let err = result.unwrap_err();
2241        assert!(
2242            matches!(
2243                err,
2244                crate::nut10::Error::NUT11(Error::ImpossibleMultisigConfiguration {
2245                    required: 2,
2246                    available: 1,
2247                })
2248            ),
2249            "Expected ImpossibleMultisigConfiguration, got: {err:?}"
2250        );
2251    }
2252
2253    #[test]
2254    fn test_conditions_impossible_refund_multisig() {
2255        let refund_key1 = PublicKey::from_str(
2256            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2257        )
2258        .unwrap();
2259
2260        // 1 refund key, requiring 3 refund sigs is impossible
2261        let result = Conditions::new(None, None, Some(vec![refund_key1]), None, None, Some(3));
2262        assert!(
2263            result.is_err(),
2264            "3-of-1 refund multisig should be impossible"
2265        );
2266        let err = result.unwrap_err();
2267        assert!(
2268            matches!(
2269                err,
2270                crate::nut10::Error::NUT11(Error::ImpossibleRefundMultisigConfiguration {
2271                    required: 3,
2272                    available: 1,
2273                })
2274            ),
2275            "Expected ImpossibleRefundMultisigConfiguration, got: {err:?}"
2276        );
2277    }
2278
2279    #[test]
2280    fn test_conditions_valid_refund_multisig() {
2281        let refund_key1 = PublicKey::from_str(
2282            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2283        )
2284        .unwrap();
2285        let refund_key2 = PublicKey::from_str(
2286            "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
2287        )
2288        .unwrap();
2289
2290        // 2 refund keys, requiring 2 should succeed
2291        let result = Conditions::new(
2292            Some(crate::util::unix_time() + 3600),
2293            None,
2294            Some(vec![refund_key1, refund_key2]),
2295            None,
2296            None,
2297            Some(2),
2298        );
2299        assert!(result.is_ok(), "2-of-2 refund multisig should be valid");
2300    }
2301
2302    /// Two keys sharing an x-coordinate, the way a caller repeating a key
2303    /// with the other parity byte would supply them.
2304    fn duplicate_x_only_pair() -> (PublicKey, PublicKey) {
2305        let secret_key = SecretKey::generate();
2306        let mut bytes = secret_key.public_key().to_bytes();
2307        bytes[0] = 0x02;
2308        let pk_02 = PublicKey::from_slice(&bytes).unwrap();
2309        bytes[0] = 0x03;
2310        let pk_03 = PublicKey::from_slice(&bytes).unwrap();
2311        (pk_02, pk_03)
2312    }
2313
2314    fn assert_duplicate_pubkey(err: crate::nut10::Error) {
2315        assert!(
2316            matches!(err, crate::nut10::Error::NUT11(Error::DuplicatePubkey)),
2317            "Expected DuplicatePubkey, got: {err:?}"
2318        );
2319    }
2320
2321    /// A key repeated between `data` and the `pubkeys` tag is malformed under
2322    /// NUT-11. Collapsing it silently would change the signer set the caller
2323    /// asked for, so construction rejects it instead.
2324    #[test]
2325    fn test_duplicate_key_rejected_at_construction() {
2326        let (pk_02, pk_03) = duplicate_x_only_pair();
2327
2328        let conditions = Conditions {
2329            locktime: None,
2330            pubkeys: Some(vec![pk_03]),
2331            refund_keys: None,
2332            num_sigs: None,
2333            sig_flag: SigFlag::SigInputs,
2334            num_sigs_refund: None,
2335        };
2336
2337        let result: Result<Secret, _> =
2338            SpendingConditions::new_p2pk(pk_02, Some(conditions)).try_into();
2339
2340        assert_duplicate_pubkey(result.expect_err("duplicate key should be rejected"));
2341    }
2342
2343    /// The threshold must not change which error a repeated key produces:
2344    /// 1-of-[A, A] and 2-of-[A, A] are the same malformed input.
2345    #[test]
2346    fn test_duplicate_key_multisig_reports_the_duplicate() {
2347        let (pk_02, pk_03) = duplicate_x_only_pair();
2348
2349        let conditions = Conditions {
2350            locktime: None,
2351            pubkeys: Some(vec![pk_03]),
2352            refund_keys: None,
2353            num_sigs: Some(2),
2354            sig_flag: SigFlag::SigInputs,
2355            num_sigs_refund: None,
2356        };
2357
2358        let result: Result<Secret, _> =
2359            SpendingConditions::new_p2pk(pk_02, Some(conditions)).try_into();
2360
2361        assert_duplicate_pubkey(result.expect_err("2-of-2 over one key should be rejected"));
2362    }
2363
2364    /// A threshold above the number of distinct keys is still its own error.
2365    #[test]
2366    fn test_impossible_multisig_is_still_reported() {
2367        let data = SecretKey::generate().public_key();
2368        let other = SecretKey::generate().public_key();
2369
2370        let conditions = Conditions {
2371            locktime: None,
2372            pubkeys: Some(vec![other]),
2373            refund_keys: None,
2374            num_sigs: Some(3),
2375            sig_flag: SigFlag::SigInputs,
2376            num_sigs_refund: None,
2377        };
2378
2379        let result: Result<Secret, _> =
2380            SpendingConditions::new_p2pk(data, Some(conditions)).try_into();
2381        let err = result.expect_err("3-of-2 should be rejected");
2382
2383        assert!(
2384            matches!(
2385                err,
2386                crate::nut10::Error::NUT11(Error::ImpossibleMultisigConfiguration {
2387                    required: 3,
2388                    available: 2,
2389                })
2390            ),
2391            "Expected ImpossibleMultisigConfiguration, got: {err:?}"
2392        );
2393    }
2394
2395    #[test]
2396    fn test_duplicate_refund_key_rejected_at_construction() {
2397        let (pk_02, pk_03) = duplicate_x_only_pair();
2398
2399        let conditions = Conditions {
2400            locktime: Some(unix_time() + 3600),
2401            pubkeys: None,
2402            refund_keys: Some(vec![pk_02, pk_03]),
2403            num_sigs: None,
2404            sig_flag: SigFlag::SigInputs,
2405            num_sigs_refund: None,
2406        };
2407
2408        let result: Result<Secret, _> =
2409            SpendingConditions::new_p2pk(SecretKey::generate().public_key(), Some(conditions))
2410                .try_into();
2411
2412        assert_duplicate_pubkey(result.expect_err("duplicate refund key should be rejected"));
2413    }
2414
2415    /// The primary and refund pathways are checked separately, so a key that
2416    /// can sign before the locktime and reclaim after it stays legal.
2417    #[test]
2418    fn test_key_may_appear_in_both_pathways() {
2419        let pubkey = SecretKey::generate().public_key();
2420
2421        let conditions = Conditions {
2422            locktime: Some(unix_time() + 3600),
2423            pubkeys: None,
2424            refund_keys: Some(vec![pubkey]),
2425            num_sigs: None,
2426            sig_flag: SigFlag::SigInputs,
2427            num_sigs_refund: None,
2428        };
2429
2430        let secret: Result<Secret, _> =
2431            SpendingConditions::new_p2pk(pubkey, Some(conditions)).try_into();
2432
2433        assert!(
2434            secret.is_ok(),
2435            "a key in both pathways is not a duplicate: {secret:?}"
2436        );
2437    }
2438
2439    #[test]
2440    fn test_htlc_duplicate_pubkeys_rejected_at_construction() {
2441        use bitcoin::hashes::{sha256, Hash};
2442
2443        let (pk_02, pk_03) = duplicate_x_only_pair();
2444
2445        let conditions = Conditions {
2446            locktime: None,
2447            pubkeys: Some(vec![pk_02, pk_03]),
2448            refund_keys: None,
2449            num_sigs: None,
2450            sig_flag: SigFlag::SigInputs,
2451            num_sigs_refund: None,
2452        };
2453
2454        let result: Result<Secret, _> = SpendingConditions::HTLCConditions {
2455            data: sha256::Hash::hash(b"preimage"),
2456            conditions: Some(conditions),
2457        }
2458        .try_into();
2459
2460        assert_duplicate_pubkey(result.expect_err("duplicate HTLC key should be rejected"));
2461    }
2462
2463    #[test]
2464    fn test_htlc_duplicate_refund_key_rejected_at_construction() {
2465        use bitcoin::hashes::{sha256, Hash};
2466
2467        let (pk_02, pk_03) = duplicate_x_only_pair();
2468
2469        let conditions = Conditions {
2470            locktime: Some(unix_time() + 3600),
2471            pubkeys: None,
2472            refund_keys: Some(vec![pk_02, pk_03]),
2473            num_sigs: None,
2474            sig_flag: SigFlag::SigInputs,
2475            num_sigs_refund: None,
2476        };
2477
2478        let result: Result<Secret, _> = SpendingConditions::HTLCConditions {
2479            data: sha256::Hash::hash(b"preimage"),
2480            conditions: Some(conditions),
2481        }
2482        .try_into();
2483
2484        assert_duplicate_pubkey(result.expect_err("duplicate refund key should be rejected"));
2485    }
2486
2487    /// `Conditions::new` cannot see the P2PK `data` key, but it still owns the
2488    /// two lists it is given.
2489    #[test]
2490    fn test_conditions_new_rejects_duplicates_in_each_list() {
2491        let (pk_02, pk_03) = duplicate_x_only_pair();
2492
2493        let err = Conditions::new(None, Some(vec![pk_02, pk_03]), None, None, None, None)
2494            .expect_err("duplicate pubkeys should be rejected");
2495        assert_duplicate_pubkey(err);
2496
2497        let err = Conditions::new(
2498            Some(unix_time() + 3600),
2499            None,
2500            Some(vec![pk_02, pk_03]),
2501            None,
2502            None,
2503            None,
2504        )
2505        .expect_err("duplicate refund keys should be rejected");
2506        assert_duplicate_pubkey(err);
2507    }
2508
2509    /// Slot indices exist only for P2BK blinding. A plain lock is bounded by
2510    /// what verification accepts, and verification never counts slots.
2511    #[test]
2512    fn test_plain_p2pk_is_not_slot_capped() {
2513        let data = SecretKey::generate().public_key();
2514        let pubkeys: Vec<PublicKey> = (0..11)
2515            .map(|_| SecretKey::generate().public_key())
2516            .collect();
2517
2518        let conditions = Conditions {
2519            locktime: None,
2520            pubkeys: Some(pubkeys),
2521            refund_keys: None,
2522            num_sigs: Some(1),
2523            sig_flag: SigFlag::SigInputs,
2524            num_sigs_refund: None,
2525        };
2526
2527        let secret: Secret = SpendingConditions::new_p2pk(data, Some(conditions))
2528            .try_into()
2529            .expect("twelve keys are a valid NUT-11 lock");
2530
2531        let nut10: Nut10Secret = secret.try_into().unwrap();
2532        let requirements = get_pubkeys_and_required_sigs(&nut10, unix_time()).unwrap();
2533
2534        assert_eq!(requirements.pubkeys.len(), 12);
2535    }
2536
2537    /// The HTLC `data` is a preimage hash, not a key, so it never occupies a
2538    /// slot and the P2BK ceiling must not reach this path.
2539    #[test]
2540    fn test_plain_htlc_is_not_slot_capped() {
2541        use bitcoin::hashes::{sha256, Hash};
2542
2543        let pubkeys: Vec<PublicKey> = (0..11)
2544            .map(|_| SecretKey::generate().public_key())
2545            .collect();
2546        let refund_key = SecretKey::generate().public_key();
2547
2548        let conditions = Conditions {
2549            locktime: None,
2550            pubkeys: Some(pubkeys.clone()),
2551            refund_keys: None,
2552            num_sigs: Some(1),
2553            sig_flag: SigFlag::SigInputs,
2554            num_sigs_refund: None,
2555        };
2556        let hash = sha256::Hash::hash(b"preimage");
2557
2558        let result: Result<Secret, _> = SpendingConditions::HTLCConditions {
2559            data: hash,
2560            conditions: Some(conditions),
2561        }
2562        .try_into();
2563        assert!(
2564            result.is_ok(),
2565            "eleven pubkeys should build: {:?}",
2566            result.err()
2567        );
2568
2569        let conditions = Conditions {
2570            locktime: Some(unix_time() + 3600),
2571            pubkeys: Some(pubkeys[..10].to_vec()),
2572            refund_keys: Some(vec![refund_key]),
2573            num_sigs: Some(1),
2574            sig_flag: SigFlag::SigInputs,
2575            num_sigs_refund: Some(1),
2576        };
2577
2578        let result: Result<Secret, _> = SpendingConditions::HTLCConditions {
2579            data: hash,
2580            conditions: Some(conditions),
2581        }
2582        .try_into();
2583        assert!(
2584            result.is_ok(),
2585            "ten pubkeys plus a refund key should build: {:?}",
2586            result.err()
2587        );
2588    }
2589
2590    /// Refund keys only become spendable once the locktime passes, so omitting
2591    /// it would lock the funds behind a path that never opens.
2592    #[test]
2593    fn test_conditions_refund_keys_require_locktime() {
2594        let refund_key = PublicKey::from_str(
2595            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2596        )
2597        .unwrap();
2598
2599        let err = Conditions::new(None, None, Some(vec![refund_key]), None, None, None)
2600            .expect_err("refund keys without a locktime should be rejected");
2601
2602        assert!(
2603            matches!(
2604                err,
2605                crate::nut10::Error::NUT11(Error::RefundKeysRequireLocktime)
2606            ),
2607            "Expected RefundKeysRequireLocktime, got: {err:?}"
2608        );
2609    }
2610
2611    #[test]
2612    fn test_conditions_impossible_refund_no_keys() {
2613        // No refund keys, requiring 1 refund sig is impossible
2614        let result = Conditions::new(None, None, None, None, None, Some(1));
2615        assert!(
2616            result.is_err(),
2617            "1-of-0 refund multisig should be impossible"
2618        );
2619        let err = result.unwrap_err();
2620        assert!(
2621            matches!(
2622                err,
2623                crate::nut10::Error::NUT11(Error::ImpossibleRefundMultisigConfiguration {
2624                    required: 1,
2625                    available: 0,
2626                })
2627            ),
2628            "Expected ImpossibleRefundMultisigConfiguration, got: {err:?}"
2629        );
2630    }
2631
2632    #[test]
2633    fn test_conditions_num_sigs_exact_boundary() {
2634        let pubkey1 = PublicKey::from_str(
2635            "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e",
2636        )
2637        .unwrap();
2638        let pubkey2 = PublicKey::from_str(
2639            "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904",
2640        )
2641        .unwrap();
2642
2643        // 2 additional pubkeys + 1 data key = 3, requiring exactly 3 should succeed
2644        let result = Conditions::new(
2645            None,
2646            Some(vec![pubkey1, pubkey2]),
2647            None,
2648            Some(3),
2649            None,
2650            None,
2651        );
2652        assert!(result.is_ok(), "3-of-3 (exact keys count) should be valid");
2653
2654        // Requiring 4 should fail
2655        let result = Conditions::new(
2656            None,
2657            Some(vec![pubkey1, pubkey2]),
2658            None,
2659            Some(4),
2660            None,
2661            None,
2662        );
2663        assert!(result.is_err(), "4-of-3 should be impossible");
2664    }
2665
2666    #[test]
2667    fn test_p2pk_num_sigs_zero_bypasses_signature_requirement() {
2668        // n_sigs=0 makes verify_p2pk() evaluate `valid_sig_count >= required_sigs`
2669        // as `0 >= 0` which is trivially true. An empty P2PKWitness therefore
2670        // passes verification with no cryptographic check at all.
2671        //
2672        // Per bkb's record of NUT-11: n_sigs must be "a positive integer".
2673        // n_sigs=0 is out-of-spec and produces an anyone-can-spend P2PK proof.
2674        //
2675        // FAILS on HEAD (verify_p2pk returns Ok). PASSES once fixed.
2676        let data_pubkey = PublicKey::from_str(
2677            "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
2678        )
2679        .unwrap();
2680
2681        let conditions = Conditions {
2682            locktime: None,
2683            pubkeys: None,
2684            refund_keys: None,
2685            num_sigs: Some(0), // invalid: not a positive integer per NUT-11
2686            sig_flag: SigFlag::SigInputs,
2687            num_sigs_refund: None,
2688        };
2689
2690        let result: Result<Secret, _> =
2691            SpendingConditions::new_p2pk(data_pubkey, Some(conditions)).try_into();
2692
2693        assert!(matches!(
2694            result,
2695            Err(crate::nut10::Error::NUT11(Error::ZeroSignaturesRequired))
2696        ));
2697    }
2698
2699    #[test]
2700    fn test_sig_all_p2pk_nsigs_zero_bypasses_sig_check() {
2701        let data_pubkey = PublicKey::from_str(
2702            "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
2703        )
2704        .unwrap();
2705        let extra_pubkey = PublicKey::from_str(
2706            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
2707        )
2708        .unwrap();
2709
2710        // Note: We bypass Conditions::new() by constructing it directly,
2711        // to test the low-level verify routine. But because we implemented
2712        // ZeroSignaturesRequired inside TryFrom for Conditions too,
2713        // Secret::try_into() will fail on building the conditions!
2714        // To make the test actually exercise verify_sig_all_p2pk on a "bad" Secret,
2715        // we have to assert that either creation fails or verify_sig_all_p2pk fails.
2716        // Given our fix prevents creation entirely, the test should check that.
2717        let nut10_secret = Nut10Secret::new(
2718            crate::nuts::nut10::Kind::P2PK,
2719            crate::nuts::nut10::SecretData::new(
2720                data_pubkey.to_string(),
2721                Some(vec![
2722                    vec!["pubkeys".to_string(), extra_pubkey.to_string()],
2723                    vec!["n_sigs".to_string(), "0".to_string()],
2724                    vec!["sigflag".to_string(), "SIG_ALL".to_string()],
2725                ]),
2726            ),
2727        );
2728        let conditions_res = crate::nuts::nut10::Conditions::try_from(
2729            nut10_secret.secret_data().tags().cloned().unwrap(),
2730        );
2731        assert!(
2732            conditions_res.is_err(),
2733            "Conditions should fail to parse due to n_sigs=0"
2734        );
2735    }
2736
2737    #[test]
2738    fn test_refund_path_num_sigs_refund_zero_bypasses_signature_check() {
2739        // When n_sigs_refund=Some(0) and refund_keys are present,
2740        // get_pubkeys_and_required_sigs sets required_sigs=0 for the RefundPath.
2741        // In verify_p2pk the guard
2742        //   if refund_path.required_sigs == 0 { return Ok(()); }
2743        // fires once locktime has passed, letting anyone spend with an empty
2744        // witness and no valid refund-key signature.
2745        //
2746        // FAILS on HEAD (verify_p2pk returns Ok). PASSES once the fix is applied.
2747
2748        let data_pubkey = PublicKey::from_str(
2749            "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
2750        )
2751        .unwrap();
2752
2753        let refund_pubkey = PublicKey::from_str(
2754            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
2755        )
2756        .unwrap();
2757
2758        let nut10_secret = Nut10Secret::new(
2759            crate::nuts::nut10::Kind::P2PK,
2760            crate::nuts::nut10::SecretData::new(
2761                data_pubkey.to_string(),
2762                Some(vec![
2763                    vec!["locktime".to_string(), "1".to_string()],
2764                    vec!["refund".to_string(), refund_pubkey.to_string()],
2765                    vec!["n_sigs".to_string(), "1".to_string()],
2766                    vec!["n_sigs_refund".to_string(), "0".to_string()],
2767                ]),
2768            ),
2769        );
2770        let conditions_res = crate::nuts::nut10::Conditions::try_from(
2771            nut10_secret.secret_data().tags().cloned().unwrap(),
2772        );
2773        assert!(
2774            conditions_res.is_err(),
2775            "Conditions should fail to parse due to n_sigs=0"
2776        );
2777    }
2778
2779    #[test]
2780    fn test_sig_all_p2pk_nsigs_refund_zero_bypasses_refund_sig_check() {
2781        let data_pubkey = PublicKey::from_str(
2782            "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
2783        )
2784        .unwrap();
2785        let refund_pubkey = PublicKey::from_str(
2786            "02a9acc1e48c25eeeb9289b5031cc57da9fe72f3fe2861d264bdc074209b107ba2",
2787        )
2788        .unwrap();
2789
2790        let nut10_secret = Nut10Secret::new(
2791            crate::nuts::nut10::Kind::P2PK,
2792            crate::nuts::nut10::SecretData::new(
2793                data_pubkey.to_string(),
2794                Some(vec![
2795                    vec!["locktime".to_string(), "1".to_string()],
2796                    vec!["refund".to_string(), refund_pubkey.to_string()],
2797                    vec!["n_sigs".to_string(), "1".to_string()],
2798                    vec!["n_sigs_refund".to_string(), "0".to_string()],
2799                    vec!["sigflag".to_string(), "SIG_ALL".to_string()],
2800                ]),
2801            ),
2802        );
2803        let conditions_res = crate::nuts::nut10::Conditions::try_from(
2804            nut10_secret.secret_data().tags().cloned().unwrap(),
2805        );
2806        assert!(
2807            conditions_res.is_err(),
2808            "Conditions should fail to parse due to n_sigs_refund=0"
2809        );
2810    }
2811}