Skip to content

Commit f04d2ee

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 f04d2ee

File tree

3 files changed

+90
-51
lines changed

3 files changed

+90
-51
lines changed

lightning/src/ln/channel.rs

Lines changed: 78 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,40 @@ where
1072410735
)));
1072510736
}
1072610737

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

1073710767
let splice_funding = FundingScope::for_splice(
1073810768
&self.funding,
1073910769
&self.context,
10740-
our_funding_contribution_satoshis,
10741-
their_funding_contribution_satoshis,
10770+
our_funding_contribution,
10771+
their_funding_contribution,
1074210772
msg.funding_pubkey,
1074310773
)?;
1074410774

@@ -10763,7 +10793,8 @@ where
1076310793
ES::Target: EntropySource,
1076410794
L::Target: Logger,
1076510795
{
10766-
let splice_funding = self.validate_splice_init(msg, our_funding_contribution_satoshis)?;
10796+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10797+
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
1076710798

1076810799
log_info!(
1076910800
logger,
@@ -10777,7 +10808,7 @@ where
1077710808
let prev_funding_input = self.funding.to_splice_funding_input();
1077810809
let funding_negotiation_context = FundingNegotiationContext {
1077910810
is_initiator: false,
10780-
our_funding_contribution_satoshis,
10811+
our_funding_contribution,
1078110812
their_funding_contribution_satoshis: Some(their_funding_contribution_satoshis),
1078210813
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1078310814
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
@@ -10815,7 +10846,7 @@ where
1081510846

1081610847
Ok(msgs::SpliceAck {
1081710848
channel_id: self.context.channel_id,
10818-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10849+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1081910850
funding_pubkey,
1082010851
require_confirmed_inputs: None,
1082110852
})
@@ -10862,15 +10893,23 @@ where
1086210893
},
1086310894
};
1086410895

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

1086910908
let splice_funding = FundingScope::for_splice(
1087010909
&self.funding,
1087110910
&self.context,
10872-
our_funding_contribution_satoshis,
10873-
their_funding_contribution_satoshis,
10911+
our_funding_contribution,
10912+
their_funding_contribution,
1087410913
msg.funding_pubkey,
1087510914
)?;
1087610915

@@ -12468,7 +12507,7 @@ where
1246812507
};
1246912508
let funding_negotiation_context = FundingNegotiationContext {
1247012509
is_initiator: true,
12471-
our_funding_contribution_satoshis: funding_satoshis as i64,
12510+
our_funding_contribution: SignedAmount::from_sat(funding_satoshis as i64),
1247212511
// TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled
1247312512
their_funding_contribution_satoshis: None,
1247412513
funding_tx_locktime,
@@ -12578,10 +12617,11 @@ where
1257812617
L::Target: Logger,
1257912618
{
1258012619
// TODO(dual_funding): Take these as input once supported
12581-
let our_funding_satoshis = 0u64;
12620+
let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64);
1258212621
let our_funding_inputs = Vec::new();
1258312622

12584-
let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
12623+
let channel_value_satoshis =
12624+
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
1258512625
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
1258612626
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
1258712627
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
@@ -12608,9 +12648,7 @@ where
1260812648
current_chain_height,
1260912649
logger,
1261012650
false,
12611-
12612-
our_funding_satoshis,
12613-
12651+
our_funding_contribution_sats,
1261412652
counterparty_pubkeys,
1261512653
channel_type,
1261612654
holder_selected_channel_reserve_satoshis,
@@ -12625,7 +12663,7 @@ where
1262512663

1262612664
let funding_negotiation_context = FundingNegotiationContext {
1262712665
is_initiator: false,
12628-
our_funding_contribution_satoshis: our_funding_satoshis as i64,
12666+
our_funding_contribution,
1262912667
their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64),
1263012668
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1263112669
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
@@ -12649,7 +12687,7 @@ where
1264912687
is_initiator: false,
1265012688
inputs_to_contribute: our_funding_inputs,
1265112689
shared_funding_input: None,
12652-
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_satoshis),
12690+
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
1265312691
outputs_to_contribute: Vec::new(),
1265412692
}
1265512693
).map_err(|err| {
@@ -12730,7 +12768,7 @@ where
1273012768
}),
1273112769
channel_type: Some(self.funding.get_channel_type().clone()),
1273212770
},
12733-
funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis
12771+
funding_satoshis: self.funding_negotiation_context.our_funding_contribution.to_sat()
1273412772
as u64,
1273512773
second_per_commitment_point,
1273612774
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)