Skip to content

Commit 5dcfd9e

Browse files
authored
Merge pull request #4722 from TheBlueMatt/2026-06-0.2-backports-3
2 parents fb67c37 + c47d874 commit 5dcfd9e

12 files changed

Lines changed: 356 additions & 43 deletions

File tree

lightning-invoice/src/lib.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,17 +1498,22 @@ impl Bolt11Invoice {
14981498
self.signed_invoice.features()
14991499
}
15001500

1501-
/// Recover the payee's public key (only to be used if none was included in the invoice)
1501+
/// Get the invoice's payee public key.
1502+
///
1503+
/// This uses the explicitly included payee public key, if present, otherwise it recovers the
1504+
/// payee public key from the signature. Prefer [`Self::get_payee_pub_key`] for clarity.
15021505
pub fn recover_payee_pub_key(&self) -> PublicKey {
1503-
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
1506+
self.get_payee_pub_key()
15041507
}
15051508

1506-
/// Recover the payee's public key if one was included in the invoice, otherwise return the
1507-
/// recovered public key from the signature
1509+
/// Get the invoice's payee public key, preferring an explicitly included payee public key and
1510+
/// falling back to recovering the key from the signature.
15081511
pub fn get_payee_pub_key(&self) -> PublicKey {
15091512
match self.payee_pub_key() {
15101513
Some(pk) => *pk,
1511-
None => self.recover_payee_pub_key(),
1514+
None => {
1515+
self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
1516+
},
15121517
}
15131518
}
15141519

@@ -2044,6 +2049,44 @@ mod test {
20442049
assert!(new_signed.check_signature());
20452050
}
20462051

2052+
#[test]
2053+
fn recover_payee_pub_key_uses_included_payee_pub_key() {
2054+
use crate::*;
2055+
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
2056+
use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
2057+
use core::time::Duration;
2058+
2059+
let secp_ctx = Secp256k1::new();
2060+
let private_key = SecretKey::from_slice(&[42; 32]).unwrap();
2061+
let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);
2062+
2063+
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
2064+
.description("Test".to_string())
2065+
.payment_hash(sha256::Hash::from_slice(&[0; 32][..]).unwrap())
2066+
.payment_secret(PaymentSecret([21; 32]))
2067+
.payee_pub_key(public_key)
2068+
.min_final_cltv_expiry_delta(144)
2069+
.duration_since_epoch(Duration::from_secs(1234567))
2070+
.build_signed(|hash| secp_ctx.sign_ecdsa_recoverable(hash, &private_key))
2071+
.unwrap();
2072+
2073+
let signed_raw = invoice.into_signed_raw();
2074+
let (raw_invoice, hash, signature) = signed_raw.into_parts();
2075+
let (_orig_rid, sig_bytes) = signature.0.serialize_compact();
2076+
let bad_rid = RecoveryId::from_i32(2).unwrap();
2077+
let bad_sig = RecoverableSignature::from_compact(&sig_bytes, bad_rid).unwrap();
2078+
let bad_signed_raw = SignedRawBolt11Invoice {
2079+
raw_invoice,
2080+
hash,
2081+
signature: Bolt11InvoiceSignature(bad_sig),
2082+
};
2083+
let bad_invoice = Bolt11Invoice::from_signed(bad_signed_raw).unwrap();
2084+
2085+
assert_eq!(bad_invoice.payee_pub_key(), Some(&public_key));
2086+
assert_eq!(bad_invoice.recover_payee_pub_key(), public_key);
2087+
assert_eq!(bad_invoice.get_payee_pub_key(), public_key);
2088+
}
2089+
20472090
#[test]
20482091
fn test_check_feature_bits() {
20492092
use crate::TaggedField::*;

lightning-rapid-gossip-sync/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ where
153153
/// Sync gossip data from a file.
154154
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
155155
///
156+
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
157+
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
158+
/// the graph such that it leads to eventual OOM on the client.
159+
///
156160
/// `network_graph`: The network graph to apply the updates to
157161
///
158162
/// `sync_path`: Path to the file where the gossip update data is located
@@ -172,6 +176,10 @@ where
172176
/// Update network graph from binary data.
173177
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
174178
///
179+
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
180+
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
181+
/// the graph such that it leads to eventual OOM on the client.
182+
///
175183
/// `update_data`: `&[u8]` binary stream that comprises the update data
176184
#[cfg(feature = "std")]
177185
pub fn update_network_graph(&self, update_data: &[u8]) -> Result<u32, GraphSyncError> {
@@ -182,6 +190,10 @@ where
182190
/// Update network graph from binary data.
183191
/// Returns the last sync timestamp to be used the next time rapid sync data is queried.
184192
///
193+
/// You should consider the gossip data source as semi-trusted. It is generally the case that it
194+
/// can DoS the client either by omitting data which leads to pathfinding failure or by bloating
195+
/// the graph such that it leads to eventual OOM on the client.
196+
///
185197
/// `update_data`: `&[u8]` binary stream that comprises the update data
186198
/// `current_time_unix`: `Option<u64>` optional current timestamp to verify data age
187199
pub fn update_network_graph_no_std(

lightning-rapid-gossip-sync/src/processing.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use lightning::ln::msgs::{
99
DecodeError, ErrorAction, LightningError, SocketAddress, UnsignedChannelUpdate,
1010
UnsignedNodeAnnouncement,
1111
};
12-
use lightning::routing::gossip::{NetworkGraph, NodeAlias, NodeId};
12+
use lightning::routing::gossip::{
13+
NetworkGraph, NodeAlias, NodeId, CHAN_COUNT_ESTIMATE, NODE_COUNT_ESTIMATE,
14+
};
1315
use lightning::util::logger::Logger;
1416
use lightning::util::ser::{BigSize, FixedLengthReader, Readable};
1517
use lightning::{log_debug, log_given_level, log_gossip, log_trace, log_warn};
@@ -115,17 +117,27 @@ where
115117
}
116118
};
117119

120+
const MAX_NODE_COUNT: u32 = (NODE_COUNT_ESTIMATE as u32) * 10;
121+
const MAX_CHANNEL_COUNT: u64 = (CHAN_COUNT_ESTIMATE as u64) * 10;
122+
118123
let node_id_count: u32 = Readable::read(read_cursor)?;
124+
if node_id_count > MAX_NODE_COUNT {
125+
return Err(LightningError {
126+
err: "RGS data contained nonsense number of nodes to update".to_owned(),
127+
action: ErrorAction::IgnoreError,
128+
}
129+
.into());
130+
}
119131
let mut node_ids: Vec<NodeId> = Vec::with_capacity(core::cmp::min(
120132
node_id_count,
121133
MAX_INITIAL_NODE_ID_VECTOR_CAPACITY,
122134
) as usize);
123-
124135
let network_graph = &self.network_graph;
125136
let mut node_modifications: Vec<UnsignedNodeAnnouncement> = Vec::new();
126137

138+
let read_only_network_graph = network_graph.read_only();
139+
127140
if parse_node_details {
128-
let read_only_network_graph = network_graph.read_only();
129141
for _ in 0..node_id_count {
130142
let mut pubkey_bytes = [0u8; 33];
131143
read_cursor.read_exact(&mut pubkey_bytes)?;
@@ -237,9 +249,12 @@ where
237249
}
238250
}
239251

252+
let original_graph_channel_count = read_only_network_graph.channels().len() as u32;
253+
core::mem::drop(read_only_network_graph);
254+
240255
let mut previous_scid: u64 = 0;
241256
let announcement_count: u32 = Readable::read(read_cursor)?;
242-
for _ in 0..announcement_count {
257+
for i in 0..announcement_count {
243258
let features = Readable::read(read_cursor)?;
244259

245260
// handle SCID
@@ -284,6 +299,10 @@ where
284299
}
285300
}
286301

302+
if (original_graph_channel_count as u64) + (i as u64) > MAX_CHANNEL_COUNT {
303+
continue;
304+
}
305+
287306
let announcement_result = network_graph.add_channel_from_partial_announcement(
288307
short_channel_id,
289308
funding_sats,
@@ -329,6 +348,13 @@ where
329348
previous_scid = 0;
330349

331350
let update_count: u32 = Readable::read(read_cursor)?;
351+
if update_count as u64 > MAX_CHANNEL_COUNT {
352+
return Err(LightningError {
353+
err: "RGS data contained nonsense number of channels to update".to_owned(),
354+
action: ErrorAction::IgnoreError,
355+
}
356+
.into());
357+
}
332358
log_debug!(self.logger, "Processing RGS update from {} with {} nodes, {} channel announcements and {} channel updates.",
333359
latest_seen_timestamp, node_id_count, announcement_count, update_count);
334360
if update_count == 0 {

lightning/src/ln/channel.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,9 +1959,12 @@ where
19591959

19601960
let tx_abort = should_ack.then(|| {
19611961
let logger = WithChannelContext::from(logger, &self.context(), None);
1962-
let reason =
1963-
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
1964-
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
1962+
let reason = String::from_utf8_lossy(&msg.data);
1963+
log_info!(
1964+
logger,
1965+
"Counterparty failed interactive transaction negotiation: {}",
1966+
log_msg!(reason)
1967+
);
19651968
msgs::TxAbort {
19661969
channel_id: msg.channel_id,
19671970
data: "Acknowledged tx_abort".to_string().into_bytes(),

lightning/src/ln/invoice_utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ mod test {
12881288
assert!(!invoice.features().unwrap().supports_basic_mpp());
12891289

12901290
let payment_params = PaymentParameters::from_node_id(
1291-
invoice.recover_payee_pub_key(),
1291+
invoice.get_payee_pub_key(),
12921292
invoice.min_final_cltv_expiry_delta() as u32,
12931293
)
12941294
.with_bolt11_features(invoice.features().unwrap().clone())
@@ -1350,7 +1350,7 @@ mod test {
13501350
payment_secret,
13511351
payment_amt,
13521352
payment_preimage_opt,
1353-
invoice.recover_payee_pub_key(),
1353+
invoice.get_payee_pub_key(),
13541354
);
13551355
do_claim_payment_along_route(ClaimAlongRouteArgs::new(
13561356
&nodes[0],

lightning/src/ln/peer_handler.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
4545
use crate::routing::gossip::{NodeAlias, NodeId};
4646
use crate::sign::{NodeSigner, Recipient};
4747
use crate::types::features::{InitFeatures, NodeFeatures};
48-
use crate::types::string::PrintableString;
4948
use crate::util::atomic_counter::AtomicCounter;
5049
use crate::util::logger::{Level, Logger, WithContext};
5150
use crate::util::ser::{VecWriter, Writeable, Writer};
@@ -2415,7 +2414,7 @@ where
24152414
logger,
24162415
"Got Err message from {}: {}",
24172416
their_node_id,
2418-
PrintableString(&msg.data)
2417+
log_msg!(msg.data)
24192418
);
24202419
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
24212420
if msg.channel_id.is_zero() {
@@ -2427,7 +2426,7 @@ where
24272426
logger,
24282427
"Got warning message from {}: {}",
24292428
their_node_id,
2430-
PrintableString(&msg.data)
2429+
log_msg!(msg.data)
24312430
);
24322431
},
24332432

@@ -3213,7 +3212,7 @@ where
32133212
msgs::ErrorAction::DisconnectPeer { msg } => {
32143213
if let Some(msg) = msg.as_ref() {
32153214
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
3216-
node_id, msg.data);
3215+
node_id, log_msg!(msg.data));
32173216
} else {
32183217
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {}",
32193218
node_id);
@@ -3228,7 +3227,7 @@ where
32283227
},
32293228
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
32303229
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler for node {} with message {}",
3231-
node_id, msg.data);
3230+
node_id, log_msg!(msg.data));
32323231
// We do not have the peers write lock, so we just store that we're
32333232
// about to disconnect the peer and do it after we finish
32343233
// processing most messages.
@@ -3254,7 +3253,7 @@ where
32543253
msgs::ErrorAction::SendErrorMessage { ref msg } => {
32553254
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler for node {} with message {}",
32563255
node_id,
3257-
msg.data);
3256+
log_msg!(msg.data));
32583257
self.enqueue_message(
32593258
&mut *get_peer_for_forwarding!(&node_id)?,
32603259
msg,
@@ -3266,7 +3265,7 @@ where
32663265
} => {
32673266
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler for node {} with message {}",
32683267
node_id,
3269-
msg.data);
3268+
log_msg!(msg.data));
32703269
self.enqueue_message(
32713270
&mut *get_peer_for_forwarding!(&node_id)?,
32723271
msg,

lightning/src/onion_message/dns_resolution.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,8 @@ impl OMNameResolver {
520520
.filter_map(|data| String::from_utf8(data).ok())
521521
.filter(|data_string| data_string.len() > URI_PREFIX.len())
522522
.filter(|data_string| {
523-
data_string[..URI_PREFIX.len()].eq_ignore_ascii_case(URI_PREFIX)
523+
let pfx = &data_string.as_bytes()[..URI_PREFIX.len()];
524+
pfx.eq_ignore_ascii_case(URI_PREFIX.as_bytes())
524525
});
525526
// Check that there is exactly one TXT record that begins with
526527
// bitcoin: as required by BIP 353 (and is valid UTF-8).

lightning/src/routing/gossip.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,14 +1757,16 @@ where
17571757
}
17581758
}
17591759

1760-
// In Jan, 2025 there were about 49K channels.
1761-
// We over-allocate by a bit because 20% more is better than the double we get if we're slightly
1762-
// too low
1763-
const CHAN_COUNT_ESTIMATE: usize = 60_000;
1764-
// In Jan, 2025 there were about 15K nodes
1765-
// We over-allocate by a bit because 33% more is better than the double we get if we're slightly
1766-
// too low
1767-
const NODE_COUNT_ESTIMATE: usize = 20_000;
1760+
/// In Jan, 2025 there were about 49K channels.
1761+
///
1762+
/// We over-allocate by a bit because 20% more is better than the double we get if we're slightly
1763+
/// too low
1764+
pub const CHAN_COUNT_ESTIMATE: usize = 60_000;
1765+
/// In Jan, 2025 there were about 15K nodes
1766+
///
1767+
/// We over-allocate by a bit because 33% more is better than the double we get if we're slightly
1768+
/// too low
1769+
pub const NODE_COUNT_ESTIMATE: usize = 20_000;
17681770

17691771
impl<L: Deref> NetworkGraph<L>
17701772
where

0 commit comments

Comments
 (0)