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

  1. Check the string contains a '1' after a non-empty prefix and has at least 7 characters remaining after the last '1'
  2. Re-copy the full age key/recipient string; truncation is the usual cause
  3. 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

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


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