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

  1. Pass the intended non-empty HRP (age uses "AGE-SECRET-KEY-" for identities and "age" for recipients)
  2. Check where the HRP value originates and add a guard: if hrp == "" return a descriptive error before calling Encode
  3. 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

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


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/65232ffaa546821e. Report an issue: GitHub.