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
- Use nsec1... only for channels.buzz.private_key and npub1... for allowed_users entries.
- If the key came from another tool, re-export it in Nostr format (nsec/npub) or convert to 64-char hex.
- 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
- Keep private keys (nsec) and pubkeys (npub) in clearly named config fields.
- Add a config-schema lint that checks hrp prefixes per field.
- Never log the rejected key value at full verbosity — it may be a real secret with one typo.
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
- not bech32: {value!r}
- invalid bech32 character in {value!r}
- bad bech32 checksum in {value!r}
- expected 32-byte payload in {value!r}
- expected 64-hex or {bech_hrp}1... value
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/a5f49704f0aa8927.
Report an issue: GitHub.