Skip to content

Commit 1fa4ecd

Browse files
Compute aggregated BlindedPayInfo in path construction
1 parent 7436da4 commit 1fa4ecd

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
@@ -148,6 +179,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
148179
Ok(blinded_hops)
149180
}
150181

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

0 commit comments

Comments
 (0)