Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ x-auth: HMAC <unix_timestamp>:<hmac_hex>
Where:

- `unix_timestamp` is the current time in seconds since the Unix epoch
- `hmac_hex` is the hex-encoded result of `HMAC-SHA256(api_key_bytes, timestamp_be_bytes)`
- `hmac_hex` is the hex-encoded result of
`HMAC-SHA256(api_key_bytes, timestamp_be_bytes || grpc_request_body_bytes)`
- `api_key_bytes` is the API key string encoded as UTF-8 bytes
- `timestamp_be_bytes` is the timestamp as a big-endian 8-byte unsigned integer
- `grpc_request_body_bytes` is the raw gRPC request body sent over HTTP/2, including
the 5-byte gRPC message frame

The server rejects requests where the timestamp differs from the server's clock by more than
**60 seconds**.
Expand Down
3 changes: 2 additions & 1 deletion ldk-server-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ println!("Node ID: {}", info.node_id);

The client handles HMAC-SHA256 authentication automatically. Pass the hex-encoded API key
(found at `<storage_dir>/<network>/api_key`) and the server's TLS certificate (found at
`<storage_dir>/tls.crt`).
`<storage_dir>/tls.crt`). Each request signature covers both the timestamp and the raw gRPC
request body bytes.

## Event Streaming

Expand Down
10 changes: 5 additions & 5 deletions ldk-server-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ impl LdkServerClient {

/// Computes the HMAC-SHA256 authentication header value.
/// Format: "HMAC <timestamp>:<hmac_hex>"
/// Uses timestamp-only HMAC (no body) since TLS guarantees integrity.
fn compute_auth_header(&self) -> String {
/// The signature covers the timestamp and raw gRPC request body bytes.
fn compute_auth_header(&self, body: &[u8]) -> String {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("System time should be after Unix epoch")
.as_secs();

// HMAC-SHA256(api_key, timestamp_bytes) — no body
let mut hmac_engine: HmacEngine<sha256::Hash> = HmacEngine::new(self.api_key.as_bytes());
hmac_engine.input(&timestamp.to_be_bytes());
hmac_engine.input(body);
let hmac_result = Hmac::<sha256::Hash>::from_engine(hmac_engine);

format!("HMAC {}:{}", timestamp, hmac_result)
Expand Down Expand Up @@ -428,7 +428,7 @@ impl LdkServerClient {
let grpc_body = encode_grpc_frame(&request.encode_to_vec()).to_vec();

let url = format!("https://{}{}{}", self.base_url, GRPC_SERVICE_PREFIX, method);
let auth_header = self.compute_auth_header();
let auth_header = self.compute_auth_header(&grpc_body);

let response = self
.client
Expand Down Expand Up @@ -471,7 +471,7 @@ impl LdkServerClient {
let grpc_body = encode_grpc_frame(&request.encode_to_vec()).to_vec();

let url = format!("https://{}{}{}", self.base_url, GRPC_SERVICE_PREFIX, method);
let auth_header = self.compute_auth_header();
let auth_header = self.compute_auth_header(&grpc_body);

let response = self
.streaming_client
Expand Down
Loading