FiloSottile/age · error
invalid HRP: %q
Error message
invalid HRP: %q
What it means
bech32.Encode validates the human-readable part (HRP) before encoding. A bech32 HRP must be at least one character long; an empty string cannot form the required "hrp1" separator structure, so Encode rejects it with this error. It is used by age when serializing SSH identities and recipients.
Source
Thrown at internal/bech32/bech32.go:115
ret = append(ret, byte(acc<<(tobits-bits))&maxv)
}
} else if bits >= frombits {
return nil, fmt.Errorf("illegal zero padding")
} else if byte(acc<<(tobits-bits))&maxv != 0 {
return nil, fmt.Errorf("non-zero padding")
}
return ret, nil
}
// 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) {View on GitHub (pinned to b74dce4cdb)
Solutions
- Pass the intended non-empty HRP (age uses "AGE-SECRET-KEY-" for identities and "age" for recipients)
- Check where the HRP value originates and add a guard: if hrp == "" return a descriptive error before calling Encode
- If the HRP comes from configuration, provide a sane default and validate at load time
Example fix
// before
hrp := cfg.KeyPrefix // "" when config key missing
s, err := bech32.Encode(hrp, data) // "invalid HRP: \"\""
// after
hrp := cfg.KeyPrefix
if hrp == "" {
hrp = "age"
}
s, err := bech32.Encode(hrp, data) Defensive patterns
Strategy: validation
Validate before calling
func validHRP(hrp string) bool {
return len(hrp) >= 1
} Prevention
- Use age's constants "age" and "AGE-SECRET-KEY-" rather than dynamic HRPs
- Fail fast on missing config values that feed the HRP; never let empty strings reach Encode
- Add a unit test covering the empty-HRP path of any custom serialization code
- Default empty prefixes to the age standard values at configuration load
When it happens
Trigger: Calling bech32.Encode (directly, or via agessh encoders like EncodeIdentity/EncodeRecipient) with hrp == "" — e.g. an uninitialized constant, a variable that failed lookup, or code that passes an empty prefix when serializing key bytes.
Common situations: Refactored code where an HRP constant was deleted or set to empty; config-driven HRP selection where the config key is missing and yields ""; tests constructing bech32 strings with an omitted prefix.
Related errors
- invalid HRP character: hrp[%d]=%d
- mixed case HRP: %q
- 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/65232ffaa546821e.
Report an issue: GitHub.