bytedance/deer-flow · error · ValueError

expected {expected_hrp!r} bech32, got {hrp!r}

Error message

expected {expected_hrp!r} bech32, got {hrp!r}

What it means

After splitting the bech32 string at the last '1', the decoder checks the human-readable part against the expected one ('nsec' for private keys, 'npub' for public keys). A mismatch raises this error — it prevents, for example, pasting an npub where an nsec is required (or vice versa) from being silently accepted.

Source

Thrown at backend/app/channels/buzz_nostr.py:50


def _bech32_polymod(values: list[int]) -> int:
    gen = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3]
    chk = 1
    for v in values:
        b = chk >> 25
        chk = (chk & 0x1FFFFFF) << 5 ^ v
        for i in range(5):
            chk ^= gen[i] if ((b >> i) & 1) else 0
    return chk


def _bech32_decode(expected_hrp: str, value: str) -> bytes:
    if "1" not in value:
        raise ValueError(f"not bech32: {value!r}")
    hrp, data_part = value.rsplit("1", 1)
    if hrp != expected_hrp:
        raise ValueError(f"expected {expected_hrp!r} bech32, got {hrp!r}")
    try:
        data = [_BECH32_CHARSET.index(c) for c in data_part]
    except ValueError as exc:
        raise ValueError(f"invalid bech32 character in {value!r}") from exc
    hrp_expanded = [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp]
    if _bech32_polymod(hrp_expanded + data) != 1:
        raise ValueError(f"bad bech32 checksum in {value!r}")
    acc = bits = 0
    out = bytearray()
    for v in data[:-6]:
        acc = (acc << 5) | v
        bits += 5
        if bits >= 8:
            bits -= 8
            out.append((acc >> bits) & 0xFF)
    if len(out) != 32:
        raise ValueError(f"expected 32-byte payload in {value!r}")
    return bytes(out)

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Use nsec1... only for channels.buzz.private_key and npub1... for allowed_users entries.
  2. If the key came from another tool, re-export it in Nostr format (nsec/npub) or convert to 64-char hex.
  3. Double-check you did not swap the two fields in config.yaml.

Example fix

# before
channels:
  buzz:
    private_key: "npub1abcdef..."   # wrong hrp

# after
channels:
  buzz:
    private_key: "nsec1abcdef..."
Defensive patterns

Strategy: validation

Validate before calling

def key_matches_field(value: str, want: str) -> bool:
    return value.strip().lower().startswith(want + '1')

assert key_matches_field(cfg['private_key'], 'nsec')
assert all(key_matches_field(u, 'npub') for u in cfg.get('allowed_users', []))

Try / catch

try:
    parse_private_key(v)
except ValueError as e:
    if 'bech32' in str(e):
        hint = 'did you paste an npub where an nsec belongs (or vice versa)?'
        raise SystemExit(f'{e} — {hint}') from e
    raise

Prevention

When it happens

Trigger: parse_private_key receives an npub1... value; parse_pubkey (or allowed_users entries) receives an nsec1... value; or a key from a different bech32 scheme (e.g. lnbc..., bc1..., note1...) is pasted into a buzz config field.

Common situations: Swapping the public/private fields when configuring buzz (private_key vs allowed_users); pasting a Lightning or Bitcoin bech32 address by accident; mixing keys from another Nostr tool with different hrp conventions.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/a5f49704f0aa8927. Report an issue: GitHub.