Skip to content

Commit deec54c

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 41cbc1f commit deec54c

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};
@@ -2254,20 +2254,22 @@ impl FundingScope {
22542254
/// Constructs a `FundingScope` for splicing a channel.
22552255
#[cfg(splicing)]
22562256
fn for_splice<SP: Deref>(
2257-
prev_funding: &Self, context: &ChannelContext<SP>, our_funding_contribution_sats: i64,
2258-
their_funding_contribution_sats: i64, counterparty_funding_pubkey: PublicKey,
2257+
prev_funding: &Self, context: &ChannelContext<SP>, our_funding_contribution: SignedAmount,
2258+
their_funding_contribution: SignedAmount, counterparty_funding_pubkey: PublicKey,
22592259
) -> Result<Self, ChannelError>
22602260
where
22612261
SP::Target: SignerProvider,
22622262
{
2263+
debug_assert!(our_funding_contribution <= SignedAmount::MAX_MONEY);
2264+
22632265
let post_channel_value = prev_funding.compute_post_splice_value(
2264-
our_funding_contribution_sats,
2265-
their_funding_contribution_sats,
2266+
our_funding_contribution.to_sat(),
2267+
their_funding_contribution.to_sat(),
22662268
);
22672269

22682270
let post_value_to_self_msat = AddSigned::checked_add_signed(
22692271
prev_funding.value_to_self_msat,
2270-
our_funding_contribution_sats * 1000,
2272+
our_funding_contribution.to_sat() * 1000,
22712273
);
22722274
debug_assert!(post_value_to_self_msat.is_some());
22732275
let post_value_to_self_msat = post_value_to_self_msat.unwrap();
@@ -5965,7 +5967,7 @@ pub(super) struct FundingNegotiationContext {
59655967
/// Whether we initiated the funding negotiation.
59665968
pub is_initiator: bool,
59675969
/// The amount in satoshis we will be contributing to the channel.
5968-
pub our_funding_contribution_satoshis: i64,
5970+
pub our_funding_contribution: SignedAmount,
59695971
/// The amount in satoshis our counterparty will be contributing to the channel.
59705972
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59715973
pub their_funding_contribution_satoshis: Option<i64>,
@@ -6021,7 +6023,7 @@ impl FundingNegotiationContext {
60216023
};
60226024

60236025
// Optionally add change output
6024-
if self.our_funding_contribution_satoshis > 0 {
6026+
if self.our_funding_contribution > SignedAmount::ZERO {
60256027
let change_value_opt = calculate_change_output_value(
60266028
&self,
60276029
self.shared_funding_input.is_some(),
@@ -10636,11 +10638,22 @@ where
1063610638

1063710639
// TODO(splicing): check for quiescence
1063810640

10639-
if our_funding_contribution_satoshis < 0 {
10641+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10642+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10643+
return Err(APIError::APIMisuseError {
10644+
err: format!(
10645+
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10646+
self.context.channel_id(),
10647+
our_funding_contribution,
10648+
),
10649+
});
10650+
}
10651+
10652+
if our_funding_contribution < SignedAmount::ZERO {
1064010653
return Err(APIError::APIMisuseError {
1064110654
err: format!(
1064210655
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10643-
self.context.channel_id(), our_funding_contribution_satoshis,
10656+
self.context.channel_id(), our_funding_contribution,
1064410657
),
1064510658
});
1064610659
}
@@ -10653,7 +10666,7 @@ where
1065310666

1065410667
// Check that inputs are sufficient to cover our contribution.
1065510668
let _fee = check_v2_funding_inputs_sufficient(
10656-
our_funding_contribution_satoshis,
10669+
our_funding_contribution.to_sat(),
1065710670
&our_funding_inputs,
1065810671
true,
1065910672
true,
@@ -10677,7 +10690,7 @@ where
1067710690
let prev_funding_input = self.funding.to_splice_funding_input();
1067810691
let funding_negotiation_context = FundingNegotiationContext {
1067910692
is_initiator: true,
10680-
our_funding_contribution_satoshis,
10693+
our_funding_contribution,
1068110694
their_funding_contribution_satoshis: None,
1068210695
funding_tx_locktime: LockTime::from_consensus(locktime),
1068310696
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
@@ -10698,7 +10711,7 @@ where
1069810711

1069910712
Ok(msgs::SpliceInit {
1070010713
channel_id: self.context.channel_id,
10701-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10714+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1070210715
funding_feerate_per_kw,
1070310716
locktime,
1070410717
funding_pubkey,
@@ -10709,10 +10722,8 @@ where
1070910722
/// Checks during handling splice_init
1071010723
#[cfg(splicing)]
1071110724
pub fn validate_splice_init(
10712-
&self, msg: &msgs::SpliceInit, our_funding_contribution_satoshis: i64,
10725+
&self, msg: &msgs::SpliceInit, our_funding_contribution: SignedAmount,
1071310726
) -> Result<FundingScope, ChannelError> {
10714-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10715-
1071610727
// TODO(splicing): Add check that we are the quiescence acceptor
1071710728

1071810729
// Check if a splice has been initiated already.
@@ -10732,21 +10743,38 @@ where
1073210743
)));
1073310744
}
1073410745

10735-
if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0
10736-
{
10746+
if our_funding_contribution > SignedAmount::MAX_MONEY {
10747+
return Err(ChannelError::WarnAndDisconnect(format!(
10748+
"Channel {} cannot be spliced; our contribution exceeds total bitcoin supply: {}",
10749+
self.context.channel_id(),
10750+
our_funding_contribution,
10751+
)));
10752+
}
10753+
10754+
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
10755+
if their_funding_contribution > SignedAmount::MAX_MONEY {
10756+
return Err(ChannelError::WarnAndDisconnect(format!(
10757+
"Channel {} cannot be spliced; their contribution exceeds total bitcoin supply: {}",
10758+
self.context.channel_id(),
10759+
their_funding_contribution,
10760+
)));
10761+
}
10762+
10763+
debug_assert_eq!(our_funding_contribution, SignedAmount::ZERO);
10764+
if their_funding_contribution < SignedAmount::ZERO {
1073710765
return Err(ChannelError::WarnAndDisconnect(format!(
1073810766
"Splice-out not supported, only splice in, contribution is {} ({} + {})",
10739-
their_funding_contribution_satoshis + our_funding_contribution_satoshis,
10740-
their_funding_contribution_satoshis,
10741-
our_funding_contribution_satoshis,
10767+
their_funding_contribution + our_funding_contribution,
10768+
their_funding_contribution,
10769+
our_funding_contribution,
1074210770
)));
1074310771
}
1074410772

1074510773
let splice_funding = FundingScope::for_splice(
1074610774
&self.funding,
1074710775
&self.context,
10748-
our_funding_contribution_satoshis,
10749-
their_funding_contribution_satoshis,
10776+
our_funding_contribution,
10777+
their_funding_contribution,
1075010778
msg.funding_pubkey,
1075110779
)?;
1075210780

@@ -10771,7 +10799,8 @@ where
1077110799
ES::Target: EntropySource,
1077210800
L::Target: Logger,
1077310801
{
10774-
let splice_funding = self.validate_splice_init(msg, our_funding_contribution_satoshis)?;
10802+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
10803+
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;
1077510804

1077610805
log_info!(
1077710806
logger,
@@ -10785,7 +10814,7 @@ where
1078510814
let prev_funding_input = self.funding.to_splice_funding_input();
1078610815
let funding_negotiation_context = FundingNegotiationContext {
1078710816
is_initiator: false,
10788-
our_funding_contribution_satoshis,
10817+
our_funding_contribution,
1078910818
their_funding_contribution_satoshis: Some(their_funding_contribution_satoshis),
1079010819
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1079110820
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
@@ -10823,7 +10852,7 @@ where
1082310852

1082410853
Ok(msgs::SpliceAck {
1082510854
channel_id: self.context.channel_id,
10826-
funding_contribution_satoshis: our_funding_contribution_satoshis,
10855+
funding_contribution_satoshis: our_funding_contribution.to_sat(),
1082710856
funding_pubkey,
1082810857
require_confirmed_inputs: None,
1082910858
})
@@ -10870,15 +10899,23 @@ where
1087010899
},
1087110900
};
1087210901

10873-
let our_funding_contribution_satoshis =
10874-
funding_negotiation_context.our_funding_contribution_satoshis;
10875-
let their_funding_contribution_satoshis = msg.funding_contribution_satoshis;
10902+
let our_funding_contribution = funding_negotiation_context.our_funding_contribution;
10903+
debug_assert!(our_funding_contribution <= SignedAmount::MAX_MONEY);
10904+
10905+
let their_funding_contribution = SignedAmount::from_sat(msg.funding_contribution_satoshis);
10906+
if their_funding_contribution > SignedAmount::MAX_MONEY {
10907+
return Err(ChannelError::Warn(format!(
10908+
"Channel {} cannot be spliced; their contribution exceeds total bitcoin supply: {}",
10909+
self.context.channel_id(),
10910+
their_funding_contribution,
10911+
)));
10912+
}
1087610913

1087710914
let splice_funding = FundingScope::for_splice(
1087810915
&self.funding,
1087910916
&self.context,
10880-
our_funding_contribution_satoshis,
10881-
their_funding_contribution_satoshis,
10917+
our_funding_contribution,
10918+
their_funding_contribution,
1088210919
msg.funding_pubkey,
1088310920
)?;
1088410921

@@ -12476,7 +12513,7 @@ where
1247612513
};
1247712514
let funding_negotiation_context = FundingNegotiationContext {
1247812515
is_initiator: true,
12479-
our_funding_contribution_satoshis: funding_satoshis as i64,
12516+
our_funding_contribution: SignedAmount::from_sat(funding_satoshis as i64),
1248012517
// TODO(dual_funding) TODO(splicing) Include counterparty contribution, once that's enabled
1248112518
their_funding_contribution_satoshis: None,
1248212519
funding_tx_locktime,
@@ -12586,10 +12623,11 @@ where
1258612623
L::Target: Logger,
1258712624
{
1258812625
// TODO(dual_funding): Take these as input once supported
12589-
let our_funding_satoshis = 0u64;
12626+
let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64);
1259012627
let our_funding_inputs = Vec::new();
1259112628

12592-
let channel_value_satoshis = our_funding_satoshis.saturating_add(msg.common_fields.funding_satoshis);
12629+
let channel_value_satoshis =
12630+
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
1259312631
let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
1259412632
channel_value_satoshis, msg.common_fields.dust_limit_satoshis);
1259512633
let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
@@ -12616,9 +12654,7 @@ where
1261612654
current_chain_height,
1261712655
logger,
1261812656
false,
12619-
12620-
our_funding_satoshis,
12621-
12657+
our_funding_contribution_sats,
1262212658
counterparty_pubkeys,
1262312659
channel_type,
1262412660
holder_selected_channel_reserve_satoshis,
@@ -12633,7 +12669,7 @@ where
1263312669

1263412670
let funding_negotiation_context = FundingNegotiationContext {
1263512671
is_initiator: false,
12636-
our_funding_contribution_satoshis: our_funding_satoshis as i64,
12672+
our_funding_contribution,
1263712673
their_funding_contribution_satoshis: Some(msg.common_fields.funding_satoshis as i64),
1263812674
funding_tx_locktime: LockTime::from_consensus(msg.locktime),
1263912675
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
@@ -12657,7 +12693,7 @@ where
1265712693
is_initiator: false,
1265812694
inputs_to_contribute: our_funding_inputs,
1265912695
shared_funding_input: None,
12660-
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_satoshis),
12696+
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
1266112697
outputs_to_contribute: Vec::new(),
1266212698
}
1266312699
).map_err(|err| {
@@ -12738,7 +12774,7 @@ where
1273812774
}),
1273912775
channel_type: Some(self.funding.get_channel_type().clone()),
1274012776
},
12741-
funding_satoshis: self.funding_negotiation_context.our_funding_contribution_satoshis
12777+
funding_satoshis: self.funding_negotiation_context.our_funding_contribution.to_sat()
1274212778
as u64,
1274312779
second_per_commitment_point,
1274412780
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

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

91999199
// Inbound V2 channels with contributed inputs are not considered unfunded.
92009200
if let Some(unfunded_chan) = chan.as_unfunded_v2() {
9201-
if unfunded_chan.funding_negotiation_context.our_funding_contribution_satoshis > 0 {
9201+
if unfunded_chan.funding_negotiation_context.our_funding_contribution > SignedAmount::ZERO {
92029202
continue;
92039203
}
92049204
}

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::policy::MAX_STANDARD_TX_WEIGHT;
@@ -1886,8 +1886,8 @@ pub(super) fn calculate_change_output_value(
18861886
context: &FundingNegotiationContext, is_splice: bool, shared_output_funding_script: &ScriptBuf,
18871887
funding_outputs: &Vec<TxOut>, change_output_dust_limit: u64,
18881888
) -> Result<Option<u64>, AbortReason> {
1889-
assert!(context.our_funding_contribution_satoshis > 0);
1890-
let our_funding_contribution_satoshis = context.our_funding_contribution_satoshis as u64;
1889+
assert!(context.our_funding_contribution > SignedAmount::ZERO);
1890+
let our_funding_contribution_satoshis = context.our_funding_contribution.to_sat() as u64;
18911891

18921892
let mut total_input_satoshis = 0u64;
18931893
let mut our_funding_inputs_weight = 0u64;
@@ -1964,7 +1964,8 @@ mod tests {
19641964
use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey};
19651965
use bitcoin::transaction::Version;
19661966
use bitcoin::{
1967-
OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, Witness,
1967+
OutPoint, PubkeyHash, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut,
1968+
WPubkeyHash, Witness,
19681969
};
19691970
use core::ops::Deref;
19701971

@@ -2993,7 +2994,7 @@ mod tests {
29932994
// There is leftover for change
29942995
let context = FundingNegotiationContext {
29952996
is_initiator: true,
2996-
our_funding_contribution_satoshis: our_contributed as i64,
2997+
our_funding_contribution: SignedAmount::from_sat(our_contributed as i64),
29972998
their_funding_contribution_satoshis: None,
29982999
funding_tx_locktime: AbsoluteLockTime::ZERO,
29993000
funding_feerate_sat_per_1000_weight,
@@ -3016,7 +3017,7 @@ mod tests {
30163017
// Insufficient inputs, no leftover
30173018
let context = FundingNegotiationContext {
30183019
is_initiator: false,
3019-
our_funding_contribution_satoshis: 130_000,
3020+
our_funding_contribution: SignedAmount::from_sat(130_000),
30203021
..context
30213022
};
30223023
assert_eq!(
@@ -3027,7 +3028,7 @@ mod tests {
30273028
// Very small leftover
30283029
let context = FundingNegotiationContext {
30293030
is_initiator: false,
3030-
our_funding_contribution_satoshis: 118_000,
3031+
our_funding_contribution: SignedAmount::from_sat(118_000),
30313032
..context
30323033
};
30333034
assert_eq!(
@@ -3038,7 +3039,7 @@ mod tests {
30383039
// Small leftover, but not dust
30393040
let context = FundingNegotiationContext {
30403041
is_initiator: false,
3041-
our_funding_contribution_satoshis: 117_992,
3042+
our_funding_contribution: SignedAmount::from_sat(117_992),
30423043
..context
30433044
};
30443045
assert_eq!(
@@ -3049,7 +3050,7 @@ mod tests {
30493050
// Larger fee, smaller change
30503051
let context = FundingNegotiationContext {
30513052
is_initiator: true,
3052-
our_funding_contribution_satoshis: our_contributed as i64,
3053+
our_funding_contribution: SignedAmount::from_sat(our_contributed as i64),
30533054
funding_feerate_sat_per_1000_weight: funding_feerate_sat_per_1000_weight * 3,
30543055
..context
30553056
};

0 commit comments

Comments
 (0)