Skip to content

Commit 89b33bb

Browse files
Compute aggregated BlindedPayInfo in path construction
1 parent 576d2db commit 89b33bb

File tree

2 files changed

+137
-6
lines changed

2 files changed

+137
-6
lines changed

lightning/src/blinded_path/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ pub(crate) mod utils;
1616
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
1717

1818
use crate::blinded_path::payment::BlindedPaymentTlvs;
19-
use crate::sign::EntropySource;
19+
use crate::offers::invoice::BlindedPayInfo;
2020
use crate::ln::msgs::DecodeError;
21+
use crate::sign::EntropySource;
2122
use crate::util::ser::{Readable, Writeable, Writer};
2223

2324
use crate::io;
@@ -79,20 +80,22 @@ impl BlindedPath {
7980
/// Create a blinded path for a payment, to be forwarded along `path`. The last node
8081
/// in `path` will be the destination node.
8182
///
82-
/// Errors if `path` is empty or a node id in `path` is invalid.
83+
/// Errors if `path` is empty, a node id in `path` is invalid, or [`BlindedPayInfo`] calculation
84+
/// results in an integer overflow.
8385
// TODO: make all payloads the same size with padding + add dummy hops
8486
pub fn new_for_payment<ES: EntropySource, T: secp256k1::Signing + secp256k1::Verification>(
8587
path: &[(PublicKey, BlindedPaymentTlvs)], entropy_source: &ES, secp_ctx: &Secp256k1<T>
86-
) -> Result<Self, ()> {
88+
) -> Result<(BlindedPayInfo, Self), ()> {
8789
if path.len() < 1 { return Err(()) }
8890
let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
8991
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
9092

91-
Ok(BlindedPath {
93+
let blinded_payinfo = payment::compute_payinfo(path)?;
94+
Ok((blinded_payinfo, BlindedPath {
9295
introduction_node_id: path[0].0,
9396
blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
9497
blinded_hops: payment::blinded_hops(secp_ctx, path, &blinding_secret).map_err(|_| ())?,
95-
})
98+
}))
9699
}
97100
}
98101

lightning/src/blinded_path/payment.rs

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
//! [`BlindedPath`]: crate::blinded_path::BlindedPath
44
55
use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
6-
6+
use core::convert::TryFrom;
77
use crate::blinded_path::BlindedHop;
88
use crate::blinded_path::utils;
99
use crate::io;
1010
use crate::ln::PaymentSecret;
1111
use crate::ln::features::BlindedHopFeatures;
1212
use crate::ln::msgs::DecodeError;
13+
use crate::offers::invoice::BlindedPayInfo;
1314
use crate::prelude::*;
1415
use crate::util::ser::{Readable, Writeable, Writer};
1516

@@ -45,6 +46,36 @@ pub enum BlindedPaymentTlvs {
4546
},
4647
}
4748

49+
impl BlindedPaymentTlvs {
50+
// The fee used to get from the current hop to the next hop in the path.
51+
fn fee_base_msat(&self) -> u32 {
52+
match self {
53+
Self::Forward { payment_relay, .. } => payment_relay.fee_base_msat,
54+
_ => 0,
55+
}
56+
}
57+
// The fee used to get from the current hop to the next hop in the path.
58+
fn fee_proportional_millionths(&self) -> u32 {
59+
match self {
60+
Self::Forward { payment_relay, .. } => payment_relay.fee_proportional_millionths,
61+
_ => 0,
62+
}
63+
}
64+
// The delta used to get from the current hop to the next hop in the path.
65+
fn cltv_expiry_delta(&self) -> u16 {
66+
match self {
67+
Self::Forward { payment_relay, .. } => payment_relay.cltv_expiry_delta,
68+
_ => 0,
69+
}
70+
}
71+
fn htlc_minimum_msat(&self) -> u64 {
72+
match self {
73+
Self::Forward { payment_constraints, .. } | Self::Receive { payment_constraints, .. } =>
74+
payment_constraints.htlc_minimum_msat,
75+
}
76+
}
77+
}
78+
4879
/// Parameters for relaying over a given [`BlindedHop`].
4980
///
5081
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
@@ -138,6 +169,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
138169
unblinded_path.iter().map(|(_, tlvs)| tlvs), session_priv)
139170
}
140171

172+
pub(super) fn compute_payinfo(
173+
path: &[(PublicKey, BlindedPaymentTlvs)]
174+
) -> Result<BlindedPayInfo, ()> {
175+
let mut curr_base_fee: u128 = 0;
176+
let mut curr_prop_mil: u128 = 0;
177+
for (_, payment_tlvs) in path.iter().rev().skip(1) {
178+
let next_base_fee = payment_tlvs.fee_base_msat() as u128;
179+
let next_prop_mil = payment_tlvs.fee_proportional_millionths() as u128;
180+
curr_base_fee =
181+
((next_base_fee * 1_000_000 + (curr_base_fee * (1_000_000 + next_prop_mil))) + 1_000_000 - 1)
182+
/ 1_000_000;
183+
curr_prop_mil =
184+
(((curr_prop_mil + next_prop_mil) * 1_000_000 + curr_prop_mil * next_prop_mil) + 1_000_000 - 1)
185+
/ 1_000_000;
186+
}
187+
Ok(BlindedPayInfo {
188+
fee_base_msat: u32::try_from(curr_base_fee).map_err(|_| ())?,
189+
fee_proportional_millionths: u32::try_from(curr_prop_mil).map_err(|_| ())?,
190+
cltv_expiry_delta: path.iter().map(|(_, tlvs)| tlvs.cltv_expiry_delta())
191+
.try_fold(0u16, |acc, delta| acc.checked_add(delta)).ok_or(())?,
192+
htlc_minimum_msat: path.iter().map(|(_, tlvs)| tlvs.htlc_minimum_msat()).max().unwrap_or(0),
193+
// TODO: this field isn't present in route blinding encrypted data
194+
htlc_maximum_msat: 21_000_000 * 100_000_000 * 1_000, // Total bitcoin supply
195+
// TODO: when there are blinded hop features, take the subset of them here
196+
features: BlindedHopFeatures::empty(),
197+
})
198+
}
199+
141200
impl_writeable_msg!(PaymentRelay, {
142201
cltv_expiry_delta,
143202
fee_proportional_millionths,
@@ -148,3 +207,72 @@ impl_writeable_msg!(PaymentConstraints, {
148207
max_cltv_expiry,
149208
htlc_minimum_msat
150209
}, {});
210+
211+
#[cfg(test)]
212+
mod tests {
213+
use bitcoin::secp256k1::PublicKey;
214+
use crate::blinded_path::payment::{BlindedPaymentTlvs, PaymentConstraints, PaymentRelay};
215+
use crate::ln::PaymentSecret;
216+
use crate::ln::features::BlindedHopFeatures;
217+
218+
#[test]
219+
fn compute_payinfo() {
220+
// Taken from the spec example for aggregating blinded payment info.
221+
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
222+
let path = vec![(dummy_pk, BlindedPaymentTlvs::Forward {
223+
short_channel_id: 0,
224+
payment_relay: PaymentRelay {
225+
cltv_expiry_delta: 144,
226+
fee_proportional_millionths: 500,
227+
fee_base_msat: 100,
228+
},
229+
payment_constraints: PaymentConstraints {
230+
max_cltv_expiry: 0,
231+
htlc_minimum_msat: 100,
232+
},
233+
features: BlindedHopFeatures::empty(),
234+
}), (dummy_pk, BlindedPaymentTlvs::Forward {
235+
short_channel_id: 0,
236+
payment_relay: PaymentRelay {
237+
cltv_expiry_delta: 144,
238+
fee_proportional_millionths: 500,
239+
fee_base_msat: 100,
240+
},
241+
payment_constraints: PaymentConstraints {
242+
max_cltv_expiry: 0,
243+
htlc_minimum_msat: 1_000,
244+
},
245+
features: BlindedHopFeatures::empty(),
246+
}), (dummy_pk, BlindedPaymentTlvs::Receive {
247+
payment_secret: PaymentSecret([0; 32]),
248+
payment_constraints: PaymentConstraints {
249+
max_cltv_expiry: 0,
250+
htlc_minimum_msat: 1,
251+
},
252+
features: BlindedHopFeatures::empty(),
253+
})];
254+
let blinded_payinfo = super::compute_payinfo(&path[..]).unwrap();
255+
assert_eq!(blinded_payinfo.fee_base_msat, 201);
256+
assert_eq!(blinded_payinfo.fee_proportional_millionths, 1001);
257+
assert_eq!(blinded_payinfo.cltv_expiry_delta, 288);
258+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1_000);
259+
}
260+
261+
#[test]
262+
fn compute_payinfo_1_hop() {
263+
let dummy_pk = PublicKey::from_slice(&[2; 33]).unwrap();
264+
let path = vec![(dummy_pk, BlindedPaymentTlvs::Receive {
265+
payment_secret: PaymentSecret([0; 32]),
266+
payment_constraints: PaymentConstraints {
267+
max_cltv_expiry: 0,
268+
htlc_minimum_msat: 1,
269+
},
270+
features: BlindedHopFeatures::empty(),
271+
})];
272+
let blinded_payinfo = super::compute_payinfo(&path[..]).unwrap();
273+
assert_eq!(blinded_payinfo.fee_base_msat, 0);
274+
assert_eq!(blinded_payinfo.fee_proportional_millionths, 0);
275+
assert_eq!(blinded_payinfo.cltv_expiry_delta, 0);
276+
assert_eq!(blinded_payinfo.htlc_minimum_msat, 1);
277+
}
278+
}

0 commit comments

Comments
 (0)