@@ -10,9 +10,12 @@ 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
17
+ use core:: convert:: TryFrom ;
18
+
16
19
/// Data to construct a [`BlindedHop`] for forwarding a payment.
17
20
pub struct ForwardTlvs {
18
21
/// The short channel id this payment should be forwarded out over.
@@ -150,6 +153,46 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
150
153
utils:: construct_blinded_hops ( secp_ctx, pks, tlvs, session_priv)
151
154
}
152
155
156
+ pub ( super ) fn compute_payinfo (
157
+ intermediate_nodes : & [ ( PublicKey , ForwardTlvs ) ] , payee_tlvs : & ReceiveTlvs
158
+ ) -> Result < BlindedPayInfo , ( ) > {
159
+ let mut curr_base_fee: u64 = 0 ;
160
+ let mut curr_prop_mil: u64 = 0 ;
161
+ let mut cltv_expiry_delta: u16 = 0 ;
162
+ for ( _, tlvs) in intermediate_nodes. iter ( ) . rev ( ) {
163
+ // In the future, we'll want to take the intersection of all supported features for the
164
+ // `BlindedPayInfo`, but there are no features in that context right now.
165
+ if tlvs. features . requires_unknown_bits_from ( & BlindedHopFeatures :: empty ( ) ) { return Err ( ( ) ) }
166
+
167
+ let next_base_fee = tlvs. payment_relay . fee_base_msat as u64 ;
168
+ let next_prop_mil = tlvs. payment_relay . fee_proportional_millionths as u64 ;
169
+ // Use integer arithmetic to compute `ceil(a/b)` as `(a+b-1)/b`
170
+ // ((curr_base_fee * (1_000_000 + next_prop_mil)) / 1_000_000) + next_base_fee
171
+ curr_base_fee = curr_base_fee. checked_mul ( 1_000_000 + next_prop_mil)
172
+ . and_then ( |f| f. checked_add ( 1_000_000 - 1 ) )
173
+ . map ( |f| f / 1_000_000 )
174
+ . and_then ( |f| f. checked_add ( next_base_fee) )
175
+ . ok_or ( ( ) ) ?;
176
+ // ceil(((curr_prop_mil + 1_000_000) * (next_prop_mil + 1_000_000)) / 1_000_000) - 1_000_000
177
+ curr_prop_mil = curr_prop_mil. checked_add ( 1_000_000 )
178
+ . and_then ( |f1| next_prop_mil. checked_add ( 1_000_000 ) . and_then ( |f2| f2. checked_mul ( f1) ) )
179
+ . and_then ( |f| f. checked_add ( 1_000_000 - 1 ) )
180
+ . map ( |f| f / 1_000_000 )
181
+ . and_then ( |f| f. checked_sub ( 1_000_000 ) )
182
+ . ok_or ( ( ) ) ?;
183
+
184
+ cltv_expiry_delta = cltv_expiry_delta. checked_add ( tlvs. payment_relay . cltv_expiry_delta ) . ok_or ( ( ) ) ?;
185
+ }
186
+ Ok ( BlindedPayInfo {
187
+ fee_base_msat : u32:: try_from ( curr_base_fee) . map_err ( |_| ( ) ) ?,
188
+ fee_proportional_millionths : u32:: try_from ( curr_prop_mil) . map_err ( |_| ( ) ) ?,
189
+ cltv_expiry_delta,
190
+ htlc_minimum_msat : 1 , // TODO
191
+ htlc_maximum_msat : 21_000_000 * 100_000_000 * 1_000 , // TODO
192
+ features : BlindedHopFeatures :: empty ( ) ,
193
+ } )
194
+ }
195
+
153
196
impl_writeable_msg ! ( PaymentRelay , {
154
197
cltv_expiry_delta,
155
198
fee_proportional_millionths,
@@ -160,3 +203,69 @@ impl_writeable_msg!(PaymentConstraints, {
160
203
max_cltv_expiry,
161
204
htlc_minimum_msat
162
205
} , { } ) ;
206
+
207
+ #[ cfg( test) ]
208
+ mod tests {
209
+ use bitcoin:: secp256k1:: PublicKey ;
210
+ use crate :: blinded_path:: payment:: { ForwardTlvs , ReceiveTlvs , PaymentConstraints , PaymentRelay } ;
211
+ use crate :: ln:: PaymentSecret ;
212
+ use crate :: ln:: features:: BlindedHopFeatures ;
213
+
214
+ #[ test]
215
+ fn compute_payinfo ( ) {
216
+ // Taken from the spec example for aggregating blinded payment info. See
217
+ // https://github.com/lightning/bolts/blob/master/proposals/route-blinding.md#blinded-payments
218
+ let dummy_pk = PublicKey :: from_slice ( & [ 2 ; 33 ] ) . unwrap ( ) ;
219
+ let intermediate_nodes = vec ! [ ( dummy_pk, ForwardTlvs {
220
+ short_channel_id: 0 ,
221
+ payment_relay: PaymentRelay {
222
+ cltv_expiry_delta: 144 ,
223
+ fee_proportional_millionths: 500 ,
224
+ fee_base_msat: 100 ,
225
+ } ,
226
+ payment_constraints: PaymentConstraints {
227
+ max_cltv_expiry: 0 ,
228
+ htlc_minimum_msat: 100 ,
229
+ } ,
230
+ features: BlindedHopFeatures :: empty( ) ,
231
+ } ) , ( dummy_pk, ForwardTlvs {
232
+ short_channel_id: 0 ,
233
+ payment_relay: PaymentRelay {
234
+ cltv_expiry_delta: 144 ,
235
+ fee_proportional_millionths: 500 ,
236
+ fee_base_msat: 100 ,
237
+ } ,
238
+ payment_constraints: PaymentConstraints {
239
+ max_cltv_expiry: 0 ,
240
+ htlc_minimum_msat: 1_000 ,
241
+ } ,
242
+ features: BlindedHopFeatures :: empty( ) ,
243
+ } ) ] ;
244
+ let recv_tlvs = ReceiveTlvs {
245
+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) ,
246
+ payment_constraints : PaymentConstraints {
247
+ max_cltv_expiry : 0 ,
248
+ htlc_minimum_msat : 1 ,
249
+ } ,
250
+ } ;
251
+ let blinded_payinfo = super :: compute_payinfo ( & intermediate_nodes[ ..] , & recv_tlvs) . unwrap ( ) ;
252
+ assert_eq ! ( blinded_payinfo. fee_base_msat, 201 ) ;
253
+ assert_eq ! ( blinded_payinfo. fee_proportional_millionths, 1001 ) ;
254
+ assert_eq ! ( blinded_payinfo. cltv_expiry_delta, 288 ) ;
255
+ }
256
+
257
+ #[ test]
258
+ fn compute_payinfo_1_hop ( ) {
259
+ let recv_tlvs = ReceiveTlvs {
260
+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) ,
261
+ payment_constraints : PaymentConstraints {
262
+ max_cltv_expiry : 0 ,
263
+ htlc_minimum_msat : 1 ,
264
+ } ,
265
+ } ;
266
+ let blinded_payinfo = super :: compute_payinfo ( & [ ] , & recv_tlvs) . unwrap ( ) ;
267
+ assert_eq ! ( blinded_payinfo. fee_base_msat, 0 ) ;
268
+ assert_eq ! ( blinded_payinfo. fee_proportional_millionths, 0 ) ;
269
+ assert_eq ! ( blinded_payinfo. cltv_expiry_delta, 0 ) ;
270
+ }
271
+ }
0 commit comments