FiloSottile/age · error

invalid character data part: s[%d]=%v

Error message

invalid character data part: s[%d]=%v

What it means

The characters after the separator must all belong to the bech32 charset (qpzry9x8gf2tvdw0s3jn54khce6mua7l, case-folded). Decode indexes the charset and reports the offending rune when IndexRune returns -1, e.g. digits like 1, b, i, o are excluded.

Source

Thrown at internal/bech32/bech32.go:165

	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) {
		return "", nil, fmt.Errorf("invalid checksum")
	}
	data, err = convertBits(data[:len(data)-6], 5, 8, false)
	if err != nil {
		return "", nil, err
	}
	return hrp, data, nil
}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Fix the typo: replace excluded characters (b,i,o,1) with the intended bech32 character
  2. Re-copy the key programmatically instead of transcribing by hand
  3. Verify the input is actually bech32 and not base64/hex
Defensive patterns

Strategy: validation

Validate before calling

const charset1 = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
func validDataPart(s string) bool {
    i := strings.LastIndex(s, "1")
    if i < 0 { return false }
    for _, c := range strings.ToLower(s[i+1:]) {
        if !strings.ContainsRune(charset1, c) { return false }
    }
    return true
}

Prevention

When it happens

Trigger: Decode with a data part containing '1', 'b', 'i', or 'o' or any non-charset character, e.g. "age1tbio..." typo or random base64 text.

Common situations: Hand-typing keys and confusing excluded characters (b/i, 0/o/1), pasting base64 or base58 strings that look bech32-like.

Understand the failure class

Related errors


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