cashu/nuts/nut18/
secret.rs1use serde::{Deserialize, Serialize};
3
4use crate::nuts::nut10::{Error as Nut10Error, Kind, SpendingConditions};
5use crate::nuts::Nut10Secret;
6use crate::SecretData;
7
8#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
10pub struct Nut10SecretRequest {
11 #[serde(rename = "k")]
13 pub kind: Kind,
14 #[serde(rename = "d")]
16 pub data: String,
17 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
19 pub tags: Option<Vec<Vec<String>>>,
20}
21
22impl Nut10SecretRequest {
23 pub fn new<S, V>(kind: Kind, data: S, tags: Option<V>) -> Self
25 where
26 S: Into<String>,
27 V: Into<Vec<Vec<String>>>,
28 {
29 Self {
30 kind,
31 data: data.into(),
32 tags: tags.map(Into::into),
33 }
34 }
35}
36
37impl From<Nut10Secret> for Nut10SecretRequest {
38 fn from(secret: Nut10Secret) -> Self {
39 Self {
40 kind: secret.kind(),
41 data: secret.secret_data().data().to_string(),
42 tags: secret.secret_data().tags().cloned(),
43 }
44 }
45}
46
47impl From<Nut10SecretRequest> for Nut10Secret {
48 fn from(value: Nut10SecretRequest) -> Self {
49 Self::new(value.kind, SecretData::new(value.data, value.tags))
50 }
51}
52
53impl TryFrom<SpendingConditions> for Nut10SecretRequest {
56 type Error = Nut10Error;
57 fn try_from(conditions: SpendingConditions) -> Result<Self, Self::Error> {
58 conditions.validate()?;
59
60 Ok(match conditions {
61 SpendingConditions::P2PKConditions { data, conditions } => {
62 Self::new(Kind::P2PK, data.to_hex(), conditions)
63 }
64 SpendingConditions::HTLCConditions { data, conditions } => {
65 Self::new(Kind::HTLC, data.to_string(), conditions)
66 }
67 })
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_nut10_secret_request_serialization() {
77 let request = Nut10SecretRequest::new(
78 Kind::P2PK,
79 "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
80 Some(vec![vec!["key".to_string(), "value".to_string()]]),
81 );
82
83 let json = serde_json::to_string(&request).unwrap();
84
85 assert!(json.contains(r#""k":"P2PK""#));
87 assert!(json.contains(r#""d":"026562"#));
88 assert!(json.contains(r#""t":[["key","#));
89 }
90
91 #[test]
92 fn test_roundtrip_serialization() {
93 let original = Nut10SecretRequest {
94 kind: Kind::P2PK,
95 data: "test_data".into(),
96 tags: Some(vec![vec!["key".to_string(), "value".to_string()]]),
97 };
98
99 let json = serde_json::to_string(&original).unwrap();
100 let decoded: Nut10SecretRequest = serde_json::from_str(&json).unwrap();
101
102 assert_eq!(original, decoded);
103 }
104
105 #[test]
108 fn test_request_rejects_duplicate_p2pk_key() {
109 use std::str::FromStr;
110
111 use crate::nuts::nut10::Conditions;
112 use crate::nuts::nut11::Error as Nut11Error;
113 use crate::PublicKey;
114
115 let key = PublicKey::from_str(
116 "026562efcfadc8e86d44da6a8adf80633d974302e62c850774db1fb36ff4cc7198",
117 )
118 .unwrap();
119
120 let result = Nut10SecretRequest::try_from(SpendingConditions::P2PKConditions {
121 data: key,
122 conditions: Some(Conditions {
123 pubkeys: Some(vec![key]),
124 ..Default::default()
125 }),
126 });
127
128 assert!(matches!(
129 result,
130 Err(Nut10Error::NUT11(Nut11Error::DuplicatePubkey))
131 ));
132 }
133
134 #[test]
135 fn test_from_nut10_secret() {
136 let secret = Nut10Secret::new(
137 Kind::P2PK,
138 SecretData::new(
139 "test_data",
140 Some(vec![vec!["key".to_string(), "value".to_string()]]),
141 ),
142 );
143
144 let request: Nut10SecretRequest = secret.clone().into();
145
146 assert_eq!(request.kind, secret.kind());
147 assert_eq!(request.data, secret.secret_data().data());
148 assert_eq!(request.tags, secret.secret_data().tags().cloned());
149 }
150
151 #[test]
152 fn test_into_nut10_secret() {
153 let request = Nut10SecretRequest {
154 kind: Kind::HTLC,
155 data: "test_hash".into(),
156 tags: None,
157 };
158
159 let secret: Nut10Secret = request.clone().into();
160
161 assert_eq!(secret.kind(), request.kind);
162 assert_eq!(secret.secret_data().data(), request.data);
163 assert_eq!(secret.secret_data().tags(), request.tags.as_ref());
164 }
165}