3
3
//! [`BlindedPath`]: crate::blinded_path::BlindedPath
4
4
5
5
use bitcoin:: secp256k1:: { self , PublicKey , Secp256k1 , SecretKey } ;
6
-
6
+ use core :: convert :: TryFrom ;
7
7
use crate :: blinded_path:: BlindedHop ;
8
8
use crate :: blinded_path:: utils;
9
9
use crate :: io;
10
10
use crate :: ln:: PaymentSecret ;
11
11
use crate :: ln:: features:: BlindedHopFeatures ;
12
12
use crate :: ln:: msgs:: DecodeError ;
13
+ use crate :: offers:: invoice:: BlindedPayInfo ;
13
14
use crate :: prelude:: * ;
14
15
use crate :: util:: ser:: { Readable , Writeable , Writer } ;
15
16
@@ -45,6 +46,36 @@ pub enum BlindedPaymentTlvs {
45
46
} ,
46
47
}
47
48
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
+
48
79
/// Parameters for relaying over a given [`BlindedHop`].
49
80
///
50
81
/// [`BlindedHop`]: crate::blinded_path::BlindedHop
@@ -138,6 +169,34 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
138
169
unblinded_path. iter ( ) . map ( |( _, tlvs) | tlvs) , session_priv)
139
170
}
140
171
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
+
141
200
impl_writeable_msg ! ( PaymentRelay , {
142
201
cltv_expiry_delta,
143
202
fee_proportional_millionths,
@@ -148,3 +207,72 @@ impl_writeable_msg!(PaymentConstraints, {
148
207
max_cltv_expiry,
149
208
htlc_minimum_msat
150
209
} , { } ) ;
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