FiloSottile/age · error
mixed case HRP: %q
Error message
mixed case HRP: %q
What it means
Bech32 strings must be entirely lowercase or entirely uppercase; mixed-case strings are ambiguous because checksum computation depends on case. bech32.Encode rejects an HRP that is neither all-upper nor all-lower before producing output.
Source
Thrown at internal/bech32/bech32.go:123
}
// Encode encodes the HRP and a bytes slice to Bech32. If the HRP is uppercase,
// the output will be uppercase.
func Encode(hrp string, data []byte) (string, error) {
values, err := convertBits(data, 8, 5, true)
if err != nil {
return "", err
}
if len(hrp) < 1 {
return "", fmt.Errorf("invalid HRP: %q", hrp)
}
for p, c := range hrp {
if c < 33 || c > 126 {
return "", fmt.Errorf("invalid HRP character: hrp[%d]=%d", p, c)
}
}
if strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp {
return "", fmt.Errorf("mixed case HRP: %q", hrp)
}
lower := strings.ToLower(hrp) == hrp
hrp = strings.ToLower(hrp)
var ret strings.Builder
ret.WriteString(hrp)
ret.WriteString("1")
for _, p := range values {
ret.WriteByte(charset[p])
}
for _, p := range createChecksum(hrp, values) {
ret.WriteByte(charset[p])
}
if lower {
return ret.String(), nil
}
return strings.ToUpper(ret.String()), nil
}
View on GitHub (pinned to b74dce4cdb)
Solutions
- Normalize the HRP to all-lowercase (or all-uppercase) before encoding: hrp = strings.ToLower(hrp)
- Use age's fixed constants ("age" / "AGE-SECRET-KEY-") which are already uniform-case
- If the HRP is user input, enforce case consistency with a validation check before calling Encode
Example fix
// before
hrp := "Age-Secret-Key"
s, err := bech32.Encode(hrp, data) // "mixed case HRP"
// after
hrp := strings.ToLower("Age-Secret-Key") // "age-secret-key"
s, err := bech32.Encode(hrp, data) Defensive patterns
Strategy: validation
Validate before calling
func uniformCase(s string) bool {
return strings.ToLower(s) == s || strings.ToUpper(s) == s
} Prevention
- Normalize HRPs with strings.ToLower before any bech32 operation
- Never build HRPs by concatenating differently-cased fragments
- Ban title-cased display strings from being used as encoding input
- Lint/review any code path where string manipulation touches key prefixes
When it happens
Trigger: Calling bech32.Encode with an HRP like "Age" or "AGE-secret" — e.g. user-supplied prefixes, constants partially uppercased by string manipulation, or HRPs assembled from mixed-case segments.
Common situations: Title-cased display strings mistakenly passed as HRPs; constants edited so only part was uppercased; concatenation of differently-cased prefixes ("AGE" + "-secret-key-").
Related errors
- invalid HRP: %q
- invalid HRP character: hrp[%d]=%d
- invalid data range: data[%d]=%d (frombits=%d)
- illegal zero padding
- non-zero padding
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/f701cab5bbc8cd6b.
Report an issue: GitHub.