Skip to content

Commit cd8952d

Browse files
Compute aggregated BlindedPayInfo in path construction
1 parent 259c09f commit cd8952d

File tree

2 files changed

+134
-6
lines changed

2 files changed

+134
-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: 126 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,33 @@ pub enum BlindedPaymentTlvs {
4546
},
4647
}
4748

49+
impl BlindedPaymentTlvs {
50+
fn fee_base_msat(&self) -> u32 {
51+
match self {
52+
Self::Forward { payment_relay, .. } => payment_relay.fee_base_msat,
53+
_ => 0,
54+
}
55+
}
56+
fn fee_proportional_millionths(&self) -> u32 {
57+
match self {
58+
Self::Forward { payment_relay, .. } => payment_relay.fee_proportional_millionths,
59+
_ => 0,
60+
}
61+
}
62+
fn cltv_expiry_delta(&self) -> u16 {
63+
match self {
64+
Self::Forward { payment_relay, .. } => payment_relay.cltv_expiry_delta,
65+
_ => 0,
66+
}
67+
}
68+
fn htlc_minimum_msat(&self) -> u64 {
69+
match self {
70+
Self::Forward { payment_constraints, .. } | Self::Receive { payment_constraints, .. } =>
71+
payment_constraints.htlc_minimum_msat,
72+
}
73+
}
74+
}
75+
4876
/// Parameters for relaying over a given [`BlindedHop`].
4977
///
5078
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
@@ -148,6 +176,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
148176
Ok(blinded_hops)
149177
}
150178

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

0 commit comments

Comments
 (0)