1use core::convert::TryFrom;
4
5use bitcoin::hashes::hmac::{Hmac, HmacEngine};
6use bitcoin::hashes::sha256::Hash as Sha256;
7use bitcoin::hashes::{Hash, HashEngine};
8use chrono::Utc;
9use serde::{Deserialize, Serialize};
10
11use lightning::util::scid_utils;
12
13use crate::lsps0::ser::{
14 string_amount, string_amount_option, LSPSMessage, RequestId, ResponseError,
15};
16use crate::prelude::{String, Vec};
17use crate::utils;
18
19pub(crate) const LSPS2_GET_INFO_METHOD_NAME: &str = "lsps2.get_info";
20pub(crate) const LSPS2_BUY_METHOD_NAME: &str = "lsps2.buy";
21
22pub(crate) const LSPS2_GET_INFO_REQUEST_UNRECOGNIZED_OR_STALE_TOKEN_ERROR_CODE: i32 = 200;
23
24pub(crate) const LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE: i32 = 201;
25pub(crate) const LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_SMALL_ERROR_CODE: i32 = 202;
26pub(crate) const LSPS2_BUY_REQUEST_PAYMENT_SIZE_TOO_LARGE_ERROR_CODE: i32 = 203;
27
28#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
29pub struct GetInfoRequest {
31 pub token: Option<String>,
33}
34
35pub struct RawOpeningFeeParams {
40 pub min_fee_msat: u64,
42 pub proportional: u32,
44 pub valid_until: chrono::DateTime<Utc>,
46 pub min_lifetime: u32,
48 pub max_client_to_self_delay: u32,
50 pub min_payment_size_msat: u64,
52 pub max_payment_size_msat: u64,
54}
55
56impl RawOpeningFeeParams {
57 pub(crate) fn into_opening_fee_params(self, promise_secret: &[u8; 32]) -> OpeningFeeParams {
58 let mut hmac = HmacEngine::<Sha256>::new(promise_secret);
59 hmac.input(&self.min_fee_msat.to_be_bytes());
60 hmac.input(&self.proportional.to_be_bytes());
61 hmac.input(self.valid_until.to_rfc3339().as_bytes());
62 hmac.input(&self.min_lifetime.to_be_bytes());
63 hmac.input(&self.max_client_to_self_delay.to_be_bytes());
64 hmac.input(&self.min_payment_size_msat.to_be_bytes());
65 hmac.input(&self.max_payment_size_msat.to_be_bytes());
66 let promise_bytes = Hmac::from_engine(hmac).to_byte_array();
67 let promise = utils::hex_str(&promise_bytes[..]);
68 OpeningFeeParams {
69 min_fee_msat: self.min_fee_msat,
70 proportional: self.proportional,
71 valid_until: self.valid_until.clone(),
72 min_lifetime: self.min_lifetime,
73 max_client_to_self_delay: self.max_client_to_self_delay,
74 min_payment_size_msat: self.min_payment_size_msat,
75 max_payment_size_msat: self.max_payment_size_msat,
76 promise,
77 }
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
82pub struct OpeningFeeParams {
88 #[serde(with = "string_amount")]
90 pub min_fee_msat: u64,
91 pub proportional: u32,
93 pub valid_until: chrono::DateTime<Utc>,
95 pub min_lifetime: u32,
97 pub max_client_to_self_delay: u32,
99 #[serde(with = "string_amount")]
101 pub min_payment_size_msat: u64,
102 #[serde(with = "string_amount")]
104 pub max_payment_size_msat: u64,
105 pub promise: String,
107}
108
109#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
111pub struct GetInfoResponse {
112 pub opening_fee_params_menu: Vec<OpeningFeeParams>,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
118pub struct BuyRequest {
119 pub opening_fee_params: OpeningFeeParams,
121 #[serde(default)]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 #[serde(with = "string_amount_option")]
125 pub payment_size_msat: Option<u64>,
126}
127
128#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
130pub struct InterceptScid(String);
131
132impl From<u64> for InterceptScid {
133 fn from(scid: u64) -> Self {
134 let block = scid_utils::block_from_scid(scid);
135 let tx_index = scid_utils::tx_index_from_scid(scid);
136 let vout = scid_utils::vout_from_scid(scid);
137
138 Self(format!("{}x{}x{}", block, tx_index, vout))
139 }
140}
141
142impl InterceptScid {
143 pub fn to_scid(&self) -> Result<u64, ()> {
145 utils::scid_from_human_readable_string(&self.0)
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
153pub struct BuyResponse {
154 pub jit_channel_scid: InterceptScid,
156 pub lsp_cltv_expiry_delta: u32,
158 #[serde(default)]
160 pub client_trusts_lsp: bool,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
164pub enum LSPS2Request {
166 GetInfo(GetInfoRequest),
168 Buy(BuyRequest),
170}
171
172#[derive(Clone, Debug, PartialEq, Eq)]
173pub enum LSPS2Response {
175 GetInfo(GetInfoResponse),
177 GetInfoError(ResponseError),
179 Buy(BuyResponse),
181 BuyError(ResponseError),
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186pub enum LSPS2Message {
188 Request(RequestId, LSPS2Request),
190 Response(RequestId, LSPS2Response),
192}
193
194impl TryFrom<LSPSMessage> for LSPS2Message {
195 type Error = ();
196
197 fn try_from(message: LSPSMessage) -> Result<Self, Self::Error> {
198 if let LSPSMessage::LSPS2(message) = message {
199 return Ok(message);
200 }
201
202 Err(())
203 }
204}
205
206impl From<LSPS2Message> for LSPSMessage {
207 fn from(message: LSPS2Message) -> Self {
208 LSPSMessage::LSPS2(message)
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use crate::alloc::string::ToString;
216 use crate::lsps2::utils::is_valid_opening_fee_params;
217
218 #[test]
219 fn into_opening_fee_params_produces_valid_promise() {
220 let min_fee_msat = 100;
221 let proportional = 21;
222 let valid_until: chrono::DateTime<Utc> =
223 chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap().into();
224 let min_lifetime = 144;
225 let max_client_to_self_delay = 128;
226 let min_payment_size_msat = 1;
227 let max_payment_size_msat = 100_000_000;
228
229 let raw = RawOpeningFeeParams {
230 min_fee_msat,
231 proportional,
232 valid_until: valid_until.clone().into(),
233 min_lifetime,
234 max_client_to_self_delay,
235 min_payment_size_msat,
236 max_payment_size_msat,
237 };
238
239 let promise_secret = [1u8; 32];
240
241 let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
242
243 assert_eq!(opening_fee_params.min_fee_msat, min_fee_msat);
244 assert_eq!(opening_fee_params.proportional, proportional);
245 assert_eq!(opening_fee_params.valid_until, valid_until);
246 assert_eq!(opening_fee_params.min_lifetime, min_lifetime);
247 assert_eq!(opening_fee_params.max_client_to_self_delay, max_client_to_self_delay);
248 assert_eq!(opening_fee_params.min_payment_size_msat, min_payment_size_msat);
249 assert_eq!(opening_fee_params.max_payment_size_msat, max_payment_size_msat);
250
251 assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
252 }
253
254 #[test]
255 fn changing_single_field_produced_invalid_params() {
256 let min_fee_msat = 100;
257 let proportional = 21;
258 let valid_until = chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap();
259 let min_lifetime = 144;
260 let max_client_to_self_delay = 128;
261 let min_payment_size_msat = 1;
262 let max_payment_size_msat = 100_000_000;
263
264 let raw = RawOpeningFeeParams {
265 min_fee_msat,
266 proportional,
267 valid_until: valid_until.into(),
268 min_lifetime,
269 max_client_to_self_delay,
270 min_payment_size_msat,
271 max_payment_size_msat,
272 };
273
274 let promise_secret = [1u8; 32];
275
276 let mut opening_fee_params = raw.into_opening_fee_params(&promise_secret);
277 opening_fee_params.min_fee_msat = min_fee_msat + 1;
278 assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
279 }
280
281 #[test]
282 fn wrong_secret_produced_invalid_params() {
283 let min_fee_msat = 100;
284 let proportional = 21;
285 let valid_until = chrono::DateTime::parse_from_rfc3339("2035-05-20T08:30:45Z").unwrap();
286 let min_lifetime = 144;
287 let max_client_to_self_delay = 128;
288 let min_payment_size_msat = 1;
289 let max_payment_size_msat = 100_000_000;
290
291 let raw = RawOpeningFeeParams {
292 min_fee_msat,
293 proportional,
294 valid_until: valid_until.into(),
295 min_lifetime,
296 max_client_to_self_delay,
297 min_payment_size_msat,
298 max_payment_size_msat,
299 };
300
301 let promise_secret = [1u8; 32];
302 let other_secret = [2u8; 32];
303
304 let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
305 assert!(!is_valid_opening_fee_params(&opening_fee_params, &other_secret));
306 }
307
308 #[test]
309 #[cfg(feature = "std")]
310 fn expired_params_produces_invalid_params() {
312 let min_fee_msat = 100;
313 let proportional = 21;
314 let valid_until = chrono::DateTime::parse_from_rfc3339("2023-05-20T08:30:45Z").unwrap();
315 let min_lifetime = 144;
316 let max_client_to_self_delay = 128;
317 let min_payment_size_msat = 1;
318 let max_payment_size_msat = 100_000_000;
319
320 let raw = RawOpeningFeeParams {
321 min_fee_msat,
322 proportional,
323 valid_until: valid_until.into(),
324 min_lifetime,
325 max_client_to_self_delay,
326 min_payment_size_msat,
327 max_payment_size_msat,
328 };
329
330 let promise_secret = [1u8; 32];
331
332 let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
333 assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
334 }
335
336 #[test]
337 fn buy_request_serialization() {
338 let min_fee_msat = 100;
339 let proportional = 21;
340 let valid_until = chrono::DateTime::parse_from_rfc3339("2023-05-20T08:30:45Z").unwrap();
341 let min_lifetime = 144;
342 let max_client_to_self_delay = 128;
343 let min_payment_size_msat = 1;
344 let max_payment_size_msat = 100_000_000;
345
346 let raw = RawOpeningFeeParams {
347 min_fee_msat,
348 proportional,
349 valid_until: valid_until.into(),
350 min_lifetime,
351 max_client_to_self_delay,
352 min_payment_size_msat,
353 max_payment_size_msat,
354 };
355
356 let promise_secret = [1u8; 32];
357
358 let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
359 let json_str = r#"{"max_client_to_self_delay":128,"max_payment_size_msat":"100000000","min_fee_msat":"100","min_lifetime":144,"min_payment_size_msat":"1","promise":"1134a5c51e3ba2e8f4259610d5e12c1bf4c50ddcd3f8af563e0a00d1fff41dea","proportional":21,"valid_until":"2023-05-20T08:30:45Z"}"#;
360 assert_eq!(json_str, serde_json::json!(opening_fee_params).to_string());
361 assert_eq!(opening_fee_params, serde_json::from_str(json_str).unwrap());
362
363 let payment_size_msat = Some(1234);
364 let buy_request_fixed =
365 BuyRequest { opening_fee_params: opening_fee_params.clone(), payment_size_msat };
366 let json_str = r#"{"opening_fee_params":{"max_client_to_self_delay":128,"max_payment_size_msat":"100000000","min_fee_msat":"100","min_lifetime":144,"min_payment_size_msat":"1","promise":"1134a5c51e3ba2e8f4259610d5e12c1bf4c50ddcd3f8af563e0a00d1fff41dea","proportional":21,"valid_until":"2023-05-20T08:30:45Z"},"payment_size_msat":"1234"}"#;
367 assert_eq!(json_str, serde_json::json!(buy_request_fixed).to_string());
368 assert_eq!(buy_request_fixed, serde_json::from_str(json_str).unwrap());
369
370 let payment_size_msat = None;
371 let buy_request_variable = BuyRequest { opening_fee_params, payment_size_msat };
372
373 let json_str = r#"{"opening_fee_params":{"max_client_to_self_delay":128,"max_payment_size_msat":"100000000","min_fee_msat":"100","min_lifetime":144,"min_payment_size_msat":"1","promise":"1134a5c51e3ba2e8f4259610d5e12c1bf4c50ddcd3f8af563e0a00d1fff41dea","proportional":21,"valid_until":"2023-05-20T08:30:45Z"}}"#;
375 assert_eq!(json_str, serde_json::json!(buy_request_variable).to_string());
376 assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());
377
378 let json_str = r#"{"opening_fee_params":{"max_client_to_self_delay":128,"max_payment_size_msat":"100000000","min_fee_msat":"100","min_lifetime":144,"min_payment_size_msat":"1","promise":"1134a5c51e3ba2e8f4259610d5e12c1bf4c50ddcd3f8af563e0a00d1fff41dea","proportional":21,"valid_until":"2023-05-20T08:30:45Z"},"payment_size_msat":null}"#;
380 assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());
381 }
382
383 #[test]
384 fn parse_spec_test_vectors() {
385 let json_str = r#"{
387 "opening_fee_params_menu": [
388 {
389 "min_fee_msat": "546000",
390 "proportional": 1200,
391 "valid_until": "2023-02-23T08:47:30.511Z",
392 "min_lifetime": 1008,
393 "max_client_to_self_delay": 2016,
394 "min_payment_size_msat": "1000",
395 "max_payment_size_msat": "1000000",
396 "promise": "abcdefghijklmnopqrstuvwxyz"
397 },
398 {
399 "min_fee_msat": "1092000",
400 "proportional": 2400,
401 "valid_until": "2023-02-27T21:23:57.984Z",
402 "min_lifetime": 1008,
403 "max_client_to_self_delay": 2016,
404 "min_payment_size_msat": "1000",
405 "max_payment_size_msat": "1000000",
406 "promise": "abcdefghijklmnopqrstuvwxyz"
407 }
408 ]
409 }"#;
410 let _get_info_response: GetInfoResponse = serde_json::from_str(json_str).unwrap();
411
412 let json_str = r#"{
413 "opening_fee_params": {
414 "min_fee_msat": "546000",
415 "proportional": 1200,
416 "valid_until": "2023-02-23T08:47:30.511Z",
417 "min_lifetime": 1008,
418 "max_client_to_self_delay": 2016,
419 "min_payment_size_msat": "1000",
420 "max_payment_size_msat": "1000000",
421 "promise": "abcdefghijklmnopqrstuvwxyz"
422 },
423 "payment_size_msat": "42000"
424 }"#;
425 let _buy_request: BuyRequest = serde_json::from_str(json_str).unwrap();
426
427 let json_str = r#"{
428 "jit_channel_scid": "29451x4815x1",
429 "lsp_cltv_expiry_delta" : 144,
430 "client_trusts_lsp": false
431 }"#;
432 let _buy_response: BuyResponse = serde_json::from_str(json_str).unwrap();
433 }
434}