|
7 | 7 | // You may not use this file except in accordance with one or both of these |
8 | 8 | // licenses. |
9 | 9 |
|
10 | | -//! Utilities for handling and manipulating onions |
| 10 | +//! Low-level onion manipulation logic and fields |
11 | 11 |
|
12 | 12 | use super::msgs::OnionErrorPacket; |
13 | 13 | use crate::blinded_path::BlindedHop; |
@@ -981,49 +981,100 @@ mod fuzzy_onion_utils { |
981 | 981 | #[cfg(test)] |
982 | 982 | pub(crate) attribution_failed_channel: Option<u64>, |
983 | 983 | } |
| 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, primary_session_priv) = match htlc_source { |
| 993 | + HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv), |
| 994 | + _ => unreachable!(), |
| 995 | + }; |
| 996 | + |
| 997 | + if path.has_trampoline_hops() { |
| 998 | + // If we have Trampoline hops, the outer onion session_priv is a hash of the inner one. |
| 999 | + let session_priv_hash = |
| 1000 | + Sha256::hash(&primary_session_priv.secret_bytes()).to_byte_array(); |
| 1001 | + let outer_session_priv = |
| 1002 | + SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!"); |
| 1003 | + process_onion_failure_inner( |
| 1004 | + secp_ctx, |
| 1005 | + logger, |
| 1006 | + path, |
| 1007 | + &outer_session_priv, |
| 1008 | + Some(primary_session_priv), |
| 1009 | + encrypted_packet, |
| 1010 | + ) |
| 1011 | + } else { |
| 1012 | + process_onion_failure_inner( |
| 1013 | + secp_ctx, |
| 1014 | + logger, |
| 1015 | + path, |
| 1016 | + primary_session_priv, |
| 1017 | + None, |
| 1018 | + encrypted_packet, |
| 1019 | + ) |
| 1020 | + } |
| 1021 | + } |
| 1022 | + |
| 1023 | + /// Decodes the attribution data that we got back from upstream on a payment we sent. |
| 1024 | + pub fn decode_fulfill_attribution_data<T: secp256k1::Signing, L: Deref>( |
| 1025 | + secp_ctx: &Secp256k1<T>, logger: &L, path: &Path, outer_session_priv: &SecretKey, |
| 1026 | + mut attribution_data: AttributionData, |
| 1027 | + ) -> Vec<u32> |
| 1028 | + where |
| 1029 | + L::Target: Logger, |
| 1030 | + { |
| 1031 | + let mut hold_times = Vec::new(); |
| 1032 | + |
| 1033 | + // Only consider hops in the regular path for attribution data. Blinded path attribution data isn't accessible. |
| 1034 | + let shared_secrets = |
| 1035 | + construct_onion_keys_generic(secp_ctx, &path.hops, None, outer_session_priv) |
| 1036 | + .map(|(shared_secret, _, _, _, _)| shared_secret); |
| 1037 | + |
| 1038 | + // Path length can reach 27 hops, but attribution data can only be conveyed back to the sender from the first 20 |
| 1039 | + // hops. Determine the number of hops to be used for attribution data. |
| 1040 | + let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS); |
| 1041 | + |
| 1042 | + for (route_hop_idx, shared_secret) in shared_secrets.enumerate().take(attributable_hop_count) { |
| 1043 | + attribution_data.crypt(shared_secret.as_ref()); |
| 1044 | + |
| 1045 | + // Calculate position relative to the last attributable hop. The last attributable hop is at position 0. We need |
| 1046 | + // to look at the chain of HMACs that does include all data up to the last attributable hop. Hold times beyond |
| 1047 | + // the last attributable hop will not be available. |
| 1048 | + let position = attributable_hop_count - route_hop_idx - 1; |
| 1049 | + let res = attribution_data.verify(&Vec::new(), shared_secret.as_ref(), position); |
| 1050 | + match res { |
| 1051 | + Ok(hold_time) => { |
| 1052 | + hold_times.push(hold_time); |
| 1053 | + |
| 1054 | + // Shift attribution data to prepare for processing the next hop. |
| 1055 | + attribution_data.shift_left(); |
| 1056 | + }, |
| 1057 | + Err(()) => { |
| 1058 | + // We will hit this if there is a node on the path that does not support fulfill attribution data. |
| 1059 | + log_debug!( |
| 1060 | + logger, |
| 1061 | + "Invalid fulfill HMAC in attribution data for node at pos {}", |
| 1062 | + route_hop_idx |
| 1063 | + ); |
| 1064 | + |
| 1065 | + break; |
| 1066 | + }, |
| 1067 | + } |
| 1068 | + } |
| 1069 | + |
| 1070 | + hold_times |
| 1071 | + } |
984 | 1072 | } |
985 | 1073 | #[cfg(fuzzing)] |
986 | 1074 | pub use self::fuzzy_onion_utils::*; |
987 | 1075 | #[cfg(not(fuzzing))] |
988 | 1076 | pub(crate) use self::fuzzy_onion_utils::*; |
989 | 1077 |
|
990 | | -pub(crate) fn process_onion_failure<T: secp256k1::Signing, L: Deref>( |
991 | | - secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, |
992 | | - encrypted_packet: OnionErrorPacket, |
993 | | -) -> DecodedOnionFailure |
994 | | -where |
995 | | - L::Target: Logger, |
996 | | -{ |
997 | | - let (path, primary_session_priv) = match htlc_source { |
998 | | - HTLCSource::OutboundRoute { ref path, ref session_priv, .. } => (path, session_priv), |
999 | | - _ => unreachable!(), |
1000 | | - }; |
1001 | | - |
1002 | | - if path.has_trampoline_hops() { |
1003 | | - // If we have Trampoline hops, the outer onion session_priv is a hash of the inner one. |
1004 | | - let session_priv_hash = Sha256::hash(&primary_session_priv.secret_bytes()).to_byte_array(); |
1005 | | - let outer_session_priv = |
1006 | | - SecretKey::from_slice(&session_priv_hash[..]).expect("You broke SHA-256!"); |
1007 | | - process_onion_failure_inner( |
1008 | | - secp_ctx, |
1009 | | - logger, |
1010 | | - path, |
1011 | | - &outer_session_priv, |
1012 | | - Some(primary_session_priv), |
1013 | | - encrypted_packet, |
1014 | | - ) |
1015 | | - } else { |
1016 | | - process_onion_failure_inner( |
1017 | | - secp_ctx, |
1018 | | - logger, |
1019 | | - path, |
1020 | | - primary_session_priv, |
1021 | | - None, |
1022 | | - encrypted_packet, |
1023 | | - ) |
1024 | | - } |
1025 | | -} |
1026 | | - |
1027 | 1078 | /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an |
1028 | 1079 | /// OutboundRoute). |
1029 | 1080 | fn process_onion_failure_inner<T: secp256k1::Signing, L: Deref>( |
@@ -1468,56 +1519,6 @@ where |
1468 | 1519 | } |
1469 | 1520 | } |
1470 | 1521 |
|
1471 | | -/// Decodes the attribution data that we got back from upstream on a payment we sent. |
1472 | | -pub(crate) fn decode_fulfill_attribution_data<T: secp256k1::Signing, L: Deref>( |
1473 | | - secp_ctx: &Secp256k1<T>, logger: &L, path: &Path, outer_session_priv: &SecretKey, |
1474 | | - mut attribution_data: AttributionData, |
1475 | | -) -> Vec<u32> |
1476 | | -where |
1477 | | - L::Target: Logger, |
1478 | | -{ |
1479 | | - let mut hold_times = Vec::new(); |
1480 | | - |
1481 | | - // Only consider hops in the regular path for attribution data. Blinded path attribution data isn't accessible. |
1482 | | - let shared_secrets = |
1483 | | - construct_onion_keys_generic(secp_ctx, &path.hops, None, outer_session_priv) |
1484 | | - .map(|(shared_secret, _, _, _, _)| shared_secret); |
1485 | | - |
1486 | | - // Path length can reach 27 hops, but attribution data can only be conveyed back to the sender from the first 20 |
1487 | | - // hops. Determine the number of hops to be used for attribution data. |
1488 | | - let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS); |
1489 | | - |
1490 | | - for (route_hop_idx, shared_secret) in shared_secrets.enumerate().take(attributable_hop_count) { |
1491 | | - attribution_data.crypt(shared_secret.as_ref()); |
1492 | | - |
1493 | | - // Calculate position relative to the last attributable hop. The last attributable hop is at position 0. We need |
1494 | | - // to look at the chain of HMACs that does include all data up to the last attributable hop. Hold times beyond |
1495 | | - // the last attributable hop will not be available. |
1496 | | - let position = attributable_hop_count - route_hop_idx - 1; |
1497 | | - let res = attribution_data.verify(&Vec::new(), shared_secret.as_ref(), position); |
1498 | | - match res { |
1499 | | - Ok(hold_time) => { |
1500 | | - hold_times.push(hold_time); |
1501 | | - |
1502 | | - // Shift attribution data to prepare for processing the next hop. |
1503 | | - attribution_data.shift_left(); |
1504 | | - }, |
1505 | | - Err(()) => { |
1506 | | - // We will hit this if there is a node on the path that does not support fulfill attribution data. |
1507 | | - log_debug!( |
1508 | | - logger, |
1509 | | - "Invalid fulfill HMAC in attribution data for node at pos {}", |
1510 | | - route_hop_idx |
1511 | | - ); |
1512 | | - |
1513 | | - break; |
1514 | | - }, |
1515 | | - } |
1516 | | - } |
1517 | | - |
1518 | | - hold_times |
1519 | | -} |
1520 | | - |
1521 | 1522 | const BADONION: u16 = 0x8000; |
1522 | 1523 | const PERM: u16 = 0x4000; |
1523 | 1524 | const NODE: u16 = 0x2000; |
@@ -2522,6 +2523,7 @@ where |
2522 | 2523 | } |
2523 | 2524 |
|
2524 | 2525 | /// Build a payment onion, returning the first hop msat and cltv values as well. |
| 2526 | +/// |
2525 | 2527 | /// `cur_block_height` should be set to the best known block height + 1. |
2526 | 2528 | pub fn create_payment_onion<T: secp256k1::Signing>( |
2527 | 2529 | secp_ctx: &Secp256k1<T>, path: &Path, session_priv: &SecretKey, total_msat: u64, |
@@ -2719,13 +2721,19 @@ pub(crate) const HMAC_LEN: usize = 4; |
2719 | 2721 | pub(crate) const HMAC_COUNT: usize = MAX_HOPS * (MAX_HOPS + 1) / 2; |
2720 | 2722 |
|
2721 | 2723 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] |
2722 | | -pub(crate) struct AttributionData { |
2723 | | - pub hold_times: [u8; MAX_HOPS * HOLD_TIME_LEN], |
2724 | | - pub hmacs: [u8; HMAC_LEN * HMAC_COUNT], |
| 2724 | +/// Attribution data allows the sender of an HTLC to identify which hop failed an HTLC robustly, |
| 2725 | +/// preventing earlier hops from corrupting the HTLC failure information (or at least allowing the |
| 2726 | +/// sender to identify the earliest hop which corrupted HTLC failure information). |
| 2727 | +/// |
| 2728 | +/// Additionally, it allows a sender to identify how long each hop along a path held an HTLC, with |
| 2729 | +/// 100ms granularity. |
| 2730 | +pub struct AttributionData { |
| 2731 | + hold_times: [u8; MAX_HOPS * HOLD_TIME_LEN], |
| 2732 | + hmacs: [u8; HMAC_LEN * HMAC_COUNT], |
2725 | 2733 | } |
2726 | 2734 |
|
2727 | 2735 | impl AttributionData { |
2728 | | - pub fn new() -> Self { |
| 2736 | + pub(crate) fn new() -> Self { |
2729 | 2737 | Self { hold_times: [0; MAX_HOPS * HOLD_TIME_LEN], hmacs: [0; HMAC_LEN * HMAC_COUNT] } |
2730 | 2738 | } |
2731 | 2739 | } |
@@ -2774,7 +2782,7 @@ impl AttributionData { |
2774 | 2782 |
|
2775 | 2783 | /// Writes the HMACs corresponding to the given position that have been added already by downstream hops. Position is |
2776 | 2784 | /// relative to the final node. The final node is at position 0. |
2777 | | - pub fn write_downstream_hmacs(&self, position: usize, w: &mut HmacEngine<Sha256>) { |
| 2785 | + pub(crate) fn write_downstream_hmacs(&self, position: usize, w: &mut HmacEngine<Sha256>) { |
2778 | 2786 | // Set the index to the first downstream HMAC that we need to include. Note that we skip the first MAX_HOPS HMACs |
2779 | 2787 | // because this is space reserved for the HMACs that we are producing for the current node. |
2780 | 2788 | let mut hmac_idx = MAX_HOPS + MAX_HOPS - position - 1; |
|
0 commit comments