{"record":{"id":"a7e15cecf838e64c","repo":"tailscale/tailscale","slug":"key-d-w","errorCode":null,"errorMessage":"key[%d]: %w","messagePattern":"key\\[(.+?)\\]: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tka/state.go","lineNumber":303,"sourceCode":"\tif numKeys := len(s.Keys); numKeys > maxKeys {\n\t\treturn fmt.Errorf(\"too many keys (%d, max %d)\", numKeys, maxKeys)\n\t}\n\tfor i, k := range s.Keys {\n\t\tif err := k.StaticValidate(); err != nil {\n\t\t\treturn fmt.Errorf(\"key[%d]: %v\", i, err)\n\t\t}\n\t}\n\t// NOTE: The max number of keys is constrained (512), so\n\t// O(n^2) is fine.\n\tfor i, k := range s.Keys {\n\t\tfor j, k2 := range s.Keys {\n\t\t\tif i == j {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tid1, err := k.ID()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"key[%d]: %w\", i, err)\n\t\t\t}\n\t\t\tid2, err := k2.ID()\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"key[%d]: %w\", j, err)\n\t\t\t}\n\n\t\t\tif bytes.Equal(id1, id2) {\n\t\t\t\treturn fmt.Errorf(\"key[%d]: duplicates key[%d]\", i, j)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateStateForTest creates a [State] that marks the given keys as trusted\n// with an arbitrary disablement value.\n//\n// This is only for use in tests, and will panic if called outside a test.","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/tka/state.go#L285-L321","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nkeys = append(keys, tka.Key{Public: pub, Votes: 1}) // Kind defaults to KeyInvalid -> \"key[0]: unknown key kind: invalid\"\n\n// after\nkeys = append(keys, tka.Key{Kind: tka.Key25519, Public: pub, Votes: 1})","handlingStrategy":"validation","validationCode":"for i, k := range keys {\n\tif _, err := k.ID(); err != nil {\n\t\treturn fmt.Errorf(\"key[%d] has unsupported kind %v: %w\", i, k.Kind, err)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["go","tailscale","tka","tailnet-lock","key-management","validation"],"backgroundTag":"unsupported-key-type","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}