1use aes::cipher::block_padding::Pkcs7;
2use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
3use aes::Aes256;
4use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
5use base64::Engine;
6use bitcoin::hashes::sha256::Hash as Sha256;
7use bitcoin::hashes::Hash;
8use bitcoin::key::XOnlyPublicKey;
9use cbc::{Decryptor, Encryptor};
10use serde::{Deserialize, Serialize};
11use std::convert::TryInto;
12use url::Url;
13
14type Aes256CbcEnc = Encryptor<Aes256>;
15type Aes256CbcDec = Decryptor<Aes256>;
16
17use crate::Tag;
18
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct PayResponse {
21 pub callback: String,
24 #[serde(rename = "maxSendable")]
26 pub max_sendable: u64,
27 #[serde(rename = "minSendable")]
30 pub min_sendable: u64,
31 pub tag: Tag,
33 pub metadata: String,
36
37 #[serde(rename = "commentAllowed")]
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub comment_allowed: Option<u32>,
42
43 #[serde(rename = "allowsNostr")]
45 pub allows_nostr: Option<bool>,
46
47 #[serde(rename = "nostrPubkey")]
49 pub nostr_pubkey: Option<XOnlyPublicKey>,
50}
51
52impl PayResponse {
53 pub fn metadata_json(&self) -> serde_json::Value {
54 serde_json::from_str(&self.metadata).unwrap()
55 }
56
57 pub fn metadata_hash(&self) -> [u8; 32] {
58 Sha256::hash(self.metadata.as_bytes()).to_byte_array()
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct LnURLPayInvoice {
64 pub pr: String,
66 pub hodl_invoice: Option<bool>,
68 #[serde(rename = "successAction")]
71 #[serde(skip_serializing_if = "Option::is_none")]
72 success_action: Option<SuccessActionParams>,
73}
74
75impl LnURLPayInvoice {
76 pub fn new(invoice: String) -> Self {
77 Self {
78 pr: invoice,
79 hodl_invoice: None,
80 success_action: None,
81 }
82 }
83
84 pub fn invoice(&self) -> &str {
85 self.pr.as_str()
86 }
87
88 pub fn success_action(&self) -> Option<SuccessAction> {
89 self.success_action.clone().map(SuccessAction::from_params)
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
94pub enum SuccessAction {
95 Message(String),
96 Url { url: Url, description: String },
97 AES(AesParams),
98 Unknown(SuccessActionParams),
99}
100
101impl SuccessAction {
102 pub fn tag(&self) -> &str {
103 match self {
104 SuccessAction::Message(_) => "message",
105 SuccessAction::Url { .. } => "url",
106 SuccessAction::AES(_) => "aes",
107 SuccessAction::Unknown(params) => params.tag.as_str(),
108 }
109 }
110
111 pub fn into_params(self) -> SuccessActionParams {
112 match self {
113 SuccessAction::Message(message) => SuccessActionParams {
114 tag: "message".to_string(),
115 message: Some(message),
116 url: None,
117 description: None,
118 ciphertext: None,
119 iv: None,
120 },
121 SuccessAction::Url { url, description } => SuccessActionParams {
122 tag: "url".to_string(),
123 message: None,
124 url: Some(url),
125 description: Some(description),
126 ciphertext: None,
127 iv: None,
128 },
129 SuccessAction::AES(params) => SuccessActionParams {
130 tag: "aes".to_string(),
131 message: None,
132 url: None,
133 description: Some(params.description),
134 ciphertext: Some(params.ciphertext),
135 iv: Some(params.iv),
136 },
137 SuccessAction::Unknown(params) => params,
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
143pub struct AesParams {
144 pub description: String,
145 pub ciphertext: String,
146 pub iv: String,
147}
148
149impl AesParams {
150 pub fn new(description: String, text: &str, preimage: &[u8; 32]) -> anyhow::Result<AesParams> {
151 let iv = bitcoin::secp256k1::rand::random::<[u8; 16]>();
152 let cipher = Aes256CbcEnc::new(preimage.into(), &iv.into());
153 let encrypted: Vec<u8> = cipher.encrypt_padded_vec_mut::<Pkcs7>(text.as_bytes());
154 let ciphertext = BASE64_STANDARD.encode(encrypted);
155
156 let iv = BASE64_STANDARD.encode(iv);
157 Ok(AesParams {
158 description,
159 ciphertext,
160 iv,
161 })
162 }
163
164 pub fn decrypt(&self, preimage: &[u8; 32]) -> anyhow::Result<String> {
165 let iv = BASE64_STANDARD.decode(&self.iv)?;
167 let ciphertext = BASE64_STANDARD.decode(&self.ciphertext)?;
168
169 if iv.len() != 16 {
171 return Err(anyhow::anyhow!("iv length is not 16"));
172 }
173 let iv: [u8; 16] = iv.try_into().unwrap();
175
176 let cipher = Aes256CbcDec::new(preimage.into(), &iv.into());
178 let decrypted: Vec<u8> = cipher
179 .decrypt_padded_vec_mut::<Pkcs7>(&ciphertext)
180 .map_err(|_| anyhow::anyhow!("decryption failed"))?;
181
182 Ok(String::from_utf8(decrypted)?)
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub struct SuccessActionParams {
188 pub tag: String,
189 pub message: Option<String>,
190 pub url: Option<Url>,
191 pub description: Option<String>,
192 pub ciphertext: Option<String>,
193 pub iv: Option<String>,
194}
195
196impl SuccessAction {
197 pub fn from_params(params: SuccessActionParams) -> Self {
198 match params.tag.as_str() {
199 "message" => {
200 if params.message.is_none() {
201 return SuccessAction::Unknown(params);
202 }
203 SuccessAction::Message(params.message.unwrap())
204 }
205 "url" => {
206 if params.url.is_none() || params.description.is_none() {
207 return SuccessAction::Unknown(params);
208 }
209 SuccessAction::Url {
210 url: params.url.unwrap(),
211 description: params.description.unwrap(),
212 }
213 }
214 "aes" => {
215 if params.description.is_none()
216 || params.ciphertext.is_none()
217 || params.iv.is_none()
218 {
219 return SuccessAction::Unknown(params);
220 }
221
222 SuccessAction::AES(AesParams {
223 description: params.description.unwrap(),
224 ciphertext: params.ciphertext.unwrap(),
225 iv: params.iv.unwrap(),
226 })
227 }
228 _ => SuccessAction::Unknown(params),
229 }
230 }
231}
232
233#[cfg(test)]
234mod test {
235 use super::*;
236
237 #[test]
238 fn test_encrypt_decrypt() {
239 let description = "test_description".to_string();
240 let text = "hello world".to_string();
241 let preimage = [1u8; 32];
242
243 let params = AesParams::new(description.clone(), &text, &preimage).unwrap();
244
245 let decrypted = params.decrypt(&preimage).unwrap();
246 assert_eq!(decrypted, text);
247 }
248}