FiloSottile/age · error
invalid character human-readable part: s[%d]=%d
Error message
invalid character human-readable part: s[%d]=%d
What it means
After splitting on the last '1', Decode validates every rune of the human-readable part is in the printable ASCII range 33-126. A rune outside that range (spaces are ok at 32? no - 32 is excluded, so space fails; control chars, and non-ASCII unicode fail) makes the HRP invalid. The library enforces the bech32 spec's printable-ASCII HRP requirement.
Source
Thrown at internal/bech32/bech32.go:154
if lower {
return ret.String(), nil
}
return strings.ToUpper(ret.String()), nil
}
// Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase.
func Decode(s string) (hrp string, data []byte, err error) {
if strings.ToLower(s) != s && strings.ToUpper(s) != s {
return "", nil, fmt.Errorf("mixed case")
}
pos := strings.LastIndex(s, "1")
if pos < 1 || pos+7 > len(s) {
return "", nil, fmt.Errorf("separator '1' at invalid position: pos=%d, len=%d", pos, len(s))
}
hrp = s[:pos]
for p, c := range hrp {
if c < 33 || c > 126 {
return "", nil, fmt.Errorf("invalid character human-readable part: s[%d]=%d", p, c)
}
}
for p, c := range s[pos+1:] {
// Fold ASCII explicitly. Unicode case folding can turn a non-ASCII
// rune into a shorter valid charset member.
if c >= 'A' && c <= 'Z' {
c += 'a' - 'A'
}
d := strings.IndexRune(charset, c)
if d == -1 {
return "", nil, fmt.Errorf("invalid character data part: s[%d]=%v", p, c)
}
data = append(data, byte(d))
}
if len(data) < 6 {
return "", nil, fmt.Errorf("data part too short")
}
if !verifyChecksum(hrp, data) {View on GitHub (pinned to b74dce4cdb)
Solutions
- Trim and clean the input of whitespace and control characters before decoding
- Re-copy the key as plain ASCII from the original source
- Strip BOM/zero-width characters from config-sourced values
Example fix
// before
callersBech32.Decode(strings.TrimSpace(cfg.Recipient)) // may still contain unicode
// after
hrp1qq := cfg.Recipient
hrp1qq = strings.TrimSpace(hrp1qq)
hrp1qq = strings.Map(func(r rune) rune { if r >= 33 && r <= 126 { return r }; return -1 }, hrp1qq) Defensive patterns
Strategy: validation
Validate before calling
func printableASCII(s string) bool {
for _, r := range s {
if r < 33 || r > 126 { return false }
}
return true
}
if !printableASCII(input) { return errors.New("input contains non-printable or non-ASCII characters") } Prevention
- Trim and sanitize values read from config/env before use
- Strip BOM and zero-width characters from copied text
- Treat key material as opaque ASCII-only strings
When it happens
Trigger: Decode with a string whose text before the last '1' contains a space, control character, or any byte/rune outside 33-126, e.g. "my key1qqqqqq" or "café1qqqq".
Common situations: Keys pasted from documents with smart quotes or trailing whitespace, config files with BOM or invisible control characters, terminal copy including ANSI codes.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- illegal zero padding
- non-zero padding
- invalid HRP character: hrp[%d]=%d
- mixed case
- separator '1' at invalid position: pos=%d, len=%d
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/87bd55db2c80e694.
Report an issue: GitHub.