cashu 0.16.0

Cashu shared types and crypto utilities, used as the foundation for the CDK and their crates
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
//! NUT-10: Spending conditions
//!
//! <https://github.com/cashubtc/nuts/blob/main/10.md>

use std::str::FromStr;

use serde::{Deserialize, Serialize};

use super::nut01::PublicKey;
use crate::{nut11, nut14};

pub mod spending_conditions;
pub use spending_conditions::{Conditions, SpendingConditions};

pub mod secret;
pub use secret::Secret;

pub mod error;
pub use error::Error;

pub mod tag;
pub use tag::{Tag, TagKind};

/// Refund path requirements (available after locktime for HTLC)
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RefundPath {
    /// Public keys that can provide valid signatures for refund
    pub pubkeys: Vec<PublicKey>,
    /// Minimum number of signatures required from the refund pubkeys
    pub required_sigs: u64,
}

/// Spending requirements for P2PK or HTLC verification
///
/// Returned by `get_pubkeys_and_required_sigs` to indicate what conditions
/// must be met to spend a proof.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SpendingRequirements {
    /// Whether a preimage is required (HTLC only, for receiver path)
    pub preimage_needed: bool,
    /// Public keys that can provide valid signatures (receiver path)
    pub pubkeys: Vec<PublicKey>,
    /// Minimum number of signatures required from the pubkeys
    pub required_sigs: u64,
    /// Refund path (available after locktime for HTLC)
    /// Per NUT-14: receiver path is ALWAYS available, refund path is available after locktime
    pub refund_path: Option<RefundPath>,
}

///  NUT10 Secret Kind
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Kind {
    /// NUT-11 P2PK
    P2PK,
    /// NUT-14 HTLC
    HTLC,
}

/// Secret Date
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SecretData {
    /// Unique random string
    nonce: String,
    /// Expresses the spending condition specific to each kind
    data: String,
    /// Additional data committed to and can be used for feature extensions
    #[serde(skip_serializing_if = "Option::is_none")]
    tags: Option<Vec<Vec<String>>>,
}

impl SecretData {
    /// Create new [`SecretData`]
    pub fn new<S, V>(data: S, tags: Option<V>) -> Self
    where
        S: Into<String>,
        V: Into<Vec<Vec<String>>>,
    {
        let nonce = crate::secret::Secret::generate().to_string();

        Self {
            nonce,
            data: data.into(),
            tags: tags.map(Into::into),
        }
    }

    /// Get the nonce
    pub fn nonce(&self) -> &str {
        &self.nonce
    }

    /// Get the data
    pub fn data(&self) -> &str {
        &self.data
    }

    /// Get the tags
    pub fn tags(&self) -> Option<&Vec<Vec<String>>> {
        self.tags.as_ref()
    }
}

/// Get the relevant public keys and required signature count for P2PK or HTLC verification
/// This is for NUT-11(P2PK) and NUT-14(HTLC)
///
/// For P2PK (NUT-11):
/// - Before locktime: only primary pubkeys path available
/// - After locktime with refund keys: refund path available
/// - After locktime without refund keys: anyone can spend
///
/// For HTLC (NUT-14):
/// - Receiver path (preimage + pubkeys): ALWAYS available
/// - Sender/Refund path (refund keys, no preimage): available AFTER locktime
///
/// From NUT-14: "This pathway is ALWAYS available to the receivers, as possession
/// of the preimage confirms performance of the Sender's wishes."
///
/// Returns `SpendingRequirements` containing:
/// - `preimage_needed`: For P2PK, always false. For HTLC, true (receiver path).
/// - `pubkeys`: The public keys for the primary/receiver path
/// - `required_sigs`: The minimum number of signatures required for primary path
/// - `refund_path`: Optional refund path (available after locktime)
pub(crate) fn get_pubkeys_and_required_sigs(
    secret: &Secret,
    current_time: u64,
) -> Result<SpendingRequirements, Error> {
    debug_assert!(
        secret.kind() == Kind::P2PK || secret.kind() == Kind::HTLC,
        "get_pubkeys_and_required_sigs called with invalid kind - this is a bug"
    );

    let conditions: Conditions = secret
        .secret_data()
        .tags()
        .cloned()
        .unwrap_or_default()
        .try_into()?;

    // Check if locktime has passed
    let locktime_passed = conditions
        .locktime
        .map(|locktime| locktime < current_time)
        .unwrap_or(false);

    match secret.kind() {
        Kind::P2PK => {
            // P2PK: never needs preimage
            // Per NUT-11: "Locktime Multisig conditions continue to apply, and the proof
            // can continue to be spent according to Locktime Multisig rules."
            // This means the primary path (data + pubkeys) is ALWAYS available.

            // Build primary pubkeys (data + pubkeys tag)
            let mut primary_keys = vec![];

            // Add the pubkey from secret.data
            let data_pubkey = PublicKey::from_str(secret.secret_data().data())?;
            primary_keys.push(data_pubkey);

            // Add any additional pubkeys from conditions
            if let Some(additional_keys) = &conditions.pubkeys {
                primary_keys.extend(additional_keys.clone());
            }

            let primary_num_sigs_required = conditions.num_sigs.unwrap_or(1);

            // Refund path is available after locktime
            let refund_path = if locktime_passed {
                if let Some(refund_keys) = &conditions.refund_keys {
                    Some(RefundPath {
                        pubkeys: refund_keys.clone(),
                        required_sigs: conditions.num_sigs_refund.unwrap_or(1),
                    })
                } else {
                    // Locktime passed, no refund keys: anyone can spend via refund path
                    Some(RefundPath {
                        pubkeys: vec![],
                        required_sigs: 0,
                    })
                }
            } else {
                None
            };

            Ok(SpendingRequirements {
                preimage_needed: false,
                pubkeys: primary_keys,
                required_sigs: primary_num_sigs_required,
                refund_path,
            })
        }
        Kind::HTLC => {
            // HTLC: receiver path (preimage + pubkeys) is ALWAYS available per NUT-14
            // "This pathway is ALWAYS available to the receivers"
            let pubkeys = conditions.pubkeys.clone().unwrap_or_default();
            let required_sigs = if pubkeys.is_empty() {
                0
            } else {
                conditions.num_sigs.unwrap_or(1)
            };

            // Refund path is available after locktime
            let refund_path = if locktime_passed {
                if let Some(refund_keys) = &conditions.refund_keys {
                    Some(RefundPath {
                        pubkeys: refund_keys.clone(),
                        required_sigs: conditions.num_sigs_refund.unwrap_or(1),
                    })
                } else {
                    // Locktime passed, no refund keys: anyone can spend via refund path
                    Some(RefundPath {
                        pubkeys: vec![],
                        required_sigs: 0,
                    })
                }
            } else {
                None
            };

            Ok(SpendingRequirements {
                preimage_needed: true,
                pubkeys,
                required_sigs,
                refund_path,
            })
        }
    }
}

use super::Proofs;

/// Trait for requests that spend proofs (SwapRequest, MeltRequest)
pub trait SpendingConditionVerification {
    /// Get the input proofs
    fn inputs(&self) -> &Proofs;

    /// Construct the message to sign for SIG_ALL verification
    ///
    /// This concatenates all relevant transaction data that must be signed.
    /// For swap: input secrets + output blinded messages
    /// For melt: input secrets + quote/payment request
    fn sig_all_msg_to_sign(&self) -> String;

    /// Check if at least one proof in the set has SIG_ALL flag set
    ///
    /// SIG_ALL requires all proofs in the transaction to be signed.
    /// If any proof has this flag, we need to verify signatures on all proofs.
    fn has_at_least_one_sig_all(&self) -> Result<bool, Error> {
        for proof in self.inputs() {
            // Try to extract spending conditions from the proof's secret
            if let Ok(spending_conditions) = super::SpendingConditions::try_from(&proof.secret) {
                // Check for SIG_ALL flag in either P2PK or HTLC conditions
                let has_sig_all = match spending_conditions {
                    super::SpendingConditions::P2PKConditions { conditions, .. } => conditions
                        .map(|c| c.sig_flag == super::SigFlag::SigAll)
                        .unwrap_or(false),
                    super::SpendingConditions::HTLCConditions { conditions, .. } => conditions
                        .map(|c| c.sig_flag == super::SigFlag::SigAll)
                        .unwrap_or(false),
                };

                if has_sig_all {
                    return Ok(true);
                }
            } else if proof.witness.is_some() {
                return Err(Error::NUT11(nut11::Error::IncorrectWitnessKind));
            }
        }

        Ok(false)
    }

    /// Verify all inputs meet SIG_ALL requirements per NUT-11
    ///
    /// When any input has SIG_ALL, all inputs must have:
    /// 1. Same kind (P2PK or HTLC)
    /// 2. SIG_ALL flag set
    /// 3. Same Secret.data
    /// 4. Same Secret.tags
    fn verify_all_inputs_match_for_sig_all(&self) -> Result<(), Error> {
        let inputs = self.inputs();

        // Get first input's properties
        let first_input = inputs.first().ok_or(Error::SpendConditionsNotMet)?;
        let first_secret = Secret::try_from(&first_input.secret)?;
        let first_kind = first_secret.kind();
        let first_data = first_secret.secret_data().data();
        let first_tags = first_secret.secret_data().tags();

        // Get first input's conditions to check SIG_ALL flag
        let first_conditions =
            super::Conditions::try_from(first_tags.cloned().unwrap_or_default())?;

        // Verify first input has SIG_ALL (it should, since we only call this function when SIG_ALL is detected)
        if first_conditions.sig_flag != super::SigFlag::SigAll {
            return Err(Error::SpendConditionsNotMet);
        }

        // Verify all remaining inputs match
        for proof in inputs.iter().skip(1) {
            let secret = Secret::try_from(&proof.secret)?;

            // Check kind matches
            if secret.kind() != first_kind {
                return Err(Error::SpendConditionsNotMet);
            }

            // Check data matches
            if secret.secret_data().data() != first_data {
                return Err(Error::SpendConditionsNotMet);
            }

            // Check tags match (this also ensures SIG_ALL flag matches, since sig_flag is part of tags)
            if secret.secret_data().tags() != first_tags {
                return Err(Error::SpendConditionsNotMet);
            }
        }

        Ok(())
    }

    /// Verify spending conditions for this transaction
    ///
    /// This is the main entry point for spending condition verification.
    /// It checks if any input has SIG_ALL and dispatches to the appropriate verification path.
    fn verify_spending_conditions(&self) -> Result<(), Error> {
        // Check if any input has SIG_ALL flag
        if self.has_at_least_one_sig_all()? {
            // at least one input has SIG_ALL
            self.verify_full_sig_all_check()
        } else {
            // none of the inputs are SIG_ALL, so we can simply check
            // each independently and verify any spending conditions
            // that may - or may not - be there.
            self.verify_inputs_individually()
        }
    }

    /// Verify spending conditions when SIG_ALL is present
    ///
    /// When SIG_ALL is set, all proofs in the transaction must be signed together.
    fn verify_full_sig_all_check(&self) -> Result<(), Error> {
        debug_assert!(
            self.has_at_least_one_sig_all()?,
            "verify_full_sig_all_check() called on proofs without SIG_ALL. This shouldn't happen"
        );
        // Verify all inputs meet SIG_ALL requirements per NUT-11:
        // All inputs must have: (1) same kind, (2) SIG_ALL flag, (3) same data, (4) same tags
        self.verify_all_inputs_match_for_sig_all()?;

        // Get the first input to determine the kind
        let first_input = self.inputs().first().ok_or(Error::SpendConditionsNotMet)?;
        let first_secret =
            Secret::try_from(&first_input.secret).map_err(|_| Error::IncorrectSecretKind)?;

        // Dispatch based on secret kind
        match first_secret.kind() {
            Kind::P2PK => {
                nut11::verify_sig_all_p2pk(first_input, self.sig_all_msg_to_sign())?;
            }
            Kind::HTLC => {
                nut14::verify_sig_all_htlc(first_input, self.sig_all_msg_to_sign())?;
            }
        }

        Ok(())
    }

    /// Verify spending conditions for each input individually
    ///
    /// Handles SIG_INPUTS mode, non-NUT-10 secrets, and any other case where inputs
    /// are verified independently rather than as a group.
    /// This function will NOT be called if any input has SIG_ALL.
    fn verify_inputs_individually(&self) -> Result<(), Error> {
        debug_assert!(
            !(self.has_at_least_one_sig_all()?),
            "verify_inputs_individually() called on SIG_ALL. This shouldn't happen"
        );
        for proof in self.inputs() {
            // Check if secret is a nut10 secret with conditions
            if let Ok(secret) = Secret::try_from(&proof.secret) {
                // Verify this function isn't being called with SIG_ALL proofs (development check)
                if let Ok(conditions) = super::Conditions::try_from(
                    secret.secret_data().tags().cloned().unwrap_or_default(),
                ) {
                    debug_assert!(
                        conditions.sig_flag != super::SigFlag::SigAll,
                        "verify_inputs_individually called with SIG_ALL proof - this is a bug"
                    );
                }

                match secret.kind() {
                    Kind::P2PK => {
                        proof.verify_p2pk()?;
                    }
                    Kind::HTLC => {
                        proof.verify_htlc()?;
                    }
                }
            }
            // If not a nut10 secret, skip verification (plain secret)
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::assert_eq;
    use std::str::FromStr;

    use super::*;

    #[test]
    fn test_secret_serialize() {
        let secret_data = SecretData::new(
            "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198".to_string(),
            Some(vec![vec![
                "key".to_string(),
                "value1".to_string(),
                "value2".to_string(),
            ]]),
        );

        let secret = Secret::new(Kind::P2PK, secret_data.clone());

        let secret_str = format!(
            r#"["P2PK",{{"nonce":"{}","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}}]"#,
            secret_data.nonce(),
        );

        assert_eq!(serde_json::to_string(&secret).unwrap(), secret_str);
    }

    #[test]
    fn test_secret_round_trip_serialization() {
        // Create a Secret instance
        let original_secret = Secret::new(
            Kind::P2PK,
            SecretData::new(
                "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198".to_string(),
                None::<Vec<Vec<String>>>,
            ),
        );

        // Serialize the Secret to JSON string
        let serialized = serde_json::to_string(&original_secret).unwrap();

        // Deserialize directly back to Secret using serde
        let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();

        // Verify the direct serde serialization/deserialization round trip works
        assert_eq!(original_secret, deserialized_secret);

        // Also verify that the conversion to crate::secret::Secret works
        let cashu_secret = crate::secret::Secret::from_str(&serialized).unwrap();
        let deserialized_from_cashu: Secret = TryFrom::try_from(&cashu_secret).unwrap();
        assert_eq!(original_secret, deserialized_from_cashu);
    }

    #[test]
    fn test_htlc_secret_round_trip() {
        // The reference BOLT11 invoice is:
        // lnbc100n1p5z3a63pp56854ytysg7e5z9fl3w5mgvrlqjfcytnjv8ff5hm5qt6gl6alxesqdqqcqzzsxqyz5vqsp5p0x0dlhn27s63j4emxnk26p7f94u0lyarnfp5yqmac9gzy4ngdss9qxpqysgqne3v0hnzt2lp0hc69xpzckk0cdcar7glvjhq60lsrfe8gejdm8c564prrnsft6ctxxyrewp4jtezrq3gxxqnfjj0f9tw2qs9y0lslmqpfu7et9

        // Payment hash (typical 32 byte hash in hex format)
        let payment_hash = "5c23fc3aec9d985bd5fc88ca8bceaccc52cf892715dd94b42b84f1b43350751e";

        // Create a Secret instance with HTLC kind
        let secret_data = SecretData::new(payment_hash.to_string(), None::<Vec<Vec<String>>>);

        let original_secret = Secret::new(Kind::HTLC, secret_data.clone());

        // Serialize the Secret to JSON string
        let serialized = serde_json::to_string(&original_secret).unwrap();

        // Validate serialized format
        let expected_json = format!(
            r#"["HTLC",{{"nonce":"{}","data":"{}"}}]"#,
            secret_data.nonce(),
            payment_hash
        );
        assert_eq!(serialized, expected_json);

        // Deserialize directly back to Secret using serde
        let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();

        // Verify the direct serde serialization/deserialization round trip works
        assert_eq!(original_secret, deserialized_secret);
        assert_eq!(deserialized_secret.kind(), Kind::HTLC);
        assert_eq!(deserialized_secret.secret_data().data, payment_hash);
    }
}