diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index a5db1c4be99..3abb0974e43 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -113,6 +113,7 @@ fn build_response( htlc_minimum_msat: 100, }, features: BlindedHopFeatures::empty(), + next_blinding_override: None, }, node_id: pubkey(43), htlc_maximum_msat: 1_000_000_000_000, diff --git a/fuzz/src/onion_hop_data.rs b/fuzz/src/onion_hop_data.rs index 36aebb4e194..91bfa142b68 100644 --- a/fuzz/src/onion_hop_data.rs +++ b/fuzz/src/onion_hop_data.rs @@ -21,7 +21,7 @@ pub fn onion_hop_data_test(data: &[u8], _out: Out) { let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42)); let _ = , - &&test_utils::TestNodeSigner, + &test_utils::TestNodeSigner, )>>::read(&mut r, (None, &&node_signer)); } @@ -34,6 +34,6 @@ pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) { let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42)); let _ = , - &&test_utils::TestNodeSigner, + &test_utils::TestNodeSigner, )>>::read(&mut r, (None, &&node_signer)); } diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 58dc68eed5c..17f255081c4 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -91,6 +91,7 @@ fn build_response( htlc_minimum_msat: 100, }, features: BlindedHopFeatures::empty(), + next_blinding_override: None, }, node_id: pubkey(43), htlc_maximum_msat: 1_000_000_000_000, diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 765e0b91f05..70f2220dcdc 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -26,7 +26,7 @@ use crate::offers::invoice_request::InvoiceRequestFields; use crate::offers::offer::OfferId; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; use crate::sign::{EntropySource, NodeSigner, Recipient}; -use crate::util::ser::{FixedLengthReader, LengthReadableArgs, HighZeroBytesDroppedBigSize, Readable, Writeable, Writer}; +use crate::util::ser::{FixedLengthReader, LengthReadableArgs, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; use core::mem; use core::ops::Deref; @@ -201,6 +201,9 @@ pub struct ForwardTlvs { /// /// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload pub features: BlindedHopFeatures, + /// Set if this [`BlindedPaymentPath`] is concatenated to another, to indicate the + /// [`BlindedPaymentPath::blinding_point`] of the appended blinded path. + pub next_blinding_override: Option, } /// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and @@ -234,7 +237,7 @@ enum BlindedPaymentTlvsRef<'a> { /// Parameters for relaying over a given [`BlindedHop`]. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct PaymentRelay { /// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`]. pub cltv_expiry_delta: u16, @@ -248,7 +251,7 @@ pub struct PaymentRelay { /// Constraints for relaying over a given [`BlindedHop`]. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct PaymentConstraints { /// The maximum total CLTV that is acceptable when relaying a payment over this [`BlindedHop`]. pub max_cltv_expiry: u32, @@ -341,7 +344,7 @@ impl Writeable for ForwardTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { let features_opt = if self.features == BlindedHopFeatures::empty() { None } - else { Some(&self.features) }; + else { Some(WithoutLength(&self.features)) }; encode_tlv_stream!(w, { (2, self.short_channel_id, required), (10, self.payment_relay, required), @@ -379,9 +382,10 @@ impl Readable for BlindedPaymentTlvs { _init_and_read_tlv_stream!(r, { (1, _padding, option), (2, scid, option), + (8, next_blinding_override, option), (10, payment_relay, option), (12, payment_constraints, required), - (14, features, option), + (14, features, (option, encoding: (BlindedHopFeatures, WithoutLength))), (65536, payment_secret, option), (65537, payment_context, (default_value, PaymentContext::unknown())), }); @@ -395,6 +399,7 @@ impl Readable for BlindedPaymentTlvs { short_channel_id, payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), + next_blinding_override, features: features.unwrap_or_else(BlindedHopFeatures::empty), })) } else { @@ -602,6 +607,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 100, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value(), @@ -618,6 +624,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1_000, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value(), @@ -675,6 +682,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value() @@ -691,6 +699,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 2_000, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value() @@ -726,6 +735,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 5_000, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value() @@ -742,6 +752,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 2_000, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: u64::max_value() @@ -781,6 +792,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: 5_000, @@ -797,6 +809,7 @@ mod tests { max_cltv_expiry: 0, htlc_minimum_msat: 1, }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: 10_000 diff --git a/lightning/src/blinded_path/utils.rs b/lightning/src/blinded_path/utils.rs index 384281c7123..240216f6f89 100644 --- a/lightning/src/blinded_path/utils.rs +++ b/lightning/src/blinded_path/utils.rs @@ -109,7 +109,7 @@ where } // Panics if `unblinded_tlvs` length is less than `unblinded_pks` length -pub(super) fn construct_blinded_hops<'a, T, I1, I2>( +pub(crate) fn construct_blinded_hops<'a, T, I1, I2>( secp_ctx: &Secp256k1, unblinded_pks: I1, mut unblinded_tlvs: I2, session_priv: &SecretKey ) -> Result, secp256k1::Error> where diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index b13b1e04d64..12c0dc33a00 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -7,24 +7,33 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use bitcoin::hashes::hex::FromHex; +use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, schnorr}; +use bitcoin::secp256k1::ecdh::SharedSecret; +use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; +use crate::blinded_path; use crate::blinded_path::payment::{BlindedPaymentPath, ForwardNode, ForwardTlvs, PaymentConstraints, PaymentContext, PaymentRelay, ReceiveTlvs}; use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason}; -use crate::ln::types::PaymentSecret; +use crate::ln::types::{ChannelId, PaymentHash, PaymentSecret}; use crate::ln::channelmanager; -use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; -use crate::ln::features::BlindedHopFeatures; +use crate::ln::channelmanager::{HTLCFailureMsg, PaymentId, RecipientOnionFields}; +use crate::ln::features::{BlindedHopFeatures, ChannelFeatures, NodeFeatures}; use crate::ln::functional_test_utils::*; use crate::ln::msgs; -use crate::ln::msgs::ChannelMessageHandler; +use crate::ln::msgs::{ChannelMessageHandler, UnsignedGossipMessage}; +use crate::ln::onion_payment; use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::{Retry, IDEMPOTENCY_TIMEOUT_TICKS}; -use crate::offers::invoice::BlindedPayInfo; +use crate::offers::invoice::{BlindedPayInfo, UnsignedBolt12Invoice}; +use crate::offers::invoice_request::UnsignedInvoiceRequest; use crate::prelude::*; -use crate::routing::router::{Payee, PaymentParameters, RouteParameters}; +use crate::routing::router::{BlindedTail, Path, Payee, PaymentParameters, RouteHop, RouteParameters}; +use crate::sign::{KeyMaterial, NodeSigner, Recipient}; use crate::util::config::UserConfig; +use crate::util::ser::WithoutLength; use crate::util::test_utils; +use lightning_invoice::RawBolt11Invoice; fn blinded_payment_path( payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64, @@ -49,6 +58,7 @@ fn blinded_payment_path( htlc_minimum_msat: intro_node_min_htlc_opt.take() .unwrap_or_else(|| channel_upds[idx - 1].htlc_minimum_msat), }, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, htlc_maximum_msat: intro_node_max_htlc_opt.take() @@ -1331,3 +1341,254 @@ fn custom_tlvs_to_blinded_path() { .with_custom_tlvs(recipient_onion_fields.custom_tlvs.clone()) ); } + +fn secret_from_hex(hex: &str) -> SecretKey { + SecretKey::from_slice(&>::from_hex(hex).unwrap()).unwrap() +} + +fn bytes_from_hex(hex: &str) -> Vec { + >::from_hex(hex).unwrap() +} + +fn pubkey_from_hex(hex: &str) -> PublicKey { + PublicKey::from_slice(&>::from_hex(hex).unwrap()).unwrap() +} + +fn update_add_msg( + amount_msat: u64, cltv_expiry: u32, blinding_point: Option, + onion_routing_packet: msgs::OnionPacket +) -> msgs::UpdateAddHTLC { + msgs::UpdateAddHTLC { + channel_id: ChannelId::from_bytes([0; 32]), + htlc_id: 0, + amount_msat, + cltv_expiry, + payment_hash: PaymentHash([0; 32]), + onion_routing_packet, + skimmed_fee_msat: None, + blinding_point, + } +} + +#[test] +fn route_blinding_spec_test_vector() { + let mut secp_ctx = Secp256k1::new(); + let bob_secret = secret_from_hex("4242424242424242424242424242424242424242424242424242424242424242"); + let bob_node_id = PublicKey::from_secret_key(&secp_ctx, &bob_secret); + let bob_unblinded_tlvs = bytes_from_hex("011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456"); + let carol_secret = secret_from_hex("4343434343434343434343434343434343434343434343434343434343434343"); + let carol_node_id = PublicKey::from_secret_key(&secp_ctx, &carol_secret); + let carol_unblinded_tlvs = bytes_from_hex("020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00"); + let dave_secret = secret_from_hex("4444444444444444444444444444444444444444444444444444444444444444"); + let dave_node_id = PublicKey::from_secret_key(&secp_ctx, &dave_secret); + let dave_unblinded_tlvs = bytes_from_hex("01230000000000000000000000000000000000000000000000000000000000000000000000020800000000000002310a060090000000fa0c06000b699105dc0e00"); + let eve_secret = secret_from_hex("4545454545454545454545454545454545454545454545454545454545454545"); + let eve_node_id = PublicKey::from_secret_key(&secp_ctx, &eve_secret); + let eve_unblinded_tlvs = bytes_from_hex("011a00000000000000000000000000000000000000000000000000000604deadbeef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206c1"); + + // Eve creates a blinded path to herself through Dave: + let dave_eve_session_priv = secret_from_hex("0101010101010101010101010101010101010101010101010101010101010101"); + let blinding_override = PublicKey::from_secret_key(&secp_ctx, &dave_eve_session_priv); + assert_eq!(blinding_override, pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")); + // Can't use the public API here as the encrypted payloads contain unknown TLVs. + let mut dave_eve_blinded_hops = blinded_path::utils::construct_blinded_hops( + &secp_ctx, [dave_node_id, eve_node_id].iter(), + &mut [WithoutLength(&dave_unblinded_tlvs), WithoutLength(&eve_unblinded_tlvs)].iter(), + &dave_eve_session_priv + ).unwrap(); + + // Concatenate an additional Bob -> Carol blinded path to the Eve -> Dave blinded path. + let bob_carol_session_priv = secret_from_hex("0202020202020202020202020202020202020202020202020202020202020202"); + let bob_blinding_point = PublicKey::from_secret_key(&secp_ctx, &bob_carol_session_priv); + let bob_carol_blinded_hops = blinded_path::utils::construct_blinded_hops( + &secp_ctx, [bob_node_id, carol_node_id].iter(), + &mut [WithoutLength(&bob_unblinded_tlvs), WithoutLength(&carol_unblinded_tlvs)].iter(), + &bob_carol_session_priv + ).unwrap(); + + let mut blinded_hops = bob_carol_blinded_hops; + blinded_hops.append(&mut dave_eve_blinded_hops); + assert_eq!( + vec![ + pubkey_from_hex("03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"), + pubkey_from_hex("02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"), + pubkey_from_hex("036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"), + pubkey_from_hex("021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae") + ], + blinded_hops.iter().map(|bh| bh.blinded_node_id).collect::>() + ); + assert_eq!( + vec![ + bytes_from_hex("cd4100ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c49982088b49f2e70b99287fdee0aa58aa39913ab405813b999f66783aa2fe637b3cda91ffc0913c30324e2c6ce327e045183e4bffecb"), + bytes_from_hex("cc0f16524fd7f8bb0b1d8d40ad71709ef140174c76faa574cac401bb8992fef76c4d004aa485dd599ed1cf2715f57ff62da5aaec5d7b10d59b04d8a9d77e472b9b3ecc2179334e411be22fa4c02b467c7e"), + bytes_from_hex("0fa0a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8db3e561d9c43815fd4bcebf6f58c546da0cd8a9bf5cebd0d554802f6c0255e28e4a27343f761fe518cd897463187991105"), + bytes_from_hex("da1a7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60724a2e4d3f0489ad884f7f3f77149209f0df51efd6b276294a02e3949c7254fbc8b5cab58212d9a78983e1cf86fe218b30c4ca8f6d8") + ], + blinded_hops.iter().map(|bh| bh.encrypted_payload.clone()).collect::>>() + ); + + let mut amt_msat = 100_000; + let session_priv = secret_from_hex("0303030303030303030303030303030303030303030303030303030303030303"); + let path = Path { + hops: vec![RouteHop { + pubkey: bob_node_id, + node_features: NodeFeatures::empty(), + short_channel_id: 42, + channel_features: ChannelFeatures::empty(), + fee_msat: 100, + cltv_expiry_delta: 42, + maybe_announced_channel: false, + }], + blinded_tail: Some(BlindedTail { + hops: blinded_hops, + blinding_point: bob_blinding_point, + excess_final_cltv_expiry_delta: 0, + final_value_msat: amt_msat + }), + }; + let cur_height = 747_000; + let (bob_onion, _, _) = onion_utils::create_payment_onion(&secp_ctx, &path, &session_priv, amt_msat, &RecipientOnionFields::spontaneous_empty(), cur_height, &PaymentHash([0; 32]), &None, [0; 32]).unwrap(); + + struct TestEcdhSigner { + node_secret: SecretKey, + } + impl NodeSigner for TestEcdhSigner { + fn ecdh( + &self, _recipient: Recipient, other_key: &PublicKey, tweak: Option<&Scalar>, + ) -> Result { + let mut node_secret = self.node_secret.clone(); + if let Some(tweak) = tweak { + node_secret = self.node_secret.mul_tweak(tweak).map_err(|_| ())?; + } + Ok(SharedSecret::new(other_key, &node_secret)) + } + fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!() } + fn get_node_id(&self, _recipient: Recipient) -> Result { unreachable!() } + fn sign_invoice( + &self, _invoice: &RawBolt11Invoice, _recipient: Recipient, + ) -> Result { unreachable!() } + fn sign_bolt12_invoice_request( + &self, _invoice_request: &UnsignedInvoiceRequest, + ) -> Result { unreachable!() } + fn sign_bolt12_invoice( + &self, _invoice: &UnsignedBolt12Invoice, + ) -> Result { unreachable!() } + fn sign_gossip_message(&self, _msg: UnsignedGossipMessage) -> Result { unreachable!() } + } + let logger = test_utils::TestLogger::with_id("".to_owned()); + + let bob_update_add = update_add_msg(110_000, 747_500, None, bob_onion); + let bob_node_signer = TestEcdhSigner { node_secret: bob_secret }; + // Can't use the public API here as we need to avoid the CLTV delta checks (test vector uses + // < MIN_CLTV_EXPIRY_DELTA). + let (bob_peeled_onion, _, next_packet_details_opt) = + match onion_payment::decode_incoming_update_add_htlc_onion( + &bob_update_add, &bob_node_signer, &logger, &secp_ctx + ) { + Ok(res) => res, + _ => panic!("Unexpected error") + }; + let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::BlindedForward { + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override + }, next_hop_hmac, new_packet_bytes + } = bob_peeled_onion { + assert_eq!(short_channel_id, 1729); + assert!(next_blinding_override.is_none()); + assert_eq!(intro_node_blinding_point, Some(bob_blinding_point)); + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 36, fee_proportional_millionths: 150, fee_base_msat: 10_000 }); + assert_eq!(features, BlindedHopFeatures::empty()); + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 748_005, htlc_minimum_msat: 1500 }); + (new_packet_bytes, next_hop_hmac) + } else { panic!() }; + + let carol_packet_details = next_packet_details_opt.unwrap(); + let carol_onion = msgs::OnionPacket { + version: 0, + public_key: carol_packet_details.next_packet_pubkey, + hop_data: carol_packet_bytes, + hmac: carol_hmac, + }; + let carol_update_add = update_add_msg( + carol_packet_details.outgoing_amt_msat, carol_packet_details.outgoing_cltv_value, + Some(pubkey_from_hex("034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")), + carol_onion + ); + let carol_node_signer = TestEcdhSigner { node_secret: carol_secret }; + let (carol_peeled_onion, _, next_packet_details_opt) = + match onion_payment::decode_incoming_update_add_htlc_onion( + &carol_update_add, &carol_node_signer, &logger, &secp_ctx + ) { + Ok(res) => res, + _ => panic!("Unexpected error") + }; + let (dave_packet_bytes, dave_hmac) = if let onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::BlindedForward { + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override + }, next_hop_hmac, new_packet_bytes + } = carol_peeled_onion { + assert_eq!(short_channel_id, 1105); + assert_eq!(next_blinding_override, Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"))); + assert!(intro_node_blinding_point.is_none()); + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 48, fee_proportional_millionths: 100, fee_base_msat: 500 }); + assert_eq!(features, BlindedHopFeatures::empty()); + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_969, htlc_minimum_msat: 1500 }); + (new_packet_bytes, next_hop_hmac) + } else { panic!() }; + + let dave_packet_details = next_packet_details_opt.unwrap(); + let dave_onion = msgs::OnionPacket { + version: 0, + public_key: dave_packet_details.next_packet_pubkey, + hop_data: dave_packet_bytes, + hmac: dave_hmac, + }; + let dave_update_add = update_add_msg( + dave_packet_details.outgoing_amt_msat, dave_packet_details.outgoing_cltv_value, + Some(pubkey_from_hex("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), + dave_onion + ); + let dave_node_signer = TestEcdhSigner { node_secret: dave_secret }; + let (dave_peeled_onion, _, next_packet_details_opt) = + match onion_payment::decode_incoming_update_add_htlc_onion( + &dave_update_add, &dave_node_signer, &logger, &secp_ctx + ) { + Ok(res) => res, + _ => panic!("Unexpected error") + }; + let (eve_packet_bytes, eve_hmac) = if let onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::BlindedForward { + short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override + }, next_hop_hmac, new_packet_bytes + } = dave_peeled_onion { + assert_eq!(short_channel_id, 561); + assert!(next_blinding_override.is_none()); + assert!(intro_node_blinding_point.is_none()); + assert_eq!(payment_relay, PaymentRelay { cltv_expiry_delta: 144, fee_proportional_millionths: 250, fee_base_msat: 0 }); + assert_eq!(features, BlindedHopFeatures::empty()); + assert_eq!(payment_constraints, PaymentConstraints { max_cltv_expiry: 747_921, htlc_minimum_msat: 1500 }); + (new_packet_bytes, next_hop_hmac) + } else { panic!() }; + + let eve_packet_details = next_packet_details_opt.unwrap(); + let eve_onion = msgs::OnionPacket { + version: 0, + public_key: eve_packet_details.next_packet_pubkey, + hop_data: eve_packet_bytes, + hmac: eve_hmac, + }; + let eve_update_add = update_add_msg( + eve_packet_details.outgoing_amt_msat, eve_packet_details.outgoing_cltv_value, + Some(pubkey_from_hex("03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")), + eve_onion + ); + let eve_node_signer = TestEcdhSigner { node_secret: eve_secret }; + // We can't decode the final payload because it contains a path_id and is missing some LDK + // specific fields. + match onion_payment::decode_incoming_update_add_htlc_onion( + &eve_update_add, &eve_node_signer, &logger, &secp_ctx + ) { + Err(HTLCFailureMsg::Malformed(msg)) => assert_eq!(msg.failure_code, INVALID_ONION_BLINDING), + _ => panic!("Unexpected error") + } +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 641810f2f4e..c84e11cef80 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -226,6 +226,10 @@ pub struct BlindedForward { /// If needed, this determines how this HTLC should be failed backwards, based on whether we are /// the introduction node. pub failure: BlindedFailure, + /// Overrides the next hop's [`msgs::UpdateAddHTLC::blinding_point`]. Set if this HTLC is being + /// forwarded within a [`BlindedPaymentPath`] that was concatenated to another blinded path that + /// starts at the next hop. + pub next_blinding_override: Option, } impl PendingHTLCRouting { @@ -3828,7 +3832,7 @@ where (onion_utils::Hop, [u8; 32], Option>), HTLCFailureMsg > { let (next_hop, shared_secret, next_packet_details_opt) = decode_incoming_update_add_htlc_onion( - msg, &self.node_signer, &self.logger, &self.secp_ctx + msg, &*self.node_signer, &*self.logger, &self.secp_ctx )?; let next_packet_details = match next_packet_details_opt { @@ -5045,7 +5049,7 @@ where let mut htlc_fails = Vec::new(); for update_add_htlc in &update_add_htlcs { let (next_hop, shared_secret, next_packet_details_opt) = match decode_incoming_update_add_htlc_onion( - &update_add_htlc, &self.node_signer, &self.logger, &self.secp_ctx + &update_add_htlc, &*self.node_signer, &*self.logger, &self.secp_ctx ) { Ok(decoded_onion) => decoded_onion, Err(htlc_fail) => { @@ -5222,7 +5226,7 @@ where let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes(); let next_hop = match onion_utils::decode_next_payment_hop( phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, - payment_hash, None, &self.node_signer + payment_hash, None, &*self.node_signer ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { @@ -5313,12 +5317,14 @@ where blinded_failure: blinded.map(|b| b.failure), }); let next_blinding_point = blinded.and_then(|b| { - let encrypted_tlvs_ss = self.node_signer.ecdh( - Recipient::Node, &b.inbound_blinding_point, None - ).unwrap().secret_bytes(); - onion_utils::next_hop_pubkey( - &self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss - ).ok() + b.next_blinding_override.or_else(|| { + let encrypted_tlvs_ss = self.node_signer.ecdh( + Recipient::Node, &b.inbound_blinding_point, None + ).unwrap().secret_bytes(); + onion_utils::next_hop_pubkey( + &self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss + ).ok() + }) }); // Forward the HTLC over the most appropriate channel with the corresponding peer, @@ -11034,6 +11040,7 @@ impl_writeable_tlv_based!(PhantomRouteHints, { impl_writeable_tlv_based!(BlindedForward, { (0, inbound_blinding_point, required), (1, failure, (default_value, BlindedFailure::FromIntroductionNode)), + (3, next_blinding_override, option), }); impl_writeable_tlv_based_enum!(PendingHTLCRouting, diff --git a/lightning/src/ln/features.rs b/lightning/src/ln/features.rs index 11e2b12dcdb..4637dfc2b25 100644 --- a/lightning/src/ln/features.rs +++ b/lightning/src/ln/features.rs @@ -98,6 +98,7 @@ impl_feature_write_without_length!(Bolt12InvoiceFeatures); impl_feature_write_without_length!(ChannelTypeFeatures); impl_feature_write_without_length!(InvoiceRequestFeatures); impl_feature_write_without_length!(OfferFeatures); +impl_feature_write_without_length!(BlindedHopFeatures); #[cfg(test)] mod tests { diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 85f1ae0aa48..ad63f28e7c8 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -1764,6 +1764,7 @@ mod fuzzy_internal_msgs { payment_constraints: PaymentConstraints, features: BlindedHopFeatures, intro_node_blinding_point: Option, + next_blinding_override: Option, }, BlindedReceive { sender_intended_htlc_amt_msat: u64, @@ -2755,8 +2756,8 @@ impl Writeable for OutboundTrampolinePayload { } -impl ReadableArgs<(Option, &NS)> for InboundOnionPayload where NS::Target: NodeSigner { - fn read(r: &mut R, args: (Option, &NS)) -> Result { +impl ReadableArgs<(Option, NS)> for InboundOnionPayload where NS::Target: NodeSigner { + fn read(r: &mut R, args: (Option, NS)) -> Result { let (update_add_blinding_point, node_signer) = args; let mut amt = None; @@ -2808,7 +2809,7 @@ impl ReadableArgs<(Option, &NS)> for InboundOnionPayload w let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho)? { ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs { - short_channel_id, payment_relay, payment_constraints, features + short_channel_id, payment_relay, payment_constraints, features, next_blinding_override })} => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || keysend_preimage.is_some() @@ -2821,6 +2822,7 @@ impl ReadableArgs<(Option, &NS)> for InboundOnionPayload w payment_constraints, features, intro_node_blinding_point, + next_blinding_override, }) }, ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { @@ -4454,7 +4456,7 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap(); if let msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } = inbound_msg { @@ -4479,7 +4481,7 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { payment_data: None, sender_intended_htlc_amt_msat, cltv_expiry_height, .. } = inbound_msg { @@ -4507,7 +4509,7 @@ mod tests { assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { payment_data: Some(FinalOnionHopData { payment_secret, @@ -4543,7 +4545,7 @@ mod tests { }; let encoded_value = msg.encode(); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).is_err()); + assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), (None, &node_signer)).is_err()); let good_type_range_tlvs = vec![ ((1 << 16) - 3, vec![42]), ((1 << 16) - 1, vec![42; 32]), @@ -4552,7 +4554,7 @@ mod tests { *custom_tlvs = &good_type_range_tlvs; } let encoded_value = msg.encode(); - let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &&node_signer)).unwrap(); + let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), (None, &node_signer)).unwrap(); match inbound_msg { msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()), _ => panic!(), @@ -4577,7 +4579,7 @@ mod tests { let target_value = >::from_hex("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap(); assert_eq!(encoded_value, target_value); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &&node_signer)).unwrap(); + let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), (None, &node_signer)).unwrap(); if let msgs::InboundOnionPayload::Receive { payment_data: None, payment_metadata: None, @@ -4807,7 +4809,7 @@ mod tests { let mut rd = Cursor::new(&big_payload[..]); let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet); - , &&test_utils::TestKeysInterface)>> + , &test_utils::TestKeysInterface)>> ::read(&mut rd, (None, &&node_signer)).unwrap(); } // see above test, needs to be a separate method for use of the serialization macros. diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index f62ca5d84e6..3afe5085966 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -75,12 +75,14 @@ pub(super) fn create_fwd_pending_htlc_info( }; let ( - short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point + short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point, + next_blinding_override ) = match hop_data { msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => - (short_channel_id, amt_to_forward, outgoing_cltv_value, None), + (short_channel_id, amt_to_forward, outgoing_cltv_value, None, None), msgs::InboundOnionPayload::BlindedForward { short_channel_id, payment_relay, payment_constraints, intro_node_blinding_point, features, + next_blinding_override, } => { let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward( msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features @@ -93,7 +95,8 @@ pub(super) fn create_fwd_pending_htlc_info( err_data: vec![0; 32], } })?; - (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point) + (short_channel_id, amt_to_forward, outgoing_cltv_value, intro_node_blinding_point, + next_blinding_override) }, msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => return Err(InboundHTLCErr { @@ -110,6 +113,7 @@ pub(super) fn create_fwd_pending_htlc_info( blinded: intro_node_blinding_point.or(msg.blinding_point) .map(|bp| BlindedForward { inbound_blinding_point: bp, + next_blinding_override, failure: intro_node_blinding_point .map(|_| BlindedFailure::FromIntroductionNode) .unwrap_or(BlindedFailure::FromBlindedNode), @@ -276,7 +280,7 @@ pub(super) fn create_recv_pending_htlc_info( /// /// [`Event::PaymentClaimable`]: crate::events::Event::PaymentClaimable pub fn peel_payment_onion( - msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1, + msg: &msgs::UpdateAddHTLC, node_signer: NS, logger: L, secp_ctx: &Secp256k1, cur_height: u32, accept_mpp_keysend: bool, allow_skimmed_fees: bool, ) -> Result where @@ -342,7 +346,7 @@ pub(super) struct NextPacketDetails { } pub(super) fn decode_incoming_update_add_htlc_onion( - msg: &msgs::UpdateAddHTLC, node_signer: &NS, logger: &L, secp_ctx: &Secp256k1, + msg: &msgs::UpdateAddHTLC, node_signer: NS, logger: L, secp_ctx: &Secp256k1, ) -> Result<(onion_utils::Hop, [u8; 32], Option), HTLCFailureMsg> where NS::Target: NodeSigner, @@ -570,7 +574,7 @@ mod tests { let msg = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, onion); let logger = test_utils::TestLogger::with_id("bob".to_string()); - let peeled = peel_payment_onion(&msg, &&bob, &&logger, &secp_ctx, cur_height, true, false) + let peeled = peel_payment_onion(&msg, &bob, &logger, &secp_ctx, cur_height, true, false) .map_err(|e| e.msg).unwrap(); let next_onion = match peeled.routing { @@ -581,7 +585,7 @@ mod tests { }; let msg2 = make_update_add_msg(amount_msat, cltv_expiry, payment_hash, next_onion); - let peeled2 = peel_payment_onion(&msg2, &&charlie, &&logger, &secp_ctx, cur_height, true, false) + let peeled2 = peel_payment_onion(&msg2, &charlie, &logger, &secp_ctx, cur_height, true, false) .map_err(|e| e.msg).unwrap(); match peeled2.routing { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 70a7c6bf8bb..a3372dda8e0 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -1133,7 +1133,7 @@ pub(crate) enum OnionDecodeErr { pub(crate) fn decode_next_payment_hop( shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash, - blinding_point: Option, node_signer: &NS, + blinding_point: Option, node_signer: NS, ) -> Result where NS::Target: NodeSigner, diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 083c32e5393..00d7b9c475a 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -4284,7 +4284,7 @@ fn peel_payment_onion_custom_tlvs() { blinding_point: None, }; let peeled_onion = crate::ln::onion_payment::peel_payment_onion( - &update_add, &&chanmon_cfgs[1].keys_manager, &&chanmon_cfgs[1].logger, &secp_ctx, + &update_add, &chanmon_cfgs[1].keys_manager, &chanmon_cfgs[1].logger, &secp_ctx, nodes[1].best_block_info().1, true, false ).unwrap(); assert_eq!(peeled_onion.incoming_amt_msat, Some(amt_msat)); diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 2d7450b91c5..43c4b7343e7 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -151,6 +151,7 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size short_channel_id, payment_relay, payment_constraints, + next_blinding_override: None, features: BlindedHopFeatures::empty(), }, node_id: details.counterparty.node_id,