Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 92 additions & 16 deletions lightning-liquidity/src/lsps2/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use core::convert::TryFrom;
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::secp256k1::PublicKey;

use serde::{Deserialize, Serialize};

use lightning::util::scid_utils;
Expand Down Expand Up @@ -66,9 +68,10 @@ pub struct LSPS2RawOpeningFeeParams {

impl LSPS2RawOpeningFeeParams {
pub(crate) fn into_opening_fee_params(
self, promise_secret: &[u8; 32],
self, promise_secret: &[u8; 32], counterparty_node_id: &PublicKey,
) -> LSPS2OpeningFeeParams {
let mut hmac = HmacEngine::<Sha256>::new(promise_secret);
hmac.input(&counterparty_node_id.serialize());
hmac.input(&self.min_fee_msat.to_be_bytes());
hmac.input(&self.proportional.to_be_bytes());
hmac.input(self.valid_until.to_rfc3339().as_bytes());
Expand Down Expand Up @@ -229,6 +232,8 @@ mod tests {
use crate::alloc::string::ToString;
use crate::lsps2::utils::is_valid_opening_fee_params;

use bitcoin::secp256k1::{Secp256k1, SecretKey};

use core::str::FromStr;

#[test]
Expand All @@ -252,8 +257,12 @@ mod tests {
};

let promise_secret = [1u8; 32];
let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);

assert_eq!(opening_fee_params.min_fee_msat, min_fee_msat);
assert_eq!(opening_fee_params.proportional, proportional);
Expand All @@ -263,7 +272,7 @@ mod tests {
assert_eq!(opening_fee_params.min_payment_size_msat, min_payment_size_msat);
assert_eq!(opening_fee_params.max_payment_size_msat, max_payment_size_msat);

assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret, &client_node_id));
}

#[test]
Expand All @@ -287,10 +296,18 @@ mod tests {
};

let promise_secret = [1u8; 32];
let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let mut opening_fee_params = raw.into_opening_fee_params(&promise_secret);
let mut opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
opening_fee_params.min_fee_msat = min_fee_msat + 1;
assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
assert!(!is_valid_opening_fee_params(
&opening_fee_params,
&promise_secret,
&client_node_id
));
}

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

let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
assert!(!is_valid_opening_fee_params(&opening_fee_params, &other_secret));
let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
assert!(!is_valid_opening_fee_params(&opening_fee_params, &other_secret, &client_node_id));
}

#[test]
fn client_mismatch_produced_invalid_params() {
let min_fee_msat = 100;
let proportional = 21;
let valid_until = LSPSDateTime::from_str("2035-05-20T08:30:45Z").unwrap();
let min_lifetime = 144;
let max_client_to_self_delay = 128;
let min_payment_size_msat = 1;
let max_payment_size_msat = 100_000_000;

let raw = LSPS2RawOpeningFeeParams {
min_fee_msat,
proportional,
valid_until,
min_lifetime,
max_client_to_self_delay,
min_payment_size_msat,
max_payment_size_msat,
};

let promise_secret = [1u8; 32];

let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let other_public_key = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcf; 32]).unwrap(),
);

let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret, &client_node_id));
assert!(!is_valid_opening_fee_params(
&opening_fee_params,
&promise_secret,
&other_public_key
));
}

#[test]
Expand All @@ -343,9 +406,17 @@ mod tests {
};

let promise_secret = [1u8; 32];

let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
assert!(!is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
assert!(!is_valid_opening_fee_params(
&opening_fee_params,
&promise_secret,
&client_node_id
));
}

#[test]
Expand All @@ -369,29 +440,34 @@ mod tests {
};

let promise_secret = [1u8; 32];

let opening_fee_params = raw.into_opening_fee_params(&promise_secret);
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"}"#;
let client_node_id = PublicKey::from_secret_key(
&Secp256k1::new(),
&SecretKey::from_slice(&[0xcd; 32]).unwrap(),
);

let opening_fee_params = raw.into_opening_fee_params(&promise_secret, &client_node_id);
println!("SERIALIZATION: {}", serde_json::json!(opening_fee_params).to_string());
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"}"#;
assert_eq!(json_str, serde_json::json!(opening_fee_params).to_string());
assert_eq!(opening_fee_params, serde_json::from_str(json_str).unwrap());

let payment_size_msat = Some(1234);
let buy_request_fixed =
LSPS2BuyRequest { opening_fee_params: opening_fee_params.clone(), payment_size_msat };
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"}"#;
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"}"#;
assert_eq!(json_str, serde_json::json!(buy_request_fixed).to_string());
assert_eq!(buy_request_fixed, serde_json::from_str(json_str).unwrap());

let payment_size_msat = None;
let buy_request_variable = LSPS2BuyRequest { opening_fee_params, payment_size_msat };

// Check we skip serialization if payment_size_msat is None.
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"}}"#;
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"}}"#;
assert_eq!(json_str, serde_json::json!(buy_request_variable).to_string());
assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());

// Check we still deserialize correctly if payment_size_msat is 'null'.
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}"#;
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}"#;
assert_eq!(buy_request_variable, serde_json::from_str(json_str).unwrap());
}

Expand Down
11 changes: 9 additions & 2 deletions lightning-liquidity/src/lsps2/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,10 @@ where
opening_fee_params_menu
.into_iter()
.map(|param| {
param.into_opening_fee_params(&self.config.promise_secret)
param.into_opening_fee_params(
&self.config.promise_secret,
counterparty_node_id,
)
})
.collect();
opening_fee_params_menu.sort_by(|a, b| {
Expand Down Expand Up @@ -1252,7 +1255,11 @@ where
}

// TODO: if payment_size_msat is specified, make sure our node has sufficient incoming liquidity from public network to receive it.
if !is_valid_opening_fee_params(&params.opening_fee_params, &self.config.promise_secret) {
if !is_valid_opening_fee_params(
&params.opening_fee_params,
&self.config.promise_secret,
counterparty_node_id,
) {
let response = LSPS2Response::BuyError(LSPSResponseError {
code: LSPS2_BUY_REQUEST_INVALID_OPENING_FEE_PARAMS_ERROR_CODE,
message: "valid_until is already past OR the promise did not match the provided parameters".to_string(),
Expand Down
4 changes: 3 additions & 1 deletion lightning-liquidity/src/lsps2/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ use crate::utils;
use bitcoin::hashes::hmac::{Hmac, HmacEngine};
use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::{Hash, HashEngine};
use bitcoin::secp256k1::PublicKey;

/// Determines if the given parameters are valid given the secret used to generate the promise.
pub fn is_valid_opening_fee_params(
fee_params: &LSPS2OpeningFeeParams, promise_secret: &[u8; 32],
fee_params: &LSPS2OpeningFeeParams, promise_secret: &[u8; 32], counterparty_node_id: &PublicKey,
) -> bool {
if is_expired_opening_fee_params(fee_params) {
return false;
}
let mut hmac = HmacEngine::<Sha256>::new(promise_secret);
hmac.input(&counterparty_node_id.serialize());
hmac.input(&fee_params.min_fee_msat.to_be_bytes());
hmac.input(&fee_params.proportional.to_be_bytes());
hmac.input(fee_params.valid_until.to_rfc3339().as_bytes());
Expand Down
6 changes: 5 additions & 1 deletion lightning-liquidity/tests/lsps2_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ fn invoice_generation_flow() {
assert_eq!(request_id, get_info_request_id);
assert_eq!(counterparty_node_id, service_node_id);
let opening_fee_params = opening_fee_params_menu.first().unwrap().clone();
assert!(is_valid_opening_fee_params(&opening_fee_params, &promise_secret));
assert!(is_valid_opening_fee_params(
&opening_fee_params,
&promise_secret,
&client_node_id
));
opening_fee_params
},
_ => panic!("Unexpected event"),
Expand Down
Loading