Skip to content

Commit 9bd2144

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 9bd2144

File tree

3 files changed

+91
-51
lines changed

3 files changed

+91
-51
lines changed

lightning/src/ln/channel.rs

Lines changed: 79 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,23 @@ 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.abs() <= SignedAmount::MAX_MONEY);
2254+
debug_assert!(their_funding_contribution.abs() <= SignedAmount::MAX_MONEY);
2255+
22532256
let post_channel_value = prev_funding.compute_post_splice_value(
2254-
our_funding_contribution_sats,
2255-
their_funding_contribution_sats,
2257+
our_funding_contribution.to_sat(),
2258+
their_funding_contribution.to_sat(),
22562259
);
22572260

22582261
let post_value_to_self_msat = AddSigned::checked_add_signed(
22592262
prev_funding.value_to_self_msat,
2260-
our_funding_contribution_sats * 1000,
2263+
our_funding_contribution.to_sat() * 1000,
22612264
);
22622265
debug_assert!(post_value_to_self_msat.is_some());
22632266
let post_value_to_self_msat = post_value_to_self_msat.unwrap();
@@ -5964,7 +5967,7 @@ pub(super) struct FundingNegotiationContext {
59645967
/// Whether we initiated the funding negotiation.
59655968
pub is_initiator: bool,
59665969
/// The amount in satoshis we will be contributing to the channel.
5967-
pub our_funding_contribution_satoshis: i64,
5970+
pub our_funding_contribution: SignedAmount,
59685971
/// The amount in satoshis our counterparty will be contributing to the channel.
59695972
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59705973
pub their_funding_contribution_satoshis: Option<i64>,
@@ -6020,7 +6023,7 @@ impl FundingNegotiationContext {
60206023
};
60216024

60226025
// Optionally add change output
6023-
if self.our_funding_contribution_satoshis > 0 {
6026+
if self.our_funding_contribution > SignedAmount::ZERO {
60246027
let change_value_opt = calculate_change_output_value(
60256028
&self,
60266029
self.shared_funding_input.is_some(),
@@ -10628,11 +10631,22 @@ where
1062810631

1062910632
// TODO(splicing): check for quiescence
1063010633

10631-
if our_funding_contribution_satoshis < 0 {
10634+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10635+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10636+
return Err(APIError::APIMisuseError {
10637+
err: format!(
10638+
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10639+
self.context.channel_id(),
10640+
our_funding_contribution,
10641+
),
10642+
});
10643+
}
10644+
10645+
if our_funding_contribution < SignedAmount::ZERO {
1063210646
return Err(APIError::APIMisuseError {
1063310647
err: format!(
1063410648
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10635-
self.context.channel_id(), our_funding_contribution_satoshis,
10649+
self.context.channel_id(), our_funding_contribution,
1063610650
),
1063710651
});
1063810652
}
@@ -10645,7 +10659,7 @@ where
1064510659

1064610660
// Check that inputs are sufficient to cover our contribution.
1064710661
let _fee = check_v2_funding_inputs_sufficient(
10648-
our_funding_contribution_satoshis,
10662+
our_funding_contribution.to_sat(),
1064910663
&our_funding_inputs,
1065010664
true,
1065110665
true,
@@ -10669,7 +10683,7 @@ where
1066910683
let prev_funding_input = self.funding.to_splice_funding_input();
1067010684
let funding_negotiation_context = FundingNegotiationContext {
1067110685
is_initiator: true,
10672-
our_funding_contribution_satoshis,
10686+
our_funding_contribution,
1067310687
their_funding_contribution_satoshis: None,
1067410688
funding_tx_locktime: LockTime::from_consensus(locktime),
1067510689
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
@@ -10690,7 +10704,7 @@ where
1069010704

1069110705
Ok(msgs::SpliceInit {
1069210706
channel_id: self.context.channel_id,
10693-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10707+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1069410708
funding_feerate_per_kw,
1069510709
locktime,
1069610710
funding_pubkey,
@@ -10701,10 +10715,8 @@ where
1070110715
/// Checks during handling splice_init
1070210716
#[cfg(splicing)]
1070310717
pub fn validate_splice_init(
10704-
&self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64,
10718+
&self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount,
1070510719
) -> Result<FundingScope, ChannelError> {
10706-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10707-
1070810720
// TODO(splicing): Add check that we are the quiescence acceptor
1070910721

1071010722
// Check if a splice has been initiated already.
@@ -10724,21 +10736,40 @@ where
1072410736
)));
1072510737
}
1072610738

10727-
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0
10728-
{
10739+
// TODO(splicing): Move this check once user-provided contributions are supported for
10740+
// counterparty-initiated splices.
10741+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10742+
return Err(ChannelError::WarnAndDisconnect(format!(
10743+
"Channel {} cannot be spliced; our contribution exceeds total bitcoin supply: {}",
10744+
self.context.channel_id(),
10745+
our_funding_contribution,
10746+
)));
10747+
}
10748+
10749+
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
10750+
if their_funding_contribution > SignedAmount::MAX_MONEY {
10751+
return Err(ChannelError::WarnAndDisconnect(format!(
10752+
"Channel {} cannot be spliced; their contribution exceeds total bitcoin supply: {}",
10753+
self.context.channel_id(),
10754+
their_funding_contribution,
10755+
)));
10756+
}
10757+
10758+
debug_assert_eq!(our_funding_contribution, SignedAmount::ZERO);
10759+
if their_funding_contribution < SignedAmount::ZERO {
1072910760
return Err(ChannelError::WarnAndDisconnect(format!(
1073010761
"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,
10762+
their_funding_contribution + our_funding_contribution,
10763+
their_funding_contribution,
10764+
our_funding_contribution,
1073410765
)));
1073510766
}
1073610767

1073710768
let splice_funding = FundingScope::for_splice(
1073810769
&self.funding,
1073910770
&self.context,
10740-
our_funding_contribution_satoshis,
10741-
their_funding_contribution_satoshis,
10771+
our_funding_contribution,
10772+
their_funding_contribution,
1074210773
msg.funding_pubkey,
1074310774
)?;
1074410775

@@ -10763,7 +10794,8 @@ where
1076310794
ES::Target: EntropySource,
1076410795
L::Target: Logger,
1076510796
{
10766-
let splice_funding = self.validate_splice_init(msg, our_funding_contribution_satoshis)?;
10797+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10798+
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
1076710799

1076810800
log_info!(
1076910801
logger,
@@ -10777,7 +10809,7 @@ where
1077710809
let prev_funding_input = self.funding.to_splice_funding_input();
1077810810
let funding_negotiation_context = FundingNegotiationContext {
1077910811
is_initiator: false,
10780-
our_funding_contribution_satoshis,
10812+
our_funding_contribution,
1078110813
their_funding_contribution_satoshis: Some(their_funding_contribution_satoshis),
1078210814
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1078310815
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
@@ -10815,7 +10847,7 @@ where
1081510847

1081610848
Ok(msgs::SpliceAck {
1081710849
channel_id: self.context.channel_id,
10818-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10850+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1081910851
funding_pubkey,
1082010852
require_confirmed_inputs: None,
1082110853
})
@@ -10862,15 +10894,23 @@ where
1086210894
},
1086310895
};
1086410896

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

1086910909
let splice_funding = FundingScope::for_splice(
1087010910
&self.funding,
1087110911
&self.context,
10872-
our_funding_contribution_satoshis,
10873-
their_funding_contribution_satoshis,
10912+
our_funding_contribution,
10913+
their_funding_contribution,
1087410914
msg.funding_pubkey,
1087510915
)?;
1087610916

@@ -12468,7 +12508,7 @@ where
1246812508
};
1246912509
let funding_negotiation_context = FundingNegotiationContext {
1247012510
is_initiator: true,
12471-
our_funding_contribution_satoshis: funding_satoshis as i64,
12511+
our_funding_contribution: SignedAmount::from_sat(funding_satoshis as i64),
1247212512
// TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled
1247312513
their_funding_contribution_satoshis: None,
1247412514
funding_tx_locktime,
@@ -12578,10 +12618,11 @@ where
1257812618
L::Target: Logger,
1257912619
{
1258012620
// TODO(dual_funding): Take these as input once supported
12581-
let our_funding_satoshis = 0u64;
12621+
let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64);
1258212622
let our_funding_inputs = Vec::new();
1258312623

12584-
let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
12624+
let channel_value_satoshis =
12625+
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
1258512626
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
1258612627
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
1258712628
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
@@ -12608,9 +12649,7 @@ where
1260812649
current_chain_height,
1260912650
logger,
1261012651
false,
12611-
12612-
our_funding_satoshis,
12613-
12652+
our_funding_contribution_sats,
1261412653
counterparty_pubkeys,
1261512654
channel_type,
1261612655
holder_selected_channel_reserve_satoshis,
@@ -12625,7 +12664,7 @@ where
1262512664

1262612665
let funding_negotiation_context = FundingNegotiationContext {
1262712666
is_initiator: false,
12628-
our_funding_contribution_satoshis: our_funding_satoshis as i64,
12667+
our_funding_contribution,
1262912668
their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64),
1263012669
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1263112670
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
@@ -12649,7 +12688,7 @@ where
1264912688
is_initiator: false,
1265012689
inputs_to_contribute: our_funding_inputs,
1265112690
shared_funding_input: None,
12652-
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_satoshis),
12691+
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
1265312692
outputs_to_contribute: Vec::new(),
1265412693
}
1265512694
).map_err(|err| {
@@ -12730,7 +12769,7 @@ where
1273012769
}),
1273112770
channel_type: Some(self.funding.get_channel_type().clone()),
1273212771
},
12733-
funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis
12772+
funding_satoshis: self.funding_negotiation_context.our_funding_contribution.to_sat()
1273412773
as u64,
1273512774
second_per_commitment_point,
1273612775
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)