diff --git a/Cargo.toml b/Cargo.toml index 8c088524416..41a91996f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,6 @@ check-cfg = [ "cfg(ldk_bench)", "cfg(ldk_test_vectors)", "cfg(taproot)", - "cfg(trampoline)", "cfg(require_route_graph_test)", "cfg(splicing)", "cfg(async_payments)", diff --git a/ci/ci-tests.sh b/ci/ci-tests.sh index 9bc23d042ec..7eacd9c4744 100755 --- a/ci/ci-tests.sh +++ b/ci/ci-tests.sh @@ -134,8 +134,6 @@ RUSTFLAGS="--cfg=taproot" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=splicing" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean -RUSTFLAGS="--cfg=trampoline" cargo test --verbose --color always -p lightning -[ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=async_payments" cargo test --verbose --color always -p lightning [ "$CI_MINIMIZE_DISK_USAGE" != "" ] && cargo clean RUSTFLAGS="--cfg=lsps1_service" cargo test --verbose --color always -p lightning-liquidity diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index e4678a2322e..b0639dd57e5 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -298,7 +298,6 @@ pub struct ForwardTlvs { } /// Data to construct a [`BlindedHop`] for forwarding a Trampoline payment. -#[cfg(trampoline)] #[derive(Clone, Debug)] pub struct TrampolineForwardTlvs { /// The node id to which the trampoline node must find a route. @@ -371,7 +370,6 @@ pub(crate) enum BlindedPaymentTlvs { /// Data to construct a [`BlindedHop`] for sending a Trampoline payment over. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop -#[cfg(trampoline)] pub(crate) enum BlindedTrampolineTlvs { /// This blinded payment data is for a forwarding node. Forward(TrampolineForwardTlvs), @@ -514,6 +512,23 @@ impl Writeable for ForwardTlvs { } } +impl Writeable for TrampolineForwardTlvs { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + let features_opt = if self.features == BlindedHopFeatures::empty() { + None + } else { + Some(WithoutLength(&self.features)) + }; + encode_tlv_stream!(w, { + (4, self.next_trampoline, required), + (10, self.payment_relay, required), + (12, self.payment_constraints, required), + (14, features_opt, option) + }); + Ok(()) + } +} + impl Writeable for ReceiveTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { @@ -591,14 +606,13 @@ impl Readable for BlindedPaymentTlvs { } } -#[cfg(trampoline)] impl Readable for BlindedTrampolineTlvs { fn read(r: &mut R) -> Result { _init_and_read_tlv_stream!(r, { + (4, next_trampoline, option), (8, next_blinding_override, option), (10, payment_relay, option), (12, payment_constraints, required), - (14, next_trampoline, option), (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, option), diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 89b83ede3eb..17494b06098 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -40,6 +40,7 @@ use crate::util::test_utils; use lightning_invoice::RawBolt11Invoice; use types::features::Features; use crate::blinded_path::BlindedHop; +use crate::routing::router::Route; pub fn blinded_payment_path( payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64, @@ -1816,7 +1817,6 @@ fn test_combined_trampoline_onion_creation_vectors() { } #[test] -#[cfg(trampoline)] fn test_trampoline_inbound_payment_decoding() { let secp_ctx = Secp256k1::new(); let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303"); @@ -1958,3 +1958,505 @@ fn test_trampoline_inbound_payment_decoding() { panic!(); }; } + +fn do_test_trampoline_single_hop_receive(success: bool) { + const TOTAL_NODE_COUNT: usize = 3; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let alice_node_id = nodes[0].node().get_our_node_id(); + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + + let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); + let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv); + let carol_blinded_hops = if success { + let payee_tlvs = UnauthenticatedReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + payment_context: PaymentContext::Bolt12Refund(Bolt12RefundContext {}), + }; + + let nonce = Nonce([42u8; 16]); + let expanded_key = nodes[2].keys_manager.get_inbound_payment_key(); + let payee_tlvs = payee_tlvs.authenticate(nonce, &expanded_key); + let carol_unblinded_tlvs = payee_tlvs.encode(); + + let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + blinded_path::utils::construct_blinded_hops( + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + ).unwrap() + } else { + let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs { + next_trampoline: alice_node_id, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + features: BlindedHopFeatures::empty(), + payment_relay: PaymentRelay { + cltv_expiry_delta: 0, + fee_proportional_millionths: 0, + fee_base_msat: 0, + }, + next_blinding_override: None, + }; + + let carol_unblinded_tlvs = payee_tlvs.encode(); + let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + blinded_path::utils::construct_blinded_hops( + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + ).unwrap() + }; + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 24, + }, + ], + hops: carol_blinded_hops, + blinding_point: carol_blinding_point, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + + check_added_monitors!(&nodes[0], 1); + + if success { + pass_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], amt_msat, payment_hash, payment_secret); + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); + } else { + let replacement_onion = { + // create a substitute onion where the last Trampoline hop is a forward + let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799"); + let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9"); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + + let mut blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); + + // append some dummy blinded hop so the intro hop looks like a forward + blinded_tail.hops.push(BlindedHop { + blinded_node_id: alice_node_id, + encrypted_payload: vec![], + }); + + let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + + // 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_packet = onion_utils::construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + None, + ).unwrap(); + + 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_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + ).unwrap(); + + outer_packet + }; + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut update_message = match first_message_event { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + }, + _ => panic!() + }; + update_message.map(|msg| { + msg.onion_routing_packet = replacement_onion.clone(); + }); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::InvalidOnion); + do_pass_along_path(args); + + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + { + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, true, payment_failed_conditions); + } + } +} + +#[test] +fn test_trampoline_single_hop_receive() { + // Simulate a payment of A (0) -> B (1) -> C(Trampoline (blinded intro)) (2) + do_test_trampoline_single_hop_receive(true); + + // Simulate a payment failure of A (0) -> B (1) -> C(Trampoline (blinded forward)) (2) + do_test_trampoline_single_hop_receive(false); +} + +#[test] +fn test_trampoline_unblinded_receive() { + // Simulate a payment of A (0) -> B (1) -> C(Trampoline) (2) + + const TOTAL_NODE_COUNT: usize = 3; + let secp_ctx = Secp256k1::new(); + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let alice_node_id = nodes[0].node().get_our_node_id(); + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let payee_tlvs = blinded_path::payment::TrampolineForwardTlvs { + next_trampoline: alice_node_id, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: amt_msat, + }, + features: BlindedHopFeatures::empty(), + payment_relay: PaymentRelay { + cltv_expiry_delta: 0, + fee_proportional_millionths: 0, + fee_base_msat: 0, + }, + next_blinding_override: None, + }; + + let carol_unblinded_tlvs = payee_tlvs.encode(); + let path = [(carol_node_id, WithoutLength(&carol_unblinded_tlvs))]; + let carol_alice_trampoline_session_priv = secret_from_hex("a0f4b8d7b6c2d0ffdfaf718f76e9decaef4d9fb38a8c4addb95c4007cc3eee03"); + let carol_blinding_point = PublicKey::from_secret_key(&secp_ctx, &carol_alice_trampoline_session_priv); + let carol_blinded_hops = blinded_path::utils::construct_blinded_hops( + &secp_ctx, path.into_iter(), &carol_alice_trampoline_session_priv + ).unwrap(); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 24, + }, + ], + hops: carol_blinded_hops, + blinding_point: carol_blinding_point, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + + let replacement_onion = { + // create a substitute onion where the last Trampoline hop is an unblinded receive, which we + // (deliberately) do not support out of the box, therefore necessitating this workaround + let trampoline_secret_key = secret_from_hex("0134928f7b7ca6769080d70f16be84c812c741f545b49a34db47ce338a205799"); + let prng_seed = secret_from_hex("fe02b4b9054302a3ddf4e1e9f7c411d644aebbd295218ab009dca94435f775a9"); + let recipient_onion_fields = RecipientOnionFields::spontaneous_empty(); + + let blinded_tail = route.paths[0].blinded_tail.clone().unwrap(); + let (mut trampoline_payloads, outer_total_msat, outer_starting_htlc_offset) = onion_utils::build_trampoline_onion_payloads(&blinded_tail, amt_msat, &recipient_onion_fields, 32, &None).unwrap(); + + // pop the last dummy hop + trampoline_payloads.pop(); + + trampoline_payloads.push(msgs::OutboundTrampolinePayload::Receive { + payment_data: Some(msgs::FinalOnionHopData { + payment_secret, + total_msat: amt_msat, + }), + sender_intended_htlc_amt_msat: amt_msat, + 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_packet = onion_utils::construct_trampoline_onion_packet( + trampoline_payloads, + trampoline_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + None, + ).unwrap(); + + 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_packet = onion_utils::construct_onion_packet( + outer_payloads, + outer_onion_keys, + prng_seed.secret_bytes(), + &payment_hash, + ).unwrap(); + + outer_packet + }; + + check_added_monitors!(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut update_message = match first_message_event { + MessageSendEvent::UpdateHTLCs { ref mut updates, .. } => { + assert_eq!(updates.update_add_htlcs.len(), 1); + updates.update_add_htlcs.get_mut(0) + }, + _ => panic!() + }; + update_message.map(|msg| { + msg.onion_routing_packet = replacement_onion.clone(); + }); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_secret(payment_secret); + do_pass_along_path(args); + + claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage); +} + +#[test] +fn test_trampoline_forward_rejection() { + const TOTAL_NODE_COUNT: usize = 3; + + let chanmon_cfgs = create_chanmon_cfgs(TOTAL_NODE_COUNT); + let node_cfgs = create_node_cfgs(TOTAL_NODE_COUNT, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(TOTAL_NODE_COUNT, &node_cfgs, &vec![None; TOTAL_NODE_COUNT]); + let mut nodes = create_network(TOTAL_NODE_COUNT, &node_cfgs, &node_chanmgrs); + + let (_, _, chan_id_alice_bob, _) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (_, _, chan_id_bob_carol, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + for i in 0..TOTAL_NODE_COUNT { // connect all nodes' blocks + connect_blocks(&nodes[i], (TOTAL_NODE_COUNT as u32) * CHAN_CONFIRM_DEPTH + 1 - nodes[i].best_block_info().1); + } + + let alice_node_id = nodes[0].node().get_our_node_id(); + let bob_node_id = nodes[1].node().get_our_node_id(); + let carol_node_id = nodes[2].node().get_our_node_id(); + + let alice_bob_scid = nodes[0].node().list_channels().iter().find(|c| c.channel_id == chan_id_alice_bob).unwrap().short_channel_id.unwrap(); + let bob_carol_scid = nodes[1].node().list_channels().iter().find(|c| c.channel_id == chan_id_bob_carol).unwrap().short_channel_id.unwrap(); + + let amt_msat = 1000; + let (payment_preimage, payment_hash, _) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + + let route = Route { + paths: vec![Path { + hops: vec![ + // Bob + RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: alice_bob_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 1000, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + }, + + // Carol + RouteHop { + pubkey: carol_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: bob_carol_scid, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, + cltv_expiry_delta: 48, + maybe_announced_channel: false, + } + ], + blinded_tail: Some(BlindedTail { + trampoline_hops: vec![ + // Carol + TrampolineHop { + pubkey: carol_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 24, + }, + + // Alice (unreachable) + TrampolineHop { + pubkey: alice_node_id, + node_features: Features::empty(), + fee_msat: amt_msat, + cltv_expiry_delta: 24, + }, + ], + hops: vec![BlindedHop{ + // Fake public key + blinded_node_id: alice_node_id, + encrypted_payload: vec![], + }], + blinding_point: alice_node_id, + excess_final_cltv_expiry_delta: 39, + final_value_msat: amt_msat, + }) + }], + route_params: None, + }; + + nodes[0].node.send_payment_with_route(route.clone(), payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0)).unwrap(); + + check_added_monitors!(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let first_message_event = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + + let route: &[&Node] = &[&nodes[1], &nodes[2]]; + let args = PassAlongPathArgs::new(&nodes[0], route, amt_msat, payment_hash, first_message_event) + .with_payment_preimage(payment_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + nodes[1].node.handle_update_fail_htlc( + nodes[2].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[1], &nodes[2], &unblinded_node_updates.commitment_signed, true, false); + } + { + let unblinded_node_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc( + nodes[1].node.get_our_node_id(), &unblinded_node_updates.update_fail_htlcs[0] + ); + do_commitment_signed_dance(&nodes[0], &nodes[1], &unblinded_node_updates.commitment_signed, false, false); + } + { + // Expect a PERM|10 (unknown_next_peer) error while we are unable to route forwarding + // Trampoline payments. + let payment_failed_conditions = PaymentFailedConditions::new() + .expected_htlc_error_data(0x4000 | 10, &[0; 0]); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + } +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index fab15bfea28..26be25198b4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -170,7 +170,6 @@ pub enum PendingHTLCRouting { incoming_cltv_expiry: Option, }, /// An HTLC which should be forwarded on to another Trampoline node. - #[cfg(trampoline)] TrampolineForward { /// The onion shared secret we build with the sender (or the preceding Trampoline node) used /// to decrypt the onion. @@ -288,7 +287,6 @@ impl PendingHTLCRouting { fn blinded_failure(&self) -> Option { match self { Self::Forward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure), - #[cfg(trampoline)] Self::TrampolineForward { blinded: Some(BlindedForward { failure, .. }), .. } => Some(*failure), Self::Receive { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode), Self::ReceiveKeysend { requires_blinded_error: true, .. } => Some(BlindedFailure::FromBlindedNode), @@ -299,7 +297,6 @@ impl PendingHTLCRouting { fn incoming_cltv_expiry(&self) -> Option { match self { Self::Forward { incoming_cltv_expiry, .. } => *incoming_cltv_expiry, - #[cfg(trampoline)] Self::TrampolineForward { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry), Self::Receive { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry), Self::ReceiveKeysend { incoming_cltv_expiry, .. } => Some(*incoming_cltv_expiry), @@ -4510,24 +4507,7 @@ where } } match decoded_hop { - onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } => { - // OUR PAYMENT! - let current_height: u32 = self.best_block.read().unwrap().height; - match create_recv_pending_htlc_info(decoded_hop, shared_secret, msg.payment_hash, - msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, - current_height) - { - Ok(info) => { - // Note that we could obviously respond immediately with an update_fulfill_htlc - // message, however that would leak that we are the recipient of this payment, so - // instead we stay symmetric with the forwarding case, only responding (after a - // delay) once they've sent us a commitment_signed! - PendingHTLCStatus::Forward(info) - }, - Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) - } - }, - #[cfg(trampoline)] + onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } | onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => { // OUR PAYMENT! let current_height: u32 = self.best_block.read().unwrap().height; @@ -4551,7 +4531,6 @@ where Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) } }, - #[cfg(trampoline)] onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { match create_fwd_pending_htlc_info(msg, decoded_hop, shared_secret, next_packet_pubkey_opt) { Ok(info) => PendingHTLCStatus::Forward(info), @@ -9067,7 +9046,6 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ for (forward_info, prev_htlc_id) in pending_forwards.drain(..) { let scid = match forward_info.routing { PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id, - #[cfg(trampoline)] PendingHTLCRouting::TrampolineForward { .. } => 0, PendingHTLCRouting::Receive { .. } => 0, PendingHTLCRouting::ReceiveKeysend { .. } => 0, @@ -12888,36 +12866,6 @@ impl_writeable_tlv_based!(BlindedForward, { (3, next_blinding_override, option), }); -#[cfg(not(trampoline))] -impl_writeable_tlv_based_enum!(PendingHTLCRouting, - (0, Forward) => { - (0, onion_packet, required), - (1, blinded, option), - (2, short_channel_id, required), - (3, incoming_cltv_expiry, option), - }, - (1, Receive) => { - (0, payment_data, required), - (1, phantom_shared_secret, option), - (2, incoming_cltv_expiry, required), - (3, payment_metadata, option), - (5, custom_tlvs, optional_vec), - (7, requires_blinded_error, (default_value, false)), - (9, payment_context, option), - }, - (2, ReceiveKeysend) => { - (0, payment_preimage, required), - (1, requires_blinded_error, (default_value, false)), - (2, incoming_cltv_expiry, required), - (3, payment_metadata, option), - (4, payment_data, option), // Added in 0.0.116 - (5, custom_tlvs, optional_vec), - (7, has_recipient_created_payment_secret, (default_value, false)), - (9, payment_context, option), - (11, invoice_request, option), - }, -); -#[cfg(trampoline)] impl_writeable_tlv_based_enum!(PendingHTLCRouting, (0, Forward) => { (0, onion_packet, required), diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 1887cb18a88..810477afbfe 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -32,7 +32,6 @@ use bitcoin::script::ScriptBuf; use bitcoin::hash_types::Txid; use crate::blinded_path::payment::{BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs, UnauthenticatedReceiveTlvs}; -#[cfg(trampoline)] use crate::blinded_path::payment::{BlindedTrampolineTlvs, TrampolineForwardTlvs}; use crate::ln::channelmanager::Verification; use crate::ln::types::ChannelId; @@ -2075,12 +2074,11 @@ mod fuzzy_internal_msgs { pub outgoing_cltv_value: u32, } - #[cfg(trampoline)] - #[cfg_attr(trampoline, allow(unused))] + #[allow(unused)] pub struct InboundTrampolineEntrypointPayload { pub amt_to_forward: u64, pub outgoing_cltv_value: u32, - pub multipath_trampoline_data: FinalOnionHopData, + pub multipath_trampoline_data: Option, pub trampoline_packet: TrampolineOnionPacket, /// The blinding point this hop needs to decrypt its Trampoline onion. /// This is used for Trampoline hops that are not the blinded path intro hop. @@ -2118,15 +2116,12 @@ mod fuzzy_internal_msgs { pub enum InboundOnionPayload { Forward(InboundOnionForwardPayload), - #[cfg(trampoline)] - #[cfg_attr(trampoline, allow(unused))] TrampolineEntrypoint(InboundTrampolineEntrypointPayload), Receive(InboundOnionReceivePayload), BlindedForward(InboundOnionBlindedForwardPayload), BlindedReceive(InboundOnionBlindedReceivePayload), } - #[cfg(trampoline)] pub struct InboundTrampolineForwardPayload { pub next_trampoline: PublicKey, /// The value, in msat, of the payment after this hop's fee is deducted. @@ -2134,7 +2129,6 @@ mod fuzzy_internal_msgs { pub outgoing_cltv_value: u32, } - #[cfg(trampoline)] pub struct InboundTrampolineBlindedForwardPayload { pub next_trampoline: PublicKey, pub payment_relay: PaymentRelay, @@ -2144,7 +2138,6 @@ mod fuzzy_internal_msgs { pub next_blinding_override: Option, } - #[cfg(trampoline)] pub enum InboundTrampolinePayload { Forward(InboundTrampolineForwardPayload), BlindedForward(InboundTrampolineBlindedForwardPayload), @@ -2209,6 +2202,15 @@ mod fuzzy_internal_msgs { /// The node id to which the trampoline node must find a route. outgoing_node_id: PublicKey, }, + #[cfg(test)] + /// LDK does not support making Trampoline payments to unblinded recipients. However, for + /// the purpose of testing our ability to receive them, we make this variant available in a + /// testing environment. + Receive { + payment_data: Option, + sender_intended_htlc_amt_msat: u64, + cltv_expiry_height: u32, + }, #[allow(unused)] /// This is the last Trampoline hop, whereupon the Trampoline forward mechanism is exited, /// and payment data is relayed using non-Trampoline blinded hops @@ -3185,6 +3187,16 @@ impl<'a> Writeable for OutboundTrampolinePayload<'a> { (14, outgoing_node_id, required) }); }, + #[cfg(test)] + Self::Receive { + ref payment_data, sender_intended_htlc_amt_msat, cltv_expiry_height, + } => { + _encode_varint_length_prefixed_tlv!(w, { + (2, HighZeroBytesDroppedBigSize(*sender_intended_htlc_amt_msat), required), + (4, HighZeroBytesDroppedBigSize(*cltv_expiry_height), required), + (8, payment_data, option) + }); + }, Self::LegacyBlindedPathEntry { amt_to_forward, outgoing_cltv_value, payment_paths, invoice_features } => { let mut blinded_path_serialization = [0u8; 2048]; // Fixed-length buffer on the stack let serialization_length = { @@ -3239,7 +3251,6 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh let mut payment_metadata: Option>> = None; let mut total_msat = None; let mut keysend_preimage: Option = None; - #[cfg(trampoline)] let mut trampoline_onion_packet: Option = None; let mut invoice_request: Option = None; let mut custom_tlvs = Vec::new(); @@ -3247,7 +3258,6 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh let tlv_len = BigSize::read(r)?; let mut rd = FixedLengthReader::new(r, tlv_len.0); - #[cfg(trampoline)] decode_tlv_stream_with_custom_tlv_decode!(&mut rd, { (2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), (4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))), @@ -3268,33 +3278,12 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh custom_tlvs.push((msg_type, value)); Ok(true) }); - #[cfg(not(trampoline))] - decode_tlv_stream_with_custom_tlv_decode!(&mut rd, { - (2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), - (4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))), - (6, short_id, option), - (8, payment_data, option), - (10, encrypted_tlvs_opt, option), - (12, intro_node_blinding_point, option), - (16, payment_metadata, option), - (18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), - (77_777, invoice_request, option), - // See https://github.com/lightning/blips/blob/master/blip-0003.md - (5482373484, keysend_preimage, option) - }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result { - if msg_type < 1 << 16 { return Ok(false) } - let mut value = Vec::new(); - msg_reader.read_to_limit(&mut value, u64::MAX)?; - custom_tlvs.push((msg_type, value)); - Ok(true) - }); if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } if intro_node_blinding_point.is_some() && update_add_blinding_point.is_some() { return Err(DecodeError::InvalidValue) } - #[cfg(trampoline)] if let Some(trampoline_onion_packet) = trampoline_onion_packet { if payment_metadata.is_some() || encrypted_tlvs_opt.is_some() || total_msat.is_some() @@ -3302,7 +3291,7 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh return Ok(Self::TrampolineEntrypoint(InboundTrampolineEntrypointPayload { amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?, outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, - multipath_trampoline_data: payment_data.ok_or(DecodeError::InvalidValue)?, + multipath_trampoline_data: payment_data, trampoline_packet: trampoline_onion_packet, current_path_key: intro_node_blinding_point, })) @@ -3391,7 +3380,6 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh } } -#[cfg(trampoline)] impl ReadableArgs<(Option, NS)> for InboundTrampolinePayload where NS::Target: NodeSigner { fn read(r: &mut R, args: (Option, NS)) -> Result { let (update_add_blinding_point, node_signer) = args; diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 46661df6807..27d9bd4d6e7 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -6,8 +6,6 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; - -#[cfg(trampoline)] use bitcoin::secp256k1::ecdh::SharedSecret; use crate::blinded_path; @@ -69,7 +67,6 @@ enum RoutingInfo { new_packet_bytes: [u8; ONION_DATA_LEN], next_hop_hmac: [u8; 32] }, - #[cfg(trampoline)] Trampoline { next_trampoline: PublicKey, // Trampoline onions are currently variable length @@ -118,14 +115,12 @@ pub(super) fn create_fwd_pending_htlc_info( err_code: 0x4000 | 22, err_data: Vec::new(), }), - #[cfg(trampoline)] onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => return Err(InboundHTLCErr { msg: "Final Node OnionHopData provided for us as an intermediary node", err_code: 0x4000 | 22, err_data: Vec::new(), }), - #[cfg(trampoline)] onion_utils::Hop::TrampolineForward { next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { ( RoutingInfo::Trampoline { @@ -141,7 +136,6 @@ pub(super) fn create_fwd_pending_htlc_info( None ) }, - #[cfg(trampoline)] onion_utils::Hop::TrampolineBlindedForward { outer_hop_data, next_trampoline_hop_data, next_trampoline_hop_hmac, new_trampoline_packet_bytes, trampoline_shared_secret, .. } => { let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward( msg.amount_msat, msg.cltv_expiry, &next_trampoline_hop_data.payment_relay, &next_trampoline_hop_data.payment_constraints, &next_trampoline_hop_data.features @@ -192,7 +186,6 @@ pub(super) fn create_fwd_pending_htlc_info( }), } } - #[cfg(trampoline)] RoutingInfo::Trampoline { next_trampoline, new_packet_bytes, next_hop_hmac, shared_secret, current_path_key } => { let next_trampoline_packet_pubkey = match next_packet_pubkey_opt { Some(Ok(pubkey)) => pubkey, @@ -272,8 +265,36 @@ pub(super) fn create_recv_pending_htlc_info( sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), intro_node_blinding_point.is_none(), true, invoice_request) } - #[cfg(trampoline)] - onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => todo!(), + onion_utils::Hop::TrampolineReceive { + trampoline_hop_data: msgs::InboundOnionReceivePayload { + payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, + cltv_expiry_height, payment_metadata, .. + }, .. + } => + (payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat, + cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none(), None), + onion_utils::Hop::TrampolineBlindedReceive { + trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload { + sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret, + intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage, + custom_tlvs, invoice_request + }, .. + } => { + check_blinded_payment_constraints( + sender_intended_htlc_amt_msat, cltv_expiry, &payment_constraints, + ) + .map_err(|()| { + InboundHTLCErr { + err_code: INVALID_ONION_BLINDING, + err_data: vec![0; 32], + msg: "Amount or cltv_expiry violated blinded payment constraints within Trampoline onion", + } + })?; + let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; + (Some(payment_data), keysend_preimage, custom_tlvs, + sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context), + intro_node_blinding_point.is_none(), true, invoice_request) + }, onion_utils::Hop::Forward { .. } => { return Err(InboundHTLCErr { err_code: 0x4000|22, @@ -288,7 +309,6 @@ pub(super) fn create_recv_pending_htlc_info( msg: "Got blinded non final data with an HMAC of 0", }) }, - #[cfg(trampoline)] onion_utils::Hop::TrampolineForward { .. } | onion_utils::Hop::TrampolineBlindedForward { .. } => { return Err(InboundHTLCErr { err_code: 0x4000|22, @@ -473,27 +493,23 @@ where NS::Target: NodeSigner, L::Target: Logger, { - macro_rules! return_malformed_err { - ($msg: expr, $err_code: expr) => { - { - log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); - let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() { - ([0; 32], INVALID_ONION_BLINDING) - } else { - (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), $err_code) - }; - return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - sha256_of_onion, - failure_code, - })); - } - } - } + let encode_malformed_error = |message: &str, err_code: u16| { + log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); + let (sha256_of_onion, failure_code) = if msg.blinding_point.is_some() || err_code == INVALID_ONION_BLINDING { + ([0; 32], INVALID_ONION_BLINDING) + } else { + (Sha256::hash(&msg.onion_routing_packet.hop_data).to_byte_array(), err_code) + }; + return Err(HTLCFailureMsg::Malformed(msgs::UpdateFailMalformedHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + sha256_of_onion, + failure_code, + })); + }; if let Err(_) = msg.onion_routing_packet.public_key { - return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6); + return encode_malformed_error("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6); } if msg.onion_routing_packet.version != 0 { @@ -503,12 +519,12 @@ where //receiving node would have to brute force to figure out which version was put in the //packet by the node that send us the message, in the case of hashing the hop_data, the //node knows the HMAC matched, so they already know what is there... - return_malformed_err!("Unknown onion packet version", 0x8000 | 0x4000 | 4); + return encode_malformed_error("Unknown onion packet version", 0x8000 | 0x4000 | 4); } let encode_relay_error = |message: &str, err_code: u16, shared_secret: [u8; 32], trampoline_shared_secret: Option<[u8; 32]>, data: &[u8]| { if msg.blinding_point.is_some() { - return_malformed_err!(message, INVALID_ONION_BLINDING) + return encode_malformed_error(message, INVALID_ONION_BLINDING) } log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); @@ -527,7 +543,7 @@ where ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { - return_malformed_err!(err_msg, err_code); + return encode_malformed_error(err_msg, err_code); }, Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret, trampoline_shared_secret }) => { return encode_relay_error(err_msg, err_code, shared_secret.secret_bytes(), trampoline_shared_secret.map(|tss| tss.secret_bytes()), &[0; 0]); @@ -560,7 +576,6 @@ where outgoing_cltv_value }) } - #[cfg(trampoline)] onion_utils::Hop::TrampolineForward { next_trampoline_hop_data: msgs::InboundTrampolineForwardPayload { amt_to_forward, outgoing_cltv_value, next_trampoline }, trampoline_shared_secret, incoming_trampoline_public_key, .. } => { let next_trampoline_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, incoming_trampoline_public_key, &trampoline_shared_secret.secret_bytes()); diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 68d7e4d7d9d..8d198e10b21 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1564,7 +1564,6 @@ pub(crate) enum Hop { }, /// This onion was received via Trampoline, and needs to be forwarded to a subsequent Trampoline /// node. - #[cfg(trampoline)] TrampolineForward { #[allow(unused)] outer_hop_data: msgs::InboundTrampolineEntrypointPayload, @@ -1577,11 +1576,10 @@ pub(crate) enum Hop { }, /// This onion was received via Trampoline, and needs to be forwarded to a subsequent Trampoline /// node. - #[allow(unused)] - #[cfg(trampoline)] TrampolineBlindedForward { outer_hop_data: msgs::InboundTrampolineEntrypointPayload, outer_shared_secret: SharedSecret, + #[allow(unused)] incoming_trampoline_public_key: PublicKey, trampoline_shared_secret: SharedSecret, next_trampoline_hop_data: msgs::InboundTrampolineBlindedForwardPayload, @@ -1617,22 +1615,22 @@ pub(crate) enum Hop { }, /// This onion payload was for us, not for forwarding to a next-hop, and it was sent to us via /// Trampoline. Contains information for verifying the incoming payment. - #[allow(unused)] - #[cfg(trampoline)] TrampolineReceive { + #[allow(unused)] outer_hop_data: msgs::InboundTrampolineEntrypointPayload, outer_shared_secret: SharedSecret, trampoline_hop_data: msgs::InboundOnionReceivePayload, + #[allow(unused)] trampoline_shared_secret: SharedSecret, }, /// This onion payload was for us, not for forwarding to a next-hop, and it was sent to us via /// Trampoline. Contains information for verifying the incoming payment. - #[allow(unused)] - #[cfg(trampoline)] TrampolineBlindedReceive { + #[allow(unused)] outer_hop_data: msgs::InboundTrampolineEntrypointPayload, outer_shared_secret: SharedSecret, trampoline_hop_data: msgs::InboundOnionBlindedReceivePayload, + #[allow(unused)] trampoline_shared_secret: SharedSecret, }, } @@ -1655,15 +1653,11 @@ impl Hop { match self { Hop::Forward { shared_secret, .. } => shared_secret, Hop::BlindedForward { shared_secret, .. } => shared_secret, - #[cfg(trampoline)] Hop::TrampolineForward { outer_shared_secret, .. } => outer_shared_secret, - #[cfg(trampoline)] Hop::TrampolineBlindedForward { outer_shared_secret, .. } => outer_shared_secret, Hop::Receive { shared_secret, .. } => shared_secret, Hop::BlindedReceive { shared_secret, .. } => shared_secret, - #[cfg(trampoline)] Hop::TrampolineReceive { outer_shared_secret, .. } => outer_shared_secret, - #[cfg(trampoline)] Hop::TrampolineBlindedReceive { outer_shared_secret, .. } => outer_shared_secret, } } @@ -1750,7 +1744,6 @@ where msgs::InboundOnionPayload::BlindedReceive(hop_data) => { Ok(Hop::BlindedReceive { shared_secret, hop_data }) }, - #[cfg(trampoline)] msgs::InboundOnionPayload::TrampolineEntrypoint(hop_data) => { let incoming_trampoline_public_key = hop_data.trampoline_packet.public_key; let trampoline_blinded_node_id_tweak = hop_data.current_path_key.map(|bp| { @@ -1828,16 +1821,60 @@ where trampoline_shared_secret, ), }), - Ok((_, None)) => Err(OnionDecodeErr::Malformed { - err_msg: "Non-final Trampoline onion data provided to us as last hop", - // todo: find more suitable error code - err_code: 0x4000 | 22, - }), - Ok((_, Some(_))) => Err(OnionDecodeErr::Malformed { - err_msg: "Final Trampoline onion data provided to us as intermediate hop", - // todo: find more suitable error code - err_code: 0x4000 | 22, - }), + Ok((msgs::InboundTrampolinePayload::BlindedForward(hop_data), None)) => { + if hop_data.intro_node_blinding_point.is_some() { + return Err(OnionDecodeErr::Relay { + err_msg: "Non-final intro node Trampoline onion data provided to us as last hop", + err_code: INVALID_ONION_BLINDING, + shared_secret, + trampoline_shared_secret: Some(SharedSecret::from_bytes( + trampoline_shared_secret, + )), + }); + } + Err(OnionDecodeErr::Malformed { + err_msg: "Non-final Trampoline onion data provided to us as last hop", + err_code: INVALID_ONION_BLINDING, + }) + }, + Ok((msgs::InboundTrampolinePayload::BlindedReceive(hop_data), Some(_))) => { + if hop_data.intro_node_blinding_point.is_some() { + return Err(OnionDecodeErr::Relay { + err_msg: "Final Trampoline intro node onion data provided to us as intermediate hop", + err_code: 0x4000 | 22, + shared_secret, + trampoline_shared_secret: Some(SharedSecret::from_bytes( + trampoline_shared_secret, + )), + }); + } + Err(OnionDecodeErr::Malformed { + err_msg: + "Final Trampoline onion data provided to us as intermediate hop", + err_code: INVALID_ONION_BLINDING, + }) + }, + Ok((msgs::InboundTrampolinePayload::Forward(_), None)) => { + Err(OnionDecodeErr::Relay { + err_msg: "Non-final Trampoline onion data provided to us as last hop", + err_code: 0x4000 | 22, + shared_secret, + trampoline_shared_secret: Some(SharedSecret::from_bytes( + trampoline_shared_secret, + )), + }) + }, + Ok((msgs::InboundTrampolinePayload::Receive(_), Some(_))) => { + Err(OnionDecodeErr::Relay { + err_msg: + "Final Trampoline onion data provided to us as intermediate hop", + err_code: 0x4000 | 22, + shared_secret, + trampoline_shared_secret: Some(SharedSecret::from_bytes( + trampoline_shared_secret, + )), + }) + }, Err(e) => Err(e), } },