From: Valentine Wallace Date: Sun, 10 Sep 2023 05:22:49 +0000 (-0400) Subject: Support receiving to 1-hop blinded payment paths. X-Git-Tag: v0.0.117-alpha1~14^2~2 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=070f7e0d5c09ec03419904aa041e2108641a5212;p=rust-lightning Support receiving to 1-hop blinded payment paths. --- diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 32181f788..39f16a916 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -119,6 +119,21 @@ impl Writeable for ReceiveTlvs { } } +// This will be removed once we support forwarding blinded HTLCs, because we'll always read a +// `BlindedPaymentTlvs` instead. +impl Readable for ReceiveTlvs { + fn read(r: &mut R) -> Result { + _init_and_read_tlv_stream!(r, { + (12, payment_constraints, required), + (65536, payment_secret, required), + }); + Ok(Self { + payment_secret: payment_secret.0.unwrap(), + payment_constraints: payment_constraints.0.unwrap() + }) + } +} + impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { // TODO: write padding diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0238c9696..d0dff57ed 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2738,7 +2738,7 @@ where let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data { msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => (short_channel_id, amt_to_forward, outgoing_cltv_value), - msgs::InboundOnionPayload::Receive { .. } => + msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => return Err(InboundOnionErr { msg: "Final Node OnionHopData provided for us as an intermediary node", err_code: 0x4000 | 22, @@ -2770,12 +2770,19 @@ where payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata, .. } => (payment_data, keysend_preimage, custom_tlvs, amt_msat, outgoing_cltv_value, payment_metadata), - _ => + msgs::InboundOnionPayload::BlindedReceive { + amt_msat, total_msat, outgoing_cltv_value, payment_secret, .. + } => { + let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat }; + (Some(payment_data), None, Vec::new(), amt_msat, outgoing_cltv_value, None) + } + msgs::InboundOnionPayload::Forward { .. } => { return Err(InboundOnionErr { err_code: 0x4000|22, err_data: Vec::new(), msg: "Got non final data with an HMAC of 0", - }), + }) + }, }; // final_incorrect_cltv_expiry if outgoing_cltv_value > cltv_expiry { @@ -2940,7 +2947,9 @@ where // We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the // inbound channel's state. onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), - onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } => { + onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } | + onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, .. } => + { return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]); } }; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 810270ca4..c617d97fe 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,11 +31,12 @@ use bitcoin::{secp256k1, Witness}; use bitcoin::blockdata::script::Script; use bitcoin::hash_types::{Txid, BlockHash}; +use crate::blinded_path::payment::ReceiveTlvs; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; use crate::ln::onion_utils; use crate::onion_message; -use crate::sign::NodeSigner; +use crate::sign::{NodeSigner, Recipient}; use crate::prelude::*; use core::convert::TryFrom; @@ -43,12 +44,13 @@ use core::fmt; use core::fmt::Debug; use core::ops::Deref; use core::str::FromStr; -use crate::io::{self, Read}; +use crate::io::{self, Cursor, Read}; use crate::io_extras::read_to_end; use crate::events::{MessageSendEventsProvider, OnionMessageProvider}; +use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter; use crate::util::logger; -use crate::util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize}; +use crate::util::ser::{LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer, WithoutLength, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, TransactionU16LenLimited, BigSize}; use crate::util::base32; use crate::routing::gossip::{NodeAlias, NodeId}; @@ -1520,6 +1522,7 @@ pub trait OnionMessageHandler : OnionMessageProvider { mod fuzzy_internal_msgs { use bitcoin::secp256k1::PublicKey; + use crate::blinded_path::payment::PaymentConstraints; use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; @@ -1548,6 +1551,14 @@ mod fuzzy_internal_msgs { amt_msat: u64, outgoing_cltv_value: u32, }, + BlindedReceive { + amt_msat: u64, + total_msat: u64, + outgoing_cltv_value: u32, + payment_secret: PaymentSecret, + payment_constraints: PaymentConstraints, + intro_node_blinding_point: PublicKey, + } } pub(crate) enum OutboundOnionPayload { @@ -2136,22 +2147,28 @@ impl Writeable for OutboundOnionPayload { impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: NodeSigner { fn read(r: &mut R, node_signer: &NS) -> Result { - let mut amt = HighZeroBytesDroppedBigSize(0u64); - let mut cltv_value = HighZeroBytesDroppedBigSize(0u32); + let mut amt = None; + let mut cltv_value = None; let mut short_id: Option = None; let mut payment_data: Option = None; + let mut encrypted_tlvs_opt: Option>> = None; + let mut intro_node_blinding_point = None; let mut payment_metadata: Option>> = None; + let mut total_msat = None; let mut keysend_preimage: Option = None; let mut custom_tlvs = Vec::new(); let tlv_len = BigSize::read(r)?; let rd = FixedLengthReader::new(r, tlv_len.0); decode_tlv_stream_with_custom_tlv_decode!(rd, { - (2, amt, required), - (4, cltv_value, required), + (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))), // See https://github.com/lightning/blips/blob/master/blip-0003.md (5482373484, keysend_preimage, option) }, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result { @@ -2162,16 +2179,44 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node Ok(true) }); - if amt.0 > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } - if let Some(short_channel_id) = short_id { - if payment_data.is_some() { return Err(DecodeError::InvalidValue) } - if payment_metadata.is_some() { return Err(DecodeError::InvalidValue); } + if amt.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + + if let Some(blinding_point) = intro_node_blinding_point { + if short_id.is_some() || payment_data.is_some() || payment_metadata.is_some() { + return Err(DecodeError::InvalidValue) + } + let enc_tlvs = encrypted_tlvs_opt.ok_or(DecodeError::InvalidValue)?.0; + let enc_tlvs_ss = node_signer.ecdh(Recipient::Node, &blinding_point, None) + .map_err(|_| DecodeError::InvalidValue)?; + let rho = onion_utils::gen_rho_from_shared_secret(&enc_tlvs_ss.secret_bytes()); + let mut s = Cursor::new(&enc_tlvs); + let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho)? { + ChaChaPolyReadAdapter { readable: ReceiveTlvs { payment_secret, payment_constraints }} => { + if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } + Ok(Self::BlindedReceive { + amt_msat: amt.ok_or(DecodeError::InvalidValue)?, + total_msat: total_msat.ok_or(DecodeError::InvalidValue)?, + outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, + payment_secret, + payment_constraints, + intro_node_blinding_point: blinding_point, + }) + }, + } + } else if let Some(short_channel_id) = short_id { + if payment_data.is_some() || payment_metadata.is_some() || encrypted_tlvs_opt.is_some() || + total_msat.is_some() + { return Err(DecodeError::InvalidValue) } Ok(Self::Forward { short_channel_id, - amt_to_forward: amt.0, - outgoing_cltv_value: cltv_value.0, + amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?, + outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, }) } else { + if encrypted_tlvs_opt.is_some() || total_msat.is_some() { + return Err(DecodeError::InvalidValue) + } if let Some(data) = &payment_data { if data.total_msat > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue); @@ -2181,8 +2226,8 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node payment_data, payment_metadata: payment_metadata.map(|w| w.0), keysend_preimage, - amt_msat: amt.0, - outgoing_cltv_value: cltv_value.0, + amt_msat: amt.ok_or(DecodeError::InvalidValue)?, + outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?, custom_tlvs, }) } diff --git a/pending_changelog/1-hop-bps.txt b/pending_changelog/1-hop-bps.txt new file mode 100644 index 000000000..aca226000 --- /dev/null +++ b/pending_changelog/1-hop-bps.txt @@ -0,0 +1,4 @@ +## Backwards Compatibility + +* Creating a blinded path to receive a payment over and then downgrading to a version of LDK prior + to 0.0.117 may result in failure to receive the payment (#2413).