{"record":{"id":"69f319e1c04f36fc","repo":"block/buzz","slug":"invalid-pubkey-in-a-tag","errorCode":null,"errorMessage":"invalid pubkey in a-tag","messagePattern":"invalid pubkey in a-tag","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":246,"sourceCode":"    state: &Arc<AppState>,\n) -> anyhow::Result<()> {\n    let actor_bytes = effective_message_author(event, &state.relay_keypair.public_key());\n    let target_ids = extract_target_event_ids(event);\n\n    if !has_e_tag(event) {\n        // a-tag deletion: verify author owns the addressable event\n        let a_tag = event\n            .tags\n            .iter()\n            .find(|t| t.kind().to_string() == \"a\")\n            .and_then(|t| t.content().map(|s| s.to_string()))\n            .ok_or_else(|| anyhow::anyhow!(\"missing e or a tag for target\"))?;\n        let parts: Vec<&str> = a_tag.splitn(3, ':').collect();\n        if parts.len() < 2 {\n            return Err(anyhow::anyhow!(\"invalid a-tag format\"));\n        }\n        let target_pubkey_bytes =\n            hex::decode(parts[1]).map_err(|_| anyhow::anyhow!(\"invalid pubkey in a-tag\"))?;\n        if target_pubkey_bytes != actor_bytes\n            && !state\n                .db\n                .is_agent_owner(tenant.community(), &target_pubkey_bytes, &actor_bytes)\n                .await?\n        {\n            return Err(anyhow::anyhow!(\"must be event author\"));\n        }\n        return Ok(());\n    }\n\n    for target_id in target_ids {\n        let target_event = state\n            .db\n            .get_event_by_id_including_deleted(tenant.community(), &target_id)\n            .await?\n            .ok_or_else(|| anyhow::anyhow!(\"target event not found\"))?;\n","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L228-L264","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert the key to 64-char lowercase hex before building the a tag (decode npub via NIP-19 first)","Strip any 0x prefix and whitespace, then assert 64 hex characters with a regex ^[0-9a-f]{64}$","Prefer SDK coordinate builders (e.g. Coordinate/EventCoordinate types) so the pubkey is formatted by the library"],"exampleFix":"// before\nlet a = format!(\"30078:{}:general\", npub_string); // npub1... → invalid pubkey in a-tag\n\n// after\nlet pk_hex = hex::encode(nip19::decode(&npub_string)?.1.to_bytes());\nlet a = format!(\"30078:{}:general\", pk_hex);","handlingStrategy":"validation","validationCode":"// Normalize any key input to 64-char lowercase hex before it enters a tag\nfn to_hex_pubkey(input: &str) -> Option<String> {\n    let s = input.trim().trim_start_matches(\"0x\").to_ascii_lowercase();\n    match (s.len() == 64, s.chars().all(|c| c.is_ascii_hexdigit())) {\n        (true, true) => Some(s),\n        _ => match nip19::decode(input) { // accept npub and convert\n            Ok(_) => Some(hex::encode(nip19::decode(input).unwrap().1.to_bytes())),\n            Err(_) => None,\n        },\n    }\n}\nlet pk = to_hex_pubkey(raw).ok_or(\"pubkey must be 64-char hex or npub\")?;","typeGuard":"const isHexPubkey = (v: string): boolean => /^[0-9a-f]{64}$/.test(v);\nconst toCoordinate = (kind: number, pk: string, d: string) =>\n  `${kind}:${toHex(pk)}:${d}`;","tryCatchPattern":"match validate_standard_deletion_event(&tenant, &event, &state).await {\n    Err(e) if e.to_string().contains(\"invalid pubkey in a-tag\") => {\n        warn_and_fix_coordinate(&event); // re-render with hex pubkey, re-sign, retry once\n    }\n    other => other,\n}","preventionTips":["Store keys internally as hex; convert npub only at the display boundary","Reject 0x prefixes and non-64-length strings at input parsing, not at publish time","Use one shared normalize_key() helper everywhere tags are built so no call site forgets"],"tags":["nostr","nip-09","pubkey","hex","validation"],"backgroundTag":"invalid-hex-pubkey","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}