@@ -10,9 +10,12 @@ use crate::io;
1010use crate :: ln:: PaymentSecret ;
1111use crate :: ln:: features:: BlindedHopFeatures ;
1212use crate :: ln:: msgs:: DecodeError ;
13+ use crate :: offers:: invoice:: BlindedPayInfo ;
1314use crate :: prelude:: * ;
1415use crate :: util:: ser:: { Readable , Writeable , Writer } ;
1516
17+ use core:: convert:: TryFrom ;
18+
1619/// Data to construct a [`BlindedHop`] for forwarding a payment.
1720pub struct ForwardTlvs {
1821 /// The short channel id this payment should be forwarded out over.
@@ -150,6 +153,47 @@ pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
150153 utils:: construct_blinded_hops ( secp_ctx, pks, tlvs, session_priv)
151154}
152155
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+ // ((next_base_fee * 1_000_000 + (curr_base_fee * (1_000_000 + next_prop_mil))) + 1_000_000 - 1) / 1_000_000
171+ curr_base_fee = next_prop_mil. checked_add ( 1_000_000 )
172+ . and_then ( |f| f. checked_mul ( curr_base_fee) )
173+ . and_then ( |f| next_base_fee. checked_mul ( 1_000_000 ) . and_then ( |base| base. checked_add ( f) ) )
174+ . and_then ( |f| f. checked_add ( 1_000_000 - 1 ) )
175+ . map ( |f| f / 1_000_000 )
176+ . ok_or ( ( ) ) ?;
177+ // ceil(((curr_prop_mil + 1_000_000) * (next_prop_mil + 1_000_000)) / 1_000_000) - 1_000_000
178+ curr_prop_mil = curr_prop_mil. checked_add ( 1_000_000 )
179+ . and_then ( |f1| next_prop_mil. checked_add ( 1_000_000 ) . and_then ( |f2| f2. checked_mul ( f1) ) )
180+ . and_then ( |f| f. checked_add ( 1_000_000 - 1 ) )
181+ . map ( |f| f / 1_000_000 )
182+ . and_then ( |f| f. checked_sub ( 1_000_000 ) )
183+ . ok_or ( ( ) ) ?;
184+
185+ cltv_expiry_delta = cltv_expiry_delta. checked_add ( tlvs. payment_relay . cltv_expiry_delta ) . ok_or ( ( ) ) ?;
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,
191+ htlc_minimum_msat : 1 , // TODO
192+ htlc_maximum_msat : 21_000_000 * 100_000_000 * 1_000 , // TODO
193+ features : BlindedHopFeatures :: empty ( ) ,
194+ } )
195+ }
196+
153197impl_writeable_msg ! ( PaymentRelay , {
154198 cltv_expiry_delta,
155199 fee_proportional_millionths,
@@ -160,3 +204,69 @@ impl_writeable_msg!(PaymentConstraints, {
160204 max_cltv_expiry,
161205 htlc_minimum_msat
162206} , { } ) ;
207+
208+ #[ cfg( test) ]
209+ mod tests {
210+ use bitcoin:: secp256k1:: PublicKey ;
211+ use crate :: blinded_path:: payment:: { ForwardTlvs , ReceiveTlvs , PaymentConstraints , PaymentRelay } ;
212+ use crate :: ln:: PaymentSecret ;
213+ use crate :: ln:: features:: BlindedHopFeatures ;
214+
215+ #[ test]
216+ fn compute_payinfo ( ) {
217+ // Taken from the spec example for aggregating blinded payment info. See
218+ // https://github.com/lightning/bolts/blob/master/proposals/route-blinding.md#blinded-payments
219+ let dummy_pk = PublicKey :: from_slice ( & [ 2 ; 33 ] ) . unwrap ( ) ;
220+ let intermediate_nodes = vec ! [ ( dummy_pk, ForwardTlvs {
221+ short_channel_id: 0 ,
222+ payment_relay: PaymentRelay {
223+ cltv_expiry_delta: 144 ,
224+ fee_proportional_millionths: 500 ,
225+ fee_base_msat: 100 ,
226+ } ,
227+ payment_constraints: PaymentConstraints {
228+ max_cltv_expiry: 0 ,
229+ htlc_minimum_msat: 100 ,
230+ } ,
231+ features: BlindedHopFeatures :: empty( ) ,
232+ } ) , ( dummy_pk, ForwardTlvs {
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: 1_000 ,
242+ } ,
243+ features: BlindedHopFeatures :: empty( ) ,
244+ } ) ] ;
245+ let recv_tlvs = ReceiveTlvs {
246+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) ,
247+ payment_constraints : PaymentConstraints {
248+ max_cltv_expiry : 0 ,
249+ htlc_minimum_msat : 1 ,
250+ } ,
251+ } ;
252+ let blinded_payinfo = super :: compute_payinfo ( & intermediate_nodes[ ..] , & recv_tlvs) . unwrap ( ) ;
253+ assert_eq ! ( blinded_payinfo. fee_base_msat, 201 ) ;
254+ assert_eq ! ( blinded_payinfo. fee_proportional_millionths, 1001 ) ;
255+ assert_eq ! ( blinded_payinfo. cltv_expiry_delta, 288 ) ;
256+ }
257+
258+ #[ test]
259+ fn compute_payinfo_1_hop ( ) {
260+ let recv_tlvs = ReceiveTlvs {
261+ payment_secret : PaymentSecret ( [ 0 ; 32 ] ) ,
262+ payment_constraints : PaymentConstraints {
263+ max_cltv_expiry : 0 ,
264+ htlc_minimum_msat : 1 ,
265+ } ,
266+ } ;
267+ let blinded_payinfo = super :: compute_payinfo ( & [ ] , & recv_tlvs) . unwrap ( ) ;
268+ assert_eq ! ( blinded_payinfo. fee_base_msat, 0 ) ;
269+ assert_eq ! ( blinded_payinfo. fee_proportional_millionths, 0 ) ;
270+ assert_eq ! ( blinded_payinfo. cltv_expiry_delta, 0 ) ;
271+ }
272+ }
0 commit comments