block/buzz · error

failed to sign member snapshot: {error}

Error message

failed to sign member snapshot: {error}

What it means

store_group_members_event builds the NIP-29 kind:39002 member snapshot event and signs it with the relay's own identity keypair via sign_with_keys. This error wraps any failure from the nostr signing operation, meaning the relay could not produce a valid signature over the snapshot event. It is a server-internal error — the snapshot was constructed but never signed or published.

Source

Thrown at crates/buzz-relay/src/handlers/side_effects.rs:1017

    let ts = member_snapshot
        .latest_member_event_timestamp(tenant.community(), channel_id, &relay_pubkey)
        .await?
        .map(|timestamp| timestamp + 1)
        .unwrap_or(now)
        .max(now);
    // A relay-signed roster of a channel the relay is itself a member of (the
    // relay's moderation-DM key participates in the {relay, recipient} DM used
    // for moderation notices) MUST retain the relay's own `p` tag. nostr's
    // default `build_with_ctx` strips any `p` tag matching the signer, which
    // would drop the relay from the snapshot and fail migration 0032's roster
    // fence against the canonical two-member DM. `allow_self_tagging` keeps the
    // snapshot faithful to `channel_members`.
    let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_MEMBERS as u16), "")
        .tags(tags)
        .allow_self_tagging()
        .custom_created_at(nostr::Timestamp::from(ts))
        .sign_with_keys(&state.relay_keypair)
        .map_err(|error| anyhow::anyhow!("failed to sign member snapshot: {error}"))?;
    let (stored, inserted) = member_snapshot
        .replace_member_event(tenant.community(), channel_id, &event)
        .await?;
    Ok(inserted.then_some(stored))
}

async fn dispatch_group_members_event(
    tenant: &TenantContext,
    state: &Arc<AppState>,
    stored: Option<buzz_core::StoredEvent>,
    relay_pubkey_hex: &str,
) {
    if let Some(stored) = stored {
        dispatch_persistent_event(
            tenant,
            state,
            &stored,
            KIND_NIP29_GROUP_MEMBERS,

View on GitHub (pinned to dad5a33865)

Solutions

  1. Verify BUZZ_RELAY_PRIVATE_KEY is a valid 64-char hex secp256k1 private key; relay startup already validates via nostr::Keys::parse — re-run with a fresh `just bootstrap` key if unsure.
  2. Restart the relay so state.relay_keypair is rebuilt from configuration rather than a stale value.
  3. Check the wrapped {error} detail in logs for the underlying secp256k1/nostr failure and fix that root cause.
  4. Run `just test-unit` for the relay crate to confirm keypair/signing setup after config changes.

Example fix

// before: keypair loaded from ad-hoc string
let keys = Keys::parse(env::var("RELAY_KEY").unwrap().as_str())?;
// after: validated at startup via relay_keypair_from_config
let keys = relay_keypair_from_config(config.relay_private_key.as_deref())?;
Defensive patterns

Strategy: try-catch

Validate before calling

let keys = nostr::Keys::parse(&hex_key)?;
if keys.public_key().to_bytes().len() != 32 { return Err(anyhow!("bad relay key")); }

Try / catch

match store_group_members_event(...) {
    Err(e) if e.to_string().contains("failed to sign member snapshot") => {
        tracing::error!("relay keypair unusable: {e:#}");
        // reload keypair from config and retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling store_group_members_event (directly or via emit_group_discovery_events / reconcile_large_channel_member_snapshots) when the relay_keypair held in state is invalid, corrupted, or fails during signing. Happens when the keypair was constructed from a malformed BUZZ_RELAY_PRIVATE_KEY or the nostr Keys object became unusable.

Common situations: Relay started with a misconfigured BUZZ_RELAY_PRIVATE_KEY (wrong length, non-hex, zero key); a relay keypair that parses but is rejected by the nostr library during secp256k1 signing; environment drift after manual edits to .env; running reconcile against a relay identity that was regenerated.

Related errors


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