block/buzz · error
failed to sign system message: {e}
Error message
failed to sign system message: {e} What it means
`emit_system_message` builds a relay-signed Nostr kind:40099 system event and signs it with the relay keypair via `sign_with_keys`. This error means the signing step failed — most commonly a malformed tag/content or a secp256k1 signing error — before any DB write occurs. It propagates to callers like handle_create_group / handle_put_user so the triggering operation can fail rather than silently skip the system message.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:709
/// `insert_event` then provides DB-enforced delivery idempotency.
///
/// Returns `Err` if the event could not be durably inserted; fanout remains
/// best-effort.
pub async fn emit_system_message(
tenant: &TenantContext,
state: &Arc<AppState>,
channel_id: Uuid,
content: serde_json::Value,
idempotency_ts: chrono::DateTime<chrono::Utc>,
) -> anyhow::Result<()> {
let channel_tag = Tag::parse(["h", &channel_id.to_string()])?;
let ts = nostr::Timestamp::from(idempotency_ts.timestamp() as u64);
let event = EventBuilder::new(Kind::Custom(40099), content.to_string())
.tags([channel_tag])
.custom_created_at(ts)
.sign_with_keys(&state.relay_keypair)
.map_err(|e| anyhow::anyhow!("failed to sign system message: {e}"))?;
// Durable insert is the completion boundary — propagate failure.
state
.db
.insert_event(tenant.community(), &event, Some(channel_id))
.await
.map_err(|e| anyhow::anyhow!("system message insert failed: {e}"))?;
// Fan out to subscribers: best-effort, clients can retrieve the persisted event.
if let Err(e) = state
.pubsub
.publish_event(tenant, EventTopic::Channel(channel_id), &event)
.await
{
warn!("System message fan-out failed: {e}");
}
Ok(())View on GitHub (pinned to dad5a33865)
Solutions
- Check the relay's secret-key configuration (env/config file) and confirm the relay keypair initializes correctly at startup.
- Inspect the inner `{e}`: a secp256k1 error means invalid key bytes; fix the key source (must be 32-byte valid secret key).
- Fail fast at relay startup with an explicit error if the signing keypair cannot be constructed, instead of deferring to message emit time.
- Verify the nostr crate version's `sign_with_keys` API and key type match what AppState stores.
- If keys come from a secret store, confirm the secret was loaded (not empty) before handling events.
Example fix
// before: keypair failure discovered only at emit time
.sign_with_keys(&state.relay_keypair)
.map_err(|e| anyhow::anyhow!("failed to sign system message: {e}"))?;
// after: validate at startup
let relay_keypair = KeyPair::from_secret(&secret)
.context("relay signing key invalid — check NOSTR_SECRET_KEY")?; // fail at boot
// then in emit:
.sign_with_keys(&state.relay_keypair)?; Defensive patterns
Strategy: validation
Validate before calling
// at relay startup, before serving:
let relay_keypair = KeyPair::from_secret(&secret_bytes)
.context("relay signing key invalid/unconfigured — check NOSTR_SECRET_KEY")?;
assert!(!secret_hex.is_empty(), "relay signing key must be set"); Type guard
fn has_valid_relay_keypair(state: &AppState) -> bool {
// secp256k1 secret keys are 32 bytes; verify the keypair can sign
KeyPair::from_secret(state.secret_key_bytes()).is_ok()
} Try / catch
match emit_system_message(&tenant, &state, channel_id, content, ts).await {
Ok(()) => (),
Err(e) if format!("{e:#}").contains("failed to sign system message") => {
error!("relay signing key misconfigured: {e:#}"); // config issue, not transient
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Fail fast at boot when the signing keypair cannot be constructed — never defer to first emit.
- Keep the relay secret key in a validated, versioned config; test key loading in CI.
- Pin the nostr crate version and re-test key parsing on upgrades.
- Alert on 'failed to sign system message' — it almost always means configuration, not runtime, trouble.
When it happens
Trigger: Any caller (handle_dm_open, handle_put_user, handle_remove_user, handle_edit_metadata, handle_delete_event_side_effect, handle_create_group) invoking emit_system_message when `EventBuilder::...sign_with_keys(&state.relay_keypair)` errors — e.g. `state.relay_keypair` is None/uninitialized because relay signing keys were not configured, or the key bytes are invalid.
Common situations: Relay started without NOSTR secret key configured so keypair construction produced a placeholder/invalid key; bad hex or short key in env config; nostr crate version change altering keypair parsing; corrupted key material in state.
Related errors
- failed to sign moderation notice: {e}
- failed to sign member snapshot: {error}
- sign roster
- io: line exceeds max ({max} bytes)
- moderation notice recipient must be a 32-byte pubkey, got {}
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30).
Data as JSON: /api/errors/ed5de0059f881a33.
Report an issue: GitHub.