From 1518e676390c58670be0499b078f9315208a73e6 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 19 Feb 2025 09:16:33 -0800 Subject: [PATCH 1/2] Clean up `Hop` match arms Essentially a follow-up to 38284a0d, deduplicating some additional code. --- lightning/src/ln/channelmanager.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8be60fa9e1f..67e004aa39f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4427,27 +4427,16 @@ where } } match decoded_hop { - onion_utils::Hop::Receive(next_hop_data) => { - // OUR PAYMENT! - let current_height: u32 = self.best_block.read().unwrap().height; - match create_recv_pending_htlc_info(msgs::InboundOnionPayload::Receive(next_hop_data), 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 send us a commitment_signed! - PendingHTLCStatus::Forward(info) - }, - Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) - } - }, - onion_utils::Hop::BlindedReceive(next_hop_data) => { + onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } => { + let inbound_onion_payload = match decoded_hop { + onion_utils::Hop::Receive(hop_data) => msgs::InboundOnionPayload::Receive(hop_data), + onion_utils::Hop::BlindedReceive(hop_data) => msgs::InboundOnionPayload::BlindedReceive(hop_data), + _ => unreachable!() + }; + // OUR PAYMENT! let current_height: u32 = self.best_block.read().unwrap().height; - match create_recv_pending_htlc_info(msgs::InboundOnionPayload::BlindedReceive(next_hop_data), shared_secret, msg.payment_hash, + match create_recv_pending_htlc_info(inbound_onion_payload, shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_underpay, msg.skimmed_fee_msat, current_height) { From 5291445ad10d735eb8f28fe11e76330115873403 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 19 Feb 2025 09:16:39 -0800 Subject: [PATCH 2/2] Calculate shared secret within hop decode function For Trampoline, we'll need to keep track of both the outer and inner onion's shared secrets. To this end, we're moving the secret calculation inside `decode_next_payment_hop` such that, when applicable, it can return both. --- lightning/src/ln/blinded_payment_tests.rs | 12 +-- lightning/src/ln/channelmanager.rs | 26 +++--- lightning/src/ln/onion_payment.rs | 79 +++++++--------- lightning/src/ln/onion_utils.rs | 107 +++++++++++++++++----- lightning/src/sign/mod.rs | 1 + 5 files changed, 140 insertions(+), 85 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 0662d5f9395..0e0939508c4 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -1561,7 +1561,7 @@ fn route_blinding_spec_test_vector() { 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) = + 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 ) { @@ -1571,7 +1571,7 @@ fn route_blinding_spec_test_vector() { let (carol_packet_bytes, carol_hmac) = if let onion_utils::Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override - }, next_hop_hmac, new_packet_bytes + }, next_hop_hmac, new_packet_bytes, .. } = bob_peeled_onion { assert_eq!(short_channel_id, 1729); assert!(next_blinding_override.is_none()); @@ -1595,7 +1595,7 @@ fn route_blinding_spec_test_vector() { carol_onion ); let carol_node_signer = TestEcdhSigner { node_secret: carol_secret }; - let (carol_peeled_onion, _, next_packet_details_opt) = + 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 ) { @@ -1605,7 +1605,7 @@ fn route_blinding_spec_test_vector() { let (dave_packet_bytes, dave_hmac) = if let onion_utils::Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override - }, next_hop_hmac, new_packet_bytes + }, 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"))); @@ -1629,7 +1629,7 @@ fn route_blinding_spec_test_vector() { dave_onion ); let dave_node_signer = TestEcdhSigner { node_secret: dave_secret }; - let (dave_peeled_onion, _, next_packet_details_opt) = + 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 ) { @@ -1639,7 +1639,7 @@ fn route_blinding_spec_test_vector() { let (eve_packet_bytes, eve_hmac) = if let onion_utils::Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, payment_relay, payment_constraints, features, intro_node_blinding_point, next_blinding_override - }, next_hop_hmac, new_packet_bytes + }, next_hop_hmac, new_packet_bytes, .. } = dave_peeled_onion { assert_eq!(short_channel_id, 561); assert!(next_blinding_override.is_none()); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 67e004aa39f..00072c1db1c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4429,8 +4429,8 @@ where match decoded_hop { onion_utils::Hop::Receive { .. } | onion_utils::Hop::BlindedReceive { .. } => { let inbound_onion_payload = match decoded_hop { - onion_utils::Hop::Receive(hop_data) => msgs::InboundOnionPayload::Receive(hop_data), - onion_utils::Hop::BlindedReceive(hop_data) => msgs::InboundOnionPayload::BlindedReceive(hop_data), + onion_utils::Hop::Receive { hop_data, .. } => msgs::InboundOnionPayload::Receive(hop_data), + onion_utils::Hop::BlindedReceive { hop_data, .. } => msgs::InboundOnionPayload::BlindedReceive(hop_data), _ => unreachable!() }; @@ -4450,14 +4450,14 @@ where Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) } }, - onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes } => { + onion_utils::Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes, .. } => { match create_fwd_pending_htlc_info(msg, msgs::InboundOnionPayload::Forward(next_hop_data), next_hop_hmac, new_packet_bytes, shared_secret, next_packet_pubkey_opt) { Ok(info) => PendingHTLCStatus::Forward(info), Err(InboundHTLCErr { err_code, err_data, msg }) => return_err!(msg, err_code, &err_data) } }, - onion_utils::Hop::BlindedForward { next_hop_data, next_hop_hmac, new_packet_bytes } => { + onion_utils::Hop::BlindedForward { next_hop_data, next_hop_hmac, new_packet_bytes, .. } => { match create_fwd_pending_htlc_info(msg, msgs::InboundOnionPayload::BlindedForward(next_hop_data), next_hop_hmac, new_packet_bytes, shared_secret, next_packet_pubkey_opt) { Ok(info) => PendingHTLCStatus::Forward(info), @@ -5685,7 +5685,7 @@ where let mut htlc_forwards = Vec::new(); 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( + let (next_hop, next_packet_details_opt) = match decode_incoming_update_add_htlc_onion( &update_add_htlc, &*self.node_signer, &*self.logger, &self.secp_ctx ) { Ok(decoded_onion) => decoded_onion, @@ -5697,6 +5697,7 @@ where let is_intro_node_blinded_forward = next_hop.is_intro_node_blinded_forward(); let outgoing_scid_opt = next_packet_details_opt.as_ref().map(|d| d.outgoing_scid); + let shared_secret = next_hop.shared_secret().secret_bytes(); // Process the HTLC on the incoming channel. match self.do_funded_channel_callback(incoming_scid, |chan: &mut FundedChannel| { @@ -5855,10 +5856,9 @@ where if let PendingHTLCRouting::Forward { ref onion_packet, .. } = routing { let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode); if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.chain_hash) { - 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 + Recipient::PhantomNode, &onion_packet.public_key.unwrap(), &onion_packet.hop_data, + onion_packet.hmac, payment_hash, None, &*self.node_signer ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { @@ -5869,15 +5869,17 @@ where // of the onion. failed_payment!(err_msg, err_code, sha256_of_onion.to_vec(), None); }, - Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => { + Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret }) => { + let phantom_shared_secret = shared_secret.secret_bytes(); failed_payment!(err_msg, err_code, Vec::new(), Some(phantom_shared_secret)); }, }; - let inbound_onion_payload = match next_hop { - onion_utils::Hop::Receive(hop_data) => msgs::InboundOnionPayload::Receive(hop_data), - onion_utils::Hop::BlindedReceive(hop_data) => msgs::InboundOnionPayload::BlindedReceive(hop_data), + let (inbound_onion_payload, shared_secret) = match next_hop { + onion_utils::Hop::Receive { hop_data, shared_secret } => (msgs::InboundOnionPayload::Receive(hop_data), shared_secret), + onion_utils::Hop::BlindedReceive { hop_data, shared_secret } => (msgs::InboundOnionPayload::BlindedReceive(hop_data), shared_secret), _ => panic!() }; + let phantom_shared_secret = shared_secret.secret_bytes(); let current_height: u32 = self.best_block.read().unwrap().height; match create_recv_pending_htlc_info(inbound_onion_payload, incoming_shared_secret, payment_hash, outgoing_amt_msat, diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index e3505e536de..bccd994bbff 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -3,10 +3,9 @@ //! Primarily features [`peel_payment_onion`], which allows the decoding of an onion statelessly //! and can be used to predict whether we'd accept a payment. -use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::hmac::{Hmac, HmacEngine}; +use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; -use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1}; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; use crate::blinded_path; use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; @@ -285,7 +284,7 @@ where NS::Target: NodeSigner, L::Target: Logger, { - let (hop, shared_secret, next_packet_details_opt) = + let (hop, next_packet_details_opt) = decode_incoming_update_add_htlc_onion(msg, node_signer, logger, secp_ctx ).map_err(|e| { let (err_code, err_data) = match e { @@ -296,7 +295,8 @@ where InboundHTLCErr { msg, err_code, err_data } })?; Ok(match hop { - onion_utils::Hop::Forward { next_hop_hmac, new_packet_bytes, .. } | onion_utils::Hop::BlindedForward { next_hop_hmac, new_packet_bytes, .. } => { + onion_utils::Hop::Forward { shared_secret, next_hop_hmac, new_packet_bytes, .. } | + onion_utils::Hop::BlindedForward { shared_secret, next_hop_hmac, new_packet_bytes, .. } => { let inbound_onion_payload = match hop { onion_utils::Hop::Forward { next_hop_data, .. } => msgs::InboundOnionPayload::Forward(next_hop_data), onion_utils::Hop::BlindedForward { next_hop_data, .. } => msgs::InboundOnionPayload::BlindedForward(next_hop_data), @@ -328,19 +328,19 @@ where // TODO: If this is potentially a phantom payment we should decode the phantom payment // onion here and check it. create_fwd_pending_htlc_info( - msg, inbound_onion_payload, next_hop_hmac, new_packet_bytes, shared_secret, + msg, inbound_onion_payload, next_hop_hmac, new_packet_bytes, shared_secret.secret_bytes(), Some(next_packet_pubkey), )? }, - onion_utils::Hop::Receive(received_data) => { + onion_utils::Hop::Receive { hop_data, shared_secret } => { create_recv_pending_htlc_info( - msgs::InboundOnionPayload::Receive(received_data), shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, + msgs::InboundOnionPayload::Receive(hop_data), shared_secret.secret_bytes(), msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_skimmed_fees, msg.skimmed_fee_msat, cur_height, )? }, - onion_utils::Hop::BlindedReceive(received_data) => { + onion_utils::Hop::BlindedReceive { hop_data, shared_secret } => { create_recv_pending_htlc_info( - msgs::InboundOnionPayload::BlindedReceive(received_data), shared_secret, msg.payment_hash, msg.amount_msat, msg.cltv_expiry, + msgs::InboundOnionPayload::BlindedReceive(hop_data), shared_secret.secret_bytes(), msg.payment_hash, msg.amount_msat, msg.cltv_expiry, None, allow_skimmed_fees, msg.skimmed_fee_msat, cur_height, )? } @@ -356,7 +356,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, -) -> Result<(onion_utils::Hop, [u8; 32], Option), HTLCFailureMsg> +) -> Result<(onion_utils::Hop, Option), HTLCFailureMsg> where NS::Target: NodeSigner, L::Target: Logger, @@ -384,16 +384,6 @@ where return_malformed_err!("invalid ephemeral pubkey", 0x8000 | 0x4000 | 6); } - let blinded_node_id_tweak = msg.blinding_point.map(|bp| { - let blinded_tlvs_ss = node_signer.ecdh(Recipient::Node, &bp, None).unwrap().secret_bytes(); - let mut hmac = HmacEngine::::new(b"blinded_node_id"); - hmac.input(blinded_tlvs_ss.as_ref()); - Scalar::from_be_bytes(Hmac::from_engine(hmac).to_byte_array()).unwrap() - }); - let shared_secret = node_signer.ecdh( - Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), blinded_node_id_tweak.as_ref() - ).unwrap().secret_bytes(); - if msg.onion_routing_packet.version != 0 { //TODO: Spec doesn't indicate if we should only hash hop_data here (and in other //sha256_of_onion error data packets), or the entire onion_routing_packet. Either way, @@ -403,58 +393,55 @@ where //node knows the HMAC matched, so they already know what is there... return_malformed_err!("Unknown onion packet version", 0x8000 | 0x4000 | 4); } - macro_rules! return_err { - ($msg: expr, $err_code: expr, $data: expr) => { - { - if msg.blinding_point.is_some() { - return_malformed_err!($msg, INVALID_ONION_BLINDING) - } - log_info!(logger, "Failed to accept/forward incoming HTLC: {}", $msg); - return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { - channel_id: msg.channel_id, - htlc_id: msg.htlc_id, - reason: HTLCFailReason::reason($err_code, $data.to_vec()) - .get_encrypted_failure_packet(&shared_secret, &None), - })); - } + let encode_relay_error = |message: &str, err_code: u16, shared_secret: [u8; 32], data: &[u8]| { + if msg.blinding_point.is_some() { + return_malformed_err!(message, INVALID_ONION_BLINDING) } - } + + log_info!(logger, "Failed to accept/forward incoming HTLC: {}", message); + return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { + channel_id: msg.channel_id, + htlc_id: msg.htlc_id, + reason: HTLCFailReason::reason(err_code, data.to_vec()) + .get_encrypted_failure_packet(&shared_secret, &None), + })); + }; let next_hop = match onion_utils::decode_next_payment_hop( - shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, + Recipient::Node, &msg.onion_routing_packet.public_key.unwrap(), &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash, msg.blinding_point, node_signer ) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { return_malformed_err!(err_msg, err_code); }, - Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code }) => { - return_err!(err_msg, err_code, &[0; 0]); + Err(onion_utils::OnionDecodeErr::Relay { err_msg, err_code, shared_secret }) => { + return encode_relay_error(err_msg, err_code, shared_secret.secret_bytes(), &[0; 0]); }, }; let next_packet_details = match next_hop { - Hop::Forward { next_hop_data: msgs::InboundOnionForwardPayload { short_channel_id, amt_to_forward, outgoing_cltv_value }, .. } => { + Hop::Forward { next_hop_data: msgs::InboundOnionForwardPayload { short_channel_id, amt_to_forward, outgoing_cltv_value }, shared_secret, .. } => { let next_packet_pubkey = onion_utils::next_hop_pubkey(secp_ctx, - msg.onion_routing_packet.public_key.unwrap(), &shared_secret); + msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes()); Some(NextPacketDetails { next_packet_pubkey, outgoing_scid: short_channel_id, outgoing_amt_msat: amt_to_forward, outgoing_cltv_value }) } - Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, ref payment_relay, ref payment_constraints, ref features, .. }, .. } => { + Hop::BlindedForward { next_hop_data: msgs::InboundOnionBlindedForwardPayload { short_channel_id, ref payment_relay, ref payment_constraints, ref features, .. }, shared_secret, .. } => { let (amt_to_forward, outgoing_cltv_value) = match check_blinded_forward( msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features ) { Ok((amt, cltv)) => (amt, cltv), Err(()) => { - return_err!("Underflow calculating outbound amount or cltv value for blinded forward", - INVALID_ONION_BLINDING, &[0; 32]); + return encode_relay_error("Underflow calculating outbound amount or cltv value for blinded forward", + INVALID_ONION_BLINDING, shared_secret.secret_bytes(), &[0; 32]); } }; let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, - msg.onion_routing_packet.public_key.unwrap(), &shared_secret); + msg.onion_routing_packet.public_key.unwrap(), &shared_secret.secret_bytes()); Some(NextPacketDetails { next_packet_pubkey, outgoing_scid: short_channel_id, outgoing_amt_msat: amt_to_forward, outgoing_cltv_value @@ -463,7 +450,7 @@ where _ => None }; - Ok((next_hop, shared_secret, next_packet_details)) + Ok((next_hop, next_packet_details)) } pub(super) fn check_incoming_htlc_cltv( diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 35de2403441..a76cd5caa4b 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -16,7 +16,7 @@ use crate::ln::msgs; use crate::offers::invoice_request::InvoiceRequest; use crate::routing::gossip::NetworkUpdate; use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters, TrampolineHop}; -use crate::sign::NodeSigner; +use crate::sign::{NodeSigner, Recipient}; use crate::types::features::{ChannelFeatures, NodeFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::errors::{self, APIError}; @@ -1419,6 +1419,8 @@ pub(crate) enum Hop { Forward { /// Onion payload data used in forwarding the payment. next_hop_data: msgs::InboundOnionForwardPayload, + /// Shared secret that was used to decrypt next_hop_data. + shared_secret: SharedSecret, /// HMAC of the next hop's onion packet. next_hop_hmac: [u8; 32], /// Bytes of the onion packet we're forwarding. @@ -1428,6 +1430,8 @@ pub(crate) enum Hop { BlindedForward { /// Onion payload data used in forwarding the payment. next_hop_data: msgs::InboundOnionBlindedForwardPayload, + /// Shared secret that was used to decrypt next_hop_data. + shared_secret: SharedSecret, /// HMAC of the next hop's onion packet. next_hop_hmac: [u8; 32], /// Bytes of the onion packet we're forwarding. @@ -1435,10 +1439,20 @@ pub(crate) enum Hop { }, /// This onion payload was for us, not for forwarding to a next-hop. Contains information for /// verifying the incoming payment. - Receive(msgs::InboundOnionReceivePayload), + Receive { + /// Onion payload data used to receive our payment. + hop_data: msgs::InboundOnionReceivePayload, + /// Shared secret that was used to decrypt hop_data. + shared_secret: SharedSecret, + }, /// This onion payload was for us, not for forwarding to a next-hop. Contains information for /// verifying the incoming payment. - BlindedReceive(msgs::InboundOnionBlindedReceivePayload), + BlindedReceive { + /// Onion payload data used to receive our payment. + hop_data: msgs::InboundOnionBlindedReceivePayload, + /// Shared secret that was used to decrypt hop_data. + shared_secret: SharedSecret, + }, } impl Hop { @@ -1454,6 +1468,15 @@ impl Hop { _ => false, } } + + pub(crate) fn shared_secret(&self) -> &SharedSecret { + match self { + Hop::Forward { shared_secret, .. } => shared_secret, + Hop::BlindedForward { shared_secret, .. } => shared_secret, + Hop::Receive { shared_secret, .. } => shared_secret, + Hop::BlindedReceive { shared_secret, .. } => shared_secret, + } + } } /// Error returned when we fail to decode the onion packet. @@ -1462,18 +1485,27 @@ pub(crate) enum OnionDecodeErr { /// The HMAC of the onion packet did not match the hop data. Malformed { err_msg: &'static str, err_code: u16 }, /// We failed to decode the onion payload. - Relay { err_msg: &'static str, err_code: u16 }, + Relay { err_msg: &'static str, err_code: u16, shared_secret: SharedSecret }, } 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, + recipient: Recipient, hop_pubkey: &PublicKey, hop_data: &[u8], hmac_bytes: [u8; 32], + payment_hash: PaymentHash, blinding_point: Option, node_signer: NS, ) -> Result where NS::Target: NodeSigner, { + let blinded_node_id_tweak = blinding_point.map(|bp| { + let blinded_tlvs_ss = node_signer.ecdh(recipient, &bp, None).unwrap().secret_bytes(); + let mut hmac = HmacEngine::::new(b"blinded_node_id"); + hmac.input(blinded_tlvs_ss.as_ref()); + Scalar::from_be_bytes(Hmac::from_engine(hmac).to_byte_array()).unwrap() + }); + let shared_secret = + node_signer.ecdh(recipient, hop_pubkey, blinded_node_id_tweak.as_ref()).unwrap(); + let decoded_hop: Result<(msgs::InboundOnionPayload, Option<_>), _> = decode_next_hop( - shared_secret, + shared_secret.secret_bytes(), hop_data, hmac_bytes, Some(payment_hash), @@ -1482,25 +1514,56 @@ where match decoded_hop { Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => { match next_hop_data { - msgs::InboundOnionPayload::Forward(next_hop_data) => { - Ok(Hop::Forward { next_hop_data, next_hop_hmac, new_packet_bytes }) - }, + msgs::InboundOnionPayload::Forward(next_hop_data) => Ok(Hop::Forward { + shared_secret, + next_hop_data, + next_hop_hmac, + new_packet_bytes, + }), msgs::InboundOnionPayload::BlindedForward(next_hop_data) => { - Ok(Hop::BlindedForward { next_hop_data, next_hop_hmac, new_packet_bytes }) + Ok(Hop::BlindedForward { + shared_secret, + next_hop_data, + next_hop_hmac, + new_packet_bytes, + }) + }, + _ => { + if blinding_point.is_some() { + return Err(OnionDecodeErr::Malformed { + err_msg: + "Final Node OnionHopData provided for us as an intermediary node", + err_code: INVALID_ONION_BLINDING, + }); + } + Err(OnionDecodeErr::Relay { + err_msg: "Final Node OnionHopData provided for us as an intermediary node", + err_code: 0x4000 | 22, + shared_secret, + }) }, - _ => Err(OnionDecodeErr::Relay { - err_msg: "Final Node OnionHopData provided for us as an intermediary node", - err_code: 0x4000 | 22, - }), } }, Ok((next_hop_data, None)) => match next_hop_data { - msgs::InboundOnionPayload::Receive(payload) => Ok(Hop::Receive(payload)), - msgs::InboundOnionPayload::BlindedReceive(payload) => Ok(Hop::BlindedReceive(payload)), - _ => Err(OnionDecodeErr::Relay { - err_msg: "Intermediate Node OnionHopData provided for us as a final node", - err_code: 0x4000 | 22, - }), + msgs::InboundOnionPayload::Receive(hop_data) => { + Ok(Hop::Receive { shared_secret, hop_data }) + }, + msgs::InboundOnionPayload::BlindedReceive(hop_data) => { + Ok(Hop::BlindedReceive { shared_secret, hop_data }) + }, + _ => { + if blinding_point.is_some() { + return Err(OnionDecodeErr::Malformed { + err_msg: "Intermediate Node OnionHopData provided for us as a final node", + err_code: INVALID_ONION_BLINDING, + }); + } + Err(OnionDecodeErr::Relay { + err_msg: "Intermediate Node OnionHopData provided for us as a final node", + err_code: 0x4000 | 22, + shared_secret, + }) + }, }, Err(e) => Err(e), } @@ -1646,6 +1709,7 @@ fn decode_next_hop, N: NextPacketBytes>( return Err(OnionDecodeErr::Relay { err_msg: "Unable to decode our hop data", err_code: error_code, + shared_secret: SharedSecret::from_bytes(shared_secret), }); }, Ok(msg) => { @@ -1654,6 +1718,7 @@ fn decode_next_hop, N: NextPacketBytes>( return Err(OnionDecodeErr::Relay { err_msg: "Unable to decode our hop data", err_code: 0x4000 | 22, + shared_secret: SharedSecret::from_bytes(shared_secret), }); } if hmac == [0; 32] { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 7aa41531ce2..3ba950f548d 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -822,6 +822,7 @@ pub trait ChannelSigner { /// /// This indicates to [`NodeSigner::sign_invoice`] what node secret key should be used to sign /// the invoice. +#[derive(Clone, Copy)] pub enum Recipient { /// The invoice should be signed with the local node secret key. Node,