@@ -17,6 +17,8 @@ use crate::lsps5::msgs::{
1717 LSPS5Message , LSPS5Request , LSPS5Response , ListWebhooksRequest , RemoveWebhookRequest ,
1818 SetWebhookRequest , WebhookNotification ,
1919} ;
20+ #[ cfg( feature = "time" ) ]
21+ use crate :: lsps5:: service:: DefaultTimeProvider ;
2022use crate :: message_queue:: MessageQueue ;
2123use crate :: prelude:: { new_hash_map, HashMap } ;
2224use crate :: sync:: { Arc , Mutex , RwLock } ;
@@ -174,7 +176,7 @@ where
174176 TP :: Target : TimeProvider ,
175177{
176178 /// Constructs an `LSPS5ClientHandler`.
177- pub ( crate ) fn new (
179+ pub ( crate ) fn new_with_time_provider (
178180 entropy_source : ES , pending_messages : Arc < MessageQueue > , pending_events : Arc < EventQueue > ,
179181 config : LSPS5ClientConfig , time_provider : TP ,
180182 ) -> Self {
@@ -273,13 +275,11 @@ where
273275 /// A unique `LSPSRequestId` for correlating the asynchronous response.
274276 ///
275277 /// Response from the LSP peer will be provided asynchronously through a
276- /// [`LSPS5Response::ListWebhooks`] or [`LSPS5Response::ListWebhooksError`] message, and this client
277- /// will then enqueue either a [`WebhooksListed`] or [`WebhooksListFailed `] event.
278+ /// [`LSPS5Response::ListWebhooks`] message, and this client
279+ /// will then enqueue a [`WebhooksListed`] event.
278280 ///
279281 /// [`WebhooksListed`]: super::event::LSPS5ClientEvent::WebhooksListed
280- /// [`WebhooksListFailed`]: super::event::LSPS5ClientEvent::WebhooksListFailed
281282 /// [`LSPS5Response::ListWebhooks`]: super::msgs::LSPS5Response::ListWebhooks
282- /// [`LSPS5Response::ListWebhooksError`]: super::msgs::LSPS5Response::ListWebhooksError
283283 pub fn list_webhooks ( & self , counterparty_node_id : PublicKey ) -> LSPSRequestId {
284284 let request_id = generate_request_id ( & self . entropy_source ) ;
285285 let now =
@@ -402,14 +402,6 @@ where
402402 } ) ;
403403 result = Ok ( ( ) ) ;
404404 } ,
405- LSPS5Response :: ListWebhooksError ( e) => {
406- event_queue_notifier. enqueue ( LSPS5ClientEvent :: WebhooksListFailed {
407- counterparty_node_id : * counterparty_node_id,
408- error : e. clone ( ) . into ( ) ,
409- request_id,
410- } ) ;
411- result = Ok ( ( ) ) ;
412- } ,
413405 _ => {
414406 result = Err ( LightningError {
415407 err : "Unexpected response type for ListWebhooks" . to_string ( ) ,
@@ -509,33 +501,21 @@ where
509501
510502 /// Parse and validate a webhook notification received from an LSP.
511503 ///
512- /// Implements the bLIP-55 / LSPS5 webhook delivery rules:
513- /// 1. Parses `notification_json` into a `WebhookNotification` (JSON-RPC 2.0).
514- /// 2. Checks that `timestamp` (from `x-lsps5-timestamp`) is within ±10 minutes of local time.
515- /// 3. Ensures `signature` (from `x-lsps5-signature`) has not been replayed within the
516- /// configured retention window.
517- /// 4. Reconstructs the exact string
518- /// `"LSPS5: DO NOT SIGN THIS MESSAGE MANUALLY: LSP: At {timestamp} I notify {body}"`
519- /// and verifies the zbase32 LN-style signature against the LSP's node ID.
504+ /// Verifies the webhook delivery by parsing the notification JSON-RPC 2.0 format,
505+ /// checking the timestamp is within ±10 minutes, ensuring no signature replay within the retention window,
506+ /// and verifying the zbase32 LN-style signature against the LSP's node ID.
520507 ///
521508 /// # Parameters
522509 /// - `counterparty_node_id`: the LSP's public key, used to verify the signature.
523510 /// - `timestamp`: ISO8601 time when the LSP created the notification.
524511 /// - `signature`: the zbase32-encoded LN signature over timestamp+body.
525512 /// - `notification`: the [`WebhookNotification`] received from the LSP.
526513 ///
527- /// On success, returns the received [`WebhookNotification`].
528- ///
529- /// Failure reasons include:
530- /// - Timestamp too old (drift > 10 minutes)
531- /// - Replay attack detected (signature reused)
532- /// - Invalid signature (crypto check fails)
514+ /// Returns the validated [`WebhookNotification`] or an error for invalid timestamp,
515+ /// replay attack, or signature verification failure.
533516 ///
534- /// Clients should call this method upon receiving a [`LSPS5ServiceEvent::SendWebhookNotification`]
535- /// event, before taking action on the notification. This guarantees that only authentic,
536- /// non-replayed notifications reach your application.
517+ /// Call this method before processing any webhook notification to ensure authenticity.
537518 ///
538- /// [`LSPS5ServiceEvent::SendWebhookNotification`]: super::event::LSPS5ServiceEvent::SendWebhookNotification
539519 /// [`WebhookNotification`]: super::msgs::WebhookNotification
540520 pub fn parse_webhook_notification (
541521 & self , counterparty_node_id : PublicKey , timestamp : & LSPSDateTime , signature : & str ,
@@ -556,6 +536,27 @@ where
556536 }
557537}
558538
539+ #[ cfg( feature = "time" ) ]
540+ impl < ES : Deref > LSPS5ClientHandler < ES , Arc < DefaultTimeProvider > >
541+ where
542+ ES :: Target : EntropySource ,
543+ {
544+ /// Constructs a `LSPS5ClientHandler` using [`DefaultTimeProvider`].
545+ #[ allow( dead_code) ]
546+ pub ( crate ) fn new (
547+ entropy_source : ES , pending_messages : Arc < MessageQueue > , pending_events : Arc < EventQueue > ,
548+ config : LSPS5ClientConfig ,
549+ ) -> Self {
550+ Self :: new_with_time_provider (
551+ entropy_source,
552+ pending_messages,
553+ pending_events,
554+ config,
555+ Arc :: new ( DefaultTimeProvider ) ,
556+ )
557+ }
558+ }
559+
559560impl < ES : Deref , TP : Deref + Clone > LSPSProtocolMessageHandler for LSPS5ClientHandler < ES , TP >
560561where
561562 ES :: Target : EntropySource ,
@@ -577,16 +578,12 @@ mod tests {
577578
578579 use super :: * ;
579580 use crate :: {
580- lsps0:: ser:: LSPSRequestId ,
581- lsps5:: { msgs:: SetWebhookResponse , service:: DefaultTimeProvider } ,
582- tests:: utils:: TestEntropy ,
581+ lsps0:: ser:: LSPSRequestId , lsps5:: msgs:: SetWebhookResponse , tests:: utils:: TestEntropy ,
583582 } ;
584583 use bitcoin:: { key:: Secp256k1 , secp256k1:: SecretKey } ;
585584
586- fn setup_test_client (
587- time_provider : Arc < dyn TimeProvider > ,
588- ) -> (
589- LSPS5ClientHandler < Arc < TestEntropy > , Arc < dyn TimeProvider > > ,
585+ fn setup_test_client ( ) -> (
586+ LSPS5ClientHandler < Arc < TestEntropy > , Arc < DefaultTimeProvider > > ,
590587 Arc < MessageQueue > ,
591588 Arc < EventQueue > ,
592589 PublicKey ,
@@ -600,7 +597,6 @@ mod tests {
600597 Arc :: clone ( & message_queue) ,
601598 Arc :: clone ( & event_queue) ,
602599 LSPS5ClientConfig :: default ( ) ,
603- time_provider,
604600 ) ;
605601
606602 let secp = Secp256k1 :: new ( ) ;
@@ -614,7 +610,7 @@ mod tests {
614610
615611 #[ test]
616612 fn test_per_peer_state_isolation ( ) {
617- let ( client, _, _, peer_1, peer_2) = setup_test_client ( Arc :: new ( DefaultTimeProvider ) ) ;
613+ let ( client, _, _, peer_1, peer_2) = setup_test_client ( ) ;
618614
619615 let req_id_1 = client
620616 . set_webhook ( peer_1, "test-app-1" . to_string ( ) , "https://example.com/hook1" . to_string ( ) )
@@ -636,7 +632,7 @@ mod tests {
636632
637633 #[ test]
638634 fn test_pending_request_tracking ( ) {
639- let ( client, _, _, peer, _) = setup_test_client ( Arc :: new ( DefaultTimeProvider ) ) ;
635+ let ( client, _, _, peer, _) = setup_test_client ( ) ;
640636 const APP_NAME : & str = "test-app" ;
641637 const WEBHOOK_URL : & str = "https://example.com/hook" ;
642638 let lsps5_app_name = LSPS5AppName :: from_string ( APP_NAME . to_string ( ) ) . unwrap ( ) ;
@@ -669,7 +665,7 @@ mod tests {
669665
670666 #[ test]
671667 fn test_handle_response_clears_pending_state ( ) {
672- let ( client, _, _, peer, _) = setup_test_client ( Arc :: new ( DefaultTimeProvider ) ) ;
668+ let ( client, _, _, peer, _) = setup_test_client ( ) ;
673669
674670 let req_id = client
675671 . set_webhook ( peer, "test-app" . to_string ( ) , "https://example.com/hook" . to_string ( ) )
@@ -699,7 +695,7 @@ mod tests {
699695
700696 #[ test]
701697 fn test_cleanup_expired_responses ( ) {
702- let ( client, _, _, _, _) = setup_test_client ( Arc :: new ( DefaultTimeProvider ) ) ;
698+ let ( client, _, _, _, _) = setup_test_client ( ) ;
703699 let time_provider = & client. time_provider ;
704700 const OLD_APP_NAME : & str = "test-app-old" ;
705701 const NEW_APP_NAME : & str = "test-app-new" ;
@@ -708,7 +704,10 @@ mod tests {
708704 let lsps5_new_app_name = LSPS5AppName :: from_string ( NEW_APP_NAME . to_string ( ) ) . unwrap ( ) ;
709705 let lsps5_webhook_url = LSPS5WebhookUrl :: from_string ( WEBHOOK_URL . to_string ( ) ) . unwrap ( ) ;
710706 let now = time_provider. duration_since_epoch ( ) ;
711- let mut peer_state = PeerState :: new ( Duration :: from_secs ( 1800 ) , Arc :: clone ( time_provider) ) ;
707+ let mut peer_state = PeerState :: < Arc < DefaultTimeProvider > > :: new (
708+ Duration :: from_secs ( 1800 ) ,
709+ Arc :: clone ( time_provider) ,
710+ ) ;
712711 peer_state. last_cleanup = Some ( LSPSDateTime :: new_from_duration_since_epoch (
713712 now. checked_sub ( Duration :: from_secs ( 120 ) ) . unwrap ( ) ,
714713 ) ) ;
@@ -756,7 +755,7 @@ mod tests {
756755
757756 #[ test]
758757 fn test_unknown_request_id_handling ( ) {
759- let ( client, _message_queue, _, peer, _) = setup_test_client ( Arc :: new ( DefaultTimeProvider ) ) ;
758+ let ( client, _message_queue, _, peer, _) = setup_test_client ( ) ;
760759
761760 let _valid_req = client
762761 . set_webhook ( peer, "test-app" . to_string ( ) , "https://example.com/hook" . to_string ( ) )
0 commit comments