Skip to content

Commit 1516d84

Browse files
committed
Check splice contributions against SignedAmount::MAX_MONEY
Splice contributions should never exceed the total bitcoin supply. This check prevents a potential overflow when converting the contribution from sats to msats. The commit additionally begins to store the contribution using SignedAmount.
1 parent 6d47f0a commit 1516d84

File tree

3 files changed

+88
-51
lines changed

3 files changed

+88
-51
lines changed

lightning/src/ln/channel.rs

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// licenses.
99

1010
use bitcoin::absolute::LockTime;
11-
use bitcoin::amount::Amount;
11+
use bitcoin::amount::{Amount, SignedAmount};
1212
use bitcoin::consensus::encode;
1313
use bitcoin::constants::ChainHash;
1414
use bitcoin::script::{Builder, Script, ScriptBuf, WScriptHash};
@@ -2244,20 +2244,22 @@ impl FundingScope {
22442244
/// Constructs a `FundingScope` for splicing a channel.
22452245
#[cfg(splicing)]
22462246
fn for_splice<SP: Deref>(
2247-
prev_funding: &Self, context: &ChannelContext<SP>, our_funding_contribution_sats: i64,
2248-
their_funding_contribution_sats: i64, counterparty_funding_pubkey: PublicKey,
2247+
prev_funding: &Self, context: &ChannelContext<SP>, our_funding_contribution: SignedAmount,
2248+
their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey,
22492249
) -> Result<Self, ChannelError>
22502250
where
22512251
SP::Target: SignerProvider,
22522252
{
2253+
debug_assert!(our_funding_contribution <= SignedAmount::MAX_MONEY);
2254+
22532255
let post_channel_value = prev_funding.compute_post_splice_value(
2254-
our_funding_contribution_sats,
2255-
their_funding_contribution_sats,
2256+
our_funding_contribution.to_sat(),
2257+
their_funding_contribution.to_sat(),
22562258
);
22572259

22582260
let post_value_to_self_msat = AddSigned::checked_add_signed(
22592261
prev_funding.value_to_self_msat,
2260-
our_funding_contribution_sats * 1000,
2262+
our_funding_contribution.to_sat() * 1000,
22612263
);
22622264
debug_assert!(post_value_to_self_msat.is_some());
22632265
let post_value_to_self_msat = post_value_to_self_msat.unwrap();
@@ -5964,7 +5966,7 @@ pub(super) struct FundingNegotiationContext {
59645966
/// Whether we initiated the funding negotiation.
59655967
pub is_initiator: bool,
59665968
/// The amount in satoshis we will be contributing to the channel.
5967-
pub our_funding_contribution_satoshis: i64,
5969+
pub our_funding_contribution: SignedAmount,
59685970
/// The amount in satoshis our counterparty will be contributing to the channel.
59695971
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59705972
pub their_funding_contribution_satoshis: Option<i64>,
@@ -6020,7 +6022,7 @@ impl FundingNegotiationContext {
60206022
};
60216023

60226024
// Optionally add change output
6023-
if self.our_funding_contribution_satoshis > 0 {
6025+
if self.our_funding_contribution > SignedAmount::ZERO {
60246026
let change_value_opt = calculate_change_output_value(
60256027
&self,
60266028
self.shared_funding_input.is_some(),
@@ -10628,11 +10630,22 @@ where
1062810630

1062910631
// TODO(splicing): check for quiescence
1063010632

10631-
if our_funding_contribution_satoshis < 0 {
10633+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10634+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10635+
return Err(APIError::APIMisuseError {
10636+
err: format!(
10637+
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10638+
self.context.channel_id(),
10639+
our_funding_contribution,
10640+
),
10641+
});
10642+
}
10643+
10644+
if our_funding_contribution < SignedAmount::ZERO {
1063210645
return Err(APIError::APIMisuseError {
1063310646
err: format!(
1063410647
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10635-
self.context.channel_id(), our_funding_contribution_satoshis,
10648+
self.context.channel_id(), our_funding_contribution,
1063610649
),
1063710650
});
1063810651
}
@@ -10645,7 +10658,7 @@ where
1064510658

1064610659
// Check that inputs are sufficient to cover our contribution.
1064710660
let _fee = check_v2_funding_inputs_sufficient(
10648-
our_funding_contribution_satoshis,
10661+
our_funding_contribution.to_sat(),
1064910662
&our_funding_inputs,
1065010663
true,
1065110664
true,
@@ -10669,7 +10682,7 @@ where
1066910682
let prev_funding_input = self.funding.to_splice_funding_input();
1067010683
let funding_negotiation_context = FundingNegotiationContext {
1067110684
is_initiator: true,
10672-
our_funding_contribution_satoshis,
10685+
our_funding_contribution,
1067310686
their_funding_contribution_satoshis: None,
1067410687
funding_tx_locktime: LockTime::from_consensus(locktime),
1067510688
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
@@ -10690,7 +10703,7 @@ where
1069010703

1069110704
Ok(msgs::SpliceInit {
1069210705
channel_id: self.context.channel_id,
10693-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10706+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1069410707
funding_feerate_per_kw,
1069510708
locktime,
1069610709
funding_pubkey,
@@ -10701,10 +10714,8 @@ where
1070110714
/// Checks during handling splice_init
1070210715
#[cfg(splicing)]
1070310716
pub fn validate_splice_init(
10704-
&self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64,
10717+
&self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount,
1070510718
) -> Result<FundingScope, ChannelError> {
10706-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10707-
1070810719
// TODO(splicing): Add check that we are the quiescence acceptor
1070910720

1071010721
// Check if a splice has been initiated already.
@@ -10724,21 +10735,38 @@ where
1072410735
)));
1072510736
}
1072610737

10727-
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0
10728-
{
10738+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10739+
return Err(ChannelError::WarnAndDisconnect(format!(
10740+
"Channel {} cannot be spliced; our contribution exceeds total bitcoin supply: {}",
10741+
self.context.channel_id(),
10742+
our_funding_contribution,
10743+
)));
10744+
}
10745+
10746+
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
10747+
if their_funding_contribution > SignedAmount::MAX_MONEY {
10748+
return Err(ChannelError::WarnAndDisconnect(format!(
10749+
"Channel {} cannot be spliced; their contribution exceeds total bitcoin supply: {}",
10750+
self.context.channel_id(),
10751+
their_funding_contribution,
10752+
)));
10753+
}
10754+
10755+
debug_assert_eq!(our_funding_contribution, SignedAmount::ZERO);
10756+
if their_funding_contribution < SignedAmount::ZERO {
1072910757
return Err(ChannelError::WarnAndDisconnect(format!(
1073010758
"Splice-out not supported, only splice in, contribution is {} ({} + {})",
10731-
their_funding_contribution_satoshis + our_funding_contribution_satoshis,
10732-
their_funding_contribution_satoshis,
10733-
our_funding_contribution_satoshis,
10759+
their_funding_contribution + our_funding_contribution,
10760+
their_funding_contribution,
10761+
our_funding_contribution,
1073410762
)));
1073510763
}
1073610764

1073710765
let splice_funding = FundingScope::for_splice(
1073810766
&self.funding,
1073910767
&self.context,
10740-
our_funding_contribution_satoshis,
10741-
their_funding_contribution_satoshis,
10768+
our_funding_contribution,
10769+
their_funding_contribution,
1074210770
msg.funding_pubkey,
1074310771
)?;
1074410772

@@ -10763,7 +10791,8 @@ where
1076310791
ES::Target: EntropySource,
1076410792
L::Target: Logger,
1076510793
{
10766-
let splice_funding = self.validate_splice_init(msg, our_funding_contribution_satoshis)?;
10794+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10795+
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
1076710796

1076810797
log_info!(
1076910798
logger,
@@ -10777,7 +10806,7 @@ where
1077710806
let prev_funding_input = self.funding.to_splice_funding_input();
1077810807
let funding_negotiation_context = FundingNegotiationContext {
1077910808
is_initiator: false,
10780-
our_funding_contribution_satoshis,
10809+
our_funding_contribution,
1078110810
their_funding_contribution_satoshis: Some(their_funding_contribution_satoshis),
1078210811
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1078310812
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
@@ -10815,7 +10844,7 @@ where
1081510844

1081610845
Ok(msgs::SpliceAck {
1081710846
channel_id: self.context.channel_id,
10818-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10847+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1081910848
funding_pubkey,
1082010849
require_confirmed_inputs: None,
1082110850
})
@@ -10862,15 +10891,23 @@ where
1086210891
},
1086310892
};
1086410893

10865-
let our_funding_contribution_satoshis =
10866-
funding_negotiation_context.our_funding_contribution_satoshis;
10867-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10894+
let our_funding_contribution = funding_negotiation_context.our_funding_contribution;
10895+
debug_assert!(our_funding_contribution <= SignedAmount::MAX_MONEY);
10896+
10897+
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
10898+
if their_funding_contribution > SignedAmount::MAX_MONEY {
10899+
return Err(ChannelError::Warn(format!(
10900+
"Channel {} cannot be spliced; their contribution exceeds total bitcoin supply: {}",
10901+
self.context.channel_id(),
10902+
their_funding_contribution,
10903+
)));
10904+
}
1086810905

1086910906
let splice_funding = FundingScope::for_splice(
1087010907
&self.funding,
1087110908
&self.context,
10872-
our_funding_contribution_satoshis,
10873-
their_funding_contribution_satoshis,
10909+
our_funding_contribution,
10910+
their_funding_contribution,
1087410911
msg.funding_pubkey,
1087510912
)?;
1087610913

@@ -12468,7 +12505,7 @@ where
1246812505
};
1246912506
let funding_negotiation_context = FundingNegotiationContext {
1247012507
is_initiator: true,
12471-
our_funding_contribution_satoshis: funding_satoshis as i64,
12508+
our_funding_contribution: SignedAmount::from_sat(funding_satoshis as i64),
1247212509
// TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled
1247312510
their_funding_contribution_satoshis: None,
1247412511
funding_tx_locktime,
@@ -12578,10 +12615,11 @@ where
1257812615
L::Target: Logger,
1257912616
{
1258012617
// TODO(dual_funding): Take these as input once supported
12581-
let our_funding_satoshis = 0u64;
12618+
let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64);
1258212619
let our_funding_inputs = Vec::new();
1258312620

12584-
let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
12621+
let channel_value_satoshis =
12622+
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
1258512623
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
1258612624
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
1258712625
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
@@ -12608,9 +12646,7 @@ where
1260812646
current_chain_height,
1260912647
logger,
1261012648
false,
12611-
12612-
our_funding_satoshis,
12613-
12649+
our_funding_contribution_sats,
1261412650
counterparty_pubkeys,
1261512651
channel_type,
1261612652
holder_selected_channel_reserve_satoshis,
@@ -12625,7 +12661,7 @@ where
1262512661

1262612662
let funding_negotiation_context = FundingNegotiationContext {
1262712663
is_initiator: false,
12628-
our_funding_contribution_satoshis: our_funding_satoshis as i64,
12664+
our_funding_contribution,
1262912665
their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64),
1263012666
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1263112667
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
@@ -12649,7 +12685,7 @@ where
1264912685
is_initiator: false,
1265012686
inputs_to_contribute: our_funding_inputs,
1265112687
shared_funding_input: None,
12652-
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_satoshis),
12688+
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
1265312689
outputs_to_contribute: Vec::new(),
1265412690
}
1265512691
).map_err(|err| {
@@ -12730,7 +12766,7 @@ where
1273012766
}),
1273112767
channel_type: Some(self.funding.get_channel_type().clone()),
1273212768
},
12733-
funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis
12769+
funding_satoshis: self.funding_negotiation_context.our_funding_contribution.to_sat()
1273412770
as u64,
1273512771
second_per_commitment_point,
1273612772
require_confirmed_inputs: None,

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use bitcoin::hashes::{Hash, HashEngine, HmacEngine};
3030

3131
use bitcoin::secp256k1::Secp256k1;
3232
use bitcoin::secp256k1::{PublicKey, SecretKey};
33-
use bitcoin::{secp256k1, Sequence};
33+
use bitcoin::{secp256k1, Sequence, SignedAmount};
3434
#[cfg(splicing)]
3535
use bitcoin::{ScriptBuf, TxIn, Weight};
3636

@@ -9401,7 +9401,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
94019401

94029402
// Inbound V2 channels with contributed inputs are not considered unfunded.
94039403
if let Some(unfunded_chan) = chan.as_unfunded_v2() {
9404-
if unfunded_chan.funding_negotiation_context.our_funding_contribution_satoshis > 0 {
9404+
if unfunded_chan.funding_negotiation_context.our_funding_contribution > SignedAmount::ZERO {
94059405
continue;
94069406
}
94079407
}

lightning/src/ln/interactivetxs.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::io_extras::sink;
1111
use crate::prelude::*;
1212

1313
use bitcoin::absolute::LockTime as AbsoluteLockTime;
14-
use bitcoin::amount::Amount;
14+
use bitcoin::amount::{Amount, SignedAmount};
1515
use bitcoin::consensus::Encodable;
1616
use bitcoin::constants::WITNESS_SCALE_FACTOR;
1717
use bitcoin::key::Secp256k1;
@@ -2077,8 +2077,8 @@ pub(super) fn calculate_change_output_value(
20772077
context: &FundingNegotiationContext, is_splice: bool, shared_output_funding_script: &ScriptBuf,
20782078
funding_outputs: &Vec<TxOut>, change_output_dust_limit: u64,
20792079
) -> Result<Option<u64>, AbortReason> {
2080-
assert!(context.our_funding_contribution_satoshis > 0);
2081-
let our_funding_contribution_satoshis = context.our_funding_contribution_satoshis as u64;
2080+
assert!(context.our_funding_contribution > SignedAmount::ZERO);
2081+
let our_funding_contribution_satoshis = context.our_funding_contribution.to_sat() as u64;
20822082

20832083
let mut total_input_satoshis = 0u64;
20842084
let mut our_funding_inputs_weight = 0u64;
@@ -2156,7 +2156,8 @@ mod tests {
21562156
use bitcoin::transaction::Version;
21572157
use bitcoin::{opcodes, WScriptHash, Weight, XOnlyPublicKey};
21582158
use bitcoin::{
2159-
OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, Witness,
2159+
OutPoint, PubkeyHash, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut,
2160+
WPubkeyHash, Witness,
21602161
};
21612162
use core::ops::Deref;
21622163

@@ -3186,7 +3187,7 @@ mod tests {
31863187
// There is leftover for change
31873188
let context = FundingNegotiationContext {
31883189
is_initiator: true,
3189-
our_funding_contribution_satoshis: our_contributed as i64,
3190+
our_funding_contribution: SignedAmount::from_sat(our_contributed as i64),
31903191
their_funding_contribution_satoshis: None,
31913192
funding_tx_locktime: AbsoluteLockTime::ZERO,
31923193
funding_feerate_sat_per_1000_weight,
@@ -3209,7 +3210,7 @@ mod tests {
32093210
// Insufficient inputs, no leftover
32103211
let context = FundingNegotiationContext {
32113212
is_initiator: false,
3212-
our_funding_contribution_satoshis: 130_000,
3213+
our_funding_contribution: SignedAmount::from_sat(130_000),
32133214
..context
32143215
};
32153216
assert_eq!(
@@ -3220,7 +3221,7 @@ mod tests {
32203221
// Very small leftover
32213222
let context = FundingNegotiationContext {
32223223
is_initiator: false,
3223-
our_funding_contribution_satoshis: 118_000,
3224+
our_funding_contribution: SignedAmount::from_sat(118_000),
32243225
..context
32253226
};
32263227
assert_eq!(
@@ -3231,7 +3232,7 @@ mod tests {
32313232
// Small leftover, but not dust
32323233
let context = FundingNegotiationContext {
32333234
is_initiator: false,
3234-
our_funding_contribution_satoshis: 117_992,
3235+
our_funding_contribution: SignedAmount::from_sat(117_992),
32353236
..context
32363237
};
32373238
assert_eq!(
@@ -3242,7 +3243,7 @@ mod tests {
32423243
// Larger fee, smaller change
32433244
let context = FundingNegotiationContext {
32443245
is_initiator: true,
3245-
our_funding_contribution_satoshis: our_contributed as i64,
3246+
our_funding_contribution: SignedAmount::from_sat(our_contributed as i64),
32463247
funding_feerate_sat_per_1000_weight: funding_feerate_sat_per_1000_weight * 3,
32473248
..context
32483249
};

0 commit comments

Comments
 (0)