block/buzz · error
sign kind:39000: {e}
Error message
sign kind:39000: {e} What it means
While republishing a channel's kind:39000 (NIP-29 channel metadata) snapshot, EventBuilder::sign_with_keys(&relay_keys) returned an error, which reconcile_channels wraps with this message. Signing is a pure secp256k1 operation over the assembled tags, and since the keys already passed Keys::parse, a failure here is essentially an internal/unexpected condition (secp256k1 backend error) rather than an input problem — the Tag::parse calls that build the tags fail separately with their own messages. The underlying error is appended as {e}.
Source
Thrown at crates/buzz-admin/src/main.rs:583
if !desc.is_empty() {
tags.push(Tag::parse(["about", desc])?);
}
}
if channel.visibility == "private" {
tags.push(Tag::parse(["private"])?);
} else {
tags.push(Tag::parse(["public"])?);
}
if channel.channel_type == "dm" {
tags.push(Tag::parse(["hidden"])?);
}
tags.push(Tag::parse(["closed"])?);
tags.push(Tag::parse(["t", &channel.channel_type])?);
let event = EventBuilder::new(Kind::Custom(39000), "")
.tags(tags)
.sign_with_keys(&relay_keys)
.map_err(|e| anyhow::anyhow!("sign kind:39000: {e}"))?;
db.replace_addressable_event(tenant.community(), &event, Some(channel.id))
.await?;
}
// kind:39001 — admins
{
let mut tags: Vec<Tag> = vec![Tag::parse(["d", &channel_id_str])?];
for m in members
.iter()
.filter(|m| m.role == "owner" || m.role == "admin")
{
let pk = hex::encode(&m.pubkey);
tags.push(Tag::parse(["p", &pk, &m.role])?);
}
let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "")
.tags(tags)
.sign_with_keys(&relay_keys)
.map_err(|e| anyhow::anyhow!("sign kind:39001: {e}"))?;View on GitHub (pinned to f956e6fe06)
Solutions
- Re-run the command once — if it succeeds, it was transient; if it persists it is deterministic and environmental.
- Capture the {e} suffix and check for secp256k1/nostr version skew: `cargo tree -p buzz-admin -i secp256k1` and ensure nostr and secp256k1 versions are the ones the workspace pins (run `cargo update` rollback or use Cargo.lock as committed).
- Re-validate the key independently (`Keys::parse` on the same value in a scratch binary or `nostr-tools key check` equivalent) to rule out a subtly corrupted value that still parses.
- If reproducible with a pinned lockfile, report it with the full {e} text — this path should be unreachable for a parsed key.
Defensive patterns
Strategy: validation
Validate before calling
# keys are the only realistic input factor: pre-verify before reconcile
python3 -c "import sys; bytes.fromhex(sys.argv[1])" "$BUZZ_RELAY_PRIVATE_KEY" \
|| { echo 'relay key is not valid hex' >&2; exit 1; } Try / catch
match cmd.status() { Err if stderr.contains("sign kind:39000") => retry once, then capture stderr + `cargo tree -i secp256k1` for a bug report } Prevention
- Build with the committed Cargo.lock (--locked) so nostr/secp256k1 stay a validated pair.
- Treat sign failures on parsed keys as environment/dependency bugs: capture {e} rather than changing channel data.
When it happens
Trigger: A secp256k1 signing error surfacing from the nostr library — in practice this almost never fires: the key parsed cleanly and the event content is empty. If you see it, capture {e}; historically such errors indicate a corrupted key object or a nostr/secp256k1 version incompatibility after a dependency bump.
Common situations: After a workspace-wide `cargo update` that pulled an incompatible nostr/secp256k1 pairing; building with a patched/miscompiled crypto backend; otherwise not reproducible on re-run.
Related errors
- sign kind:39001: {e}
- sign kind:39002: {e}
- invalid BUZZ_RELAY_PRIVATE_KEY: {e}
- BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-mem
- invalid relay key: {e}
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/a75e4627e8ae756b.
Report an issue: GitHub.