From 7708cca17baf536db38ed52274bdd8d37ec1dda7 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 14 Mar 2025 18:22:08 +0000 Subject: [PATCH 1/4] Make `process_onion_failure_inner` module-private There's no reason for anything outside of `onion_utils` to call an `_inner` method, so it shouldn't be pub. --- lightning/src/ln/onion_utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index f3399911238..3b7a369c9aa 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1036,8 +1036,7 @@ where /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). -#[inline] -pub(super) fn process_onion_failure_inner( +fn process_onion_failure_inner( secp_ctx: &Secp256k1, logger: &L, path: &Path, outer_session_priv: &SecretKey, inner_session_priv: Option<&SecretKey>, mut encrypted_packet: OnionErrorPacket, ) -> DecodedOnionFailure From 6ea357684b76f710d680c0d10ac5775b88d92fa3 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 14 Mar 2025 18:32:51 +0000 Subject: [PATCH 2/4] Clean up error handling on onion keys construction Onion keys construction can only fail if we have a key which is the inverse of a hash containing a public key which we generated (even indirectly). Because we prefer to panic if our RNG creates an insecure key (as it indicates operating is incredibly unsafe), we thus don't actually need to return an error from `construct_onion_keys_generic_callback`, which we clean up here. --- lightning/src/ln/blinded_payment_tests.rs | 12 +++--- lightning/src/ln/functional_tests.rs | 12 +++--- lightning/src/ln/onion_payment.rs | 2 +- lightning/src/ln/onion_route_tests.rs | 42 ++++++++++---------- lightning/src/ln/onion_utils.rs | 48 +++++++++-------------- 5 files changed, 52 insertions(+), 64 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 49bb82ba0a9..3c0ddc695e7 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -385,7 +385,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { ForwardCheckFail::ForwardPayloadEncodedAsReceive => { let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let cur_height = nodes[0].best_block_info().1; let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( &route.paths[0], amt_msat, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -966,7 +966,7 @@ fn do_multi_hop_receiver_fail(check: ReceiveCheckFail) { }, ReceiveCheckFail::OnionDecodeFail => { let session_priv = SecretKey::from_slice(&session_priv).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let cur_height = nodes[0].best_block_info().1; let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( @@ -2100,7 +2100,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { // pop the last dummy hop trampoline_payloads.pop(); - let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key).unwrap(); + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key); let trampoline_packet = onion_utils::construct_trampoline_onion_packet( trampoline_payloads, trampoline_onion_keys, @@ -2112,7 +2112,7 @@ fn do_test_trampoline_single_hop_receive(success: bool) { let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677"); let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); - let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv).unwrap(); + let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); let outer_packet = onion_utils::construct_onion_packet( outer_payloads, outer_onion_keys, @@ -2294,7 +2294,7 @@ fn test_trampoline_unblinded_receive() { cltv_expiry_height: 104, }); - let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key).unwrap(); + let trampoline_onion_keys = onion_utils::construct_trampoline_onion_keys(&secp_ctx, &route.paths[0].blinded_tail.as_ref().unwrap(), &trampoline_secret_key); let trampoline_packet = onion_utils::construct_trampoline_onion_packet( trampoline_payloads, trampoline_onion_keys, @@ -2306,7 +2306,7 @@ fn test_trampoline_unblinded_receive() { let outer_session_priv = secret_from_hex("e52c20461ed7acd46c4e7b591a37610519179482887bd73bf3b94617f8f03677"); let (outer_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], outer_total_msat, &recipient_onion_fields, outer_starting_htlc_offset, &None, None, Some(trampoline_packet)).unwrap(); - let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv).unwrap(); + let outer_onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.clone().paths[0], &outer_session_priv); let outer_packet = onion_utils::construct_onion_packet( outer_payloads, outer_onion_keys, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 4aaa1a3f31e..ea173065f00 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1501,7 +1501,7 @@ pub fn test_fee_spike_violation_fails_htlc() { let cur_height = nodes[1].node.best_block.read().unwrap().height + 1; - let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], 3460001, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -1687,7 +1687,7 @@ pub fn test_chan_reserve_violation_inbound_htlc_outbound_channel() { let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); let cur_height = nodes[1].node.best_block.read().unwrap().height + 1; - let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route.paths[0], 700_000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -1867,7 +1867,7 @@ pub fn test_chan_reserve_violation_inbound_htlc_inbound_chan() { let secp_ctx = Secp256k1::new(); let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); let cur_height = nodes[0].node.best_block.read().unwrap().height + 1; - let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route_2.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads( &route_2.paths[0], recv_value_2, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -3919,7 +3919,7 @@ pub fn fail_backward_pending_htlc_upon_channel_failure() { let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (onion_payloads, _amount_msat, cltv_expiry) = onion_utils::build_onion_payloads( &route.paths[0], 50_000, &recipient_onion_fields, current_height, &None, None, None).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route.paths[0], &session_priv); let onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); // Send a 0-msat update_add_htlc to fail the channel. @@ -6932,7 +6932,7 @@ pub fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() { route.paths[0].hops[0].fee_msat = send_amt; let session_priv = SecretKey::from_slice(&[42; 32]).unwrap(); let cur_height = nodes[0].node.best_block.read().unwrap().height + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret); let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads( &route.paths[0], send_amt, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -8749,7 +8749,7 @@ pub fn test_onion_value_mpp_set_calculation() { let session_priv = [3; 32]; let height = nodes[0].best_block_info().1; let session_priv = SecretKey::from_slice(&session_priv).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(our_payment_secret); let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads(&route.paths[0], 100_000, &recipient_onion_fields, height + 1, &None, None, None).unwrap(); diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index e785e1867d4..1bf4b1fc949 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -663,7 +663,7 @@ mod tests { recipient_onion.custom_tlvs.push((13377331, vec![0; 1156])); let path = Path { hops, blinded_tail: None, }; - let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv).unwrap(); + let onion_keys = super::onion_utils::construct_onion_keys(&secp_ctx, &path, &session_priv); let (onion_payloads, ..) = super::onion_utils::build_onion_payloads( &path, total_amt_msat, &recipient_onion, cur_height + 1, &Some(keysend_preimage), None, None ).unwrap(); diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 1558f4e6c79..da245402997 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -369,7 +369,7 @@ fn test_onion_failure() { run_onion_failure_test("invalid_realm", 0, &nodes, &route, &payment_hash, &payment_secret, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( &route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -388,7 +388,7 @@ fn test_onion_failure() { run_onion_failure_test("invalid_realm", 3, &nodes, &route, &payment_hash, &payment_secret, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); let cur_height = nodes[0].best_block_info().1 + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads( &route.paths[0], 40000, &recipient_onion_fields, cur_height, &None, None, None).unwrap(); @@ -411,7 +411,7 @@ fn test_onion_failure() { }, |msg| { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), NODE|2, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -421,7 +421,7 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("temporary_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { // and tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), NODE|2, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -435,7 +435,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -444,7 +444,7 @@ fn test_onion_failure() { // final node failure run_onion_failure_test_with_fail_intercept("permanent_node_failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|2, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -458,7 +458,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -469,7 +469,7 @@ fn test_onion_failure() { // final node failure run_onion_failure_test_with_fail_intercept("required_node_feature_missing", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), PERM|NODE|3, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -501,7 +501,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data, 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -515,7 +515,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), UPDATE|7, &err_data_without_type, 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -528,7 +528,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|8, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -540,7 +540,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[0].shared_secret.as_ref(), PERM|9, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -662,7 +662,7 @@ fn test_onion_failure() { let mut route = route.clone(); let height = nodes[2].best_block_info().1; route.paths[0].hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.paths[0].hops[0].cltv_expiry_delta + 1; - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads( &route.paths[0], 40000, &recipient_onion_fields, height, &None, None, None).unwrap(); @@ -674,7 +674,7 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("mpp_timeout", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { // Tamper returning error message let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let failure = onion_utils::build_failure_packet(onion_keys[1].shared_secret.as_ref(), 23, &[0;0], 0); msg.reason = failure.data; msg.attribution_data = failure.attribution_data; @@ -685,7 +685,7 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("bogus err packet with valid hmac", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![0], pad: vec![0; 255], @@ -719,7 +719,7 @@ fn test_onion_failure() { msg.amount_msat -= 1; }, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 @@ -750,7 +750,7 @@ fn test_onion_failure() { run_onion_failure_test_with_fail_intercept("0-length channel update in final node UPDATE onion failure", 200, &nodes, &route, &payment_hash, &payment_secret, |_msg| {}, |msg| { let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); - let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let mut decoded_err_packet = msgs::DecodedOnionErrorPacket { failuremsg: vec![ 0x10, 0x7, // UPDATE|7 @@ -1176,7 +1176,7 @@ fn test_trampoline_onion_payload_assembly_values() { let prng_seed = onion_utils::gen_pad_from_shared_secret(&session_priv.secret_bytes()); let payment_hash = PaymentHash(session_priv.secret_bytes()); - let onion_keys = construct_trampoline_onion_keys(&secp_ctx, &path.blinded_tail.as_ref().unwrap(), &session_priv).unwrap(); + let onion_keys = construct_trampoline_onion_keys(&secp_ctx, &path.blinded_tail.as_ref().unwrap(), &session_priv); let trampoline_packet = construct_trampoline_onion_packet( trampoline_payloads, onion_keys, @@ -1295,7 +1295,7 @@ fn test_trampoline_onion_payload_construction_vectors() { }), }; - let trampoline_onion_keys = construct_trampoline_onion_keys(&Secp256k1::new(), &trampoline_hops.blinded_tail.unwrap(), &trampoline_session_key).unwrap(); + let trampoline_onion_keys = construct_trampoline_onion_keys(&Secp256k1::new(), &trampoline_hops.blinded_tail.unwrap(), &trampoline_session_key); let trampoline_onion_packet = construct_trampoline_onion_packet(trampoline_payloads, trampoline_onion_keys, [0u8; 32], &associated_data, None).unwrap(); let trampoline_onion_packet_hex = trampoline_onion_packet.encode().to_lower_hex_string(); assert_eq!(trampoline_onion_packet_hex, "0002bc59a9abc893d75a8d4f56a6572f9a3507323a8de22abe0496ea8d37da166a8b4bba0e560f1a9deb602bfd98fe9167141d0b61d669df90c0149096d505b85d3d02806e6c12caeb308b878b6bc7f1b15839c038a6443cd3bec3a94c2293165375555f6d7720862b525930f41fddcc02260d197abd93fb58e60835fd97d9dc14e7979c12f59df08517b02e3e4d50e1817de4271df66d522c4e9675df71c635c4176a8381bc22b342ff4e9031cede87f74cc039fca74aa0a3786bc1db2e158a9a520ecb99667ef9a6bbfaf5f0e06f81c27ca48134ba2103229145937c5dc7b8ecc5201d6aeb592e78faa3c05d3a035df77628f0be9b1af3ef7d386dd5cc87b20778f47ebd40dbfcf12b9071c5d7112ab84c3e0c5c14867e684d09a18bc93ac47d73b7343e3403ef6e3b70366835988920e7d772c3719d3596e53c29c4017cb6938421a557ce81b4bb26701c25bf622d4c69f1359dc85857a375c5c74987a4d3152f66987001c68a50c4bf9e0b1dab4ad1a64b0535319bbf6c4fbe4f9c50cb65f5ef887bfb91b0a57c0f86ba3d91cbeea1607fb0c12c6c75d03bbb0d3a3019c40597027f5eebca23083e50ec79d41b1152131853525bf3fc13fb0be62c2e3ce733f59671eee5c4064863fb92ae74be9ca68b9c716f9519fd268478ee27d91d466b0de51404de3226b74217d28250ead9d2c95411e0230570f547d4cc7c1d589791623131aa73965dccc5aa17ec12b442215ce5d346df664d799190df5dd04a13"); @@ -1354,7 +1354,7 @@ fn test_trampoline_onion_payload_construction_vectors() { assert_eq!(carol_payload, "fd0255020408f3272804030c353c08247494b65bc092b48a75465e43e29be807eb2cc535ce8aaba31012b8ff1ceac5da08f3272814fd02200002bc59a9abc893d75a8d4f56a6572f9a3507323a8de22abe0496ea8d37da166a8b4bba0e560f1a9deb602bfd98fe9167141d0b61d669df90c0149096d505b85d3d02806e6c12caeb308b878b6bc7f1b15839c038a6443cd3bec3a94c2293165375555f6d7720862b525930f41fddcc02260d197abd93fb58e60835fd97d9dc14e7979c12f59df08517b02e3e4d50e1817de4271df66d522c4e9675df71c635c4176a8381bc22b342ff4e9031cede87f74cc039fca74aa0a3786bc1db2e158a9a520ecb99667ef9a6bbfaf5f0e06f81c27ca48134ba2103229145937c5dc7b8ecc5201d6aeb592e78faa3c05d3a035df77628f0be9b1af3ef7d386dd5cc87b20778f47ebd40dbfcf12b9071c5d7112ab84c3e0c5c14867e684d09a18bc93ac47d73b7343e3403ef6e3b70366835988920e7d772c3719d3596e53c29c4017cb6938421a557ce81b4bb26701c25bf622d4c69f1359dc85857a375c5c74987a4d3152f66987001c68a50c4bf9e0b1dab4ad1a64b0535319bbf6c4fbe4f9c50cb65f5ef887bfb91b0a57c0f86ba3d91cbeea1607fb0c12c6c75d03bbb0d3a3019c40597027f5eebca23083e50ec79d41b1152131853525bf3fc13fb0be62c2e3ce733f59671eee5c4064863fb92ae74be9ca68b9c716f9519fd268478ee27d91d466b0de51404de3226b74217d28250ead9d2c95411e0230570f547d4cc7c1d589791623131aa73965dccc5aa17ec12b442215ce5d346df664d799190df5dd04a13"); let outer_session_key = SecretKey::from_slice(&>::from_hex("4f777e8dac16e6dfe333066d9efb014f7a51d11762ff76eca4d3a95ada99ba3e").unwrap()).unwrap(); - let outer_onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key).unwrap(); + let outer_onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &outer_hops, &outer_session_key); let outer_onion_prng_seed = onion_utils::gen_pad_from_shared_secret(&outer_session_key.secret_bytes()); let outer_onion_packet = onion_utils::construct_onion_packet(outer_payloads, outer_onion_keys, outer_onion_prng_seed, &associated_data).unwrap(); let outer_onion_packet_hex = outer_onion_packet.encode().to_lower_hex_string(); @@ -1580,7 +1580,7 @@ fn test_phantom_invalid_onion_payload() { // Construct the onion payloads for the entire route and an invalid amount. let height = nodes[0].best_block_info().1; let session_priv = SecretKey::from_slice(&session_priv).unwrap(); - let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let mut onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv); let recipient_onion_fields = RecipientOnionFields::secret_only(payment_secret); let (mut onion_payloads, _, _) = onion_utils::build_onion_payloads( &route.paths[0], msgs::MAX_VALUE_MSAT + 1, diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 3b7a369c9aa..c6f5b30c81b 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -312,8 +312,7 @@ impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundTrampolinePayload<'a> { fn construct_onion_keys_generic_callback<'a, T, H, FType>( secp_ctx: &Secp256k1, hops: &'a [H], blinded_tail: Option<&BlindedTail>, session_priv: &SecretKey, mut callback: FType, -) -> Result<(), secp256k1::Error> -where +) where T: secp256k1::Signing, H: HopInfo, FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&'a H>, usize), @@ -338,19 +337,19 @@ where let ephemeral_pubkey = blinded_pub; - blinded_priv = blinded_priv.mul_tweak(&Scalar::from_be_bytes(blinding_factor).unwrap())?; + blinded_priv = blinded_priv + .mul_tweak(&Scalar::from_be_bytes(blinding_factor).expect("You broke SHA-256")) + .expect("Blinding are never invalid as we picked the starting private key randomly"); blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv); callback(shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx); } - - Ok(()) } // can only fail if an intermediary hop has an invalid public key or session_priv is invalid pub(super) fn construct_onion_keys( secp_ctx: &Secp256k1, path: &Path, session_priv: &SecretKey, -) -> Result, secp256k1::Error> { +) -> Vec { let mut res = Vec::with_capacity(path.hops.len()); let blinded_tail = path.blinded_tail.as_ref().and_then(|t| { @@ -377,15 +376,15 @@ pub(super) fn construct_onion_keys( mu, }); }, - )?; + ); - Ok(res) + res } // can only fail if an intermediary hop has an invalid public key or session_priv is invalid pub(super) fn construct_trampoline_onion_keys( secp_ctx: &Secp256k1, blinded_tail: &BlindedTail, session_priv: &SecretKey, -) -> Result, secp256k1::Error> { +) -> Vec { let mut res = Vec::with_capacity(blinded_tail.trampoline_hops.len()); construct_onion_keys_generic_callback( @@ -406,9 +405,9 @@ pub(super) fn construct_trampoline_onion_keys( mu, }); }, - )?; + ); - Ok(res) + res } pub(super) fn build_trampoline_onion_payloads<'a>( @@ -1123,8 +1122,7 @@ where |shared_secret, _, _, route_hop_option: Option<&RouteHop>, _| { onion_keys.push((route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret)) }, - ) - .expect("Route we used spontaneously grew invalid keys in the middle of it?"); + ); if path.has_trampoline_hops() { construct_onion_keys_generic_callback( @@ -1139,8 +1137,7 @@ where shared_secret, )) }, - ) - .expect("Route we used spontaneously grew invalid keys in the middle of it?"); + ); } // In the best case, paths can be up to 27 hops. But attribution data can only be conveyed back to the sender from @@ -2103,11 +2100,7 @@ pub(crate) fn create_payment_onion_internal( )?; let onion_keys = - construct_trampoline_onion_keys(&secp_ctx, &blinded_tail, &session_priv).map_err( - |_| APIError::InvalidRoute { - err: "Pubkey along hop was maliciously selected".to_owned(), - }, - )?; + construct_trampoline_onion_keys(&secp_ctx, &blinded_tail, &session_priv); let trampoline_packet = construct_trampoline_onion_packet( trampoline_payloads, onion_keys, @@ -2140,9 +2133,7 @@ pub(crate) fn create_payment_onion_internal( )?; let outer_session_priv = outer_session_priv_override.as_ref().unwrap_or(session_priv); - let onion_keys = construct_onion_keys(&secp_ctx, &path, outer_session_priv).map_err(|_| { - APIError::InvalidRoute { err: "Pubkey along hop was maliciously selected".to_owned() } - })?; + let onion_keys = construct_onion_keys(&secp_ctx, &path, outer_session_priv); let outer_onion_prng_seed = secondary_prng_seed.unwrap_or(prng_seed); let onion_packet = construct_onion_packet(onion_payloads, onion_keys, outer_onion_prng_seed, payment_hash) @@ -2541,8 +2532,7 @@ mod tests { let route = Route { paths: vec![path], route_params: None }; let onion_keys = - super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()) - .unwrap(); + super::construct_onion_keys(&secp_ctx, &route.paths[0], &get_test_session_key()); assert_eq!(onion_keys.len(), route.paths[0].hops.len()); onion_keys } @@ -3014,13 +3004,12 @@ mod tests { &secp_ctx, &path.blinded_tail.as_ref().unwrap(), &session_priv, - ) - .unwrap(); + ); let outer_onion_keys = { let session_priv_hash = Sha256::hash(&session_priv.secret_bytes()).to_byte_array(); let outer_session_priv = SecretKey::from_slice(&session_priv_hash[..]).unwrap(); - construct_onion_keys(&Secp256k1::new(), &path, &outer_session_priv).unwrap() + construct_onion_keys(&Secp256k1::new(), &path, &outer_session_priv) }; let htlc_source = HTLCSource::OutboundRoute { @@ -3210,8 +3199,7 @@ mod tests { None, &session_key, |shared_secret, _, _, _, _| onion_keys.push(shared_secret), - ) - .unwrap(); + ); // Construct the htlc source. let logger = TestLogger::new(); From f4a22e24ff028ae3056f8f2e73b8615ed54a6984 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Thu, 10 Apr 2025 16:35:04 +0000 Subject: [PATCH 3/4] Use `impl Iterator` rather than a callback when building onion keys The onion keys building logic is rather ancient and predates the `-> impl Trait` syntax in Rust, and thus used a callback. Here we move it to an `impl Iterator` which is much Rustier and cleans up the code a bit. --- lightning/src/ln/onion_utils.rs | 142 ++++++++++++++------------------ 1 file changed, 62 insertions(+), 80 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index c6f5b30c81b..2440219dbd1 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -308,26 +308,25 @@ impl<'a, 'b> OnionPayload<'a, 'b> for msgs::OutboundTrampolinePayload<'a> { } } -#[inline] -fn construct_onion_keys_generic_callback<'a, T, H, FType>( - secp_ctx: &Secp256k1, hops: &'a [H], blinded_tail: Option<&BlindedTail>, - session_priv: &SecretKey, mut callback: FType, -) where +fn construct_onion_keys_generic<'a, T, H>( + secp_ctx: &'a Secp256k1, hops: &'a [H], blinded_tail: Option<&'a BlindedTail>, + session_priv: &SecretKey, +) -> impl Iterator, usize)> + 'a +where T: secp256k1::Signing, H: HopInfo, - FType: FnMut(SharedSecret, [u8; 32], PublicKey, Option<&'a H>, usize), { let mut blinded_priv = session_priv.clone(); let mut blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv); - let unblinded_hops_iter = hops.iter().map(|h| (h.node_pubkey(), Some(h))); - let blinded_pks_iter = blinded_tail + let unblinded_hops = hops.iter().map(|h| (h.node_pubkey(), Some(h))); + let blinded_pubkeys = blinded_tail .map(|t| t.hops.iter()) .unwrap_or([].iter()) .skip(1) // Skip the intro node because it's included in the unblinded hops .map(|h| (&h.blinded_node_id, None)); - for (idx, (pubkey, route_hop_opt)) in unblinded_hops_iter.chain(blinded_pks_iter).enumerate() { + unblinded_hops.chain(blinded_pubkeys).enumerate().map(move |(idx, (pubkey, route_hop_opt))| { let shared_secret = SharedSecret::new(pubkey, &blinded_priv); let mut sha = Sha256::engine(); @@ -342,8 +341,8 @@ fn construct_onion_keys_generic_callback<'a, T, H, FType>( .expect("Blinding are never invalid as we picked the starting private key randomly"); blinded_pub = PublicKey::from_secret_key(secp_ctx, &blinded_priv); - callback(shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx); - } + (shared_secret, blinding_factor, ephemeral_pubkey, route_hop_opt, idx) + }) } // can only fail if an intermediary hop has an invalid public key or session_priv is invalid @@ -358,25 +357,20 @@ pub(super) fn construct_onion_keys( } Some(t) }); - construct_onion_keys_generic_callback( - secp_ctx, - &path.hops, - blinded_tail, - session_priv, - |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| { - let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref()); - - res.push(OnionKeys { - #[cfg(test)] - shared_secret, - #[cfg(test)] - blinding_factor: _blinding_factor, - ephemeral_pubkey, - rho, - mu, - }); - }, - ); + let iter = construct_onion_keys_generic(secp_ctx, &path.hops, blinded_tail, session_priv); + for (shared_secret, _blinding_factor, ephemeral_pubkey, _, _) in iter { + let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref()); + + res.push(OnionKeys { + #[cfg(test)] + shared_secret, + #[cfg(test)] + blinding_factor: _blinding_factor, + ephemeral_pubkey, + rho, + mu, + }); + } res } @@ -387,25 +381,21 @@ pub(super) fn construct_trampoline_onion_keys( ) -> Vec { let mut res = Vec::with_capacity(blinded_tail.trampoline_hops.len()); - construct_onion_keys_generic_callback( - secp_ctx, - &blinded_tail.trampoline_hops, - Some(blinded_tail), - session_priv, - |shared_secret, _blinding_factor, ephemeral_pubkey, _, _| { - let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref()); - - res.push(OnionKeys { - #[cfg(test)] - shared_secret, - #[cfg(test)] - blinding_factor: _blinding_factor, - ephemeral_pubkey, - rho, - mu, - }); - }, - ); + let hops = &blinded_tail.trampoline_hops; + let iter = construct_onion_keys_generic(secp_ctx, &hops, Some(blinded_tail), session_priv); + for (shared_secret, _blinding_factor, ephemeral_pubkey, _, _) in iter { + let (rho, mu) = gen_rho_mu_from_shared_secret(shared_secret.as_ref()); + + res.push(OnionKeys { + #[cfg(test)] + shared_secret, + #[cfg(test)] + blinding_factor: _blinding_factor, + ephemeral_pubkey, + rho, + mu, + }); + } res } @@ -1113,31 +1103,27 @@ where let mut onion_keys = Vec::with_capacity(path.hops.len() + num_trampoline_hops + num_blinded_hops); - construct_onion_keys_generic_callback( - secp_ctx, - &path.hops, - // if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion - if path.has_trampoline_hops() { None } else { path.blinded_tail.as_ref() }, - outer_session_priv, - |shared_secret, _, _, route_hop_option: Option<&RouteHop>, _| { - onion_keys.push((route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret)) - }, - ); + // if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion + let nontrampoline_bp = + if path.has_trampoline_hops() { None } else { path.blinded_tail.as_ref() }; + let nontrampoline_hops = + construct_onion_keys_generic(secp_ctx, &path.hops, nontrampoline_bp, outer_session_priv); + for (shared_secret, _, _, route_hop_option, _) in nontrampoline_hops { + onion_keys.push((route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret)); + } if path.has_trampoline_hops() { - construct_onion_keys_generic_callback( - secp_ctx, - // Trampoline hops are part of the blinded tail, so this can never panic - &path.blinded_tail.as_ref().unwrap().trampoline_hops, - path.blinded_tail.as_ref(), - inner_session_priv.expect("Trampoline hops always have an inner session priv"), - |shared_secret, _, _, trampoline_hop_option: Option<&TrampolineHop>, _| { - onion_keys.push(( - trampoline_hop_option.map(|th| ErrorHop::TrampolineHop(th)), - shared_secret, - )) - }, - ); + // Trampoline hops are part of the blinded tail, so this can never panic + let blinded_tail = path.blinded_tail.as_ref(); + let hops = &blinded_tail.unwrap().trampoline_hops; + let inner_session_priv = + inner_session_priv.expect("Trampoline hops always have an inner session priv"); + let trampoline_hops = + construct_onion_keys_generic(secp_ctx, hops, blinded_tail, inner_session_priv); + for (shared_secret, _, _, trampoline_hop_option, _) in trampoline_hops { + onion_keys + .push((trampoline_hop_option.map(|th| ErrorHop::TrampolineHop(th)), shared_secret)); + } } // In the best case, paths can be up to 27 hops. But attribution data can only be conveyed back to the sender from @@ -3191,15 +3177,11 @@ mod tests { let path = Path { hops, blinded_tail: None }; // Calculate shared secrets. - let mut onion_keys = Vec::new(); let session_key = get_test_session_key(); - construct_onion_keys_generic_callback( - &secp_ctx, - &path.hops, - None, - &session_key, - |shared_secret, _, _, _, _| onion_keys.push(shared_secret), - ); + let onion_keys: Vec<_> = + construct_onion_keys_generic(&secp_ctx, &path.hops, None, &session_key) + .map(|(key, ..)| key) + .collect(); // Construct the htlc source. let logger = TestLogger::new(); From ed0a525b69ce60eac0c4331e3ccfa41bd2e46826 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 14 Mar 2025 19:18:35 +0000 Subject: [PATCH 4/4] Use iterators directly in `process_onion_failure_inner` Now that `create_onion_keys_generic` returns an iterator rather than relying on a callback, making `process_onion_failure_inner` chain the non-trampoline and trampoline iterators is trivial. We do so here, avoiding the extra vec allocation. --- lightning/src/ln/onion_utils.rs | 43 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 2440219dbd1..5c6668ec11c 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1095,44 +1095,39 @@ where } } - let (num_blinded_hops, num_trampoline_hops) = - path.blinded_tail.as_ref().map_or((0, 0), |bt| (bt.hops.len(), bt.trampoline_hops.len())); - - // We are first collecting all the unblinded `RouteHop`s inside `onion_keys`. Then, if applicable, - // we will add all the `TrampolineHop`s, and finally, the blinded hops. - let mut onion_keys = - Vec::with_capacity(path.hops.len() + num_trampoline_hops + num_blinded_hops); + let num_blinded_hops = path.blinded_tail.as_ref().map_or(0, |bt| bt.hops.len()); // if we have Trampoline hops, the blinded hops are part of the inner Trampoline onion - let nontrampoline_bp = + let nontrampoline_bt = if path.has_trampoline_hops() { None } else { path.blinded_tail.as_ref() }; - let nontrampoline_hops = - construct_onion_keys_generic(secp_ctx, &path.hops, nontrampoline_bp, outer_session_priv); - for (shared_secret, _, _, route_hop_option, _) in nontrampoline_hops { - onion_keys.push((route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret)); - } + let nontrampolines = + construct_onion_keys_generic(secp_ctx, &path.hops, nontrampoline_bt, outer_session_priv) + .map(|(shared_secret, _, _, route_hop_option, _)| { + (route_hop_option.map(|rh| ErrorHop::RouteHop(rh)), shared_secret) + }); - if path.has_trampoline_hops() { + let trampolines = if path.has_trampoline_hops() { // Trampoline hops are part of the blinded tail, so this can never panic let blinded_tail = path.blinded_tail.as_ref(); let hops = &blinded_tail.unwrap().trampoline_hops; let inner_session_priv = inner_session_priv.expect("Trampoline hops always have an inner session priv"); - let trampoline_hops = - construct_onion_keys_generic(secp_ctx, hops, blinded_tail, inner_session_priv); - for (shared_secret, _, _, trampoline_hop_option, _) in trampoline_hops { - onion_keys - .push((trampoline_hop_option.map(|th| ErrorHop::TrampolineHop(th)), shared_secret)); - } - } + Some(construct_onion_keys_generic(secp_ctx, hops, blinded_tail, inner_session_priv).map( + |(shared_secret, _, _, route_hop_option, _)| { + (route_hop_option.map(|tram_hop| ErrorHop::TrampolineHop(tram_hop)), shared_secret) + }, + )) + } else { + None + }; // In the best case, paths can be up to 27 hops. But attribution data can only be conveyed back to the sender from // the first 20 hops. Determine the number of hops to be used for attribution data. let attributable_hop_count = usize::min(path.hops.len(), MAX_HOPS); // Handle packed channel/node updates for passing back for the route handler - let mut iterator = onion_keys.into_iter().enumerate().peekable(); - while let Some((route_hop_idx, (route_hop_option, shared_secret))) = iterator.next() { + let mut iter = nontrampolines.chain(trampolines.into_iter().flatten()).enumerate().peekable(); + while let Some((route_hop_idx, (route_hop_option, shared_secret))) = iter.next() { let route_hop = match route_hop_option.as_ref() { Some(hop) => hop, None => { @@ -1154,7 +1149,7 @@ where // For 1-hop blinded paths, the final `ErrorHop` entry is the recipient. // In our case that means that if we're on the last iteration, and there is no more than one // blinded hop, the current iteration references the last non-blinded hop. - let next_hop = iterator.peek(); + let next_hop = iter.peek(); is_from_final_non_blinded_node = next_hop.is_none() && num_blinded_hops <= 1; let failing_route_hop = if is_from_final_non_blinded_node { route_hop