Skip to content

Commit 23109b6

Browse files
authored
Merge pull request #3310 from TheBlueMatt/2024-09-unlocked-checksig
Validate `channel_update` signatures without holding a graph lock
2 parents 4e1f1a8 + 131849f commit 23109b6

File tree

1 file changed

+93
-82
lines changed

1 file changed

+93
-82
lines changed

lightning/src/routing/gossip.rs

Lines changed: 93 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2253,95 +2253,106 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
22532253
msg.timestamp
22542254
);
22552255

2256-
let mut channels = self.channels.write().unwrap();
2257-
match channels.get_mut(&msg.short_channel_id) {
2258-
None => {
2259-
core::mem::drop(channels);
2260-
self.pending_checks.check_hold_pending_channel_update(msg, full_msg)?;
2261-
return Err(LightningError {
2262-
err: "Couldn't find channel for update".to_owned(),
2263-
action: ErrorAction::IgnoreAndLog(Level::Gossip),
2264-
});
2265-
},
2266-
Some(channel) => {
2267-
if msg.htlc_maximum_msat > MAX_VALUE_MSAT {
2268-
return Err(LightningError{err:
2269-
"htlc_maximum_msat is larger than maximum possible msats".to_owned(),
2270-
action: ErrorAction::IgnoreError});
2256+
if msg.htlc_maximum_msat > MAX_VALUE_MSAT {
2257+
return Err(LightningError{err:
2258+
"htlc_maximum_msat is larger than maximum possible msats".to_owned(),
2259+
action: ErrorAction::IgnoreError});
2260+
}
2261+
2262+
let check_update_latest = |target: &Option<ChannelUpdateInfo>| -> Result<(), LightningError> {
2263+
if let Some(existing_chan_info) = target {
2264+
// The timestamp field is somewhat of a misnomer - the BOLTs use it to
2265+
// order updates to ensure you always have the latest one, only
2266+
// suggesting that it be at least the current time. For
2267+
// channel_updates specifically, the BOLTs discuss the possibility of
2268+
// pruning based on the timestamp field being more than two weeks old,
2269+
// but only in the non-normative section.
2270+
if existing_chan_info.last_update > msg.timestamp {
2271+
return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
2272+
} else if existing_chan_info.last_update == msg.timestamp {
2273+
return Err(LightningError{err: "Update had same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
22712274
}
2275+
}
2276+
Ok(())
2277+
};
22722278

2273-
if let Some(capacity_sats) = channel.capacity_sats {
2274-
// It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None).
2275-
// Don't query UTXO set here to reduce DoS risks.
2276-
if capacity_sats > MAX_VALUE_MSAT / 1000 || msg.htlc_maximum_msat > capacity_sats * 1000 {
2277-
return Err(LightningError{err:
2278-
"htlc_maximum_msat is larger than channel capacity or capacity is bogus".to_owned(),
2279-
action: ErrorAction::IgnoreError});
2280-
}
2281-
}
2282-
macro_rules! check_update_latest {
2283-
($target: expr) => {
2284-
if let Some(existing_chan_info) = $target.as_ref() {
2285-
// The timestamp field is somewhat of a misnomer - the BOLTs use it to
2286-
// order updates to ensure you always have the latest one, only
2287-
// suggesting that it be at least the current time. For
2288-
// channel_updates specifically, the BOLTs discuss the possibility of
2289-
// pruning based on the timestamp field being more than two weeks old,
2290-
// but only in the non-normative section.
2291-
if existing_chan_info.last_update > msg.timestamp {
2292-
return Err(LightningError{err: "Update older than last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
2293-
} else if existing_chan_info.last_update == msg.timestamp {
2294-
return Err(LightningError{err: "Update had same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
2295-
}
2296-
}
2297-
}
2279+
let check_msg_sanity = |channel: &ChannelInfo| -> Result<(), LightningError> {
2280+
if let Some(capacity_sats) = channel.capacity_sats {
2281+
// It's possible channel capacity is available now, although it wasn't available at announcement (so the field is None).
2282+
// Don't query UTXO set here to reduce DoS risks.
2283+
if capacity_sats > MAX_VALUE_MSAT / 1000 || msg.htlc_maximum_msat > capacity_sats * 1000 {
2284+
return Err(LightningError{err:
2285+
"htlc_maximum_msat is larger than channel capacity or capacity is bogus".to_owned(),
2286+
action: ErrorAction::IgnoreError});
22982287
}
2288+
}
22992289

2300-
macro_rules! get_new_channel_info {
2301-
() => { {
2302-
let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
2303-
{ full_msg.cloned() } else { None };
2304-
2305-
let updated_channel_update_info = ChannelUpdateInfo {
2306-
enabled: chan_enabled,
2307-
last_update: msg.timestamp,
2308-
cltv_expiry_delta: msg.cltv_expiry_delta,
2309-
htlc_minimum_msat: msg.htlc_minimum_msat,
2310-
htlc_maximum_msat: msg.htlc_maximum_msat,
2311-
fees: RoutingFees {
2312-
base_msat: msg.fee_base_msat,
2313-
proportional_millionths: msg.fee_proportional_millionths,
2314-
},
2315-
last_update_message
2316-
};
2317-
Some(updated_channel_update_info)
2318-
} }
2319-
}
2290+
if msg.channel_flags & 1 == 1 {
2291+
check_update_latest(&channel.two_to_one)
2292+
} else {
2293+
check_update_latest(&channel.one_to_two)
2294+
}
2295+
};
23202296

2321-
let msg_hash = hash_to_message!(&message_sha256d_hash(&msg)[..]);
2322-
if msg.channel_flags & 1 == 1 {
2323-
check_update_latest!(channel.two_to_one);
2324-
if let Some(sig) = sig {
2325-
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_two.as_slice()).map_err(|_| LightningError{
2297+
let node_pubkey;
2298+
{
2299+
let channels = self.channels.read().unwrap();
2300+
match channels.get(&msg.short_channel_id) {
2301+
None => {
2302+
core::mem::drop(channels);
2303+
self.pending_checks.check_hold_pending_channel_update(msg, full_msg)?;
2304+
return Err(LightningError {
2305+
err: "Couldn't find channel for update".to_owned(),
2306+
action: ErrorAction::IgnoreAndLog(Level::Gossip),
2307+
});
2308+
},
2309+
Some(channel) => {
2310+
check_msg_sanity(channel)?;
2311+
let node_id = if msg.channel_flags & 1 == 1 {
2312+
channel.node_two.as_slice()
2313+
} else {
2314+
channel.node_one.as_slice()
2315+
};
2316+
node_pubkey = PublicKey::from_slice(node_id)
2317+
.map_err(|_| LightningError{
23262318
err: "Couldn't parse source node pubkey".to_owned(),
23272319
action: ErrorAction::IgnoreAndLog(Level::Debug)
2328-
})?, "channel_update");
2329-
}
2330-
if !only_verify {
2331-
channel.two_to_one = get_new_channel_info!();
2332-
}
2333-
} else {
2334-
check_update_latest!(channel.one_to_two);
2335-
if let Some(sig) = sig {
2336-
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_one.as_slice()).map_err(|_| LightningError{
2337-
err: "Couldn't parse destination node pubkey".to_owned(),
2338-
action: ErrorAction::IgnoreAndLog(Level::Debug)
2339-
})?, "channel_update");
2340-
}
2341-
if !only_verify {
2342-
channel.one_to_two = get_new_channel_info!();
2343-
}
2344-
}
2320+
})?;
2321+
},
2322+
}
2323+
}
2324+
2325+
let msg_hash = hash_to_message!(&message_sha256d_hash(&msg)[..]);
2326+
if let Some(sig) = sig {
2327+
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &node_pubkey, "channel_update");
2328+
}
2329+
2330+
if only_verify { return Ok(()); }
2331+
2332+
let mut channels = self.channels.write().unwrap();
2333+
if let Some(channel) = channels.get_mut(&msg.short_channel_id) {
2334+
check_msg_sanity(channel)?;
2335+
2336+
let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
2337+
{ full_msg.cloned() } else { None };
2338+
2339+
let new_channel_info = Some(ChannelUpdateInfo {
2340+
enabled: chan_enabled,
2341+
last_update: msg.timestamp,
2342+
cltv_expiry_delta: msg.cltv_expiry_delta,
2343+
htlc_minimum_msat: msg.htlc_minimum_msat,
2344+
htlc_maximum_msat: msg.htlc_maximum_msat,
2345+
fees: RoutingFees {
2346+
base_msat: msg.fee_base_msat,
2347+
proportional_millionths: msg.fee_proportional_millionths,
2348+
},
2349+
last_update_message
2350+
});
2351+
2352+
if msg.channel_flags & 1 == 1 {
2353+
channel.two_to_one = new_channel_info;
2354+
} else {
2355+
channel.one_to_two = new_channel_info;
23452356
}
23462357
}
23472358

0 commit comments

Comments
 (0)