{"record":{"id":"b80b2d70829c2ced","repo":"slackhq/nebula","slug":"unable-to-unmarshal-pubkey-w","errorCode":null,"errorMessage":"unable to unmarshal pubkey: %w","messagePattern":"unable to unmarshal pubkey: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"noiseutil/nist.go","lineNumber":47,"sourceCode":"\t}\n}\n\nfunc (c nistCurve) GenerateKeypair(rng io.Reader) (noise.DHKey, error) {\n\tif rng == nil {\n\t\trng = rand.Reader\n\t}\n\tprivkey, err := c.curve.GenerateKey(rng)\n\tif err != nil {\n\t\treturn noise.DHKey{}, err\n\t}\n\tpubkey := privkey.PublicKey()\n\treturn noise.DHKey{Private: privkey.Bytes(), Public: pubkey.Bytes()}, nil\n}\n\nfunc (c nistCurve) DH(privkey, pubkey []byte) ([]byte, error) {\n\tecdhPubKey, err := c.curve.NewPublicKey(pubkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal pubkey: %w\", err)\n\t}\n\tecdhPrivKey, err := c.curve.NewPrivateKey(privkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal private key: %w\", err)\n\t}\n\n\treturn ecdhPrivKey.ECDH(ecdhPubKey)\n}\n\nfunc (c nistCurve) DHLen() int {\n\t// NOTE: Noise Protocol specifies \"DHLen\" to represent two things:\n\t// - The size of the public key\n\t// - The return size of the DH() function\n\t// But for standard NIST ECDH, the sizes of these are different.\n\t// Luckily, the flynn/noise library actually only uses this DHLen()\n\t// value to represent the public key size, so that is what we are\n\t// returning here. The length of the DH() return bytes are unaffected by\n\t// this value here.","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/noiseutil/nist.go#L29-L65","documentation":"This error is returned by nistCurve.DH in noiseutil when the peer's public key bytes cannot be parsed as a valid public key on the configured NIST curve (P-256/P-384/P-521) via ecdh.Curve.NewPublicKey. It means the Noise handshake received a remote static/ephemeral public key that is not a valid, canonical point on the expected curve. The underlying ecdh error is wrapped with %w so the root cause is preserved.","triggerScenarios":"Calling nistCurve.DH(privkey, pubkey) with pubkey bytes that are empty, the wrong length for the curve, or not a valid EC point — e.g. a remote peer configured with a key pair generated on a different curve or with a non-NIST key format.","commonSituations":"Mismatched curve configuration between peers (one on P-256, other on P-384); corrupted or truncated keys stored in config/certs; keys generated by a different library with a different encoding; a peer sending garbage during a handshake attempt.","solutions":["Verify both peers use the same NIST curve (e.g. 'P256' in the Noise config) so key lengths match","Regenerate or re-export the offending key pair with the same library used for parsing (crypto/ecdh)","Check that the public key bytes are the full uncompressed point size for the curve (65/97/133 bytes) and not a truncated or DER-encoded copy","Inspect the wrapped %w cause with errors.Unwrap/errors.As to distinguish length vs point-validity failures"],"exampleFix":"// before: mixing curves across peers\nkey, _ := nistCurve{curve: ecdh.P256()}.DH(priv, peerP384Pub) // fails to unmarshal\n// after: ensure both sides use the same curve\nif len(pub) != expectedPubLen(curveName) {\n    return nil, fmt.Errorf(\"peer pubkey wrong size for %s\", curveName)\n}\nkey, err := nistCurve{curve: ecdh.P256()}.DH(priv, pub)","handlingStrategy":"validation","validationCode":"func validNistPub(curveName string, pub []byte) bool {\n    var n int\n    switch curveName {\n    case \"P256\": n = 65\n    case \"P384\": n = 97\n    case \"P521\": n = 133\n    default: return false\n    }\n    return len(pub) == n && pub[0] == 4\n}","typeGuard":"func isEcdhUnmarshalError(err error) bool {\n    var e *ecdh.PublicKey // NewPublicKey failure is generic error; check wrapped text\n    _ = e\n    return err != nil && strings.Contains(err.Error(), \"unable to unmarshal pubkey\")\n}","tryCatchPattern":"shared, err := curve.DH(priv, pub)\nif err != nil {\n    var cause error\n    errors.As(err, &cause)\n    return fmt.Errorf(\"handshake DH failed (check peer curve/key bytes): %w\", err)\n}","preventionTips":["Pin the same Noise curve on every peer in your deploy config","Validate peer key byte length against the curve before handshakes","Never hand-edit base64 keys; always generate via the library's keygen","Log wrapped causes with errors.Unwrap when debugging key issues"],"tags":["go","noise-protocol","ecdh","key-parsing"],"backgroundTag":"invalid-public-key","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}