block/buzz · critical · anyhow::Error
invalid BUZZ_RELAY_PRIVATE_KEY: {e}
Error message
invalid BUZZ_RELAY_PRIVATE_KEY: {e} What it means
When BUZZ_RELAY_PRIVATE_KEY is set, nostr::Keys::parse validates it as a secp256k1 secret key. Non-hex characters, wrong length (not 64 hex chars / 32 bytes), a 0x prefix, or embedded whitespace/newlines all fail parsing and abort startup. Note this only fires when the key IS set — the missing-key case is handled by the separate NIP-43/require-auth-token checks.
Source
Thrown at crates/buzz-relay/src/main.rs:427
.read_database_url
.as_deref()
.unwrap_or(&config.database_url);
let search_pool = sqlx::postgres::PgPoolOptions::new()
.connect(search_db_url)
.await
.map_err(|e| anyhow::anyhow!("Search DB connection failed: {e}"))?;
let search = SearchService::new(search_pool);
info!(
replica = config.read_database_url.is_some(),
"Search service ready (Postgres FTS)"
);
let workflow_config = buzz_workflow::WorkflowConfig::default();
let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config));
let relay_keypair = if let Some(hex) = &config.relay_private_key {
nostr::Keys::parse(hex)
.map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))?
} else if !config.require_auth_token {
// Dev mode: use a deterministic keypair so addressable events (kind:39000/39001/39002)
// replace correctly across restarts. Without this, each restart generates a new pubkey
// and replace_addressable_event inserts duplicates instead of replacing.
const DEV_RELAY_PRIVKEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
let keys = nostr::Keys::parse(DEV_RELAY_PRIVKEY).expect("hardcoded dev key is valid");
tracing::warn!(
pubkey = %keys.public_key().to_hex(),
"Using hardcoded dev relay keypair (BUZZ_REQUIRE_AUTH_TOKEN=false). \
Set BUZZ_RELAY_PRIVATE_KEY for production."
);
keys
} else {
panic!(
"BUZZ_RELAY_PRIVATE_KEY must be set when BUZZ_REQUIRE_AUTH_TOKEN=true. \
A stable relay identity is required for production."
);View on GitHub (pinned to f956e6fe06)
Solutions
- Set BUZZ_RELAY_PRIVATE_KEY to the raw 64-hex-character private key (the decoded nsec content, without the bech32 prefix)
- Strip whitespace: `tr -d '[:space:]'` when injecting from files
- Double-check you did not paste the pubkey
Example fix
# before BUZZ_RELAY_PRIVATE_KEY=nsec1qw508d6qejxtdg4y5r3zarvary0c5xw7k # after (hex body of the nsec, exactly 64 chars) BUZZ_RELAY_PRIVATE_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
Defensive patterns
Strategy: validation
Validate before calling
# The relay key must be exactly 64 hex characters.
[[ "${BUZZ_RELAY_PRIVATE_KEY:-}" =~ ^[0-9a-fA-F]{64}$ ]] || { echo 'BUZZ_RELAY_PRIVATE_KEY must be 64 hex chars'; exit 1; } Type guard
fn is_valid_secret_hex(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit())
} Prevention
- Decode nsec1... values to raw hex before putting them in env
- Trim whitespace/newlines when injecting secrets from files
- Never confuse the pubkey with the private key in env naming
When it happens
Trigger: Setting the key to an nsec1... bech32 string, base64, a 0x-prefixed or 66-char value, or a secret-store value with a trailing newline or surrounding quotes.
Common situations: Copy-pasting nsec1... from Nostr clients; secret managers appending newlines; pasting the public key where the private key belongs.
Related errors
- BUZZ_RELAY_PRIVATE_KEY is required when BUZZ_REQUIRE_RELAY_M
- Configuration error: {e}
- RELAY_OWNER_PUBKEY required when BUZZ_REQUIRE_RELAY_MEMBERSH
- Cannot derive a community host from BUZZ_RELAY_URL ({:?}); a
- Redis pool creation failed: {e}
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/b620c2c4d4cc38df.
Report an issue: GitHub.