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

  1. Normalize the HRP to all-lowercase (or all-uppercase) before encoding: hrp = strings.ToLower(hrp)
  2. Use age's fixed constants ("age" / "AGE-SECRET-KEY-") which are already uniform-case
  3. 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

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


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