FiloSottile/age · error
separator '1' at invalid position: pos=%d, len=%d
Error message
separator '1' at invalid position: pos=%d, len=%d
What it means
bech32.Decode rejects an input whose last '1' separator is misplaced. In bech32 the separator must appear at position >= 1 (so the human-readable part is non-empty) and leave at least 6 checksum characters plus one data char after it (pos+7 <= len). This guard prevents decoding strings without a separator, with an empty HRP, or with an impossibly short data part.
Source
Thrown at internal/bech32/bech32.go:149
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
}
// Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase.
func Decode(s string) (hrp string, data []byte, err error) {
if strings.ToLower(s) != s && strings.ToUpper(s) != s {
return "", nil, fmt.Errorf("mixed case")
}
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))View on GitHub (pinned to b74dce4cdb)
Solutions
- Check the string contains a '1' after a non-empty prefix and has at least 7 characters remaining after the last '1'
- Re-copy the full age key/recipient string; truncation is the usual cause
- Ensure you are not passing a hex or raw X25519 key where a bech32 AGE- prefixed string is required
Example fix
// before
callersBech32.Decode("QQQQ")
// after
callersBech32.Decode("AGE1QQQQ") Defensive patterns
Strategy: validation
Validate before calling
func validBech32Shape(s string) bool {
pos := strings.LastIndex(s, "1")
return pos >= 1 && pos+7 <= len(s)
}
if !validBech32Shape(input) { return errors.New("not a valid bech32 string") } Type guard
func looksLikeBech32(s string) bool {
i := strings.LastIndex(s, "1")
return i >= 1 && i+7 <= len(s)
} Prevention
- Store age keys as whole strings; never slice or display them truncated
- Use clipboard/programmatic copy instead of manual transcription
- Validate shape before passing keys into library calls
When it happens
Trigger: Calling Decode (directly or via ParseIdentity/ParseRecipient in age) with a string lacking '1', starting with '1', or with fewer than 7 characters after the last '1', e.g. "abc12" or "1qqqq".
Common situations: Pasting a truncated age recipient/xidentity key, copying only part of an address, or passing a raw hex key instead of the bech32-encoded form.
Related errors
- mixed case
- illegal zero padding
- non-zero padding
- invalid character human-readable part: s[%d]=%d
- invalid character data part: s[%d]=%v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/6fb3837f9d2ecadb.
Report an issue: GitHub.