Skip to content

Commit 86c6960

Browse files
authored
Merge pull request #4040 from tnull/2025-09-lsps2-have-promise-commit-to-client-pubkey
Commit to client's node id in bLIP-52/LSPS2 promise
2 parents 5b6b691 + e4aece2 commit 86c6960

File tree

4 files changed

+109
-20
lines changed

4 files changed

+109
-20
lines changed

lightning-liquidity/src/lsps2/msgs.rs

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use core::convert::TryFrom;
1717
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
1818
use bitcoin::hashes::sha256::Hash as Sha256;
1919
use bitcoin::hashes::{Hash, HashEngine};
20+
use bitcoin::secp256k1::PublicKey;
21+
2022
use serde::{Deserialize, Serialize};
2123

2224
use lightning::util::scid_utils;
@@ -66,9 +68,10 @@ pub struct LSPS2RawOpeningFeeParams {
6668

6769
impl LSPS2RawOpeningFeeParams {
6870
pub(crate) fn into_opening_fee_params(
69-
self, promise_secret: &[u8; 32],
71+
self, promise_secret: &[u8; 32], counterparty_node_id: &PublicKey,
7072
) -> LSPS2OpeningFeeParams {
7173
let mut hmac = HmacEngine::<Sha256>::new(promise_secret);
74+
hmac.input(&counterparty_node_id.serialize());
7275
hmac.input(&self.min_fee_msat.to_be_bytes());
7376
hmac.input(&self.proportional.to_be_bytes());
7477
hmac.input(self.valid_until.to_rfc3339().as_bytes());
@@ -229,6 +232,8 @@ mod tests {
229232
use crate::alloc::string::ToString;
230233
use crate::lsps2::utils::is_valid_opening_fee_params;
231234

235+
use bitcoin::secp256k1::{Secp256k1, SecretKey};
236+
232237
use core::str::FromStr;
233238

234239
#[test]
@@ -252,8 +257,12 @@ mod tests {
252257
};
253258

254259
let promise_secret = [1u8; 32];
260+
let client_node_id = PublicKey::from_secret_key(
261+
&Secp256k1::new(),
262+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
263+
);
255264

256-
let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
265+
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
257266

258267
assert_eq!(opening_fee_params.min_fee_msat, min_fee_msat);
259268
assert_eq!(opening_fee_params.proportional, proportional);
@@ -263,7 +272,7 @@ mod tests {
263272
assert_eq!(opening_fee_params.min_payment_size_msat, min_payment_size_msat);
264273
assert_eq!(opening_fee_params.max_payment_size_msat, max_payment_size_msat);
265274

266-
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
275+
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret, &client_node_id));
267276
}
268277

269278
#[test]
@@ -287,10 +296,18 @@ mod tests {
287296
};
288297

289298
let promise_secret = [1u8; 32];
299+
let client_node_id = PublicKey::from_secret_key(
300+
&Secp256k1::new(),
301+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
302+
);
290303

291-
let mut opening_fee_params = raw.into_opening_fee_params(&promise_secret);
304+
let mut opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
292305
opening_fee_params.min_fee_msat = min_fee_msat + 1;
293-
assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
306+
assert!(!is_valid_opening_fee_params(
307+
&opening_fee_params,
308+
&promise_secret,
309+
&client_node_id
310+
));
294311
}
295312

296313
#[test]
@@ -316,8 +333,54 @@ mod tests {
316333
let promise_secret = [1u8; 32];
317334
let other_secret = [2u8; 32];
318335

319-
let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
320-
assert!(!is_valid_opening_fee_params(&opening_fee_params, &other_secret));
336+
let client_node_id = PublicKey::from_secret_key(
337+
&Secp256k1::new(),
338+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
339+
);
340+
341+
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
342+
assert!(!is_valid_opening_fee_params(&opening_fee_params, &other_secret, &client_node_id));
343+
}
344+
345+
#[test]
346+
fn client_mismatch_produced_invalid_params() {
347+
let min_fee_msat = 100;
348+
let proportional = 21;
349+
let valid_until = LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap();
350+
let min_lifetime = 144;
351+
let max_client_to_self_delay = 128;
352+
let min_payment_size_msat = 1;
353+
let max_payment_size_msat = 100_000_000;
354+
355+
let raw = LSPS2RawOpeningFeeParams {
356+
min_fee_msat,
357+
proportional,
358+
valid_until,
359+
min_lifetime,
360+
max_client_to_self_delay,
361+
min_payment_size_msat,
362+
max_payment_size_msat,
363+
};
364+
365+
let promise_secret = [1u8; 32];
366+
367+
let client_node_id = PublicKey::from_secret_key(
368+
&Secp256k1::new(),
369+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
370+
);
371+
372+
let other_public_key = PublicKey::from_secret_key(
373+
&Secp256k1::new(),
374+
&SecretKey::from_slice(&[0xcf; 32]).unwrap(),
375+
);
376+
377+
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
378+
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret, &client_node_id));
379+
assert!(!is_valid_opening_fee_params(
380+
&opening_fee_params,
381+
&promise_secret,
382+
&other_public_key
383+
));
321384
}
322385

323386
#[test]
@@ -343,9 +406,17 @@ mod tests {
343406
};
344407

345408
let promise_secret = [1u8; 32];
346-
347-
let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
348-
assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
409+
let client_node_id = PublicKey::from_secret_key(
410+
&Secp256k1::new(),
411+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
412+
);
413+
414+
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
415+
assert!(!is_valid_opening_fee_params(
416+
&opening_fee_params,
417+
&promise_secret,
418+
&client_node_id
419+
));
349420
}
350421

351422
#[test]
@@ -369,29 +440,34 @@ mod tests {
369440
};
370441

371442
let promise_secret = [1u8; 32];
372-
373-
let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
374-
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"}"#;
443+
let client_node_id = PublicKey::from_secret_key(
444+
&Secp256k1::new(),
445+
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
446+
);
447+
448+
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
449+
println!("SERIALIZATION: {}", serde_json::json!(opening_fee_params).to_string());
450+
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":"75eb57db4c37dc092a37f1d2e0026c5ff36a7834a717ea97c41d91a8d5b50ce8","proportional":21,"valid_until":"2023-05-20T08:30:45Z"}"#;
375451
assert_eq!(json_str, serde_json::json!(opening_fee_params).to_string());
376452
assert_eq!(opening_fee_params, serde_json::from_str(json_str).unwrap());
377453

378454
let payment_size_msat = Some(1234);
379455
let buy_request_fixed =
380456
LSPS2BuyRequest { opening_fee_params: opening_fee_params.clone(), payment_size_msat };
381-
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"}"#;
457+
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":"75eb57db4c37dc092a37f1d2e0026c5ff36a7834a717ea97c41d91a8d5b50ce8","proportional":21,"valid_until":"2023-05-20T08:30:45Z"},"payment_size_msat":"1234"}"#;
382458
assert_eq!(json_str, serde_json::json!(buy_request_fixed).to_string());
383459
assert_eq!(buy_request_fixed, serde_json::from_str(json_str).unwrap());
384460

385461
let payment_size_msat = None;
386462
let buy_request_variable = LSPS2BuyRequest { opening_fee_params, payment_size_msat };
387463

388464
// Check we skip serialization if payment_size_msat is None.
389-
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"}}"#;
465+
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":"75eb57db4c37dc092a37f1d2e0026c5ff36a7834a717ea97c41d91a8d5b50ce8","proportional":21,"valid_until":"2023-05-20T08:30:45Z"}}"#;
390466
assert_eq!(json_str, serde_json::json!(buy_request_variable).to_string());
391467
assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());
392468

393469
// Check we still deserialize correctly if payment_size_msat is 'null'.
394-
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}"#;
470+
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":"75eb57db4c37dc092a37f1d2e0026c5ff36a7834a717ea97c41d91a8d5b50ce8","proportional":21,"valid_until":"2023-05-20T08:30:45Z"},"payment_size_msat":null}"#;
395471
assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());
396472
}
397473

lightning-liquidity/src/lsps2/service.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,10 @@ where
630630
opening_fee_params_menu
631631
.into_iter()
632632
.map(|param| {
633-
param.into_opening_fee_params(&self.config.promise_secret)
633+
param.into_opening_fee_params(
634+
&self.config.promise_secret,
635+
counterparty_node_id,
636+
)
634637
})
635638
.collect();
636639
opening_fee_params_menu.sort_by(|a, b| {
@@ -1252,7 +1255,11 @@ where
12521255
}
12531256

12541257
// TODO: if payment_size_msat is specified, make sure our node has sufficient incoming liquidity from public network to receive it.
1255-
if !is_valid_opening_fee_params(&params.opening_fee_params, &self.config.promise_secret) {
1258+
if !is_valid_opening_fee_params(
1259+
&params.opening_fee_params,
1260+
&self.config.promise_secret,
1261+
counterparty_node_id,
1262+
) {
12561263
let response = LSPS2Response::BuyError(LSPSResponseError {
12571264
code: LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE,
12581265
message: "valid_until is already past OR the promise did not match the provided parameters".to_string(),

lightning-liquidity/src/lsps2/utils.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ use crate::utils;
1313
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
1414
use bitcoin::hashes::sha256::Hash as Sha256;
1515
use bitcoin::hashes::{Hash, HashEngine};
16+
use bitcoin::secp256k1::PublicKey;
1617

1718
/// Determines if the given parameters are valid given the secret used to generate the promise.
1819
pub fn is_valid_opening_fee_params(
19-
fee_params: &LSPS2OpeningFeeParams, promise_secret: &[u8; 32],
20+
fee_params: &LSPS2OpeningFeeParams, promise_secret: &[u8; 32], counterparty_node_id: &PublicKey,
2021
) -> bool {
2122
if is_expired_opening_fee_params(fee_params) {
2223
return false;
2324
}
2425
let mut hmac = HmacEngine::<Sha256>::new(promise_secret);
26+
hmac.input(&counterparty_node_id.serialize());
2527
hmac.input(&fee_params.min_fee_msat.to_be_bytes());
2628
hmac.input(&fee_params.proportional.to_be_bytes());
2729
hmac.input(fee_params.valid_until.to_rfc3339().as_bytes());

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,11 @@ fn invoice_generation_flow() {
192192
assert_eq!(request_id, get_info_request_id);
193193
assert_eq!(counterparty_node_id, service_node_id);
194194
let opening_fee_params = opening_fee_params_menu.first().unwrap().clone();
195-
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
195+
assert!(is_valid_opening_fee_params(
196+
&opening_fee_params,
197+
&promise_secret,
198+
&client_node_id
199+
));
196200
opening_fee_params
197201
},
198202
_ => panic!("Unexpected event"),

0 commit comments

Comments
 (0)