@@ -29,7 +29,9 @@ use super::service::TimeProvider;
2929
3030use alloc:: collections:: VecDeque ;
3131use alloc:: string:: String ;
32+
3233use bitcoin:: secp256k1:: PublicKey ;
34+
3335use lightning:: ln:: msgs:: { ErrorAction , LightningError } ;
3436use lightning:: sign:: EntropySource ;
3537use lightning:: util:: logger:: Level ;
@@ -129,33 +131,38 @@ impl PeerState {
129131 }
130132}
131133
132- /// LSPS5 client handler.
134+ /// Client‐side handler for the LSPS5 (bLIP-55) webhook registration protocol.
135+ ///
136+ /// `LSPS5ClientHandler` is the primary interface for LSP clients
137+ /// to register, list, and remove webhook endpoints with an LSP, and to parse
138+ /// and validate incoming signed notifications.
139+ ///
140+ /// # Core Capabilities
141+ ///
142+ /// - `set_webhook(peer, app_name, url)` -> register or update a webhook (`lsps5.set_webhook`)
143+ /// - `list_webhooks(peer)` -> retrieve all registered webhooks (`lsps5.list_webhooks`)
144+ /// - `remove_webhook(peer, name)` -> delete a webhook (`lsps5.remove_webhook`)
145+ /// - `parse_webhook_notification(...)` -> verify signature, timestamp, replay, and emit event
146+ ///
147+ /// [`bLIP-55 / LSPS5 specification`]: https://github.com/lightning/blips/pull/55/files
133148pub struct LSPS5ClientHandler < ES : Deref >
134149where
135150 ES :: Target : EntropySource ,
136151{
137- /// Pending messages to be sent.
138152 pending_messages : Arc < MessageQueue > ,
139- /// Event queue for emitting events.
140153 pending_events : Arc < EventQueue > ,
141- /// Entropy source.
142154 entropy_source : ES ,
143- /// Per peer state for tracking requests.
144155 per_peer_state : RwLock < HashMap < PublicKey , Mutex < PeerState > > > ,
145- /// Client configuration.
146156 config : LSPS5ClientConfig ,
147- /// Time provider for LSPS5 service.
148157 time_provider : Arc < dyn TimeProvider > ,
149- /// Map of recently used signatures to prevent replay attacks.
150158 recent_signatures : Mutex < VecDeque < ( String , LSPSDateTime ) > > ,
151159}
152160
153161impl < ES : Deref > LSPS5ClientHandler < ES >
154162where
155163 ES :: Target : EntropySource ,
156164{
157- /// Creates a new LSPS5 client handler with the provided entropy source, message queue,
158- /// event queue, and LSPS5ClientConfig.
165+ /// Constructs an `LSPS5ClientHandler`.
159166 #[ cfg( feature = "time" ) ]
160167 pub ( crate ) fn new (
161168 entropy_source : ES , pending_messages : Arc < MessageQueue > , pending_events : Arc < EventQueue > ,
@@ -171,6 +178,8 @@ where
171178 )
172179 }
173180
181+ /// Constructs an `LSPS5ClientHandler` with a custom time provider.
182+ /// Useful for non-std environments that don't have access to the system clock.
174183 pub ( crate ) fn new_with_custom_time_provider (
175184 entropy_source : ES , pending_messages : Arc < MessageQueue > , pending_events : Arc < EventQueue > ,
176185 config : LSPS5ClientConfig , time_provider : Arc < dyn TimeProvider > ,
@@ -204,20 +213,27 @@ where
204213 Ok ( f ( & mut * peer_state_lock) )
205214 }
206215
207- /// Register a webhook with the LSP .
216+ /// Register or update a webhook endpoint under a human-readable name .
208217 ///
209- /// Implements the `lsps5.set_webhook` method from bLIP-55 .
218+ /// Sends a `lsps5.set_webhook` JSON-RPC request to the given LSP peer .
210219 ///
211220 /// # Parameters
212- /// * `app_name` - A human-readable UTF-8 string that gives a name to the webhook (max 64 bytes).
213- /// * `webhook` - The URL of the webhook that the LSP can use to push notifications (max 1024 chars).
221+ /// - `counterparty_node_id`: The LSP node ID to contact.
222+ /// - `app_name`: A UTF-8 name for this webhook.
223+ /// - `webhook_url`: HTTPS URL for push notifications.
214224 ///
215225 /// # Returns
216- /// * Success - the request ID that was used.
217- /// * Error - validation error or error sending the request.
226+ /// A unique `LSPSRequestId` for correlating the asynchronous response.
218227 ///
219- /// Response will be provided asynchronously through the event queue as a
220- /// WebhookRegistered or WebhookRegistrationFailed event.
228+ /// Response from the LSP peer will be provided asynchronously through
229+ /// the event queue as a WebhookRegistered or WebhookRegistrationFailed event.
230+ ///
231+ /// **Note**: Ensure the app name is valid and its length does not exceed [`MAX_APP_NAME_LENGTH`].
232+ /// **Note**: Ensure the URL is valid and its length does not exceed [`MAX_WEBHOOK_URL_LENGTH`]
233+ /// and that the URL points to a public host.
234+ ///
235+ /// [`MAX_WEBHOOK_URL_LENGTH`]: super::msgs::MAX_WEBHOOK_URL_LENGTH
236+ /// [`MAX_APP_NAME_LENGTH`]: super::msgs::MAX_APP_NAME_LENGTH
221237 pub fn set_webhook (
222238 & self , counterparty_node_id : PublicKey , app_name : String , webhook_url : String ,
223239 ) -> Result < LSPSRequestId , LightningError > {
@@ -254,13 +270,16 @@ where
254270 Ok ( request_id)
255271 }
256272
257- /// List all registered webhooks.
273+ /// List all webhook names currently registered with the LSP.
274+ ///
275+ /// Sends a `lsps5.list_webhooks` JSON-RPC request to the peer.
258276 ///
259- /// Implements the `lsps5.list_webhooks` method from bLIP-55.
277+ /// # Parameters
278+ /// - `counterparty_node_id`: The LSP node ID to query.
260279 ///
261280 /// # Returns
262- /// * Success - the request ID that was used .
263- /// * Error - error sending the request.
281+ /// A unique `LSPSRequestId` for correlating the asynchronous response .
282+ ///
264283 ///
265284 /// Response will be provided asynchronously through the event queue as a
266285 /// WebhooksListed or WebhooksListFailed event.
@@ -282,16 +301,16 @@ where
282301 Ok ( request_id)
283302 }
284303
285- /// Remove a webhook by app_name .
304+ /// Remove a previously registered webhook by its name .
286305 ///
287- /// Implements the `lsps5.remove_webhook` method from bLIP-55 .
306+ /// Sends a `lsps5.remove_webhook` JSON-RPC request to the peer .
288307 ///
289308 /// # Parameters
290- /// * `app_name` - The name of the webhook to remove.
309+ /// - `counterparty_node_id`: The LSP node ID to contact.
310+ /// - `app_name`: The name of the webhook to remove.
291311 ///
292312 /// # Returns
293- /// * Success - the request ID that was used.
294- /// * Error - error sending the request.
313+ /// A unique `LSPSRequestId` for correlating the asynchronous response.
295314 ///
296315 /// Response will be provided asynchronously through the event queue as a
297316 /// WebhookRemoved or WebhookRemovalFailed event.
@@ -320,7 +339,6 @@ where
320339 Ok ( request_id)
321340 }
322341
323- /// Handle received messages from the LSP.
324342 fn handle_message (
325343 & self , message : LSPS5Message , counterparty_node_id : & PublicKey ,
326344 ) -> Result < ( ) , LightningError > {
@@ -462,20 +480,7 @@ where
462480 }
463481 }
464482
465- /// Verify a webhook notification signature from an LSP.
466- ///
467- /// This can be used by a notification delivery service to verify
468- /// the authenticity of notifications received from an LSP.
469- ///
470- /// # Parameters
471- /// * `timestamp` - The ISO8601 timestamp from the notification.
472- /// * `signature` - The signature string from the notification.
473- /// * `notification` - The webhook notification object.
474- ///
475- /// # Returns
476- /// * On success: `true` if the signature is valid.
477- /// * On error: LightningError with error description.
478- pub fn verify_notification_signature (
483+ fn verify_notification_signature (
479484 & self , counterparty_node_id : PublicKey , signature_timestamp : & LSPSDateTime ,
480485 signature : & str , notification : & WebhookNotification ,
481486 ) -> Result < bool , LightningError > {
@@ -512,7 +517,6 @@ where
512517 }
513518 }
514519
515- /// Check if a signature has been used before.
516520 fn check_signature_exists ( & self , signature : & str ) -> Result < ( ) , LightningError > {
517521 let recent_signatures = self . recent_signatures . lock ( ) . unwrap ( ) ;
518522
@@ -528,7 +532,6 @@ where
528532 Ok ( ( ) )
529533 }
530534
531- /// Store a signature with timestamp for replay attack prevention.
532535 fn store_signature ( & self , signature : String ) -> Result < ( ) , LightningError > {
533536 let now =
534537 LSPSDateTime :: new_from_duration_since_epoch ( self . time_provider . duration_since_epoch ( ) ) ;
@@ -552,20 +555,35 @@ where
552555 Ok ( ( ) )
553556 }
554557
555- /// Parse a webhook notification received from an LSP.
558+ /// Parse and validate a webhook notification received from an LSP.
556559 ///
557- /// This can be used by a client implementation to handle webhook
558- /// notifications after they're delivered through a push notification
559- /// system.
560+ /// Implements the bLIP-55 / LSPS5 webhook delivery rules:
561+ /// 1. Parses `notification_json` into a `WebhookNotification` (JSON-RPC 2.0).
562+ /// 2. Checks that `timestamp` (from `x-lsps5-timestamp`) is within ±10 minutes of local time.
563+ /// 3. Ensures `signature` (from `x-lsps5-signature`) has not been replayed within the
564+ /// configured retention window.
565+ /// 4. Reconstructs the exact string
566+ /// `"LSPS5: DO NOT SIGN THIS MESSAGE MANUALLY: LSP: At {timestamp} I notify {body}"`
567+ /// and verifies the zbase32 LN-style signature against the LSP’s node ID.
560568 ///
561569 /// # Parameters
562- /// * `timestamp` - The ISO8601 timestamp from the notification.
563- /// * `signature` - The signature from the notification.
564- /// * `notification_json` - The JSON string of the notification object.
570+ /// - `counterparty_node_id`: the LSP’s public key, used to verify the signature.
571+ /// - `timestamp`: ISO8601 time when the LSP created the notification.
572+ /// - `signature`: the zbase32-encoded LN signature over timestamp+body.
573+ /// - `notification_json`: the JSON string of the JSON-RPC notification object.
565574 ///
566- /// # Returns
567- /// * On success: The parsed webhook notification.
568- /// * On error: LightningError with error description.
575+ /// On success, emits `LSPS5ClientEvent::WebhookNotificationReceived`
576+ /// and returns the parsed `WebhookNotification`.
577+ ///
578+ /// Failure reasons include:
579+ /// - Timestamp too old (drift > 10 minutes)
580+ /// - Replay attack detected (signature reused)
581+ /// - Invalid signature (crypto check fails)
582+ /// - JSON parse errors for malformed `notification_json`
583+ ///
584+ /// Clients should call this method upon receiving a SendWebhookNotifications
585+ /// event, before taking action on the notification. This guarantees that only authentic,
586+ /// non-replayed notifications reach your application.
569587 pub fn parse_webhook_notification (
570588 & self , counterparty_node_id : PublicKey , timestamp : & LSPSDateTime , signature : & str ,
571589 notification_json : & str ,
0 commit comments