gotify/server · error · errCannotParseToken
%w: public key must be %d bytes
Error message
%w: public key must be %d bytes
What it means
For the 4-field (public-key) form, the second field must decode to exactly ed25519.PublicKeySize (32 bytes). Otherwise ParseEnhancedToken returns errCannotParseToken wrapped with "public key must be 32 bytes", because token verification needs a valid ed25519 public key.
Source
Thrown at auth/token.go:136
}
ident := fields[0]
pkOrPubkeyB64 := fields[1]
pkOrPubkeyBytesLen := base64.RawURLEncoding.DecodedLen(len(pkOrPubkeyB64))
pkOrPubkey, err := base64.RawURLEncoding.DecodeString(pkOrPubkeyB64)
if err != nil {
return nil, fmt.Errorf("%w: base64 decode failed: %w", errCannotParseToken, err)
}
if len(fields) == 2 {
if pkOrPubkeyBytesLen != ed25519.SeedSize {
return nil, fmt.Errorf("%w: private key must be %d bytes", errCannotParseToken, ed25519.SeedSize)
}
return &EnhancedToken{
ident: ident,
pubOrPrivKey: pkOrPubkey,
}, nil
}
if pkOrPubkeyBytesLen != ed25519.PublicKeySize {
return nil, fmt.Errorf("%w: public key must be %d bytes", errCannotParseToken, ed25519.PublicKeySize)
}
timestampStr := fields[2]
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("%w: timestamp must be an integer: %w", errCannotParseToken, err)
}
signatureB64 := fields[3]
signatureBytesLen := base64.RawURLEncoding.DecodedLen(len(signatureB64))
if signatureBytesLen != ed25519.SignatureSize {
return nil, fmt.Errorf("%w: signature must be %d bytes", errCannotParseToken, ed25519.SignatureSize)
}
signature, err := base64.RawURLEncoding.DecodeString(signatureB64)
if err != nil {
return nil, fmt.Errorf("%w: base64 decode failed: %w", errCannotParseToken, err)
}
sha512 := sha512.New()
sha512.Write([]byte("iat=")) // query-like encoding to give us some semantic headroom should we need more fields in the future
fmt.Fprintf(sha512, "%d", timestamp)View on GitHub (pinned to 14bfc25627)
Solutions
- Encode the ed25519 public key with base64.RawURLEncoding (43 chars for 32 bytes).
- Confirm the value in the pubkey slot is the public key, not the seed or signature.
- Regenerate the token via PublicForm()/String() to guarantee correct structure.
- If you only have the private seed, use the 2-field token form instead.
Example fix
// before: signature in pubkey slot tok := "gtfy" + ident + "." + b64(signature) + "." + challenge + "." + sig // after: public key in pubkey slot tok := "gtfy" + ident + "." + b64(pubKey) + "." + challenge + "." + sig
Defensive patterns
Strategy: validation
Validate before calling
fields := strings.Split(strings.TrimPrefix(tok, "gtfy"), ".")
if len(fields) == 4 && base64.RawURLEncoding.DecodedLen(len(fields[1])) != ed25519.PublicKeySize {
return errors.New("4-field token pubkey must decode to 32 bytes")
} Type guard
func isEd25519PubKeySegment(seg string) bool {
return len(seg) == 43 && base64.RawURLEncoding.DecodedLen(len(seg)) == ed25519.PublicKeySize
} Try / catch
if _, err := auth.ParseEnhancedToken(raw); err != nil {
if strings.Contains(err.Error(), "public key must be") {
return fmt.Errorf("pubkey slot holds wrong material: %w", err)
}
} Prevention
- Put the ed25519 public key — never a seed or signature — in the pubkey slot.
- Serialize 4-field tokens with PublicForm() to guarantee slot ordering.
- Check the base64 segment is 43 characters before parsing.
When it happens
Trigger: Parsing a "gtfyident.<pubkey>.<challenge>.<signature>" token where the pubkey field decodes to a length other than 32 bytes — e.g. a seed/signature pasted in the pubkey slot, or a corrupted/truncated key segment.
Common situations: Manual token assembly with the wrong key slot; copy/paste dropping characters from the 43-char base64 segment; encoding the key with hex or padded base64 before insertion.
Related errors
- %w: private key must be %d bytes
- invalid token
- %w: token must start with %s
- %w: token must have 2 or 4 fields separated by dots
- %w: base64 decode failed: %w
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/2b07fc43d4a970d3.
Report an issue: GitHub.