Skip to content

Commit 76f8cc1

Browse files
Support constructing BlindedPaths for payments.
1 parent d224f98 commit 76f8cc1

File tree

3 files changed

+186
-2
lines changed

3 files changed

+186
-2
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
//! Creating blinded paths and related utilities live here.
1111
12+
pub mod payment;
1213
pub(crate) mod message;
1314
pub(crate) mod utils;
1415

@@ -73,6 +74,27 @@ impl BlindedPath {
7374
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
7475
})
7576
}
77+
78+
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
79+
/// in `path` will be the destination node.
80+
///
81+
/// Errors if `path` is empty or a node id in `path` is invalid.
82+
// TODO: make all payloads the same size with padding + add dummy hops
83+
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
84+
intermediate_nodes: &[(PublicKey, payment::ForwardTlvs)], payee_node_id: PublicKey,
85+
payee_tlvs: payment::ReceiveTlvs, entropy_source: &ES, secp_ctx: &Secp256k1<T>
86+
) -> Result<Self, ()> {
87+
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
88+
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
89+
90+
Ok(BlindedPath {
91+
introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.0),
92+
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
93+
blinded_hops: payment::blinded_hops(
94+
secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret
95+
).map_err(|_| ())?,
96+
})
97+
}
7698
}
7799

78100
impl Writeable for BlindedPath {

lightning/src/blinded_path/payment.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! Data structures and methods for constructing [`BlindedPath`]s to send a payment over.
2+
//!
3+
//! [`BlindedPath`]: crate::blinded_path::BlindedPath
4+
5+
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
6+
7+
use crate::blinded_path::BlindedHop;
8+
use crate::blinded_path::utils;
9+
use crate::io;
10+
use crate::ln::PaymentSecret;
11+
use crate::ln::features::BlindedHopFeatures;
12+
use crate::ln::msgs::DecodeError;
13+
use crate::prelude::*;
14+
use crate::util::ser::{Readable, Writeable, Writer};
15+
16+
/// Data to construct a [`BlindedHop`] for forwarding a payment.
17+
pub struct ForwardTlvs {
18+
/// The short channel id this payment should be forwarded out over.
19+
short_channel_id: u64,
20+
/// Payment parameters for relaying over [`Self::short_channel_id`].
21+
payment_relay: PaymentRelay,
22+
/// Payment constraints for relaying over [`Self::short_channel_id`].
23+
payment_constraints: PaymentConstraints,
24+
/// Supported and required features when relaying a payment onion containing this object's
25+
/// corresponding [`BlindedHop::encrypted_payload`].
26+
///
27+
/// [`BlindedHop::encrypted_payload`]: crate::blinded_path::BlindedHop::encrypted_payload
28+
features: BlindedHopFeatures,
29+
}
30+
31+
/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
32+
/// may not be valid if received by another lightning implementation.
33+
pub struct ReceiveTlvs {
34+
/// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together.
35+
payment_secret: PaymentSecret,
36+
/// Constraints for the receiver of this payment.
37+
payment_constraints: PaymentConstraints,
38+
}
39+
40+
/// Data to construct a [`BlindedHop`] for sending a payment over.
41+
///
42+
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
43+
pub(crate) enum BlindedPaymentTlvs {
44+
/// This blinded payment data is for a forwarding node.
45+
Forward(ForwardTlvs),
46+
/// This blinded payment data is for the receiving node.
47+
Receive(ReceiveTlvs),
48+
}
49+
50+
// Used to include forward and receive TLVs in the same iterator for encoding.
51+
enum BlindedPaymentTlvsRef<'a> {
52+
Forward(&'a ForwardTlvs),
53+
Receive(&'a ReceiveTlvs),
54+
}
55+
56+
/// Parameters for relaying over a given [`BlindedHop`].
57+
///
58+
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
59+
pub struct PaymentRelay {
60+
/// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for this [`BlindedHop`].
61+
///
62+
///[`BlindedHop`]: crate::blinded_path::BlindedHop
63+
pub cltv_expiry_delta: u16,
64+
/// Liquidity fee charged (in millionths of the amount transferred) for relaying a payment over
65+
/// this [`BlindedHop`], (i.e., 10,000 is 1%).
66+
///
67+
///[`BlindedHop`]: crate::blinded_path::BlindedHop
68+
pub fee_proportional_millionths: u32,
69+
/// Base fee charged (in millisatoshi) for relaying a payment over this [`BlindedHop`].
70+
///
71+
///[`BlindedHop`]: crate::blinded_path::BlindedHop
72+
pub fee_base_msat: u32,
73+
}
74+
75+
/// Constraints for relaying over a given [`BlindedHop`].
76+
///
77+
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
78+
pub struct PaymentConstraints {
79+
/// The maximum total CLTV delta that is acceptable when relaying a payment over this
80+
/// [`BlindedHop`].
81+
///
82+
///[`BlindedHop`]: crate::blinded_path::BlindedHop
83+
pub max_cltv_expiry: u32,
84+
/// The minimum value, in msat, that may be relayed over this [`BlindedHop`].
85+
pub htlc_minimum_msat: u64,
86+
}
87+
88+
impl_writeable_tlv_based!(ForwardTlvs, {
89+
(2, short_channel_id, required),
90+
(10, payment_relay, required),
91+
(12, payment_constraints, required),
92+
(14, features, required),
93+
});
94+
95+
impl_writeable_tlv_based!(ReceiveTlvs, {
96+
(12, payment_constraints, required),
97+
(65536, payment_secret, required),
98+
});
99+
100+
impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
101+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
102+
// TODO: write padding
103+
match self {
104+
Self::Forward(tlvs) => tlvs.write(w)?,
105+
Self::Receive(tlvs) => tlvs.write(w)?,
106+
}
107+
Ok(())
108+
}
109+
}
110+
111+
impl Readable for BlindedPaymentTlvs {
112+
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
113+
_init_and_read_tlv_stream!(r, {
114+
(1, _padding, option),
115+
(2, scid, option),
116+
(10, payment_relay, option),
117+
(12, payment_constraints, required),
118+
(14, features, option),
119+
(65536, payment_secret, option),
120+
});
121+
let _padding: Option<utils::Padding> = _padding;
122+
123+
if let Some(short_channel_id) = scid {
124+
if payment_secret.is_some() { return Err(DecodeError::InvalidValue) }
125+
Ok(BlindedPaymentTlvs::Forward(ForwardTlvs {
126+
short_channel_id,
127+
payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?,
128+
payment_constraints: payment_constraints.0.unwrap(),
129+
features: features.ok_or(DecodeError::InvalidValue)?,
130+
}))
131+
} else {
132+
if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) }
133+
Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs {
134+
payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?,
135+
payment_constraints: payment_constraints.0.unwrap(),
136+
}))
137+
}
138+
}
139+
}
140+
141+
/// Construct blinded payment hops for the given `intermediate_nodes` and payee info.
142+
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
143+
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[(PublicKey, ForwardTlvs)],
144+
payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey
145+
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
146+
let pks = intermediate_nodes.iter().map(|(pk, _)| pk)
147+
.chain(core::iter::once(&payee_node_id));
148+
let tlvs = intermediate_nodes.iter().map(|(_, tlvs)| BlindedPaymentTlvsRef::Forward(tlvs))
149+
.chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs)));
150+
utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv)
151+
}
152+
153+
impl_writeable_msg!(PaymentRelay, {
154+
cltv_expiry_delta,
155+
fee_proportional_millionths,
156+
fee_base_msat
157+
}, {});
158+
159+
impl_writeable_msg!(PaymentConstraints, {
160+
max_cltv_expiry,
161+
htlc_minimum_msat
162+
}, {});

lightning/src/blinded_path/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ pub(super) fn construct_blinded_hops<'a, T, I1, I2>(
111111
) -> Result<Vec<BlindedHop>, secp256k1::Error>
112112
where
113113
T: secp256k1::Signing + secp256k1::Verification,
114-
I1: ExactSizeIterator<Item=&'a PublicKey>,
114+
I1: Iterator<Item=&'a PublicKey>,
115115
I2: Iterator,
116116
I2::Item: Writeable
117117
{
118-
let mut blinded_hops = Vec::with_capacity(unblinded_pks.len());
118+
let mut blinded_hops = Vec::with_capacity(unblinded_pks.size_hint().0);
119119
construct_keys_callback(
120120
secp_ctx, unblinded_pks, None, session_priv,
121121
|blinded_node_id, _, _, encrypted_payload_rho, _, _| {

0 commit comments

Comments
 (0)