Buzz-Auth Functionalities: Handling NIP-42, NIP-98, API Tokens, and Rate Limiting

buzz-auth is the security core of the Buzz relay that validates Nostr-based NIP-42 and NIP-98 authentication schemes and provides a generic rate-limiting interface for protecting PubKey-scoped and IP-scoped operations.

The buzz-auth crate serves as the authentication and security foundation for Block's Buzz Nostr relay implementation. It handles cryptographic verification of Nostr Identity Protocol (NIP) events, manages API token validation, and implements a flexible rate-limiting system. Understanding the functionalities of buzz-auth in handling NIP-42, NIP-98, API tokens, and rate limiting is essential for developers integrating with or operating Buzz infrastructure.

NIP-42 Challenge-Response Authentication

Located in crates/buzz-auth/src/nip42.rs, buzz-auth implements the NIP-42 protocol for WebSocket session authentication using signed kind = 22242 events.

Challenge Generation

The generate_challenge() function creates a cryptographically secure 32-byte random hex string. This challenge is transmitted to the client, who must include it in a signed authentication event to prove ownership of their private key.

Event Verification

The verify_nip42_event() function validates the client's signed response against the server-generated challenge. According to the Block buzz source code, this verification includes:

  • Confirming the event kind is Authentication (22242)
  • Verifying the Schnorr signature via buzz_core::verify_event
  • Matching the challenge tag against the server-generated challenge
  • Normalizing and comparing the relay URL tag (treating localhost and 127.0.0.1 as equivalent)
  • Checking the timestamp is within ±TIMESTAMP_TOLERANCE_SECS (60 seconds)

Verification failures return specific AuthError variants defined in crates/buzz-auth/src/error.rs: InvalidSignature, ChallengeMismatch, RelayUrlMismatch, or EventExpired.

let challenge = buzz_auth::generate_challenge();
// Client signs an AUTH event (kind 22242) containing the challenge
// Server verification:
let ok = buzz_auth::verify_nip42_event(&event, &challenge, "wss://relay.example.com");

NIP-98 HTTP Bearer Token Authentication

The crates/buzz-auth/src/nip98.rs module implements NIP-98 for stateless HTTP authentication, validating base64-encoded bearer tokens containing kind = 27235 events.

Bearer Token Structure

The system expects an Authorization header formatted as Nostr <base64>, where the payload is a JSON-encoded Nostr event of kind HttpAuth (27235).

Verification Logic

The verify_nip98_event(event_json, expected_url, expected_method, body) function executes a six-step validation pipeline:

  1. Confirms kind == HttpAuth (27235)
  2. Verifies the Schnorr signature using buzz_core::verify_event
  3. Validates created_at is within ±60 seconds of current time
  4. Normalizes and compares the u tag (URL) against expected_url
  5. Performs case-insensitive comparison of the method tag against expected_method
  6. If a payload tag exists and request body is provided, validates that SHA-256(body) matches the hex payload

On success, the function returns the authenticated public key. Failures yield AuthError::Nip98Invalid with safe diagnostic messaging that avoids leaking sensitive implementation details.

let auth_header = req.headers().get("Authorization").unwrap(); // "Nostr <base64>"
let event_json = base64::decode(&auth_header[6..]).unwrap();
let pubkey = buzz_auth::verify_nip98_event(
    &event_json,
    "https://api.example.com/submit",
    "POST",
    Some(req.body_bytes()),
)?;

Rate Limiting Interface

Defined in crates/buzz-auth/src/rate_limit.rs, the rate-limiting system provides a pluggable trait architecture for throttling both authenticated users and raw IP connections.

RateLimiter Trait and Configuration

The RateLimiter trait defines two async methods:

  • check_and_increment: PubKey-scoped, community-aware limits for authenticated operations
  • check_ip_connection: Global IP-scoped connection limits

RateLimitConfig holds per-tier limits distinguishing between human users and agent tokens. LimitType enumerates rate-limited categories (messages, API calls, GIF searches, WebSocket events, and IP connections) and provides Redis key suffixes (msg, api, gif, ws, conn).

Redis Key Scoping

Helper functions construct scoped Redis keys:

  • PubKey limits: buzz:{community}:ratelimit:{pubkey}:{suffix} (community-scoped)
  • IP limits: Global scope via ip_rate_limit_key

RateLimitResult returns whether the request is allowed, the current counter, the configured limit, and seconds until the window resets. A no-op AlwaysAllowRateLimiter implementation is provided in crates/buzz-auth/src/lib.rs for unit testing scenarios.

let limiter = buzz_auth::AlwaysAllowRateLimiter; // test stub
let ctx = tenant_context(); // resolved community
let result = limiter
    .check_and_increment(&ctx, &pubkey, buzz_auth::LimitType::Messages, 60, 60)
    .await?;
if !result.allowed {
    // Return 429 Too Many Requests
}

Error Handling

crates/buzz-auth/src/error.rs centralizes error handling through the AuthError enum, used by both NIP-42 and NIP-98 verification modules. This ensures consistent error propagation and safe messaging across the authentication boundary without exposing internal validation logic.

Summary

  • NIP-42 Authentication: Implements challenge-response WebSocket authentication via generate_challenge() and verify_nip42_event(), validating kind 22242 events with 60-second timestamp tolerance and relay URL normalization.
  • NIP-98 Authentication: Validates HTTP bearer tokens containing base64-encoded kind 27235 events, verifying signatures, timestamps, URL/method tags, and optional SHA-256 payload hashes.
  • API Token Handling: Both protocols rely on buzz_core::verify_event for Schnorr signature verification and return the authenticated public key upon success.
  • Rate Limiting: Provides a RateLimiter trait with check_and_increment for community-scoped PubKey limits and check_ip_connection for global IP throttling, using Redis keys formatted as buzz:{community}:ratelimit:{pubkey}:{suffix}.

Frequently Asked Questions

What distinguishes NIP-42 from NIP-98 authentication in buzz-auth?

NIP-42 provides session-based authentication for WebSocket connections using challenge-response mechanics with ephemeral challenges generated by generate_challenge(), while NIP-98 offers stateless HTTP authentication where each request carries a self-contained bearer token (kind 27235) that the server validates independently without maintaining session state.

How does buzz-auth implement rate limiting for different user tiers?

The RateLimitConfig struct defines per-tier limits distinguishing human users from automated agents. The check_and_increment method on the RateLimiter trait accepts these configuration parameters to enforce different thresholds based on the authenticated PubKey's assigned tier, while LimitType categorizes operations (messages, API calls, GIF searches) for granular control.

What validation occurs when a NIP-42 challenge expires?

The verify_nip42_event() function checks the event's created_at timestamp against the current time with a tolerance of TIMESTAMP_TOLERANCE_SECS (60 seconds). If the event falls outside this window, the function returns AuthError::EventExpired, rejecting the authentication attempt and requiring the client to request a fresh challenge.

Can buzz-auth operate without a Redis backend for rate limiting?

Yes. The crate provides the AlwaysAllowRateLimiter no-op implementation that satisfies the RateLimiter trait by always returning allowed: true, enabling unit testing and development environments to function without Redis infrastructure. Production deployments typically implement the trait with Redis-backed storage using the rate_limit_key and ip_rate_limit_key helpers.

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 →