mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-10 05:33:40 +01:00
Merge pull request #2413 from valentinewallace/2023-07-route-blinding
Route blinding MVP
This commit is contained in:
commit
daf79f515f
10 changed files with 346 additions and 70 deletions
|
@ -11,18 +11,21 @@
|
||||||
// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
|
// To modify it, modify msg_target_template.txt and run gen_target.sh instead.
|
||||||
|
|
||||||
use crate::utils::test_logger;
|
use crate::utils::test_logger;
|
||||||
|
use lightning::util::test_utils;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
|
pub fn onion_hop_data_test<Out: test_logger::Output>(data: &[u8], _out: Out) {
|
||||||
use lightning::util::ser::Readable;
|
use lightning::util::ser::ReadableArgs;
|
||||||
let mut r = ::std::io::Cursor::new(data);
|
let mut r = ::std::io::Cursor::new(data);
|
||||||
let _ = <lightning::ln::msgs::InboundOnionPayload as Readable>::read(&mut r);
|
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
|
||||||
|
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) {
|
pub extern "C" fn onion_hop_data_run(data: *const u8, datalen: usize) {
|
||||||
use lightning::util::ser::Readable;
|
use lightning::util::ser::ReadableArgs;
|
||||||
let data = unsafe { std::slice::from_raw_parts(data, datalen) };
|
let data = unsafe { std::slice::from_raw_parts(data, datalen) };
|
||||||
let mut r = ::std::io::Cursor::new(data);
|
let mut r = ::std::io::Cursor::new(data);
|
||||||
let _ = <lightning::ln::msgs::InboundOnionPayload as Readable>::read(&mut r);
|
let node_signer = test_utils::TestNodeSigner::new(test_utils::privkey(42));
|
||||||
|
let _ = <lightning::ln::msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestNodeSigner>>::read(&mut r, &&node_signer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,19 @@ impl BlindedPath {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a one-hop blinded path for a payment.
|
||||||
|
pub fn one_hop_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
|
||||||
|
payee_node_id: PublicKey, payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES,
|
||||||
|
secp_ctx: &Secp256k1<T>
|
||||||
|
) -> Result<(BlindedPayInfo, Self), ()> {
|
||||||
|
// This value is not considered in pathfinding for 1-hop blinded paths, because it's intended to
|
||||||
|
// be in relation to a specific channel.
|
||||||
|
let htlc_maximum_msat = u64::max_value();
|
||||||
|
Self::new_for_payment(
|
||||||
|
&[], payee_node_id, payee_tlvs, htlc_maximum_msat, entropy_source, secp_ctx
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`.
|
/// Create a blinded path for a payment, to be forwarded along `intermediate_nodes`.
|
||||||
///
|
///
|
||||||
/// Errors if:
|
/// Errors if:
|
||||||
|
@ -85,7 +98,7 @@ impl BlindedPath {
|
||||||
///
|
///
|
||||||
/// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
|
/// [`ForwardTlvs`]: crate::blinded_path::payment::ForwardTlvs
|
||||||
// TODO: make all payloads the same size with padding + add dummy hops
|
// TODO: make all payloads the same size with padding + add dummy hops
|
||||||
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
|
pub(crate) fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
|
||||||
intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
|
intermediate_nodes: &[payment::ForwardNode], payee_node_id: PublicKey,
|
||||||
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
|
payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, entropy_source: &ES,
|
||||||
secp_ctx: &Secp256k1<T>
|
secp_ctx: &Secp256k1<T>
|
||||||
|
|
|
@ -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: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
|
||||||
|
_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> {
|
impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
|
||||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||||
// TODO: write padding
|
// TODO: write padding
|
||||||
|
|
113
lightning/src/ln/blinded_payment_tests.rs
Normal file
113
lightning/src/ln/blinded_payment_tests.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// This file is Copyright its original authors, visible in version control
|
||||||
|
// history.
|
||||||
|
//
|
||||||
|
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||||
|
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||||
|
// You may not use this file except in accordance with one or both of these
|
||||||
|
// licenses.
|
||||||
|
|
||||||
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
|
use crate::blinded_path::BlindedPath;
|
||||||
|
use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs};
|
||||||
|
use crate::events::MessageSendEventsProvider;
|
||||||
|
use crate::ln::channelmanager;
|
||||||
|
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
|
||||||
|
use crate::ln::features::Bolt12InvoiceFeatures;
|
||||||
|
use crate::ln::functional_test_utils::*;
|
||||||
|
use crate::ln::outbound_payment::Retry;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::routing::router::{PaymentParameters, RouteParameters};
|
||||||
|
use crate::util::config::UserConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_hop_blinded_path() {
|
||||||
|
do_one_hop_blinded_path(true);
|
||||||
|
do_one_hop_blinded_path(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_one_hop_blinded_path(success: bool) {
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(2);
|
||||||
|
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
|
||||||
|
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
||||||
|
let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0).0.contents;
|
||||||
|
|
||||||
|
let amt_msat = 5000;
|
||||||
|
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None);
|
||||||
|
let payee_tlvs = ReceiveTlvs {
|
||||||
|
payment_secret,
|
||||||
|
payment_constraints: PaymentConstraints {
|
||||||
|
max_cltv_expiry: u32::max_value(),
|
||||||
|
htlc_minimum_msat: chan_upd.htlc_minimum_msat,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut secp_ctx = Secp256k1::new();
|
||||||
|
let blinded_path = BlindedPath::one_hop_for_payment(
|
||||||
|
nodes[1].node.get_our_node_id(), payee_tlvs, &chanmon_cfgs[1].keys_manager, &secp_ctx
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let route_params = RouteParameters {
|
||||||
|
payment_params: PaymentParameters::blinded(vec![blinded_path]),
|
||||||
|
final_value_msat: amt_msat
|
||||||
|
};
|
||||||
|
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(),
|
||||||
|
PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
|
||||||
|
check_added_monitors(&nodes[0], 1);
|
||||||
|
pass_along_route(&nodes[0], &[&[&nodes[1]]], amt_msat, payment_hash, payment_secret);
|
||||||
|
if success {
|
||||||
|
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
|
||||||
|
} else {
|
||||||
|
fail_payment(&nodes[0], &[&nodes[1]], payment_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mpp_to_one_hop_blinded_path() {
|
||||||
|
let chanmon_cfgs = create_chanmon_cfgs(4);
|
||||||
|
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
|
||||||
|
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
|
||||||
|
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
|
||||||
|
let mut secp_ctx = Secp256k1::new();
|
||||||
|
|
||||||
|
create_announced_chan_between_nodes(&nodes, 0, 1);
|
||||||
|
create_announced_chan_between_nodes(&nodes, 0, 2);
|
||||||
|
let chan_upd_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3).0.contents;
|
||||||
|
create_announced_chan_between_nodes(&nodes, 2, 3).0.contents;
|
||||||
|
|
||||||
|
let amt_msat = 15_000_000;
|
||||||
|
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None);
|
||||||
|
let payee_tlvs = ReceiveTlvs {
|
||||||
|
payment_secret,
|
||||||
|
payment_constraints: PaymentConstraints {
|
||||||
|
max_cltv_expiry: u32::max_value(),
|
||||||
|
htlc_minimum_msat: chan_upd_1_3.htlc_minimum_msat,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let blinded_path = BlindedPath::one_hop_for_payment(
|
||||||
|
nodes[3].node.get_our_node_id(), payee_tlvs, &chanmon_cfgs[3].keys_manager, &secp_ctx
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let bolt12_features: Bolt12InvoiceFeatures =
|
||||||
|
channelmanager::provided_invoice_features(&UserConfig::default()).to_context();
|
||||||
|
let route_params = RouteParameters {
|
||||||
|
payment_params: PaymentParameters::blinded(vec![blinded_path])
|
||||||
|
.with_bolt12_features(bolt12_features).unwrap(),
|
||||||
|
final_value_msat: amt_msat,
|
||||||
|
};
|
||||||
|
nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap();
|
||||||
|
check_added_monitors(&nodes[0], 2);
|
||||||
|
|
||||||
|
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]];
|
||||||
|
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||||
|
assert_eq!(events.len(), 2);
|
||||||
|
|
||||||
|
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
|
||||||
|
pass_along_path(&nodes[0], expected_route[0], amt_msat, payment_hash.clone(),
|
||||||
|
Some(payment_secret), ev.clone(), false, None);
|
||||||
|
|
||||||
|
let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events);
|
||||||
|
pass_along_path(&nodes[0], expected_route[1], amt_msat, payment_hash.clone(),
|
||||||
|
Some(payment_secret), ev.clone(), true, None);
|
||||||
|
claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage);
|
||||||
|
}
|
|
@ -2795,7 +2795,7 @@ where
|
||||||
let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data {
|
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 } =>
|
msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } =>
|
||||||
(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 {
|
return Err(InboundOnionErr {
|
||||||
msg: "Final Node OnionHopData provided for us as an intermediary node",
|
msg: "Final Node OnionHopData provided for us as an intermediary node",
|
||||||
err_code: 0x4000 | 22,
|
err_code: 0x4000 | 22,
|
||||||
|
@ -2827,12 +2827,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, ..
|
||||||
} =>
|
} =>
|
||||||
(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 {
|
return Err(InboundOnionErr {
|
||||||
err_code: 0x4000|22,
|
err_code: 0x4000|22,
|
||||||
err_data: Vec::new(),
|
err_data: Vec::new(),
|
||||||
msg: "Got non final data with an HMAC of 0",
|
msg: "Got non final data with an HMAC of 0",
|
||||||
}),
|
})
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// final_incorrect_cltv_expiry
|
// final_incorrect_cltv_expiry
|
||||||
if outgoing_cltv_value > cltv_expiry {
|
if outgoing_cltv_value > cltv_expiry {
|
||||||
|
@ -2972,7 +2979,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_hop = match onion_utils::decode_next_payment_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) {
|
let next_hop = match onion_utils::decode_next_payment_hop(
|
||||||
|
shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac,
|
||||||
|
msg.payment_hash, &self.node_signer
|
||||||
|
) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
||||||
return_malformed_err!(err_msg, err_code);
|
return_malformed_err!(err_msg, err_code);
|
||||||
|
@ -2994,7 +3004,9 @@ where
|
||||||
// We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
|
// We'll do receive checks in [`Self::construct_pending_htlc_info`] so we have access to the
|
||||||
// inbound channel's state.
|
// inbound channel's state.
|
||||||
onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)),
|
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]);
|
return_err!("Final Node OnionHopData provided for us as an intermediary node", 0x4000 | 22, &[0; 0]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3981,7 +3993,10 @@ where
|
||||||
let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode);
|
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.genesis_hash) {
|
if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.genesis_hash) {
|
||||||
let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes();
|
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) {
|
let next_hop = match onion_utils::decode_next_payment_hop(
|
||||||
|
phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac,
|
||||||
|
payment_hash, &self.node_signer
|
||||||
|
) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => {
|
||||||
let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner();
|
let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner();
|
||||||
|
|
|
@ -43,6 +43,9 @@ pub mod wire;
|
||||||
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
|
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
|
||||||
// about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below.
|
// about an unnecessary mut. Thus, we silence the unused_mut warning in two test modules below.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
mod blinded_payment_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
mod functional_tests;
|
mod functional_tests;
|
||||||
|
|
|
@ -31,22 +31,26 @@ use bitcoin::{secp256k1, Witness};
|
||||||
use bitcoin::blockdata::script::Script;
|
use bitcoin::blockdata::script::Script;
|
||||||
use bitcoin::hash_types::{Txid, BlockHash};
|
use bitcoin::hash_types::{Txid, BlockHash};
|
||||||
|
|
||||||
|
use crate::blinded_path::payment::ReceiveTlvs;
|
||||||
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
|
use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret};
|
||||||
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
|
use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures};
|
||||||
use crate::ln::onion_utils;
|
use crate::ln::onion_utils;
|
||||||
use crate::onion_message;
|
use crate::onion_message;
|
||||||
|
use crate::sign::{NodeSigner, Recipient};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
use core::ops::Deref;
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
use crate::io::{self, Read};
|
use crate::io::{self, Cursor, Read};
|
||||||
use crate::io_extras::read_to_end;
|
use crate::io_extras::read_to_end;
|
||||||
|
|
||||||
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
|
use crate::events::{MessageSendEventsProvider, OnionMessageProvider};
|
||||||
|
use crate::util::chacha20poly1305rfc::ChaChaPolyReadAdapter;
|
||||||
use crate::util::logger;
|
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::util::base32;
|
||||||
|
|
||||||
use crate::routing::gossip::{NodeAlias, NodeId};
|
use crate::routing::gossip::{NodeAlias, NodeId};
|
||||||
|
@ -1517,6 +1521,8 @@ pub trait OnionMessageHandler : OnionMessageProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod fuzzy_internal_msgs {
|
mod fuzzy_internal_msgs {
|
||||||
|
use bitcoin::secp256k1::PublicKey;
|
||||||
|
use crate::blinded_path::payment::PaymentConstraints;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::ln::{PaymentPreimage, PaymentSecret};
|
use crate::ln::{PaymentPreimage, PaymentSecret};
|
||||||
|
|
||||||
|
@ -1545,6 +1551,14 @@ mod fuzzy_internal_msgs {
|
||||||
amt_msat: u64,
|
amt_msat: u64,
|
||||||
outgoing_cltv_value: u32,
|
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 {
|
pub(crate) enum OutboundOnionPayload {
|
||||||
|
@ -1562,6 +1576,17 @@ mod fuzzy_internal_msgs {
|
||||||
amt_msat: u64,
|
amt_msat: u64,
|
||||||
outgoing_cltv_value: u32,
|
outgoing_cltv_value: u32,
|
||||||
},
|
},
|
||||||
|
BlindedForward {
|
||||||
|
encrypted_tlvs: Vec<u8>,
|
||||||
|
intro_node_blinding_point: Option<PublicKey>,
|
||||||
|
},
|
||||||
|
BlindedReceive {
|
||||||
|
amt_msat: u64,
|
||||||
|
total_msat: u64,
|
||||||
|
outgoing_cltv_value: u32,
|
||||||
|
encrypted_tlvs: Vec<u8>,
|
||||||
|
intro_node_blinding_point: Option<PublicKey>, // Set if the introduction node of the blinded path is the final node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DecodedOnionErrorPacket {
|
pub struct DecodedOnionErrorPacket {
|
||||||
|
@ -2097,29 +2122,53 @@ impl Writeable for OutboundOnionPayload {
|
||||||
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
|
(16, payment_metadata.as_ref().map(|m| WithoutLength(m)), option)
|
||||||
}, custom_tlvs.iter());
|
}, custom_tlvs.iter());
|
||||||
},
|
},
|
||||||
|
Self::BlindedForward { encrypted_tlvs, intro_node_blinding_point } => {
|
||||||
|
_encode_varint_length_prefixed_tlv!(w, {
|
||||||
|
(10, *encrypted_tlvs, required_vec),
|
||||||
|
(12, intro_node_blinding_point, option)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Self::BlindedReceive {
|
||||||
|
amt_msat, total_msat, outgoing_cltv_value, encrypted_tlvs,
|
||||||
|
intro_node_blinding_point,
|
||||||
|
} => {
|
||||||
|
_encode_varint_length_prefixed_tlv!(w, {
|
||||||
|
(2, HighZeroBytesDroppedBigSize(*amt_msat), required),
|
||||||
|
(4, HighZeroBytesDroppedBigSize(*outgoing_cltv_value), required),
|
||||||
|
(10, *encrypted_tlvs, required_vec),
|
||||||
|
(12, intro_node_blinding_point, option),
|
||||||
|
(18, HighZeroBytesDroppedBigSize(*total_msat), required)
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Readable for InboundOnionPayload {
|
impl<NS: Deref> ReadableArgs<&NS> for InboundOnionPayload where NS::Target: NodeSigner {
|
||||||
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
|
fn read<R: Read>(r: &mut R, node_signer: &NS) -> Result<Self, DecodeError> {
|
||||||
let mut amt = HighZeroBytesDroppedBigSize(0u64);
|
let mut amt = None;
|
||||||
let mut cltv_value = HighZeroBytesDroppedBigSize(0u32);
|
let mut cltv_value = None;
|
||||||
let mut short_id: Option<u64> = None;
|
let mut short_id: Option<u64> = None;
|
||||||
let mut payment_data: Option<FinalOnionHopData> = None;
|
let mut payment_data: Option<FinalOnionHopData> = None;
|
||||||
|
let mut encrypted_tlvs_opt: Option<WithoutLength<Vec<u8>>> = None;
|
||||||
|
let mut intro_node_blinding_point = None;
|
||||||
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
|
let mut payment_metadata: Option<WithoutLength<Vec<u8>>> = None;
|
||||||
|
let mut total_msat = None;
|
||||||
let mut keysend_preimage: Option<PaymentPreimage> = None;
|
let mut keysend_preimage: Option<PaymentPreimage> = None;
|
||||||
let mut custom_tlvs = Vec::new();
|
let mut custom_tlvs = Vec::new();
|
||||||
|
|
||||||
let tlv_len = BigSize::read(r)?;
|
let tlv_len = BigSize::read(r)?;
|
||||||
let rd = FixedLengthReader::new(r, tlv_len.0);
|
let rd = FixedLengthReader::new(r, tlv_len.0);
|
||||||
decode_tlv_stream_with_custom_tlv_decode!(rd, {
|
decode_tlv_stream_with_custom_tlv_decode!(rd, {
|
||||||
(2, amt, required),
|
(2, amt, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
|
||||||
(4, cltv_value, required),
|
(4, cltv_value, (option, encoding: (u32, HighZeroBytesDroppedBigSize))),
|
||||||
(6, short_id, option),
|
(6, short_id, option),
|
||||||
(8, payment_data, option),
|
(8, payment_data, option),
|
||||||
|
(10, encrypted_tlvs_opt, option),
|
||||||
|
(12, intro_node_blinding_point, option),
|
||||||
(16, payment_metadata, option),
|
(16, payment_metadata, option),
|
||||||
|
(18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
|
||||||
// See https://github.com/lightning/blips/blob/master/blip-0003.md
|
// See https://github.com/lightning/blips/blob/master/blip-0003.md
|
||||||
(5482373484, keysend_preimage, option)
|
(5482373484, keysend_preimage, option)
|
||||||
}, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
|
}, |msg_type: u64, msg_reader: &mut FixedLengthReader<_>| -> Result<bool, DecodeError> {
|
||||||
|
@ -2130,16 +2179,44 @@ impl Readable for InboundOnionPayload {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
if amt.0 > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) }
|
if amt.unwrap_or(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 let Some(blinding_point) = intro_node_blinding_point {
|
||||||
if payment_metadata.is_some() { return Err(DecodeError::InvalidValue); }
|
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 {
|
Ok(Self::Forward {
|
||||||
short_channel_id,
|
short_channel_id,
|
||||||
amt_to_forward: amt.0,
|
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
|
||||||
outgoing_cltv_value: cltv_value.0,
|
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
if encrypted_tlvs_opt.is_some() || total_msat.is_some() {
|
||||||
|
return Err(DecodeError::InvalidValue)
|
||||||
|
}
|
||||||
if let Some(data) = &payment_data {
|
if let Some(data) = &payment_data {
|
||||||
if data.total_msat > MAX_VALUE_MSAT {
|
if data.total_msat > MAX_VALUE_MSAT {
|
||||||
return Err(DecodeError::InvalidValue);
|
return Err(DecodeError::InvalidValue);
|
||||||
|
@ -2149,22 +2226,14 @@ impl Readable for InboundOnionPayload {
|
||||||
payment_data,
|
payment_data,
|
||||||
payment_metadata: payment_metadata.map(|w| w.0),
|
payment_metadata: payment_metadata.map(|w| w.0),
|
||||||
keysend_preimage,
|
keysend_preimage,
|
||||||
amt_msat: amt.0,
|
amt_msat: amt.ok_or(DecodeError::InvalidValue)?,
|
||||||
outgoing_cltv_value: cltv_value.0,
|
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
|
||||||
custom_tlvs,
|
custom_tlvs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and
|
|
||||||
// onion message packets.
|
|
||||||
impl ReadableArgs<()> for InboundOnionPayload {
|
|
||||||
fn read<R: Read>(r: &mut R, _arg: ()) -> Result<Self, DecodeError> {
|
|
||||||
<Self as Readable>::read(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Writeable for Ping {
|
impl Writeable for Ping {
|
||||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
|
||||||
self.ponglen.write(w)?;
|
self.ponglen.write(w)?;
|
||||||
|
@ -2582,7 +2651,8 @@ mod tests {
|
||||||
use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket};
|
use crate::ln::msgs::{self, FinalOnionHopData, OnionErrorPacket};
|
||||||
use crate::ln::msgs::SocketAddress;
|
use crate::ln::msgs::SocketAddress;
|
||||||
use crate::routing::gossip::{NodeAlias, NodeId};
|
use crate::routing::gossip::{NodeAlias, NodeId};
|
||||||
use crate::util::ser::{Writeable, Readable, Hostname, TransactionU16LenLimited};
|
use crate::util::ser::{Writeable, Readable, ReadableArgs, Hostname, TransactionU16LenLimited};
|
||||||
|
use crate::util::test_utils;
|
||||||
|
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::util::address::Address;
|
use bitcoin::util::address::Address;
|
||||||
|
@ -3674,8 +3744,11 @@ mod tests {
|
||||||
let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
|
let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
|
||||||
assert_eq!(encoded_value, target_value);
|
assert_eq!(encoded_value, target_value);
|
||||||
|
|
||||||
let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
if let msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } = inbound_msg {
|
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
|
||||||
|
if let msgs::InboundOnionPayload::Forward {
|
||||||
|
short_channel_id, amt_to_forward, outgoing_cltv_value
|
||||||
|
} = inbound_msg {
|
||||||
assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
|
assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
|
||||||
assert_eq!(amt_to_forward, 0x0badf00d01020304);
|
assert_eq!(amt_to_forward, 0x0badf00d01020304);
|
||||||
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
||||||
|
@ -3696,8 +3769,11 @@ mod tests {
|
||||||
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
|
let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
|
||||||
assert_eq!(encoded_value, target_value);
|
assert_eq!(encoded_value, target_value);
|
||||||
|
|
||||||
let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
if let msgs::InboundOnionPayload::Receive { payment_data: None, amt_msat, outgoing_cltv_value, .. } = inbound_msg {
|
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
|
||||||
|
if let msgs::InboundOnionPayload::Receive {
|
||||||
|
payment_data: None, amt_msat, outgoing_cltv_value, ..
|
||||||
|
} = inbound_msg {
|
||||||
assert_eq!(amt_msat, 0x0badf00d01020304);
|
assert_eq!(amt_msat, 0x0badf00d01020304);
|
||||||
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
assert_eq!(outgoing_cltv_value, 0xffffffff);
|
||||||
} else { panic!(); }
|
} else { panic!(); }
|
||||||
|
@ -3721,7 +3797,8 @@ mod tests {
|
||||||
let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
|
let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
|
||||||
assert_eq!(encoded_value, target_value);
|
assert_eq!(encoded_value, target_value);
|
||||||
|
|
||||||
let inbound_msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
|
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
|
||||||
if let msgs::InboundOnionPayload::Receive {
|
if let msgs::InboundOnionPayload::Receive {
|
||||||
payment_data: Some(FinalOnionHopData {
|
payment_data: Some(FinalOnionHopData {
|
||||||
payment_secret,
|
payment_secret,
|
||||||
|
@ -3756,7 +3833,8 @@ mod tests {
|
||||||
outgoing_cltv_value: 0xffffffff,
|
outgoing_cltv_value: 0xffffffff,
|
||||||
};
|
};
|
||||||
let encoded_value = msg.encode();
|
let encoded_value = msg.encode();
|
||||||
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..])).is_err());
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
|
assert!(msgs::InboundOnionPayload::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).is_err());
|
||||||
let good_type_range_tlvs = vec![
|
let good_type_range_tlvs = vec![
|
||||||
((1 << 16) - 3, vec![42]),
|
((1 << 16) - 3, vec![42]),
|
||||||
((1 << 16) - 1, vec![42; 32]),
|
((1 << 16) - 1, vec![42; 32]),
|
||||||
|
@ -3765,7 +3843,7 @@ mod tests {
|
||||||
*custom_tlvs = good_type_range_tlvs.clone();
|
*custom_tlvs = good_type_range_tlvs.clone();
|
||||||
}
|
}
|
||||||
let encoded_value = msg.encode();
|
let encoded_value = msg.encode();
|
||||||
let inbound_msg = Readable::read(&mut Cursor::new(&encoded_value[..])).unwrap();
|
let inbound_msg = ReadableArgs::read(&mut Cursor::new(&encoded_value[..]), &&node_signer).unwrap();
|
||||||
match inbound_msg {
|
match inbound_msg {
|
||||||
msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
|
msgs::InboundOnionPayload::Receive { custom_tlvs, .. } => assert!(custom_tlvs.is_empty()),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
@ -3789,7 +3867,8 @@ mod tests {
|
||||||
let encoded_value = msg.encode();
|
let encoded_value = msg.encode();
|
||||||
let target_value = hex::decode("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
|
let target_value = hex::decode("2e02080badf00d010203040404ffffffffff0000000146c6616b021234ff0000000146c6616f084242424242424242").unwrap();
|
||||||
assert_eq!(encoded_value, target_value);
|
assert_eq!(encoded_value, target_value);
|
||||||
let inbound_msg: msgs::InboundOnionPayload = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
|
let inbound_msg: msgs::InboundOnionPayload = ReadableArgs::read(&mut Cursor::new(&target_value[..]), &&node_signer).unwrap();
|
||||||
if let msgs::InboundOnionPayload::Receive {
|
if let msgs::InboundOnionPayload::Receive {
|
||||||
payment_data: None,
|
payment_data: None,
|
||||||
payment_metadata: None,
|
payment_metadata: None,
|
||||||
|
@ -3952,7 +4031,10 @@ mod tests {
|
||||||
// payload length to be encoded over multiple bytes rather than a single u8.
|
// payload length to be encoded over multiple bytes rather than a single u8.
|
||||||
let big_payload = encode_big_payload().unwrap();
|
let big_payload = encode_big_payload().unwrap();
|
||||||
let mut rd = Cursor::new(&big_payload[..]);
|
let mut rd = Cursor::new(&big_payload[..]);
|
||||||
<msgs::InboundOnionPayload as Readable>::read(&mut rd).unwrap();
|
|
||||||
|
let node_signer = test_utils::TestKeysInterface::new(&[42; 32], Network::Testnet);
|
||||||
|
<msgs::InboundOnionPayload as ReadableArgs<&&test_utils::TestKeysInterface>>
|
||||||
|
::read(&mut rd, &&node_signer).unwrap();
|
||||||
}
|
}
|
||||||
// see above test, needs to be a separate method for use of the serialization macros.
|
// see above test, needs to be a separate method for use of the serialization macros.
|
||||||
fn encode_big_payload() -> Result<Vec<u8>, io::Error> {
|
fn encode_big_payload() -> Result<Vec<u8>, io::Error> {
|
||||||
|
|
|
@ -12,7 +12,8 @@ use crate::ln::channelmanager::{HTLCSource, RecipientOnionFields};
|
||||||
use crate::ln::msgs;
|
use crate::ln::msgs;
|
||||||
use crate::ln::wire::Encode;
|
use crate::ln::wire::Encode;
|
||||||
use crate::routing::gossip::NetworkUpdate;
|
use crate::routing::gossip::NetworkUpdate;
|
||||||
use crate::routing::router::{Path, RouteHop};
|
use crate::routing::router::{BlindedTail, Path, RouteHop};
|
||||||
|
use crate::sign::NodeSigner;
|
||||||
use crate::util::chacha20::{ChaCha20, ChaChaReader};
|
use crate::util::chacha20::{ChaCha20, ChaChaReader};
|
||||||
use crate::util::errors::{self, APIError};
|
use crate::util::errors::{self, APIError};
|
||||||
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
|
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer, LengthCalculatingWriter};
|
||||||
|
@ -169,7 +170,9 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
|
||||||
let mut cur_value_msat = 0u64;
|
let mut cur_value_msat = 0u64;
|
||||||
let mut cur_cltv = starting_htlc_offset;
|
let mut cur_cltv = starting_htlc_offset;
|
||||||
let mut last_short_channel_id = 0;
|
let mut last_short_channel_id = 0;
|
||||||
let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(path.hops.len());
|
let mut res: Vec<msgs::OutboundOnionPayload> = Vec::with_capacity(
|
||||||
|
path.hops.len() + path.blinded_tail.as_ref().map_or(0, |t| t.hops.len())
|
||||||
|
);
|
||||||
|
|
||||||
for (idx, hop) in path.hops.iter().rev().enumerate() {
|
for (idx, hop) in path.hops.iter().rev().enumerate() {
|
||||||
// First hop gets special values so that it can check, on receipt, that everything is
|
// First hop gets special values so that it can check, on receipt, that everything is
|
||||||
|
@ -177,8 +180,31 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
|
||||||
// the intended recipient).
|
// the intended recipient).
|
||||||
let value_msat = if cur_value_msat == 0 { hop.fee_msat } else { cur_value_msat };
|
let value_msat = if cur_value_msat == 0 { hop.fee_msat } else { cur_value_msat };
|
||||||
let cltv = if cur_cltv == starting_htlc_offset { hop.cltv_expiry_delta + starting_htlc_offset } else { cur_cltv };
|
let cltv = if cur_cltv == starting_htlc_offset { hop.cltv_expiry_delta + starting_htlc_offset } else { cur_cltv };
|
||||||
res.insert(0, if idx == 0 {
|
if idx == 0 {
|
||||||
msgs::OutboundOnionPayload::Receive {
|
if let Some(BlindedTail {
|
||||||
|
blinding_point, hops, final_value_msat, excess_final_cltv_expiry_delta, ..
|
||||||
|
}) = &path.blinded_tail {
|
||||||
|
let mut blinding_point = Some(*blinding_point);
|
||||||
|
for (i, blinded_hop) in hops.iter().enumerate() {
|
||||||
|
if i == hops.len() - 1 {
|
||||||
|
cur_value_msat += final_value_msat;
|
||||||
|
cur_cltv += excess_final_cltv_expiry_delta;
|
||||||
|
res.push(msgs::OutboundOnionPayload::BlindedReceive {
|
||||||
|
amt_msat: *final_value_msat,
|
||||||
|
total_msat,
|
||||||
|
outgoing_cltv_value: cltv,
|
||||||
|
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
|
||||||
|
intro_node_blinding_point: blinding_point.take(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.push(msgs::OutboundOnionPayload::BlindedForward {
|
||||||
|
encrypted_tlvs: blinded_hop.encrypted_payload.clone(),
|
||||||
|
intro_node_blinding_point: blinding_point.take(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(msgs::OutboundOnionPayload::Receive {
|
||||||
payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
|
payment_data: if let Some(secret) = recipient_onion.payment_secret.take() {
|
||||||
Some(msgs::FinalOnionHopData {
|
Some(msgs::FinalOnionHopData {
|
||||||
payment_secret: secret,
|
payment_secret: secret,
|
||||||
|
@ -190,14 +216,15 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o
|
||||||
custom_tlvs: recipient_onion.custom_tlvs.clone(),
|
custom_tlvs: recipient_onion.custom_tlvs.clone(),
|
||||||
amt_msat: value_msat,
|
amt_msat: value_msat,
|
||||||
outgoing_cltv_value: cltv,
|
outgoing_cltv_value: cltv,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msgs::OutboundOnionPayload::Forward {
|
res.insert(0, msgs::OutboundOnionPayload::Forward {
|
||||||
short_channel_id: last_short_channel_id,
|
short_channel_id: last_short_channel_id,
|
||||||
amt_to_forward: value_msat,
|
amt_to_forward: value_msat,
|
||||||
outgoing_cltv_value: cltv,
|
outgoing_cltv_value: cltv,
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
cur_value_msat += hop.fee_msat;
|
cur_value_msat += hop.fee_msat;
|
||||||
if cur_value_msat >= 21000000 * 100000000 * 1000 {
|
if cur_value_msat >= 21000000 * 100000000 * 1000 {
|
||||||
return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()});
|
return Err(APIError::InvalidRoute{err: "Channel fees overflowed?".to_owned()});
|
||||||
|
@ -859,8 +886,11 @@ 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) -> Result<Hop, OnionDecodeErr> {
|
pub(crate) fn decode_next_payment_hop<NS: Deref>(
|
||||||
match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), ()) {
|
shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash,
|
||||||
|
node_signer: &NS,
|
||||||
|
) -> Result<Hop, OnionDecodeErr> where NS::Target: NodeSigner {
|
||||||
|
match decode_next_hop(shared_secret, hop_data, hmac_bytes, Some(payment_hash), node_signer) {
|
||||||
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
|
Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)),
|
||||||
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
|
Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => {
|
||||||
Ok(Hop::Forward {
|
Ok(Hop::Forward {
|
||||||
|
|
|
@ -1234,7 +1234,9 @@ impl OutboundPayments {
|
||||||
if route.paths.len() < 1 {
|
if route.paths.len() < 1 {
|
||||||
return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()}));
|
return Err(PaymentSendFailure::ParameterError(APIError::InvalidRoute{err: "There must be at least one path to send over".to_owned()}));
|
||||||
}
|
}
|
||||||
if recipient_onion.payment_secret.is_none() && route.paths.len() > 1 {
|
if recipient_onion.payment_secret.is_none() && route.paths.len() > 1
|
||||||
|
&& !route.paths.iter().any(|p| p.blinded_tail.is_some())
|
||||||
|
{
|
||||||
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
|
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError{err: "Payment secret is required for multi-path payments".to_owned()}));
|
||||||
}
|
}
|
||||||
let mut total_value = 0;
|
let mut total_value = 0;
|
||||||
|
@ -1245,10 +1247,6 @@ impl OutboundPayments {
|
||||||
path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
|
path_errs.push(Err(APIError::InvalidRoute{err: "Path didn't go anywhere/had bogus size".to_owned()}));
|
||||||
continue 'path_check;
|
continue 'path_check;
|
||||||
}
|
}
|
||||||
if path.blinded_tail.is_some() {
|
|
||||||
path_errs.push(Err(APIError::InvalidRoute{err: "Sending to blinded paths isn't supported yet".to_owned()}));
|
|
||||||
continue 'path_check;
|
|
||||||
}
|
|
||||||
let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
|
let dest_hop_idx = if path.blinded_tail.is_some() && path.blinded_tail.as_ref().unwrap().hops.len() > 1 {
|
||||||
usize::max_value() } else { path.hops.len() - 1 };
|
usize::max_value() } else { path.hops.len() - 1 };
|
||||||
for (idx, hop) in path.hops.iter().enumerate() {
|
for (idx, hop) in path.hops.iter().enumerate() {
|
||||||
|
|
4
pending_changelog/1-hop-bps.txt
Normal file
4
pending_changelog/1-hop-bps.txt
Normal file
|
@ -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).
|
Loading…
Add table
Reference in a new issue