Skip to content

Commit e143d43

Browse files
WIP
1 parent 004c5c3 commit e143d43

File tree

7 files changed

+636
-2533
lines changed

7 files changed

+636
-2533
lines changed

lightning-liquidity/src/lsps5/client.rs

Lines changed: 72 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ use super::service::TimeProvider;
2929

3030
use alloc::collections::VecDeque;
3131
use alloc::string::String;
32+
3233
use bitcoin::secp256k1::PublicKey;
34+
3335
use lightning::ln::msgs::{ErrorAction, LightningError};
3436
use lightning::sign::EntropySource;
3537
use 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
133148
pub struct LSPS5ClientHandler<ES: Deref>
134149
where
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

153161
impl<ES: Deref> LSPS5ClientHandler<ES>
154162
where
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

Comments
 (0)