How NIP-42 WebSocket Authentication Works in Block Buzz: A Complete Technical Guide

Block Buzz implements NIP-42 authentication through a challenge-response flow where the relay issues a random challenge, the client signs a kind 22242 event containing that challenge and relay URL, and the server verifies Schnorr signatures and host-binding before granting access to protected operations.

Block Buzz is a Nostr relay implementation that uses NIP-42 WebSocket authentication to secure client connections before allowing event publication or subscription to protected feeds. Unlike simple token-based systems, the protocol uses cryptographic challenge-response verification tied to Nostr keypairs, ensuring only key holders can authenticate. This deep dive examines the complete authentication flow from the initial WebSocket handshake through server-side verification, referencing the actual source code across the buzz-ws-client, buzz-auth, and buzz-relay crates.

The NIP-42 Challenge-Response Flow

NIP-42 defines a standard method for proving ownership of a Nostr public key during a WebSocket session. Block Buzz splits this implementation between client-side event construction and server-side cryptographic verification.

Step 1: Initiating the WebSocket Connection

When a client connects to Block Buzz, the authentication process begins immediately upon socket establishment. In buzz-ws-client/src/connection.rs, the NostrWsConnection::connect method establishes the WebSocket and begins listening for the initial AUTH challenge from the relay.

The client enters a waiting state after connection, anticipating the relay's challenge message before proceeding with any other operations.

Step 2: Receiving the AUTH Challenge

The relay transmits a JSON message with the format ["AUTH", {"challenge":"..."}] immediately after the WebSocket opens. The client parses incoming text frames through parse_relay_message and stores the challenge in the pending_challenge field of the NostrWsConnection struct (lines 41-45 of connection.rs).

This challenge is a random string that prevents replay attacks and binds the authentication to the specific session.

Step 3: Building the AUTH Event (kind 22242)

Using the stored challenge, the client constructs a NIP-42 AUTH event (kind 22242) via the build_auth_event function in buzz-ws-client/src/message.rs. This specialized event contains three critical components:

  • challenge: The random string supplied by the relay
  • relay: The URL of the relay for host-binding verification
  • auth tag (optional): A NIP-OA OAuth-style token for additional authorization layers

The relay URL inclusion prevents cross-relay replay attacks by binding the signature to a specific host.

Step 4: Signing and Transmitting the Response

The client signs the constructed event with its private key (keys) and transmits it back to the relay using NostrWsConnection::authenticate (lines 79-84 of connection.rs). The message format follows Nostr's standard structure: ["AUTH", <event>].

This transmission contains the cryptographic proof that the client possesses the private key corresponding to their claimed public key.

Step 5: Server-Side Verification

The Block Buzz relay forwards the AUTH event to the verification service in buzz-auth/src/nip42.rs. The verify_nip42_auth_event function performs four critical validation checks:

  1. Signature verification: Schnorr signature validation against the event's public key
  2. Challenge freshness: The challenge must match the one previously issued to that connection and be ≤ 1024 bytes
  3. Host-binding: The relay tag must contain the same host as the connection URL (per NIP-42 row 44)
  4. Optional NIP-OA validation: If an auth tag is present, it validates as a bearer token

Failure of any check results in immediate rejection with a descriptive error message.

Step 6: Confirmation and State Management

Upon successful verification, the server returns an OK message: ["OK", <event_id>, true, "..."]. The client waits for this confirmation using wait_for_ok and marks authentication as successful upon receiving accepted == true, logging debug!("NIP-42 authentication successful").

The relay records the authenticated public key in the connection state (buzz-relay/src/handlers/auth.rs), attaching the AuthContext to the WebSocket session for subsequent access control checks.

Client-Side Implementation in buzz-ws-client

The client library handles authentication transparently during connection setup. Here's how you would initiate an authenticated connection:

use buzz_ws_client::{NostrWsConnection, Keys};

// Initialize with your private key
let keys = Keys::generate();

// Connect and automatically handle NIP-42 flow
let mut conn = NostrWsConnection::connect("wss://relay.example.com", keys).await?;

// The connection is now authenticated and ready for operations
conn.send_event(event).await?;

The NostrWsConnection struct maintains the pending_challenge state internally, handling the challenge-response cycle without requiring manual intervention from the application developer.

Server-Side Verification in buzz-auth

The buzz-auth crate provides the cryptographic verification layer used by the relay. The core verification logic in buzz-auth/src/nip42.rs ensures strict compliance with the NIP-42 specification:

// Pseudo-code representation of the verification flow
pub fn verify_nip42_auth_event(
    event: &Event,
    expected_challenge: &str,
    relay_url: &str
) -> Result<AuthContext, AuthError> {
    // 1. Verify Schnorr signature
    event.verify_signature()?;
    
    // 2. Check challenge matches and size ≤ 1024 bytes
    if event.challenge != expected_challenge || event.challenge.len() > 1024 {
        return Err(AuthError::InvalidChallenge);
    }
    
    // 3. Verify host-binding (relay tag matches connection URL)
    if !event.tags.relay.contains(relay_url) {
        return Err(AuthError::HostMismatch);
    }
    
    // 4. Return authenticated context with pubkey
    Ok(AuthContext::new(event.pubkey))
}

This strict validation prevents common attacks including signature replay across different relays and challenge reuse within the same session.

Relay Integration and Access Control

The buzz-relay crate integrates authentication into the connection lifecycle through buzz-relay/src/handlers/auth.rs. After verification, the relay associates the public key with the WebSocket connection in buzz-relay/src/connection.rs.

All subsequent operations check this authentication state before execution:

  • Event publishing: send_event validates the connection has an authenticated pubkey
  • Subscription filtering: REQ and COUNT commands check AuthContext for protected filters
  • Administrative actions: Channel moderation requires valid authentication

The connection state tracks both the authenticated public key and timeout values, automatically expiring sessions after periods of inactivity to maintain security.

Summary

  • Block Buzz implements NIP-42 WebSocket authentication as a challenge-response protocol requiring cryptographic proof of key ownership.
  • The client library (buzz-ws-client) automatically handles challenge reception, event construction (kind 22242), and transmission via NostrWsConnection::authenticate.
  • Server verification (buzz-auth/src/nip42.rs) enforces Schnorr signature validation, challenge freshness (≤ 1024 bytes), and strict host-binding to prevent relay spoofing.
  • The relay (buzz-relay) maintains authentication state across the session, gating sensitive operations behind verified AuthContext checks.
  • Optional NIP-OA tokens can be included in AUTH events for additional OAuth-style authorization layers.

Frequently Asked Questions

What is NIP-42 and why is it used in Block Buzz?

NIP-42 is a Nostr protocol extension that defines WebSocket authentication using signed events rather than static tokens. Block Buzz uses this standard to ensure that only individuals with access to specific private keys can publish events or access restricted relays, providing cryptographic certainty of identity without requiring passwords or API keys.

How does Block Buzz prevent replay attacks in WebSocket authentication?

Block Buzz prevents replay attacks through challenge freshness and host-binding. Each connection receives a unique random challenge that must be included in the signed AUTH event, and the event must include the specific relay URL (verified in buzz-auth/src/nip42.rs). This binds the signature to both a specific session and a specific relay, making intercepted AUTH events useless for replay attacks on other connections or relays.

What happens if the relay URL doesn't match the event's relay tag?

If the relay tag in the AUTH event (kind 22242) does not match the host of the connection URL, the verify_nip42_auth_event function in buzz-auth/src/nip42.rs returns a Host Mismatch error. This verification (specified in NIP-42 row 44) ensures that authentication credentials cannot be stolen and reused across different relay infrastructure, maintaining strict per-relay security boundaries.

Can Block Buzz authenticate clients using OAuth tokens alongside NIP-42?

Yes, Block Buzz supports NIP-OA (Optional OAuth) tags within NIP-42 AUTH events. When building the AUTH event in buzz-ws-client/src/message.rs, clients can include an auth tag containing a bearer token. The server-side verification in buzz-auth/src/nip42.rs validates this token if present, allowing hybrid authentication that combines Nostr cryptographic identity with traditional OAuth authorization schemes for additional access control layers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →