Skip to content

Commit 02c3600

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 d11475a commit 02c3600

File tree

3 files changed

+133
-90
lines changed

3 files changed

+133
-90
lines changed

lightning/src/ln/channelmanager.rs

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

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

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

12461250
PendingHTLCStatus::Forward(PendingHTLCInfo {
12471251
routing: PendingHTLCRouting::Receive {
1248-
payment_data,
1252+
payment_data: payment_data.unwrap(),
12491253
incoming_cltv_expiry: msg.cltv_expiry,
12501254
},
12511255
payment_hash: msg.payment_hash.clone(),
@@ -1939,61 +1943,86 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
19391943
routing: PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry },
19401944
incoming_shared_secret, payment_hash, amt_to_forward, .. },
19411945
prev_funding_outpoint } => {
1942-
let prev_hop = HTLCPreviousHopData {
1943-
short_channel_id: prev_short_channel_id,
1944-
outpoint: prev_funding_outpoint,
1945-
htlc_id: prev_htlc_id,
1946-
incoming_packet_shared_secret: incoming_shared_secret,
1947-
};
1948-
1949-
let mut total_value = 0;
1950-
let payment_secret_opt =
1951-
if let &Some(ref data) = &payment_data { Some(data.payment_secret.clone()) } else { None };
1952-
let htlcs = channel_state.claimable_htlcs.entry((payment_hash, payment_secret_opt))
1953-
.or_insert(Vec::new());
1954-
htlcs.push(ClaimableHTLC {
1955-
prev_hop,
1946+
let claimable_htlc = ClaimableHTLC {
1947+
prev_hop: HTLCPreviousHopData {
1948+
short_channel_id: prev_short_channel_id,
1949+
outpoint: prev_funding_outpoint,
1950+
htlc_id: prev_htlc_id,
1951+
incoming_packet_shared_secret: incoming_shared_secret,
1952+
},
19561953
value: amt_to_forward,
19571954
payment_data: payment_data.clone(),
19581955
cltv_expiry: incoming_cltv_expiry,
1959-
});
1960-
if let &Some(ref data) = &payment_data {
1961-
for htlc in htlcs.iter() {
1962-
total_value += htlc.value;
1963-
if htlc.payment_data.as_ref().unwrap().total_msat != data.total_msat {
1964-
total_value = msgs::MAX_VALUE_MSAT;
1965-
}
1966-
if total_value >= msgs::MAX_VALUE_MSAT { break; }
1967-
}
1968-
if total_value >= msgs::MAX_VALUE_MSAT || total_value > data.total_msat {
1969-
for htlc in htlcs.iter() {
1970-
let mut htlc_msat_height_data = byte_utils::be64_to_array(htlc.value).to_vec();
1971-
htlc_msat_height_data.extend_from_slice(
1972-
&byte_utils::be32_to_array(self.best_block.read().unwrap().height()),
1973-
);
1974-
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
1975-
short_channel_id: htlc.prev_hop.short_channel_id,
1976-
outpoint: prev_funding_outpoint,
1977-
htlc_id: htlc.prev_hop.htlc_id,
1978-
incoming_packet_shared_secret: htlc.prev_hop.incoming_packet_shared_secret,
1979-
}), payment_hash,
1980-
HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: htlc_msat_height_data }
1981-
));
1982-
}
1983-
} else if total_value == data.total_msat {
1984-
new_events.push(events::Event::PaymentReceived {
1985-
payment_hash,
1986-
payment_secret: Some(data.payment_secret),
1987-
amt: total_value,
1988-
});
1956+
};
1957+
1958+
macro_rules! fail_htlc {
1959+
($htlc: expr) => {
1960+
let mut htlc_msat_height_data = byte_utils::be64_to_array($htlc.value).to_vec();
1961+
htlc_msat_height_data.extend_from_slice(
1962+
&byte_utils::be32_to_array(self.best_block.read().unwrap().height()),
1963+
);
1964+
failed_forwards.push((HTLCSource::PreviousHopData(HTLCPreviousHopData {
1965+
short_channel_id: $htlc.prev_hop.short_channel_id,
1966+
outpoint: prev_funding_outpoint,
1967+
htlc_id: $htlc.prev_hop.htlc_id,
1968+
incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret,
1969+
}), payment_hash,
1970+
HTLCFailReason::Reason { failure_code: 0x4000 | 15, data: htlc_msat_height_data }
1971+
));
19891972
}
1990-
} else {
1991-
new_events.push(events::Event::PaymentReceived {
1992-
payment_hash,
1993-
payment_secret: None,
1994-
amt: amt_to_forward,
1995-
});
19961973
}
1974+
1975+
// Check that the payment hash and secret are known. Note that we
1976+
// MUST take care to handle the "unknown payment hash" and
1977+
// "incorrect payment secret" cases here identically or we'd expose
1978+
// that we are the ultimately recipient of the given payment hash.
1979+
// Further, we must not expose whether we have any other HTLCs
1980+
// associated with the same payment_hash pending or not.
1981+
let mut payment_secrets = self.pending_inbound_payments.lock().unwrap();
1982+
match payment_secrets.entry(payment_hash) {
1983+
hash_map::Entry::Vacant(_) => {
1984+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as we didn't have a corresponding inbound payment.", log_bytes!(payment_hash.0));
1985+
fail_htlc!(claimable_htlc);
1986+
},
1987+
hash_map::Entry::Occupied(inbound_payment) => {
1988+
if inbound_payment.get().payment_secret != payment_data.payment_secret {
1989+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", log_bytes!(payment_hash.0));
1990+
fail_htlc!(claimable_htlc);
1991+
} else if inbound_payment.get().min_value_msat.is_some() && inbound_payment.get().min_value_msat.unwrap() > payment_data.total_msat {
1992+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our minimum value (had {}, needed {}).",
1993+
log_bytes!(payment_hash.0), payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
1994+
fail_htlc!(claimable_htlc);
1995+
} else {
1996+
let mut total_value = 0;
1997+
let htlcs = channel_state.claimable_htlcs.entry(payment_hash)
1998+
.or_insert(Vec::new());
1999+
htlcs.push(claimable_htlc);
2000+
for htlc in htlcs.iter() {
2001+
total_value += htlc.value;
2002+
if htlc.payment_data.total_msat != payment_data.total_msat {
2003+
total_value = msgs::MAX_VALUE_MSAT;
2004+
}
2005+
if total_value >= msgs::MAX_VALUE_MSAT { break; }
2006+
}
2007+
if total_value >= msgs::MAX_VALUE_MSAT || total_value > payment_data.total_msat {
2008+
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);
2009+
for htlc in htlcs.iter() {
2010+
fail_htlc!(htlc);
2011+
}
2012+
} else if total_value == payment_data.total_msat {
2013+
// Only ever generate at most one PaymentReceived
2014+
// per registered payment_hash, even if it isn't
2015+
// claimed.
2016+
inbound_payment.remove_entry();
2017+
new_events.push(events::Event::PaymentReceived {
2018+
payment_hash,
2019+
payment_secret: Some(payment_data.payment_secret),
2020+
amt: total_value,
2021+
});
2022+
}
2023+
}
2024+
},
2025+
};
19972026
},
19982027
HTLCForwardInfo::AddHTLC { .. } => {
19992028
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
@@ -2083,11 +2112,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
20832112
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
20842113

20852114
let mut channel_state = Some(self.channel_state.lock().unwrap());
2086-
let payment_secrets = self.pending_inbound_payments.lock().unwrap();
2087-
let payment_secret = if let Some(secret) = payment_secrets.get(&payment_hash) {
2088-
Some(secret.payment_secret)
2089-
} else { return false; };
2090-
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(*payment_hash, payment_secret));
2115+
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(payment_hash);
20912116
if let Some(mut sources) = removed_source {
20922117
for htlc in sources.drain(..) {
20932118
if channel_state.is_none() { channel_state = Some(self.channel_state.lock().unwrap()); }
@@ -2269,11 +2294,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22692294
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
22702295

22712296
let mut channel_state = Some(self.channel_state.lock().unwrap());
2272-
let payment_secrets = self.pending_inbound_payments.lock().unwrap();
2273-
let payment_secret = if let Some(secret) = payment_secrets.get(&payment_hash) {
2274-
Some(secret.payment_secret)
2275-
} else { return false; };
2276-
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&(payment_hash, payment_secret));
2297+
let removed_source = channel_state.as_mut().unwrap().claimable_htlcs.remove(&payment_hash);
22772298
if let Some(mut sources) = removed_source {
22782299
assert!(!sources.is_empty());
22792300

@@ -2289,13 +2310,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22892310
// provide the preimage, so worrying too much about the optimal handling isn't worth
22902311
// it.
22912312

2292-
let (is_mpp, mut valid_mpp) = if let &Some(ref data) = &sources[0].payment_data {
2293-
assert!(payment_secret.is_some());
2294-
(true, data.total_msat >= expected_amount)
2295-
} else {
2296-
assert!(payment_secret.is_none());
2297-
(false, false)
2298-
};
2313+
let is_mpp = true;
2314+
let mut valid_mpp = sources[0].payment_data.total_msat >= expected_amount;
22992315

23002316
for htlc in sources.iter() {
23012317
if !is_mpp || !valid_mpp { break; }
@@ -3652,7 +3668,7 @@ where
36523668
});
36533669

36543670
if let Some(height) = height_opt {
3655-
channel_state.claimable_htlcs.retain(|&(ref payment_hash, _), htlcs| {
3671+
channel_state.claimable_htlcs.retain(|payment_hash, htlcs| {
36563672
htlcs.retain(|htlc| {
36573673
// If height is approaching the number of blocks we think it takes us to get
36583674
// our commitment transaction confirmed before the HTLC expires, plus the
@@ -4026,7 +4042,8 @@ impl Writeable for PendingHTLCInfo {
40264042
},
40274043
&PendingHTLCRouting::Receive { ref payment_data, ref incoming_cltv_expiry } => {
40284044
1u8.write(writer)?;
4029-
payment_data.write(writer)?;
4045+
payment_data.payment_secret.write(writer)?;
4046+
payment_data.total_msat.write(writer)?;
40304047
incoming_cltv_expiry.write(writer)?;
40314048
},
40324049
}
@@ -4047,7 +4064,10 @@ impl Readable for PendingHTLCInfo {
40474064
short_channel_id: Readable::read(reader)?,
40484065
},
40494066
1u8 => PendingHTLCRouting::Receive {
4050-
payment_data: Readable::read(reader)?,
4067+
payment_data: msgs::FinalOnionHopData {
4068+
payment_secret: Readable::read(reader)?,
4069+
total_msat: Readable::read(reader)?,
4070+
},
40514071
incoming_cltv_expiry: Readable::read(reader)?,
40524072
},
40534073
_ => return Err(DecodeError::InvalidValue),
@@ -4119,12 +4139,29 @@ impl_writeable!(HTLCPreviousHopData, 0, {
41194139
incoming_packet_shared_secret
41204140
});
41214141

4122-
impl_writeable!(ClaimableHTLC, 0, {
4123-
prev_hop,
4124-
value,
4125-
payment_data,
4126-
cltv_expiry
4127-
});
4142+
impl Writeable for ClaimableHTLC {
4143+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::std::io::Error> {
4144+
self.prev_hop.write(writer)?;
4145+
self.value.write(writer)?;
4146+
self.payment_data.payment_secret.write(writer)?;
4147+
self.payment_data.total_msat.write(writer)?;
4148+
self.cltv_expiry.write(writer)
4149+
}
4150+
}
4151+
4152+
impl Readable for ClaimableHTLC {
4153+
fn read<R: ::std::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
4154+
Ok(ClaimableHTLC {
4155+
prev_hop: Readable::read(reader)?,
4156+
value: Readable::read(reader)?,
4157+
payment_data: msgs::FinalOnionHopData {
4158+
payment_secret: Readable::read(reader)?,
4159+
total_msat: Readable::read(reader)?,
4160+
},
4161+
cltv_expiry: Readable::read(reader)?,
4162+
})
4163+
}
4164+
}
41284165

41294166
impl Writeable for HTLCSource {
41304167
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
@@ -5098,22 +5098,25 @@ fn test_onchain_to_onchain_claim() {
50985098

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

51085110
create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known());
51095111
let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known());
5112+
create_announced_chan_between_nodes(&nodes, 2, 3, InitFeatures::known(), InitFeatures::known());
51105113

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

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

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

lightning/src/ln/onion_route_tests.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,8 @@ fn test_onion_failure() {
268268
}
269269
let channels = [create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()), create_announced_chan_between_nodes(&nodes, 1, 2, InitFeatures::known(), InitFeatures::known())];
270270
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
271-
let net_graph_msg_handler = &nodes[0].net_graph_msg_handler;
272271
let logger = test_utils::TestLogger::new();
273-
let route = get_route(&nodes[0].node.get_our_node_id(), &net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 40000, TEST_FINAL_CLTV, &logger).unwrap();
272+
let route = get_route(&nodes[0].node.get_our_node_id(), &nodes[0].net_graph_msg_handler.network_graph.read().unwrap(), &nodes[2].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &Vec::new(), 40000, TEST_FINAL_CLTV, &logger).unwrap();
274273
// positve case
275274
send_payment(&nodes[0], &vec!(&nodes[1], &nodes[2])[..], 40000, 40_000);
276275

@@ -328,6 +327,7 @@ fn test_onion_failure() {
328327
}, ||{
329328
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
330329
}, true, Some(NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: false}));
330+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
331331

332332
// intermediate node failure
333333
run_onion_failure_test_with_fail_intercept("permanent_node_failure", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| {
@@ -346,6 +346,7 @@ fn test_onion_failure() {
346346
}, ||{
347347
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
348348
}, false, Some(PERM|NODE|2), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
349+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
349350

350351
// intermediate node failure
351352
run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 100, &nodes, &route, &payment_hash, &payment_secret, |msg| {
@@ -366,6 +367,7 @@ fn test_onion_failure() {
366367
}, ||{
367368
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
368369
}, false, Some(PERM|NODE|3), Some(msgs::HTLCFailChannelUpdate::NodeFailure{node_id: route.paths[0][1].pubkey, is_permanent: true}));
370+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
369371

370372
run_onion_failure_test("invalid_onion_version", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { msg.onion_routing_packet.version = 1; }, ||{}, true,
371373
Some(BADONION|PERM|4), None);
@@ -439,6 +441,7 @@ fn test_onion_failure() {
439441
run_onion_failure_test("unknown_payment_hash", 2, &nodes, &route, &payment_hash, &payment_secret, |_| {}, || {
440442
nodes[2].node.fail_htlc_backwards(&payment_hash, &None);
441443
}, false, Some(PERM|15), None);
444+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[2]);
442445

443446
run_onion_failure_test("final_expiry_too_soon", 1, &nodes, &route, &payment_hash, &payment_secret, |msg| {
444447
let height = msg.cltv_expiry - CLTV_CLAIM_BUFFER - LATENCY_GRACE_PERIOD_BLOCKS + 1;

0 commit comments

Comments
 (0)