cashu/nuts/
nut10.rs

1//! NUT-10: Spending conditions
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/10.md>
4
5use std::fmt;
6use std::str::FromStr;
7
8use serde::de::{self, Deserializer, SeqAccess, Visitor};
9use serde::ser::SerializeTuple;
10use serde::{Deserialize, Serialize, Serializer};
11use thiserror::Error;
12
13/// NUT13 Error
14#[derive(Debug, Error)]
15pub enum Error {
16    /// Secret error
17    #[error(transparent)]
18    Secret(#[from] crate::secret::Error),
19    /// Serde Json error
20    #[error(transparent)]
21    SerdeJsonError(#[from] serde_json::Error),
22}
23
24///  NUT10 Secret Kind
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub enum Kind {
27    /// NUT-11 P2PK
28    P2PK,
29    /// NUT-14 HTLC
30    HTLC,
31}
32
33/// Secret Date
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub struct SecretData {
36    /// Unique random string
37    nonce: String,
38    /// Expresses the spending condition specific to each kind
39    data: String,
40    /// Additional data committed to and can be used for feature extensions
41    #[serde(skip_serializing_if = "Option::is_none")]
42    tags: Option<Vec<Vec<String>>>,
43}
44
45impl SecretData {
46    /// Create new [`SecretData`]
47    pub fn new<S, V>(data: S, tags: Option<V>) -> Self
48    where
49        S: Into<String>,
50        V: Into<Vec<Vec<String>>>,
51    {
52        let nonce = crate::secret::Secret::generate().to_string();
53
54        Self {
55            nonce,
56            data: data.into(),
57            tags: tags.map(|v| v.into()),
58        }
59    }
60
61    /// Get the nonce
62    pub fn nonce(&self) -> &str {
63        &self.nonce
64    }
65
66    /// Get the data
67    pub fn data(&self) -> &str {
68        &self.data
69    }
70
71    /// Get the tags
72    pub fn tags(&self) -> Option<&Vec<Vec<String>>> {
73        self.tags.as_ref()
74    }
75}
76
77/// NUT10 Secret
78#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub struct Secret {
80    ///  Kind of the spending condition
81    kind: Kind,
82    /// Secret Data
83    secret_data: SecretData,
84}
85
86impl Secret {
87    /// Create new [`Secret`]
88    pub fn new<S, V>(kind: Kind, data: S, tags: Option<V>) -> Self
89    where
90        S: Into<String>,
91        V: Into<Vec<Vec<String>>>,
92    {
93        let secret_data = SecretData::new(data, tags);
94        Self { kind, secret_data }
95    }
96
97    /// Get the kind
98    pub fn kind(&self) -> Kind {
99        self.kind
100    }
101
102    /// Get the secret data
103    pub fn secret_data(&self) -> &SecretData {
104        &self.secret_data
105    }
106}
107
108impl Serialize for Secret {
109    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: Serializer,
112    {
113        // Create a tuple representing the struct fields
114        let secret_tuple = (&self.kind, &self.secret_data);
115
116        // Serialize the tuple as a JSON array
117        let mut s = serializer.serialize_tuple(2)?;
118
119        s.serialize_element(&secret_tuple.0)?;
120        s.serialize_element(&secret_tuple.1)?;
121        s.end()
122    }
123}
124
125impl TryFrom<Secret> for crate::secret::Secret {
126    type Error = Error;
127    fn try_from(secret: Secret) -> Result<crate::secret::Secret, Self::Error> {
128        Ok(crate::secret::Secret::from_str(&serde_json::to_string(
129            &secret,
130        )?)?)
131    }
132}
133
134// Custom visitor for deserializing Secret
135struct SecretVisitor;
136
137impl<'de> Visitor<'de> for SecretVisitor {
138    type Value = Secret;
139
140    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
141        formatter.write_str("a tuple with two elements: [Kind, SecretData]")
142    }
143
144    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
145    where
146        A: SeqAccess<'de>,
147    {
148        // Deserialize the kind (first element)
149        let kind = seq
150            .next_element()?
151            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
152
153        // Deserialize the secret_data (second element)
154        let secret_data = seq
155            .next_element()?
156            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
157
158        // Make sure there are no additional elements
159        if seq.next_element::<serde::de::IgnoredAny>()?.is_some() {
160            return Err(de::Error::invalid_length(3, &self));
161        }
162
163        Ok(Secret { kind, secret_data })
164    }
165}
166
167impl<'de> Deserialize<'de> for Secret {
168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        deserializer.deserialize_seq(SecretVisitor)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use std::assert_eq;
179    use std::str::FromStr;
180
181    use super::*;
182
183    #[test]
184    fn test_secret_serialize() {
185        let secret = Secret {
186            kind: Kind::P2PK,
187            secret_data: SecretData {
188                nonce: "5d11913ee0f92fefdc82a6764fd2457a".to_string(),
189                data: "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"
190                    .to_string(),
191                tags: Some(vec![vec![
192                    "key".to_string(),
193                    "value1".to_string(),
194                    "value2".to_string(),
195                ]]),
196            },
197        };
198
199        let secret_str = r#"["P2PK",{"nonce":"5d11913ee0f92fefdc82a6764fd2457a","data":"026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198","tags":[["key","value1","value2"]]}]"#;
200
201        assert_eq!(serde_json::to_string(&secret).unwrap(), secret_str);
202    }
203
204    #[test]
205    fn test_secret_round_trip_serialization() {
206        // Create a Secret instance
207        let original_secret = Secret {
208            kind: Kind::P2PK,
209            secret_data: SecretData {
210                nonce: "5d11913ee0f92fefdc82a6764fd2457a".to_string(),
211                data: "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198"
212                    .to_string(),
213                tags: None,
214            },
215        };
216
217        // Serialize the Secret to JSON string
218        let serialized = serde_json::to_string(&original_secret).unwrap();
219
220        // Deserialize directly back to Secret using serde
221        let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();
222
223        // Verify the direct serde serialization/deserialization round trip works
224        assert_eq!(original_secret, deserialized_secret);
225
226        // Also verify that the conversion to crate::secret::Secret works
227        let cashu_secret = crate::secret::Secret::from_str(&serialized).unwrap();
228        let deserialized_from_cashu: Secret = TryFrom::try_from(&cashu_secret).unwrap();
229        assert_eq!(original_secret, deserialized_from_cashu);
230    }
231
232    #[test]
233    fn test_htlc_secret_round_trip() {
234        // The reference BOLT11 invoice is:
235        // lnbc100n1p5z3a63pp56854ytysg7e5z9fl3w5mgvrlqjfcytnjv8ff5hm5qt6gl6alxesqdqqcqzzsxqyz5vqsp5p0x0dlhn27s63j4emxnk26p7f94u0lyarnfp5yqmac9gzy4ngdss9qxpqysgqne3v0hnzt2lp0hc69xpzckk0cdcar7glvjhq60lsrfe8gejdm8c564prrnsft6ctxxyrewp4jtezrq3gxxqnfjj0f9tw2qs9y0lslmqpfu7et9
236
237        // Payment hash (typical 32 byte hash in hex format)
238        let payment_hash = "5c23fc3aec9d985bd5fc88ca8bceaccc52cf892715dd94b42b84f1b43350751e";
239
240        // Create a Secret instance with HTLC kind
241        let original_secret = Secret {
242            kind: Kind::HTLC,
243            secret_data: SecretData {
244                nonce: "7a9128b3f9612549f9278958337a5d7f".to_string(),
245                data: payment_hash.to_string(),
246                tags: None,
247            },
248        };
249
250        // Serialize the Secret to JSON string
251        let serialized = serde_json::to_string(&original_secret).unwrap();
252
253        // Validate serialized format
254        let expected_json = format!(
255            r#"["HTLC",{{"nonce":"7a9128b3f9612549f9278958337a5d7f","data":"{}"}}]"#,
256            payment_hash
257        );
258        assert_eq!(serialized, expected_json);
259
260        // Deserialize directly back to Secret using serde
261        let deserialized_secret: Secret = serde_json::from_str(&serialized).unwrap();
262
263        // Verify the direct serde serialization/deserialization round trip works
264        assert_eq!(original_secret, deserialized_secret);
265        assert_eq!(deserialized_secret.kind, Kind::HTLC);
266        assert_eq!(deserialized_secret.secret_data.data, payment_hash);
267    }
268}