@@ -50,42 +50,215 @@ impl StaticInvoiceStore {
5050 }
5151
5252 pub ( crate ) async fn handle_static_invoice_requested (
53- & self , recipient_id : Vec < u8 > , invoice_slot : u16 ,
53+ & self , recipient_id : & [ u8 ] , invoice_slot : u16 ,
5454 ) -> Result < Option < StaticInvoice > , lightning:: io:: Error > {
5555 Self :: check_rate_limit ( & self . request_rate_limiter , & recipient_id) ?;
5656
5757 let ( secondary_namespace, key) = Self :: get_storage_location ( invoice_slot, recipient_id) ;
5858
59- self . kv_store . read ( STATIC_INVOICES_PRIMARY_NAMESPACE , & secondary_namespace, & key) . and_then (
60- |data| {
59+ self . kv_store
60+ . read ( STATIC_INVOICES_PRIMARY_NAMESPACE , & secondary_namespace, & key)
61+ . and_then ( |data| {
6162 data. try_into ( ) . map ( Some ) . map_err ( |e| {
6263 lightning:: io:: Error :: new (
6364 lightning:: io:: ErrorKind :: InvalidData ,
6465 format ! ( "Failed to parse static invoice: {:?}" , e) ,
6566 )
6667 } )
67- } ,
68- )
68+ } )
69+ . or_else (
70+ |e| {
71+ if e. kind ( ) == lightning:: io:: ErrorKind :: NotFound {
72+ Ok ( None )
73+ } else {
74+ Err ( e)
75+ }
76+ } ,
77+ )
6978 }
7079
7180 pub ( crate ) async fn handle_persist_static_invoice (
7281 & self , invoice : StaticInvoice , invoice_slot : u16 , recipient_id : Vec < u8 > ,
7382 ) -> Result < ( ) , lightning:: io:: Error > {
7483 Self :: check_rate_limit ( & self . persist_rate_limiter , & recipient_id) ?;
7584
76- let ( secondary_namespace, key) = Self :: get_storage_location ( invoice_slot, recipient_id) ;
85+ let ( secondary_namespace, key) = Self :: get_storage_location ( invoice_slot, & recipient_id) ;
7786
7887 let mut buf = Vec :: new ( ) ;
7988 invoice. write ( & mut buf) ?;
8089
8190 self . kv_store . write ( STATIC_INVOICES_PRIMARY_NAMESPACE , & secondary_namespace, & key, buf)
8291 }
8392
84- fn get_storage_location ( invoice_slot : u16 , recipient_id : Vec < u8 > ) -> ( String , String ) {
85- let hash = Sha256 :: hash ( & recipient_id) . to_byte_array ( ) ;
93+ fn get_storage_location ( invoice_slot : u16 , recipient_id : & [ u8 ] ) -> ( String , String ) {
94+ let hash = Sha256 :: hash ( recipient_id) . to_byte_array ( ) ;
8695 let secondary_namespace = hex_utils:: to_string ( & hash) ;
8796
8897 let key = format ! ( "{:05}" , invoice_slot) ;
8998 ( secondary_namespace, key)
9099 }
91100}
101+
102+ #[ cfg( test) ]
103+ mod tests {
104+ use std:: { sync:: Arc , time:: Duration } ;
105+
106+ use bitcoin:: {
107+ key:: { Keypair , Secp256k1 } ,
108+ secp256k1:: { PublicKey , SecretKey } ,
109+ } ;
110+ use lightning:: blinded_path:: {
111+ message:: BlindedMessagePath ,
112+ payment:: { BlindedPayInfo , BlindedPaymentPath } ,
113+ BlindedHop ,
114+ } ;
115+ use lightning:: ln:: inbound_payment:: ExpandedKey ;
116+ use lightning:: offers:: {
117+ nonce:: Nonce ,
118+ offer:: OfferBuilder ,
119+ static_invoice:: { StaticInvoice , StaticInvoiceBuilder } ,
120+ } ;
121+ use lightning:: sign:: EntropySource ;
122+ use lightning:: util:: test_utils:: TestStore ;
123+ use lightning_types:: features:: BlindedHopFeatures ;
124+
125+ use crate :: { payment:: static_invoice_store:: StaticInvoiceStore , types:: DynStore } ;
126+
127+ #[ tokio:: test]
128+ async fn static_invoice_store_test ( ) {
129+ let store: Arc < DynStore > = Arc :: new ( TestStore :: new ( false ) ) ;
130+ let static_invoice_store = StaticInvoiceStore :: new ( Arc :: clone ( & store) ) ;
131+
132+ let static_invoice = invoice ( ) ;
133+ let recipient_id = vec ! [ 1 , 1 , 1 ] ;
134+ assert ! ( static_invoice_store
135+ . handle_persist_static_invoice( static_invoice. clone( ) , 0 , recipient_id. clone( ) )
136+ . await
137+ . is_ok( ) ) ;
138+
139+ let requested_invoice =
140+ static_invoice_store. handle_static_invoice_requested ( & recipient_id, 0 ) . await . unwrap ( ) ;
141+
142+ assert_eq ! ( requested_invoice. unwrap( ) , static_invoice) ;
143+
144+ assert ! ( static_invoice_store
145+ . handle_static_invoice_requested( & recipient_id, 1 )
146+ . await
147+ . unwrap( )
148+ . is_none( ) ) ;
149+
150+ assert ! ( static_invoice_store
151+ . handle_static_invoice_requested( & [ 2 , 2 , 2 ] , 0 )
152+ . await
153+ . unwrap( )
154+ . is_none( ) ) ;
155+ }
156+
157+ fn invoice ( ) -> StaticInvoice {
158+ let node_id = recipient_pubkey ( ) ;
159+ let payment_paths = payment_paths ( ) ;
160+ let now = now ( ) ;
161+ let expanded_key = ExpandedKey :: new ( [ 42 ; 32 ] ) ;
162+ let entropy = FixedEntropy { } ;
163+ let nonce = Nonce :: from_entropy_source ( & entropy) ;
164+ let secp_ctx = Secp256k1 :: new ( ) ;
165+
166+ let offer = OfferBuilder :: deriving_signing_pubkey ( node_id, & expanded_key, nonce, & secp_ctx)
167+ . path ( blinded_path ( ) )
168+ . build ( )
169+ . unwrap ( ) ;
170+
171+ StaticInvoiceBuilder :: for_offer_using_derived_keys (
172+ & offer,
173+ payment_paths. clone ( ) ,
174+ vec ! [ blinded_path( ) ] ,
175+ now,
176+ & expanded_key,
177+ nonce,
178+ & secp_ctx,
179+ )
180+ . unwrap ( )
181+ . build_and_sign ( & secp_ctx)
182+ . unwrap ( )
183+ }
184+
185+ fn now ( ) -> Duration {
186+ std:: time:: SystemTime :: now ( )
187+ . duration_since ( std:: time:: SystemTime :: UNIX_EPOCH )
188+ . expect ( "SystemTime::now() should come after SystemTime::UNIX_EPOCH" )
189+ }
190+
191+ fn payment_paths ( ) -> Vec < BlindedPaymentPath > {
192+ vec ! [
193+ BlindedPaymentPath :: from_blinded_path_and_payinfo(
194+ pubkey( 40 ) ,
195+ pubkey( 41 ) ,
196+ vec![
197+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
198+ BlindedHop { blinded_node_id: pubkey( 44 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
199+ ] ,
200+ BlindedPayInfo {
201+ fee_base_msat: 1 ,
202+ fee_proportional_millionths: 1_000 ,
203+ cltv_expiry_delta: 42 ,
204+ htlc_minimum_msat: 100 ,
205+ htlc_maximum_msat: 1_000_000_000_000 ,
206+ features: BlindedHopFeatures :: empty( ) ,
207+ } ,
208+ ) ,
209+ BlindedPaymentPath :: from_blinded_path_and_payinfo(
210+ pubkey( 40 ) ,
211+ pubkey( 41 ) ,
212+ vec![
213+ BlindedHop { blinded_node_id: pubkey( 45 ) , encrypted_payload: vec![ 0 ; 45 ] } ,
214+ BlindedHop { blinded_node_id: pubkey( 46 ) , encrypted_payload: vec![ 0 ; 46 ] } ,
215+ ] ,
216+ BlindedPayInfo {
217+ fee_base_msat: 1 ,
218+ fee_proportional_millionths: 1_000 ,
219+ cltv_expiry_delta: 42 ,
220+ htlc_minimum_msat: 100 ,
221+ htlc_maximum_msat: 1_000_000_000_000 ,
222+ features: BlindedHopFeatures :: empty( ) ,
223+ } ,
224+ ) ,
225+ ]
226+ }
227+
228+ fn blinded_path ( ) -> BlindedMessagePath {
229+ BlindedMessagePath :: from_blinded_path (
230+ pubkey ( 40 ) ,
231+ pubkey ( 41 ) ,
232+ vec ! [
233+ BlindedHop { blinded_node_id: pubkey( 42 ) , encrypted_payload: vec![ 0 ; 43 ] } ,
234+ BlindedHop { blinded_node_id: pubkey( 43 ) , encrypted_payload: vec![ 0 ; 44 ] } ,
235+ ] ,
236+ )
237+ }
238+
239+ fn pubkey ( byte : u8 ) -> PublicKey {
240+ let secp_ctx = Secp256k1 :: new ( ) ;
241+ PublicKey :: from_secret_key ( & secp_ctx, & privkey ( byte) )
242+ }
243+
244+ fn privkey ( byte : u8 ) -> SecretKey {
245+ SecretKey :: from_slice ( & [ byte; 32 ] ) . unwrap ( )
246+ }
247+
248+ fn recipient_keys ( ) -> Keypair {
249+ let secp_ctx = Secp256k1 :: new ( ) ;
250+ Keypair :: from_secret_key ( & secp_ctx, & SecretKey :: from_slice ( & [ 43 ; 32 ] ) . unwrap ( ) )
251+ }
252+
253+ fn recipient_pubkey ( ) -> PublicKey {
254+ recipient_keys ( ) . public_key ( )
255+ }
256+
257+ struct FixedEntropy ;
258+
259+ impl EntropySource for FixedEntropy {
260+ fn get_secure_random_bytes ( & self ) -> [ u8 ; 32 ] {
261+ [ 42 ; 32 ]
262+ }
263+ }
264+ }
0 commit comments