block/buzz · error

moderation notice recipient must be a 32-byte pubkey, got {}

Error message

moderation notice recipient must be a 32-byte pubkey, got {}

What it means

`send_moderation_notice` in buzz-relay validates that the notice recipient is exactly 32 bytes — the canonical size of a Nostr/secp256k1 public key — before building and signing the notice event. A non-32-byte slice means a malformed or truncated pubkey was passed in from an upstream handler (report resolution, ban, timeout), and the relay refuses to send a DM to an invalid key.

Source

Thrown at crates/buzz-relay/src/handlers/moderation_notices.rs:92

/// Deliver a moderation notice to `recipient` in this community's
/// relay-authored DM thread (created on first use, reused after).
///
/// Idempotent and concurrency-safe: the notice event is constructed
/// deterministically from `idempotency_ts` (the outbox row's `created_at`) so
/// that two workers racing on the same outbox row produce byte-identical Nostr
/// events. The `insert_event` ON CONFLICT DO NOTHING constraint then ensures
/// exactly one row is durably persisted. Pass `row.created_at` as
/// `idempotency_ts`.
pub async fn send_moderation_notice(
    tenant: &TenantContext,
    state: &Arc<AppState>,
    recipient_pubkey: &[u8],
    notice: ModerationNotice,
    idempotency_ts: chrono::DateTime<chrono::Utc>,
) -> anyhow::Result<()> {
    if recipient_pubkey.len() != 32 {
        anyhow::bail!(
            "moderation notice recipient must be a 32-byte pubkey, got {}",
            recipient_pubkey.len()
        );
    }
    let relay_pubkey = state.relay_keypair.public_key();
    let relay_pubkey_bytes = relay_pubkey.to_bytes();
    let relay_pubkey_hex = hex::encode(relay_pubkey_bytes);

    // Never DM the relay key itself (would create a self-DM and is meaningless).
    if recipient_pubkey == relay_pubkey_bytes.as_slice() {
        return Ok(());
    }

    // 1. Create/reuse the two-party DM channel {relay mod key, recipient}.
    //    `open_dm` is participant-hash idempotent, so re-delivery to the same
    //    user reuses the one thread per (community, user).
    let (dm_channel, was_created) = state
        .db

View on GitHub (pinned to eed74bde2f)

Solutions

  1. Hex-decode the recipient pubkey and assert it is 64 hex chars (32 bytes) before calling any moderation notice function.
  2. Log the offending value length at the call site and fix the producer of the malformed pubkey (DB row, event tag, or API payload).
  3. Strip any 'npub'/'0x' prefixes and use a strict bech32/hex decoder that errors instead of truncating.
  4. Add a length assertion in the upstream handler when the pubkey is first parsed.

Example fix

// before
let pubkey = decode_pubkey(raw)?; // may yield non-32 bytes
send_moderation_notice(&state, &pubkey, notice, ts).await?;
// after
let pubkey = decode_pubkey(raw)?;
anyhow::ensure!(pubkey.len() == 32, "invalid recipient pubkey length {}", pubkey.len());
send_moderation_notice(&state, &pubkey, notice, ts).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_valid_pubkey(bytes: &[u8]) -> anyhow::Result<()> {
    anyhow::ensure!(bytes.len() == 32, "recipient pubkey must be 32 bytes, got {}", bytes.len());
    Ok(())
}

Type guard

fn as_pubkey(bytes: &[u8]) -> Option<&[u8; 32]> {
    bytes.try_into().ok()
}

Try / catch

match send_moderation_notice(&state, recipient, notice, ts).await {
  Err(e) if e.to_string().contains("must be a 32-byte pubkey") => {
    tracing::warn!(len = recipient.len(), "skipping notice: invalid recipient pubkey");
  }
  Err(e) => return Err(e),
  Ok(v) => Ok(v),
}

Prevention

When it happens

Trigger: Calling `send_moderation_notice` (directly or via `deliver_reporter_notice`, `deliver_affected_user_notice`, `handle_ban`, `handle_timeout`, `resolve_report_decision_only`) with a `recipient_pubkey` byte slice whose length is not 32 — e.g. a hex-decoded value of wrong length, an empty slice, or a 33-byte compressed key.

Common situations: A database row storing a truncated or corrupted pubkey; decoding a hex string with an odd number of characters or extra prefix ('0x' or 'npub' not stripped); a caller passing a slice of the wrong type (e.g. x-only vs full key) from another subsystem.

Related errors


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