Skip to content

Commit c44ecfc

Browse files
Compute aggregated BlindedPayInfo in path construction
1 parent 32d8084 commit c44ecfc

File tree

2 files changed

+135
-6
lines changed

2 files changed

+135
-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: 127 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

@@ -40,6 +41,36 @@ pub enum BlindedPaymentTlvs {
4041
},
4142
}
4243

44+
impl BlindedPaymentTlvs {
45+
// The fee used to get from the current hop to the next hop in the path.
46+
fn fee_base_msat(&self) -> u32 {
47+
match self {
48+
Self::Forward { payment_relay, .. } => payment_relay.fee_base_msat,
49+
_ => 0,
50+
}
51+
}
52+
// The fee used to get from the current hop to the next hop in the path.
53+
fn fee_proportional_millionths(&self) -> u32 {
54+
match self {
55+
Self::Forward { payment_relay, .. } => payment_relay.fee_proportional_millionths,
56+
_ => 0,
57+
}
58+
}
59+
// The delta used to get from the current hop to the next hop in the path.
60+
fn cltv_expiry_delta(&self) -> u16 {
61+
match self {
62+
Self::Forward { payment_relay, .. } => payment_relay.cltv_expiry_delta,
63+
_ => 0,
64+
}
65+
}
66+
fn htlc_minimum_msat(&self) -> u64 {
67+
match self {
68+
Self::Forward { payment_constraints, .. } | Self::Receive { payment_constraints, .. } =>
69+
payment_constraints.htlc_minimum_msat,
70+
}
71+
}
72+
}
73+
4374
/// Parameters for relaying over a given [`BlindedHop`].
4475
///
4576
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
@@ -131,6 +162,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
131162
unblinded_path.iter().map(|(_, tlvs)| tlvs), session_priv)
132163
}
133164

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

0 commit comments

Comments
 (0)