lightning_liquidity/lsps2/
msgs.rs

1//! Message, request, and other primitive types used to implement LSPS2.
2
3use 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)]
29/// A request made to an LSP to learn their current channel fees and parameters.
30pub struct GetInfoRequest {
31	/// An optional token to provide to the LSP.
32	pub token: Option<String>,
33}
34
35/// Fees and parameters for a JIT Channel without the promise.
36///
37/// The promise will be calculated automatically for the LSP and this type converted
38/// into an [`OpeningFeeParams`] for transit over the wire.
39pub struct RawOpeningFeeParams {
40	/// The minimum fee required for the channel open.
41	pub min_fee_msat: u64,
42	/// A fee proportional to the size of the initial payment.
43	pub proportional: u32,
44	/// An [`ISO8601`](https://www.iso.org/iso-8601-date-and-time-format.html) formatted date for which these params are valid.
45	pub valid_until: chrono::DateTime<Utc>,
46	/// The number of blocks after confirmation that the LSP promises it will keep the channel alive without closing.
47	pub min_lifetime: u32,
48	/// The maximum number of blocks that the client is allowed to set its `to_self_delay` parameter.
49	pub max_client_to_self_delay: u32,
50	/// The minimum payment size that the LSP will accept when opening a channel.
51	pub min_payment_size_msat: u64,
52	/// The maximum payment size that the LSP will accept when opening a channel.
53	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)]
82/// Fees and parameters for a JIT Channel including the promise.
83///
84/// The promise is an HMAC calculated using a secret known to the LSP and the rest of the fields as input.
85/// It exists so the LSP can verify the authenticity of a client provided OpeningFeeParams by recalculating
86/// the promise using the secret. Once verified they can be confident it was not modified by the client.
87pub struct OpeningFeeParams {
88	/// The minimum fee required for the channel open.
89	#[serde(with = "string_amount")]
90	pub min_fee_msat: u64,
91	/// A fee proportional to the size of the initial payment.
92	pub proportional: u32,
93	/// An [`ISO8601`](https://www.iso.org/iso-8601-date-and-time-format.html) formatted date for which these params are valid.
94	pub valid_until: chrono::DateTime<Utc>,
95	/// The number of blocks after confirmation that the LSP promises it will keep the channel alive without closing.
96	pub min_lifetime: u32,
97	/// The maximum number of blocks that the client is allowed to set its `to_self_delay` parameter.
98	pub max_client_to_self_delay: u32,
99	/// The minimum payment size that the LSP will accept when opening a channel.
100	#[serde(with = "string_amount")]
101	pub min_payment_size_msat: u64,
102	/// The maximum payment size that the LSP will accept when opening a channel.
103	#[serde(with = "string_amount")]
104	pub max_payment_size_msat: u64,
105	/// The HMAC used to verify the authenticity of these parameters.
106	pub promise: String,
107}
108
109/// A response to a [`GetInfoRequest`]
110#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
111pub struct GetInfoResponse {
112	/// A set of opening fee parameters.
113	pub opening_fee_params_menu: Vec<OpeningFeeParams>,
114}
115
116/// A request to buy a JIT channel.
117#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
118pub struct BuyRequest {
119	/// The fee parameters you would like to use.
120	pub opening_fee_params: OpeningFeeParams,
121	/// The size of the initial payment you expect to receive.
122	#[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/// A newtype that holds a `short_channel_id` in human readable format of BBBxTTTx000.
129#[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	/// Try to convert a [`InterceptScid`] into a u64 used by LDK.
144	pub fn to_scid(&self) -> Result<u64, ()> {
145		utils::scid_from_human_readable_string(&self.0)
146	}
147}
148
149/// A response to a [`BuyRequest`].
150///
151/// Includes information needed to construct an invoice.
152#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
153pub struct BuyResponse {
154	/// The intercept short channel id used by LSP to identify need to open channel.
155	pub jit_channel_scid: InterceptScid,
156	/// The locktime expiry delta the lsp requires.
157	pub lsp_cltv_expiry_delta: u32,
158	/// A flag that indicates who is trusting who.
159	#[serde(default)]
160	pub client_trusts_lsp: bool,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
164/// An enum that captures all the valid JSON-RPC requests in the LSPS2 protocol.
165pub enum LSPS2Request {
166	/// A request to learn an LSP's channel fees and parameters.
167	GetInfo(GetInfoRequest),
168	/// A request to buy a JIT channel from an LSP.
169	Buy(BuyRequest),
170}
171
172#[derive(Clone, Debug, PartialEq, Eq)]
173/// An enum that captures all the valid JSON-RPC responses in the LSPS2 protocol.
174pub enum LSPS2Response {
175	/// A successful response to a [`LSPS2Request::GetInfo`] request.
176	GetInfo(GetInfoResponse),
177	/// An error response to a [`LSPS2Request::GetInfo`] request.
178	GetInfoError(ResponseError),
179	/// A successful response to a [`LSPS2Request::Buy`] request.
180	Buy(BuyResponse),
181	/// An error response to a [`LSPS2Request::Buy`] request.
182	BuyError(ResponseError),
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186/// An enum that captures all valid JSON-RPC messages in the LSPS2 protocol.
187pub enum LSPS2Message {
188	/// An LSPS2 JSON-RPC request.
189	Request(RequestId, LSPS2Request),
190	/// An LSPS2 JSON-RPC response.
191	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	// TODO: We need to find a way to check expiry times in no-std builds.
311	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		// Check we skip serialization if payment_size_msat is None.
374		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		// Check we still deserialize correctly if payment_size_msat is 'null'.
379		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		// Here, we simply assert that we're able to parse all examples given in LSPS2.
386		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}