{"record":{"id":"7096bf43fce8235d","repo":"rustdesk/rustdesk-server","slug":"key-pair-is-invalid","errorCode":null,"errorMessage":"Key pair is INVALID","messagePattern":"Key pair is INVALID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/utils.rs","lineNumber":64,"sourceCode":"    let secret_key = secret_key.unwrap();\n\n    let pk1 = base64::decode(pk);\n    if pk1.is_err() {\n        bail!(\"Invalid public key\");\n    }\n    let pk1 = pk1.unwrap();\n\n    let public_key = sign::PublicKey::from_slice(pk1.as_slice());\n    if public_key.is_none() {\n        bail!(\"Invalid Public key\");\n    }\n    let public_key = public_key.unwrap();\n\n    let random_data_to_test = b\"This is meh.\";\n    let signed_data = sign::sign(random_data_to_test, &secret_key);\n    let verified_data = sign::verify(&signed_data, &public_key);\n    if verified_data.is_err() {\n        bail!(\"Key pair is INVALID\");\n    }\n    let verified_data = verified_data.unwrap();\n\n    if random_data_to_test != &verified_data[..] {\n        bail!(\"Key pair is INVALID\");\n    }\n\n    Ok(())\n}\n\nfn doctor_tcp(address: std::net::IpAddr, port: &str, desc: &str) {\n    let start = std::time::Instant::now();\n    let conn = format!(\"{address}:{port}\");\n    if let Ok(_stream) = TcpStream::connect(conn.as_str()) {\n        let elapsed = std::time::Instant::now().duration_since(start);\n        println!(\n            \"TCP Port {} ({}): OK in {} ms\",\n            port,","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/rustdesk/rustdesk-server/blob/a7736be5e40f85bfc141120dce587e836e5d4b80/src/utils.rs#L46-L82","documentation":"After both keys construct successfully, validate_keypair signs a fixed test string and verifies it; if `sign::verify` returns Err, the keys do not form a usable Ed25519 signing pair and the code bails with \"Key pair is INVALID\". Both inputs are individually well-formed but the pair fails the sign/verify round-trip.","triggerScenarios":"Signing with secret key A and verifying with an unrelated public key B (mismatched pair), or a corrupted key that decodes to the right length but is cryptographically unusable.","commonSituations":"Mixing id_ed25519 and id_ed25519.pub from different generations; keys regenerated on the server while old ones are checked; wrong file passed from a directory holding several keypairs.","solutions":["Use the secret key and public key from the SAME generated keypair; regenerate the pair together if unsure.","Compare fingerprints of both keys to confirm they belong together before running validation.","Replace the deployed key pair on clients with the freshly generated pair after regenerating."],"exampleFix":"// before: mixed pair\nhbbs --doctor -k '<sk from pair A>' -K '<pk from pair B>'\n// after: matched pair\nhbbs --doctor -k '<sk from pair A>' -K '<pk from pair A>'","handlingStrategy":"validation","validationCode":"// confirm the pair matches before calling the validator\nlet sig = sign::sign(b\"probe\", &secret_key);\nassert!(sign::verify(&sig, &public_key).is_ok(), \"secret/public keys do not match\");","typeGuard":null,"tryCatchPattern":"match validate_keypair(&pk, &sk) {\n    Err(e) if e.to_string().contains(\"INVALID\") => eprintln!(\"Keys are not a matched pair - regenerate\"),\n    Err(e) => eprintln!(\"keypair check failed: {e}\"),\n    Ok(()) => println!(\"keypair OK\"),\n}","preventionTips":["Always regenerate secret and public keys together; never mix across generations.","Distribute matched keypairs atomically to clients after rotation.","Keep one keypair directory per server to avoid picking wrong files."],"tags":["ed25519","keypair-mismatch","signature","cryptography"],"backgroundTag":"checksum-mismatch","analyzedSha":"a7736be5e40f85bfc141120dce587e836e5d4b80","analyzedAt":"2026-09-09T21:56:29.933Z","contentChangedAt":"2026-09-09T21:56:29.933Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}