{"record":{"id":"d5cd8c6228bc1807","repo":"OpenNHP/opennhp","slug":"relay-server-d-and-d-share-the-same-publickey","errorCode":null,"errorMessage":"relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)","messagePattern":"relay: server #(.+?) and #(.+?) share the same publicKeyBase64 \\(fingerprint (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/relay/config.go","lineNumber":236,"sourceCode":"\t// front-end, or port-multiplexed identities) is a valid topology and\n\t// must not be a hard config-load failure. Only same-pubkey + same-addr\n\t// is the unambiguous copy-paste error.\n\ttype addrOrigin struct {\n\t\tserver   int\n\t\tinstance int\n\t}\n\tseenAddr := make(map[string]addrOrigin)\n\tfor i := range cfg.Servers {\n\t\tc := &cfg.Servers[i]\n\t\tif c.PubKeyBase64 == \"\" {\n\t\t\treturn fmt.Errorf(\"relay: server #%d missing publicKeyBase64\", i)\n\t\t}\n\t\tfp, err := utils.PubKeyFingerprintFromBase64(c.PubKeyBase64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"relay: server #%d publicKeyBase64 invalid: %w\", i, err)\n\t\t}\n\t\tif dup, ok := seenFP[fp]; ok {\n\t\t\treturn fmt.Errorf(\"relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)\", dup, i, fp)\n\t\t}\n\t\tseenFP[fp] = i\n\n\t\tif len(c.Instances) == 0 {\n\t\t\treturn fmt.Errorf(\"relay: server #%d (fingerprint %s) has no [[Servers.Instances]]\", i, fp)\n\t\t}\n\t\tfor j := range c.Instances {\n\t\t\tinst := &c.Instances[j]\n\t\t\tif inst.Host == \"\" {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d missing host\", i, j)\n\t\t\t}\n\t\t\tif inst.Port <= 0 {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d missing or invalid port\", i, j)\n\t\t\t}\n\t\t\taddr := fmt.Sprintf(\"%s:%d\", inst.Host, inst.Port)\n\t\t\t// Scope to this server's pubkey: same identity reusing an\n\t\t\t// address is the copy-paste error we reject; a sibling\n\t\t\t// identity on the same address is allowed (see seenAddr docs).","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/config.go#L218-L254","documentation":"normalize fingerprints each server's public key and rejects configs where two [[Servers]] entries share the same fingerprint — a server identity can only be declared once. The error reports both duplicate indices and the shared fingerprint to locate the copy-paste.","triggerScenarios":"Two [[Servers]] blocks in config.toml carry identical publicKeyBase64 values, so the second one's fingerprint is already in seenFP when normalize iterates it (via LoadConfig at startup or direct test invocation).","commonSituations":"Copying a [[Servers]] block to add another instance of the same server instead of adding a second [[Servers.Instances]] under the existing block; merging configs that both contain the same server; accidentally reusing one server's key for multiple entries.","solutions":["Merge duplicate blocks: keep one [[Servers]] and put all its endpoints in multiple [[Servers.Instances]] entries under it","If the servers are genuinely distinct, generate/obtain the correct distinct public key for the second entry","Run a quick check like `grep publicKeyBase64 config.toml | sort | uniq -d` to find the duplicated key before deploying"],"exampleFix":"// before\n[[Servers]]\npubKeyBase64 = \"KEY_A\"\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n[[Servers]]\npubKeyBase64 = \"KEY_A\"\n[[Servers.Instances]]\nhost = \"10.0.0.6\"\nport = 10161\n// after\n[[Servers]]\npubKeyBase64 = \"KEY_A\"\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n[[Servers.Instances]]\nhost = \"10.0.0.6\"\nport = 10161","handlingStrategy":"validation","validationCode":"seen := map[string]bool{}\nfor _, s := range cfg.Servers {\n\tif seen[s.PubKeyBase64] {\n\t\treturn errors.New(\"duplicate server publicKeyBase64 in config\")\n\t}\n\tseen[s.PubKeyBase64] = true\n}","typeGuard":null,"tryCatchPattern":"if err := cfg.Normalize(); err != nil {\n\tif strings.Contains(err.Error(), \"share the same publicKeyBase64\") {\n\t\treturn fmt.Errorf(\"merge duplicate [[Servers]] blocks into multiple instances: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["To add endpoints for the same server, add [[Servers.Instances]], not another [[Servers]]","Run `grep publicKeyBase64 config.toml | sort | uniq -d` in CI","Deduplicate keys when merging config fragments"],"tags":["relay","config","duplicate","public-key"],"backgroundTag":"conflicting-config-options","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}