Skip to content

Commit 7436da4

Browse files
Support constructing BlindedPaths for payments.
1 parent 5ae2389 commit 7436da4

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
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

1516
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1617

18+
use crate::blinded_path::payment::BlindedPaymentTlvs;
1719
use crate::sign::EntropySource;
1820
use crate::ln::msgs::DecodeError;
1921
use crate::util::ser::{Readable, Writeable, Writer};
@@ -73,6 +75,25 @@ impl BlindedPath {
7375
blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
7476
})
7577
}
78+
79+
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
80+
/// in `path` will be the destination node.
81+
///
82+
/// Errors if `path` is empty or a node id in `path` is invalid.
83+
// TODO: make all payloads the same size with padding + add dummy hops
84+
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
85+
path: &[(PublicKey, BlindedPaymentTlvs)], entropy_source: &ES, secp_ctx: &Secp256k1<T>
86+
) -> Result<Self, ()> {
87+
if path.len() < 1 { return Err(()) }
88+
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
89+
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
90+
91+
Ok(BlindedPath {
92+
introduction_node_id: path[0].0,
93+
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
94+
blinded_hops: payment::blinded_hops(secp_ctx, path, &blinding_secret).map_err(|_| ())?,
95+
})
96+
}
7697
}
7798

7899
impl Writeable for BlindedPath {

lightning/src/blinded_path/payment.rs

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

0 commit comments

Comments
 (0)