cashu/nuts/nut14/
mod.rs

1//! NUT-14: Hashed Time Lock Contacts (HTLC)
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/14.md>
4
5use std::str::FromStr;
6
7use bitcoin::hashes::sha256::Hash as Sha256Hash;
8use bitcoin::hashes::Hash;
9use bitcoin::secp256k1::schnorr::Signature;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use super::nut00::Witness;
14use super::nut10::Secret;
15use super::nut11::valid_signatures;
16use super::{Conditions, Proof};
17use crate::ensure_cdk;
18use crate::util::unix_time;
19
20pub mod serde_htlc_witness;
21
22/// NUT14 Errors
23#[derive(Debug, Error)]
24pub enum Error {
25    /// Incorrect secret kind
26    #[error("Secret is not a HTLC secret")]
27    IncorrectSecretKind,
28    /// HTLC locktime has already passed
29    #[error("Locktime in past")]
30    LocktimeInPast,
31    /// Hash Required
32    #[error("Hash required")]
33    HashRequired,
34    /// Hash is not valid
35    #[error("Hash is not valid")]
36    InvalidHash,
37    /// Preimage does not match
38    #[error("Preimage does not match")]
39    Preimage,
40    /// Witness Signatures not provided
41    #[error("Witness did not provide signatures")]
42    SignaturesNotProvided,
43    /// Secp256k1 error
44    #[error(transparent)]
45    Secp256k1(#[from] bitcoin::secp256k1::Error),
46    /// NUT11 Error
47    #[error(transparent)]
48    NUT11(#[from] super::nut11::Error),
49    #[error(transparent)]
50    /// Serde Error
51    Serde(#[from] serde_json::Error),
52}
53
54/// HTLC Witness
55#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
56#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
57pub struct HTLCWitness {
58    /// Primage
59    pub preimage: String,
60    /// Signatures
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub signatures: Option<Vec<String>>,
63}
64
65impl Proof {
66    /// Verify HTLC
67    pub fn verify_htlc(&self) -> Result<(), Error> {
68        let secret: Secret = self.secret.clone().try_into()?;
69        let conditions: Option<Conditions> =
70            secret.secret_data.tags.and_then(|c| c.try_into().ok());
71
72        let htlc_witness = match &self.witness {
73            Some(Witness::HTLCWitness(witness)) => witness,
74            _ => return Err(Error::IncorrectSecretKind),
75        };
76
77        if let Some(conditions) = conditions {
78            // Check locktime
79            if let Some(locktime) = conditions.locktime {
80                // If locktime is in passed and no refund keys provided anyone can spend
81                if locktime.lt(&unix_time()) && conditions.refund_keys.is_none() {
82                    return Ok(());
83                }
84
85                // If refund keys are provided verify p2pk signatures
86                if let (Some(refund_key), Some(signatures)) =
87                    (conditions.refund_keys, &self.witness)
88                {
89                    let signatures = signatures
90                        .signatures()
91                        .ok_or(Error::SignaturesNotProvided)?
92                        .iter()
93                        .map(|s| Signature::from_str(s))
94                        .collect::<Result<Vec<Signature>, _>>()?;
95
96                    // If secret includes refund keys check that there is a valid signature
97                    if valid_signatures(self.secret.as_bytes(), &refund_key, &signatures)?.ge(&1) {
98                        return Ok(());
99                    }
100                }
101            }
102            // If pubkeys are present check there is a valid signature
103            if let Some(pubkey) = conditions.pubkeys {
104                let req_sigs = conditions.num_sigs.unwrap_or(1);
105
106                let signatures = htlc_witness
107                    .signatures
108                    .as_ref()
109                    .ok_or(Error::SignaturesNotProvided)?;
110
111                let signatures = signatures
112                    .iter()
113                    .map(|s| Signature::from_str(s))
114                    .collect::<Result<Vec<Signature>, _>>()?;
115
116                let valid_sigs = valid_signatures(self.secret.as_bytes(), &pubkey, &signatures)?;
117                ensure_cdk!(valid_sigs >= req_sigs, Error::IncorrectSecretKind);
118            }
119        }
120
121        if secret.kind.ne(&super::Kind::HTLC) {
122            return Err(Error::IncorrectSecretKind);
123        }
124
125        let hash_lock =
126            Sha256Hash::from_str(&secret.secret_data.data).map_err(|_| Error::InvalidHash)?;
127
128        let preimage_hash = Sha256Hash::hash(htlc_witness.preimage.as_bytes());
129
130        if hash_lock.ne(&preimage_hash) {
131            return Err(Error::Preimage);
132        }
133
134        Ok(())
135    }
136
137    /// Add Preimage
138    #[inline]
139    pub fn add_preimage(&mut self, preimage: String) {
140        self.witness = Some(Witness::HTLCWitness(HTLCWitness {
141            preimage,
142            signatures: None,
143        }))
144    }
145}