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

  1. Check the relay's secret-key configuration (env/config file) and confirm the relay keypair initializes correctly at startup.
  2. Inspect the inner `{e}`: a secp256k1 error means invalid key bytes; fix the key source (must be 32-byte valid secret key).
  3. Fail fast at relay startup with an explicit error if the signing keypair cannot be constructed, instead of deferring to message emit time.
  4. Verify the nostr crate version's `sign_with_keys` API and key type match what AppState stores.
  5. 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

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


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30). Data as JSON: /api/errors/ed5de0059f881a33. Report an issue: GitHub.