From ca4d4c348fefa22779924068c3112d8c5bd62dfd Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 10 Jun 2026 11:05:36 +0200 Subject: [PATCH] Reject oversized splice-out amounts Validate splice-out requests against outbound capacity after converting the requested satoshi amount to millisatoshis with overflow handling. This prevents values above the spendable channel balance from slipping past the guard due to a unit mismatch. Keep splice integration coverage aligned with the corrected capacity semantics by rejecting an amount one satoshi above outbound capacity and deriving the full-cycle splice-out amount from the channel's current spendable capacity. AI-Assisted-By: OpenAI Codex Co-Authored-By: HAL 9000 This finding was discovered by Project Loupe --- src/lib.rs | 4 +++- tests/common/mod.rs | 4 +++- tests/integration_tests_rust.rs | 12 ++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7465dfabf..e4d4ea1c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1700,7 +1700,9 @@ impl Node { if let Some(channel_details) = open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) { - if splice_amount_sats > channel_details.outbound_capacity_msat { + let splice_amount_msat = + splice_amount_sats.checked_mul(1_000).ok_or(Error::ChannelSplicingFailed)?; + if splice_amount_msat > channel_details.outbound_capacity_msat { return Err(Error::ChannelSplicingFailed); } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d7775e67b..adeb327bf 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1364,7 +1364,9 @@ pub(crate) async fn do_channel_full_cycle( println!("\nB splices out to pay A"); let addr_a = node_a.onchain_payment().new_address().unwrap(); - let splice_out_sat = funding_amount_sat / 2; + let available_splice_out_sat = node_b.list_channels()[0].outbound_capacity_msat / 1000; + let splice_out_sat = available_splice_out_sat / 2; + assert!(splice_out_sat > 500_000); node_b.splice_out(&user_channel_id_b, node_a.node_id(), &addr_a, splice_out_sat).unwrap(); expect_splice_negotiated_event!(node_a, node_b.node_id()); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 1ea6c4584..4e901e7e9 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -1036,6 +1036,18 @@ async fn splice_channel() { ); assert_eq!(node_b.list_balances().total_lightning_balance_sats, 0); + let address = node_a.onchain_payment().new_address().unwrap(); + let excessive_splice_out_sats = node_a.list_channels()[0].outbound_capacity_msat / 1000 + 1; + assert_eq!( + node_a.splice_out( + &user_channel_id_a, + node_b.node_id(), + &address, + excessive_splice_out_sats + ), + Err(NodeError::ChannelSplicingFailed), + ); + // Test that splicing and payments fail when there are insufficient funds let address = node_b.onchain_payment().new_address().unwrap(); let amount_msat = 400_000_000;