Skip to content

Commit fedd89d

Browse files
committed
Req+check payment secrets for inbound payments pre-PaymentReceived
Our current PaymentReceived API is incredibly easy to mis-use - the "obvious" way to implement a client is to always call `ChannelManager::claim_funds` in response to a `PaymentReceived` event. However, users are *required* to check the payment secret and value against the expected values before claiming in order to avoid a number of potentially funds-losing attacks. Instead, if we rely on payment secrets being pre-registered with the ChannelManager before we receive HTLCs for a payment we can simply check the payment secrets and never generate `PaymentReceived` events if they do not match. Further, when the user knows the value to expect in advance, we can have them register it as well, allowing us to check it for them. Other implementations already require payment secrets for inbound payments, so this shouldn't materially lose compatibility.
1 parent eebabd1 commit fedd89d

File tree

2 files changed

+105
-76
lines changed

2 files changed

+105
-76
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ enum PendingHTLCRouting {
9595
short_channel_id: u64, // This should be NonZero<u64> eventually when we bump MSRV
9696
},
9797
Receive {
98-
payment_data: Option<msgs::FinalOnionHopData>,
98+
payment_data: msgs::FinalOnionHopData,
9999
incoming_cltv_expiry: u32, // Used to track when we should expire pending HTLCs that go unclaimed
100100
},
101101
}
@@ -159,7 +159,7 @@ struct ClaimableHTLC {
159159
/// total_msat (which may differ from value if this is a Multi-Path Payment) and a
160160
/// payment_secret which prevents path-probing attacks and can associate different HTLCs which
161161
/// are part of the same payment.
162-
payment_data: Option<msgs::FinalOnionHopData>,
162+
payment_data: msgs::FinalOnionHopData,
163163
cltv_expiry: u32,
164164
}
165165

@@ -331,7 +331,7 @@ pub(super) struct ChannelHolder<Signer: Sign> {
331331
/// Note that while this is held in the same mutex as the channels themselves, no consistency
332332
/// guarantees are made about the channels given here actually existing anymore by the time you
333333
/// go to read them!
334-
claimable_htlcs: HashMap<(PaymentHash, Option<PaymentSecret>), Vec<ClaimableHTLC>>,
334+
claimable_htlcs: HashMap<PaymentHash, Vec<ClaimableHTLC>>,
335335
/// Messages to send to peers - pushed to in the same lock that they are generated in (except
336336
/// for broadcast messages, where ordering isn't as strict).
337337
pub(super) pending_msg_events: Vec<MessageSendEvent>,
@@ -1237,14 +1237,18 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
12371237
msgs::OnionHopDataFormat::FinalNode { payment_data } => payment_data,
12381238
};
12391239

1240+
if payment_data.is_none() {
1241+
return_err!("We require payment_secrets", 0x4000|0x2000|3, &[0;0]);
1242+
}
1243+
12401244
// Note that we could obviously respond immediately with an update_fulfill_htlc
12411245
// message, however that would leak that we are the recipient of this payment, so
12421246
// instead we stay symmetric with the forwarding case, only responding (after a
12431247
// delay) once they've send us a commitment_signed!
12441248

12451249
PendingHTLCStatus::Forward(PendingHTLCInfo {
12461250
routing: PendingHTLCRouting::Receive {
1247-
payment_data,
1251+
payment_data: payment_data.unwrap(),
12481252
incoming_cltv_expiry: msg.cltv_expiry,
12491253
},
12501254
payment_hash: msg.payment_hash.clone(),
@@ -1923,60 +1927,74 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19231927
routing: PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry },
19241928
incoming_shared_secret, payment_hash, amt_to_forward, .. },
19251929
prev_funding_outpoint } => {
1926-
let prev_hop = HTLCPreviousHopData {
1927-
short_channel_id: prev_short_channel_id,
1928-
outpoint: prev_funding_outpoint,
1929-
htlc_id: prev_htlc_id,
1930-
incoming_packet_shared_secret: incoming_shared_secret,
1931-
};
1932-
1933-
let mut total_value = 0;
1934-
let payment_secret_opt =
1935-
if let &Some(ref data) = &payment_data { Some(data.payment_secret.clone()) } else { None };
1936-
let htlcs = channel_state.claimable_htlcs.entry((payment_hash, payment_secret_opt))
1937-
.or_insert(Vec::new());
1938-
htlcs.push(ClaimableHTLC {
1939-
prev_hop,
1930+
let claimable_htlc = ClaimableHTLC {
1931+
prev_hop: HTLCPreviousHopData {
1932+
short_channel_id: prev_short_channel_id,
1933+
outpoint: prev_funding_outpoint,
1934+
htlc_id: prev_htlc_id,
1935+
incoming_packet_shared_secret: incoming_shared_secret,
1936+
},
19401937
value: amt_to_forward,
19411938
payment_data: payment_data.clone(),
19421939
cltv_expiry: incoming_cltv_expiry,
1943-
});
1944-
if let &Some(ref data) = &payment_data {
1940+
};
1941+
1942+
macro_rules! fail_htlc {
1943+
($htlc: expr) => {
1944+
let mut htlc_msat_height_data = byte_utils::be64_to_array($htlc.value).to_vec();
1945+
htlc_msat_height_data.extend_from_slice(
1946+
&byte_utils::be32_to_array(self.best_block.read().unwrap().height()),
1947+
);
1948+
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
1949+
short_channel_id: $htlc.prev_hop.short_channel_id,
1950+
outpoint: prev_funding_outpoint,
1951+
htlc_id: $htlc.prev_hop.htlc_id,
1952+
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
1953+
}), payment_hash,
1954+
HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: htlc_msat_height_data }
1955+
));
1956+
}
1957+
}
1958+
1959+
// Check that the payment hash and secret are known. Note that we
1960+
// MUST take care to handle the "unknown payment hash" and
1961+
// "incorrect payment secret" cases here identically or we'd expose
1962+
// that we are the ultimately recipient of the given payment hash.
1963+
// Further, we must not expose whether we have any other HTLCs
1964+
// associated with the same payment_hash pending or not.
1965+
if {
1966+
let payment_secrets = self.pending_inbound_payments.lock().unwrap();
1967+
if let Some(inbound_payment) = payment_secrets.get(&payment_hash) {
1968+
inbound_payment.payment_secret != payment_data.payment_secret ||
1969+
(inbound_payment.min_value_msat.is_some() && inbound_payment.min_value_msat.unwrap() > payment_data.total_msat)
1970+
} else { true }
1971+
} {
1972+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
1973+
fail_htlc!(claimable_htlc);
1974+
} else {
1975+
let mut total_value = 0;
1976+
let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
1977+
.or_insert(Vec::new());
1978+
htlcs.push(claimable_htlc);
19451979
for htlc in htlcs.iter() {
19461980
total_value += htlc.value;
1947-
if htlc.payment_data.as_ref().unwrap().total_msat != data.total_msat {
1981+
if htlc.payment_data.total_msat != payment_data.total_msat {
19481982
total_value = msgs::MAX_VALUE_MSAT;
19491983
}
19501984
if total_value >= msgs::MAX_VALUE_MSAT { break; }
19511985
}
1952-
if total_value >= msgs::MAX_VALUE_MSAT || total_value > data.total_msat {
1986+
if total_value >= msgs::MAX_VALUE_MSAT || total_value > payment_data.total_msat {
1987+
log_trace!(self.logger, "Failing HTLCs with payment_hash {} as the total value {} ran over expected value {}", log_bytes!(payment_hash.0), total_value, payment_data.total_msat);
19531988
for htlc in htlcs.iter() {
1954-
let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
1955-
htlc_msat_height_data.extend_from_slice(
1956-
&byte_utils::be32_to_array(self.best_block.read().unwrap().height()),
1957-
);
1958-
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
1959-
short_channel_id: htlc.prev_hop.short_channel_id,
1960-
outpoint: prev_funding_outpoint,
1961-
htlc_id: htlc.prev_hop.htlc_id,
1962-
incoming_packet_shared_secret: htlc.prev_hop.incoming_packet_shared_secret,
1963-
}), payment_hash,
1964-
HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: htlc_msat_height_data }
1965-
));
1989+
fail_htlc!(htlc);
19661990
}
1967-
} else if total_value == data.total_msat {
1991+
} else if total_value == payment_data.total_msat {
19681992
new_events.push(events::Event::PaymentReceived {
19691993
payment_hash,
1970-
payment_secret: Some(data.payment_secret),
1994+
payment_secret: Some(payment_data.payment_secret),
19711995
amt: total_value,
19721996
});
19731997
}
1974-
} else {
1975-
new_events.push(events::Event::PaymentReceived {
1976-
payment_hash,
1977-
payment_secret: None,
1978-
amt: amt_to_forward,
1979-
});
19801998
}
19811999
},
19822000
HTLCForwardInfo::AddHTLC { .. } => {
@@ -2067,11 +2085,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
20672085
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
20682086

20692087
let mut channel_state = Some(self.channel_state.lock().unwrap());
2070-
let payment_secrets = self.payment_secrets.lock().unwrap();
2071-
let payment_secret = if let Some(secret) = payment_secrets.get(&payment_hash) {
2072-
Some(secret.payment_secret)
2073-
} else { return false; };
2074-
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(*payment_hash, payment_secret));
2088+
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(payment_hash);
20752089
if let Some(mut sources) = removed_source {
20762090
for htlc in sources.drain(..) {
20772091
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
@@ -2253,11 +2267,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22532267
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
22542268

22552269
let mut channel_state = Some(self.channel_state.lock().unwrap());
2256-
let payment_secrets = self.payment_secrets.lock().unwrap();
2257-
let payment_secret = if let Some(secret) = payment_secrets.get(&payment_hash) {
2258-
Some(secret.payment_secret)
2259-
} else { return false; };
2260-
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(payment_hash, payment_secret));
2270+
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash);
22612271
if let Some(mut sources) = removed_source {
22622272
assert!(!sources.is_empty());
22632273

@@ -2273,13 +2283,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22732283
// provide the preimage, so worrying too much about the optimal handling isn't worth
22742284
// it.
22752285

2276-
let (is_mpp, mut valid_mpp) = if let &Some(ref data) = &sources[0].payment_data {
2277-
assert!(payment_secret.is_some());
2278-
(true, data.total_msat >= expected_amount)
2279-
} else {
2280-
assert!(payment_secret.is_none());
2281-
(false, false)
2282-
};
2286+
let is_mpp = true;
2287+
let mut valid_mpp = sources[0].payment_data.total_msat >= expected_amount;
22832288

22842289
for htlc in sources.iter() {
22852290
if !is_mpp || !valid_mpp { break; }
@@ -3564,7 +3569,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
35643569
});
35653570

35663571
if let Some(height) = height_opt {
3567-
channel_state.claimable_htlcs.retain(|&(ref payment_hash, _), htlcs| {
3572+
channel_state.claimable_htlcs.retain(|payment_hash, htlcs| {
35683573
htlcs.retain(|htlc| {
35693574
// If height is approaching the number of blocks we think it takes us to get
35703575
// our commitment transaction confirmed before the HTLC expires, plus the
@@ -4063,7 +4068,8 @@ impl Writeable for PendingHTLCInfo {
40634068
},
40644069
&PendingHTLCRouting::Receive { ref payment_data, ref incoming_cltv_expiry } => {
40654070
1u8.write(writer)?;
4066-
payment_data.write(writer)?;
4071+
payment_data.payment_secret.write(writer)?;
4072+
payment_data.total_msat.write(writer)?;
40674073
incoming_cltv_expiry.write(writer)?;
40684074
},
40694075
}
@@ -4084,7 +4090,10 @@ impl Readable for PendingHTLCInfo {
40844090
short_channel_id: Readable::read(reader)?,
40854091
},
40864092
1u8 => PendingHTLCRouting::Receive {
4087-
payment_data: Readable::read(reader)?,
4093+
payment_data: msgs::FinalOnionHopData {
4094+
payment_secret: Readable::read(reader)?,
4095+
total_msat: Readable::read(reader)?,
4096+
},
40884097
incoming_cltv_expiry: Readable::read(reader)?,
40894098
},
40904099
_ => return Err(DecodeError::InvalidValue),
@@ -4156,12 +4165,29 @@ impl_writeable!(HTLCPreviousHopData, 0, {
41564165
incoming_packet_shared_secret
41574166
});
41584167

4159-
impl_writeable!(ClaimableHTLC, 0, {
4160-
prev_hop,
4161-
value,
4162-
payment_data,
4163-
cltv_expiry
4164-
});
4168+
impl Writeable for ClaimableHTLC {
4169+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
4170+
self.prev_hop.write(writer)?;
4171+
self.value.write(writer)?;
4172+
self.payment_data.payment_secret.write(writer)?;
4173+
self.payment_data.total_msat.write(writer)?;
4174+
self.cltv_expiry.write(writer)
4175+
}
4176+
}
4177+
4178+
impl Readable for ClaimableHTLC {
4179+
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
4180+
Ok(ClaimableHTLC {
4181+
prev_hop: Readable::read(reader)?,
4182+
value: Readable::read(reader)?,
4183+
payment_data: msgs::FinalOnionHopData {
4184+
payment_secret: Readable::read(reader)?,
4185+
total_msat: Readable::read(reader)?,
4186+
},
4187+
cltv_expiry: Readable::read(reader)?,
4188+
})
4189+
}
4190+
}
41654191

41664192
impl Writeable for HTLCSource {
41674193
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {

lightning/src/ln/functional_tests.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5097,22 +5097,25 @@ fn test_onchain_to_onchain_claim() {
50975097

50985098
#[test]
50995099
fn test_duplicate_payment_hash_one_failure_one_success() {
5100-
// Topology : A --> B --> C
5100+
// Topology : A --> B --> C --> D
51015101
// We route 2 payments with same hash between B and C, one will be timeout, the other successfully claim
5102-
let chanmon_cfgs = create_chanmon_cfgs(3);
5103-
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
5104-
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
5105-
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
5102+
// Note that because C will refuse to generate two payment secrets for the same payment hash,
5103+
// we forward one of the payments onwards to D.
5104+
let chanmon_cfgs = create_chanmon_cfgs(4);
5105+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
5106+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
5107+
let mut nodes = create_network(4, &node_cfgs, &node_chanmgrs);
51065108

51075109
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
51085110
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known());
5111+
create_announced_chan_between_nodes(&nodes, 2, 3, InitFeatures::known(), InitFeatures::known());
51095112

51105113
let (our_payment_preimage, duplicate_payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 900000);
51115114

5112-
let (_, _, payment_secret) = get_payment_preimage_hash!(nodes[1]);
5115+
let payment_secret = nodes[3].node.get_payment_secret(duplicate_payment_hash, None, 1008).unwrap();
51135116
let route = get_route(&nodes[0].node.get_our_node_id(), &nodes[0].net_graph_msg_handler.network_graph.read().unwrap(),
5114-
&nodes[2].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 900000, TEST_FINAL_CLTV, nodes[0].logger).unwrap();
5115-
send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2]]], 900000, duplicate_payment_hash, payment_secret);
5117+
&nodes[3].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 900000, TEST_FINAL_CLTV, nodes[0].logger).unwrap();
5118+
send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[2], &nodes[3]]], 900000, duplicate_payment_hash, payment_secret);
51165119

51175120
let commitment_txn = get_local_commitment_txn!(nodes[2], chan_2.2);
51185121
assert_eq!(commitment_txn[0].input.len(), 1);

0 commit comments

Comments
 (0)