block/buzz · error · IngestError::Rejected

invalid pubkey in a-tag

Error message

invalid pubkey in a-tag

What it means

The 'a'-tag deletion's second coordinate segment (the publisher pubkey) failed hex::decode. The relay decodes parts[1] as raw hex bytes to compare against the deletion's effective author, so any non-hex characters — an npub prefix, a bech32 string, a '0x' prefix, or a typo — make the coordinate unusable and the deletion is rejected before authorization even runs.

Source

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

    state: &Arc<AppState>,
) -> anyhow::Result<()> {
    let actor_bytes = effective_message_author(event, &state.relay_keypair.public_key());
    let target_ids = extract_target_event_ids(event);

    if !has_e_tag(event) {
        // a-tag deletion: verify author owns the addressable event
        let a_tag = event
            .tags
            .iter()
            .find(|t| t.kind().to_string() == "a")
            .and_then(|t| t.content().map(|s| s.to_string()))
            .ok_or_else(|| anyhow::anyhow!("missing e or a tag for target"))?;
        let parts: Vec<&str> = a_tag.splitn(3, ':').collect();
        if parts.len() < 2 {
            return Err(anyhow::anyhow!("invalid a-tag format"));
        }
        let target_pubkey_bytes =
            hex::decode(parts[1]).map_err(|_| anyhow::anyhow!("invalid pubkey in a-tag"))?;
        if target_pubkey_bytes != actor_bytes
            && !state
                .db
                .is_agent_owner(tenant.community(), &target_pubkey_bytes, &actor_bytes)
                .await?
        {
            return Err(anyhow::anyhow!("must be event author"));
        }
        return Ok(());
    }

    for target_id in target_ids {
        let target_event = state
            .db
            .get_event_by_id_including_deleted(tenant.community(), &target_id)
            .await?
            .ok_or_else(|| anyhow::anyhow!("target event not found"))?;

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Convert the key to 64-char lowercase hex before building the a tag (decode npub via NIP-19 first)
  2. Strip any 0x prefix and whitespace, then assert 64 hex characters with a regex ^[0-9a-f]{64}$
  3. Prefer SDK coordinate builders (e.g. Coordinate/EventCoordinate types) so the pubkey is formatted by the library

Example fix

// before
let a = format!("30078:{}:general", npub_string); // npub1... → invalid pubkey in a-tag

// after
let pk_hex = hex::encode(nip19::decode(&npub_string)?.1.to_bytes());
let a = format!("30078:{}:general", pk_hex);
Defensive patterns

Strategy: validation

Validate before calling

// Normalize any key input to 64-char lowercase hex before it enters a tag
fn to_hex_pubkey(input: &str) -> Option<String> {
    let s = input.trim().trim_start_matches("0x").to_ascii_lowercase();
    match (s.len() == 64, s.chars().all(|c| c.is_ascii_hexdigit())) {
        (true, true) => Some(s),
        _ => match nip19::decode(input) { // accept npub and convert
            Ok(_) => Some(hex::encode(nip19::decode(input).unwrap().1.to_bytes())),
            Err(_) => None,
        },
    }
}
let pk = to_hex_pubkey(raw).ok_or("pubkey must be 64-char hex or npub")?;

Type guard

const isHexPubkey = (v: string): boolean => /^[0-9a-f]{64}$/.test(v);
const toCoordinate = (kind: number, pk: string, d: string) =>
  `${kind}:${toHex(pk)}:${d}`;

Try / catch

match validate_standard_deletion_event(&tenant, &event, &state).await {
    Err(e) if e.to_string().contains("invalid pubkey in a-tag") => {
        warn_and_fix_coordinate(&event); // re-render with hex pubkey, re-sign, retry once
    }
    other => other,
}

Prevention

When it happens

Trigger: Putting an npub/npub1... bech32 string where the hex pubkey belongs; prefixing with 0x; uppercase O/I or non-ASCII characters mixed into the hex; truncating the 64-char hex key.

Common situations: Client displays keys as npub and a developer pastes that form into the tag; key copied from a UI that inserted a space or ellipsis; NIP-19 conversion step skipped when building coordinates.

Related errors


AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16). Data as JSON: /api/errors/69f319e1c04f36fc. Report an issue: GitHub.