Skip to content

Commit b38f117

Browse files
authored
Merge pull request #4268 from TheBlueMatt/2025-12-dont-seal-structs
Make `AttributionData` actually pub since its used in the public API
2 parents e663517 + bd57823 commit b38f117

File tree

5 files changed

+97
-96
lines changed

5 files changed

+97
-96
lines changed

fuzz/src/process_onion_failure.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ use lightning::{
99
ln::{
1010
channelmanager::{HTLCSource, PaymentId},
1111
msgs::OnionErrorPacket,
12+
onion_utils,
1213
},
1314
routing::router::{BlindedTail, Path, RouteHop, TrampolineHop},
1415
types::features::{ChannelFeatures, NodeFeatures},
1516
util::logger::Logger,
17+
util::ser::Readable,
1618
};
1719

1820
// Imports that need to be added manually
@@ -126,19 +128,18 @@ fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
126128
let failure_data = get_slice!(failure_len);
127129

128130
let attribution_data = if get_bool!() {
129-
Some(lightning::ln::AttributionData {
130-
hold_times: get_slice!(80).try_into().unwrap(),
131-
hmacs: get_slice!(840).try_into().unwrap(),
132-
})
131+
let mut bytes = get_slice!(80 + 840);
132+
let data: onion_utils::AttributionData = Readable::read(&mut bytes).unwrap();
133+
Some(data)
133134
} else {
134135
None
135136
};
136137
let encrypted_packet =
137138
OnionErrorPacket { data: failure_data.into(), attribution_data: attribution_data.clone() };
138-
lightning::ln::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
139+
onion_utils::process_onion_failure(&secp_ctx, &logger, &htlc_source, encrypted_packet);
139140

140141
if let Some(attribution_data) = attribution_data {
141-
lightning::ln::decode_fulfill_attribution_data(
142+
onion_utils::decode_fulfill_attribution_data(
142143
&secp_ctx,
143144
&logger,
144145
&path,

lightning/src/events/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ use crate::blinded_path::payment::{
2525
use crate::chain::transaction;
2626
use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
2727
use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
28+
use crate::ln::msgs;
29+
use crate::ln::onion_utils::LocalHTLCFailureReason;
2830
use crate::ln::types::ChannelId;
29-
use crate::ln::{msgs, LocalHTLCFailureReason};
3031
use crate::offers::invoice::Bolt12Invoice;
3132
use crate::offers::invoice_request::InvoiceRequest;
3233
use crate::offers::static_invoice::StaticInvoice;

lightning/src/ln/mod.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,17 @@ pub mod channel;
4141
#[cfg(not(fuzzing))]
4242
pub(crate) mod channel;
4343

44-
pub(crate) mod onion_utils;
44+
pub mod onion_utils;
4545
mod outbound_payment;
4646
pub mod wire;
4747

4848
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
4949
pub(crate) mod interactivetxs;
5050

51-
pub use onion_utils::{create_payment_onion, LocalHTLCFailureReason};
5251
// Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro
5352
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
5453
// about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below.
5554

56-
#[cfg(fuzzing)]
57-
pub use onion_utils::decode_fulfill_attribution_data;
58-
#[cfg(fuzzing)]
59-
pub use onion_utils::process_onion_failure;
60-
61-
#[cfg(fuzzing)]
62-
pub use onion_utils::AttributionData;
63-
6455
#[cfg(test)]
6556
#[allow(unused_mut)]
6657
mod async_payments_tests;

lightning/src/ln/msgs.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4366,7 +4366,7 @@ mod tests {
43664366
InboundOnionForwardPayload, InboundOnionReceivePayload, OutboundTrampolinePayload,
43674367
TrampolineOnionPacket,
43684368
};
4369-
use crate::ln::onion_utils::{AttributionData, HMAC_COUNT, HMAC_LEN, HOLD_TIME_LEN, MAX_HOPS};
4369+
use crate::ln::onion_utils::AttributionData;
43704370
use crate::ln::types::ChannelId;
43714371
use crate::routing::gossip::{NodeAlias, NodeId};
43724372
use crate::types::features::{
@@ -5899,13 +5899,10 @@ mod tests {
58995899
channel_id: ChannelId::from_bytes([2; 32]),
59005900
htlc_id: 2316138423780173,
59015901
reason: [1; 32].to_vec(),
5902-
attribution_data: Some(AttributionData {
5903-
hold_times: [3; MAX_HOPS * HOLD_TIME_LEN],
5904-
hmacs: [3; HMAC_LEN * HMAC_COUNT],
5905-
}),
5902+
attribution_data: Some(AttributionData::new()),
59065903
};
59075904
let encoded_value = update_fail_htlc.encode();
5908-
let target_value = <Vec<u8>>::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d0020010101010101010101010101010101010101010101010101010101010101010101fd03980303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303").unwrap();
5905+
let target_value = <Vec<u8>>::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d0020010101010101010101010101010101010101010101010101010101010101010101fd03980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
59095906
assert_eq!(encoded_value, target_value);
59105907
}
59115908

lightning/src/ln/onion_utils.rs

Lines changed: 84 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10+
//! Low-level onion manipulation logic and fields
11+
1012
use super::msgs::OnionErrorPacket;
1113
use crate::blinded_path::BlindedHop;
1214
use crate::crypto::chacha20::ChaCha20;
@@ -979,27 +981,79 @@ mod fuzzy_onion_utils {
979981
#[cfg(test)]
980982
pub(crate) attribution_failed_channel: Option<u64>,
981983
}
984+
985+
pub fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
986+
secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource,
987+
encrypted_packet: OnionErrorPacket,
988+
) -> DecodedOnionFailure
989+
where
990+
L::Target: Logger,
991+
{
992+
let (path, session_priv) = match htlc_source {
993+
HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv),
994+
_ => unreachable!(),
995+
};
996+
997+
process_onion_failure_inner(secp_ctx, logger, path, &session_priv, None, encrypted_packet)
998+
}
999+
1000+
/// Decodes the attribution data that we got back from upstream on a payment we sent.
1001+
pub fn decode_fulfill_attribution_data<T: secp256k1::Signing, L: Deref>(
1002+
secp_ctx: &Secp256k1<T>, logger: &L, path: &Path, outer_session_priv: &SecretKey,
1003+
mut attribution_data: AttributionData,
1004+
) -> Vec<u32>
1005+
where
1006+
L::Target: Logger,
1007+
{
1008+
let mut hold_times = Vec::new();
1009+
1010+
// Only consider hops in the regular path for attribution data. Blinded path attribution data isn't accessible.
1011+
let shared_secrets =
1012+
construct_onion_keys_generic(secp_ctx, &path.hops, None, outer_session_priv)
1013+
.map(|(shared_secret, _, _, _, _)| shared_secret);
1014+
1015+
// Path length can reach 27 hops, but attribution data can only be conveyed back to the sender from the first 20
1016+
// hops. Determine the number of hops to be used for attribution data.
1017+
let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS);
1018+
1019+
for (route_hop_idx, shared_secret) in
1020+
shared_secrets.enumerate().take(attributable_hop_count)
1021+
{
1022+
attribution_data.crypt(shared_secret.as_ref());
1023+
1024+
// Calculate position relative to the last attributable hop. The last attributable hop is at position 0. We need
1025+
// to look at the chain of HMACs that does include all data up to the last attributable hop. Hold times beyond
1026+
// the last attributable hop will not be available.
1027+
let position = attributable_hop_count - route_hop_idx - 1;
1028+
let res = attribution_data.verify(&Vec::new(), shared_secret.as_ref(), position);
1029+
match res {
1030+
Ok(hold_time) => {
1031+
hold_times.push(hold_time);
1032+
1033+
// Shift attribution data to prepare for processing the next hop.
1034+
attribution_data.shift_left();
1035+
},
1036+
Err(()) => {
1037+
// We will hit this if there is a node on the path that does not support fulfill attribution data.
1038+
log_debug!(
1039+
logger,
1040+
"Invalid fulfill HMAC in attribution data for node at pos {}",
1041+
route_hop_idx
1042+
);
1043+
1044+
break;
1045+
},
1046+
}
1047+
}
1048+
1049+
hold_times
1050+
}
9821051
}
9831052
#[cfg(fuzzing)]
9841053
pub use self::fuzzy_onion_utils::*;
9851054
#[cfg(not(fuzzing))]
9861055
pub(crate) use self::fuzzy_onion_utils::*;
9871056

988-
pub fn process_onion_failure<T: secp256k1::Signing, L: Deref>(
989-
secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource,
990-
encrypted_packet: OnionErrorPacket,
991-
) -> DecodedOnionFailure
992-
where
993-
L::Target: Logger,
994-
{
995-
let (path, session_priv) = match htlc_source {
996-
HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv),
997-
_ => unreachable!(),
998-
};
999-
1000-
process_onion_failure_inner(secp_ctx, logger, path, &session_priv, None, encrypted_packet)
1001-
}
1002-
10031057
/// Process failure we got back from upstream on a payment we sent (implying htlc_source is an
10041058
/// OutboundRoute).
10051059
fn process_onion_failure_inner<T: secp256k1::Signing, L: Deref>(
@@ -1449,56 +1503,6 @@ where
14491503
}
14501504
}
14511505

1452-
/// Decodes the attribution data that we got back from upstream on a payment we sent.
1453-
pub fn decode_fulfill_attribution_data<T: secp256k1::Signing, L: Deref>(
1454-
secp_ctx: &Secp256k1<T>, logger: &L, path: &Path, outer_session_priv: &SecretKey,
1455-
mut attribution_data: AttributionData,
1456-
) -> Vec<u32>
1457-
where
1458-
L::Target: Logger,
1459-
{
1460-
let mut hold_times = Vec::new();
1461-
1462-
// Only consider hops in the regular path for attribution data. Blinded path attribution data isn't accessible.
1463-
let shared_secrets =
1464-
construct_onion_keys_generic(secp_ctx, &path.hops, None, outer_session_priv)
1465-
.map(|(shared_secret, _, _, _, _)| shared_secret);
1466-
1467-
// Path length can reach 27 hops, but attribution data can only be conveyed back to the sender from the first 20
1468-
// hops. Determine the number of hops to be used for attribution data.
1469-
let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS);
1470-
1471-
for (route_hop_idx, shared_secret) in shared_secrets.enumerate().take(attributable_hop_count) {
1472-
attribution_data.crypt(shared_secret.as_ref());
1473-
1474-
// Calculate position relative to the last attributable hop. The last attributable hop is at position 0. We need
1475-
// to look at the chain of HMACs that does include all data up to the last attributable hop. Hold times beyond
1476-
// the last attributable hop will not be available.
1477-
let position = attributable_hop_count - route_hop_idx - 1;
1478-
let res = attribution_data.verify(&Vec::new(), shared_secret.as_ref(), position);
1479-
match res {
1480-
Ok(hold_time) => {
1481-
hold_times.push(hold_time);
1482-
1483-
// Shift attribution data to prepare for processing the next hop.
1484-
attribution_data.shift_left();
1485-
},
1486-
Err(()) => {
1487-
// We will hit this if there is a node on the path that does not support fulfill attribution data.
1488-
log_debug!(
1489-
logger,
1490-
"Invalid fulfill HMAC in attribution data for node at pos {}",
1491-
route_hop_idx
1492-
);
1493-
1494-
break;
1495-
},
1496-
}
1497-
}
1498-
1499-
hold_times
1500-
}
1501-
15021506
const BADONION: u16 = 0x8000;
15031507
const PERM: u16 = 0x4000;
15041508
const NODE: u16 = 0x2000;
@@ -2522,6 +2526,7 @@ where
25222526
}
25232527

25242528
/// Build a payment onion, returning the first hop msat and cltv values as well.
2529+
///
25252530
/// `cur_block_height` should be set to the best known block height + 1.
25262531
pub fn create_payment_onion<T: secp256k1::Signing>(
25272532
secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64,
@@ -2711,22 +2716,28 @@ fn decode_next_hop<T, R: ReadableArgs<T>, N: NextPacketBytes>(
27112716
}
27122717
}
27132718

2714-
pub const HOLD_TIME_LEN: usize = 4;
2715-
pub const MAX_HOPS: usize = 20;
2716-
pub const HMAC_LEN: usize = 4;
2719+
pub(crate) const HOLD_TIME_LEN: usize = 4;
2720+
pub(crate) const MAX_HOPS: usize = 20;
2721+
pub(crate) const HMAC_LEN: usize = 4;
27172722

27182723
// Define the number of HMACs in the attributable data block. For the first node, there are 20 HMACs, and then for every
27192724
// subsequent node, the number of HMACs decreases by 1. 20 + 19 + 18 + ... + 1 = 20 * 21 / 2 = 210.
2720-
pub const HMAC_COUNT: usize = MAX_HOPS * (MAX_HOPS + 1) / 2;
2725+
pub(crate) const HMAC_COUNT: usize = MAX_HOPS * (MAX_HOPS + 1) / 2;
27212726

27222727
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
2728+
/// Attribution data allows the sender of an HTLC to identify which hop failed an HTLC robustly,
2729+
/// preventing earlier hops from corrupting the HTLC failure information (or at least allowing the
2730+
/// sender to identify the earliest hop which corrupted HTLC failure information).
2731+
///
2732+
/// Additionally, it allows a sender to identify how long each hop along a path held an HTLC, with
2733+
/// 100ms granularity.
27232734
pub struct AttributionData {
2724-
pub hold_times: [u8; MAX_HOPS * HOLD_TIME_LEN],
2725-
pub hmacs: [u8; HMAC_LEN * HMAC_COUNT],
2735+
hold_times: [u8; MAX_HOPS * HOLD_TIME_LEN],
2736+
hmacs: [u8; HMAC_LEN * HMAC_COUNT],
27262737
}
27272738

27282739
impl AttributionData {
2729-
pub fn new() -> Self {
2740+
pub(crate) fn new() -> Self {
27302741
Self { hold_times: [0; MAX_HOPS * HOLD_TIME_LEN], hmacs: [0; HMAC_LEN * HMAC_COUNT] }
27312742
}
27322743
}
@@ -2775,7 +2786,7 @@ impl AttributionData {
27752786

27762787
/// Writes the HMACs corresponding to the given position that have been added already by downstream hops. Position is
27772788
/// relative to the final node. The final node is at position 0.
2778-
pub fn write_downstream_hmacs(&self, position: usize, w: &mut HmacEngine<Sha256>) {
2789+
pub(crate) fn write_downstream_hmacs(&self, position: usize, w: &mut HmacEngine<Sha256>) {
27792790
// Set the index to the first downstream HMAC that we need to include. Note that we skip the first MAX_HOPS HMACs
27802791
// because this is space reserved for the HMACs that we are producing for the current node.
27812792
let mut hmac_idx = MAX_HOPS + MAX_HOPS - position - 1;

0 commit comments

Comments
 (0)