tailscale/tailscale · error
key[%d]: %w
Error message
key[%d]: %w
What it means
Duplicate-key detection in State.staticValidateCheckpoint must compute each key's KeyID (Key.ID()). Only Key25519 keys have a defined ID — Key.ID() returns 'unknown key kind: N' for anything else (KeyInvalid = 0, or a kind introduced by a newer version). This site wraps that error with %w, preserving errors.Unwrap, and prefixes the index i of the offending key from the outer loop. A checkpoint containing any non-25519 key can never validate.
Source
Thrown at tka/state.go:303
if numKeys := len(s.Keys); numKeys > maxKeys {
return fmt.Errorf("too many keys (%d, max %d)", numKeys, maxKeys)
}
for i, k := range s.Keys {
if err := k.StaticValidate(); err != nil {
return fmt.Errorf("key[%d]: %v", i, err)
}
}
// NOTE: The max number of keys is constrained (512), so
// O(n^2) is fine.
for i, k := range s.Keys {
for j, k2 := range s.Keys {
if i == j {
continue
}
id1, err := k.ID()
if err != nil {
return fmt.Errorf("key[%d]: %w", i, err)
}
id2, err := k2.ID()
if err != nil {
return fmt.Errorf("key[%d]: %w", j, err)
}
if bytes.Equal(id1, id2) {
return fmt.Errorf("key[%d]: duplicates key[%d]", i, j)
}
}
}
return nil
}
// CreateStateForTest creates a [State] that marks the given keys as trusted
// with an arbitrary disablement value.
//
// This is only for use in tests, and will panic if called outside a test.View on GitHub (pinned to 6e0912f979)
Solutions
- Unwrap the error — 'unknown key kind: N' tells you the numeric kind; check State.Keys[i].Kind for the index in the message.
- Set Kind: tka.Key25519 and Public to the 32-byte ed25519 public key on every key you trust.
- On version skew, upgrade tailscaled on the rejecting node so both ends understand the same key kinds.
- Pre-check each key with k.ID() before committing — it fails fast with the same underlying cause.
Example fix
// before
keys = append(keys, tka.Key{Public: pub, Votes: 1}) // Kind defaults to KeyInvalid -> "key[0]: unknown key kind: invalid"
// after
keys = append(keys, tka.Key{Kind: tka.Key25519, Public: pub, Votes: 1}) Defensive patterns
Strategy: validation
Validate before calling
for i, k := range keys {
if _, err := k.ID(); err != nil {
return fmt.Errorf("key[%d] has unsupported kind %v: %w", i, k.Kind, err)
}
} Prevention
- Always set Kind: tka.Key25519 explicitly when constructing keys — the zero value KeyInvalid fails ID().
- Pre-check k.ID() for every key before committing checkpoints; it is the same check StaticValidate will run.
- Reject unknown KeyKind values at your trust boundary instead of letting them reach TKA state.
When it happens
Trigger: A checkpoint AUM validated via AUM.StaticValidate (Authority.Inform/Apply, builder, verification) whose State.Keys[i].Kind is not Key25519 — typically Kind left at the zero value KeyInvalid because the struct was built without setting it.
Common situations: Constructing tka.Key without the Kind field (zero value is KeyInvalid); CBOR state decoded with the kind field absent; forward-compat rejection when a node sees a key kind from a newer Tailscale release.
Related errors
- key[%d]: %v
- key[%d]: duplicates key[%d]
- nested signatures must nest a signature
- ErrNoSuchKey
- missing checkpoint state
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/a7e15cecf838e64c.
Report an issue: GitHub.