{"record":{"id":"cfeaca086d7d0a3f","repo":"grafana/k6","slug":"unable-to-convert-ecdh-public-key-to-ecdsa-public","errorCode":null,"errorMessage":"unable to convert ECDH public key to ECDSA public key, curve: %s","messagePattern":"unable to convert ECDH public key to ECDSA public key, curve: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/webcrypto/elliptic_curve.go","lineNumber":711,"sourceCode":"\t}, nil\n}\n\nfunc convertPublicECDHtoECDSA(k *ecdh.PublicKey) (*ecdsa.PublicKey, error) {\n\tvar crv elliptic.Curve\n\tswitch k.Curve() {\n\tcase ecdh.P256():\n\t\tcrv = elliptic.P256()\n\tcase ecdh.P384():\n\t\tcrv = elliptic.P384()\n\tcase ecdh.P521():\n\t\tcrv = elliptic.P521()\n\tdefault:\n\t\treturn nil, errors.New(\"curve not supported for converting to ECDSA key\")\n\t}\n\n\tx, y := elliptic.Unmarshal(crv, k.Bytes()) //nolint:staticcheck // we need to use the Unmarshal function\n\tif x == nil {\n\t\treturn nil, fmt.Errorf(\"unable to convert ECDH public key to ECDSA public key, curve: %s\", crv.Params().Name)\n\t}\n\n\treturn &ecdsa.PublicKey{\n\t\tCurve: crv,\n\t\tX:     x,\n\t\tY:     y,\n\t}, nil\n}\n\nfunc ensureKeysUseSameCurve(k1, k2 CryptoKey) error {\n\tecAlg1, ok1 := k1.Algorithm.(EcKeyAlgorithm)\n\tecAlg2, ok2 := k2.Algorithm.(EcKeyAlgorithm)\n\tif !ok1 || !ok2 {\n\t\treturn errors.New(\"keys are not valid elliptic curve keys\")\n\t}\n\n\tif ecAlg1.NamedCurve != ecAlg2.NamedCurve {\n\t\treturn errors.New(\"keys have different curves \" + string(ecAlg1.NamedCurve) + \" and \" + string(ecAlg2.NamedCurve))","sourceCodeStart":693,"sourceCodeEnd":729,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/webcrypto/elliptic_curve.go#L693-L729","documentation":"Internal k6 WebCrypto error raised when an ECDH public key's encoded point cannot be unmarshalled onto its named curve. convertPublicECDHtoECDSA (internal/js/modules/k6/webcrypto/elliptic_curve.go:696-719) first maps ECDH curves P-256/P-384/P-521 to their ECDSA equivalents (X25519 is rejected earlier by the 'curve not supported' branch), then calls the deprecated elliptic.Unmarshal; a nil x return means the bytes are not a valid (on-curve) point. It is reached from exportKey('jwk') on ECDH keys via exportECJWK/convertECDHtoECDSAKey.","triggerScenarios":"crypto.subtle.exportKey('jwk', ecdhKey) where the key material was produced from corrupted or hand-crafted bytes (e.g. an importKey('jwk', ...) whose x/y coordinates do not lie on the stated curve, or raw bytes of wrong length). Regular keys generated by generateKey never hit this because their points are always valid.","commonSituations":"Importing a JWK whose x/y were truncated, padded incorrectly, or copied from a different curve; feeding base64url-decoded secrets as raw ECDH public keys; interop with another runtime that serialized the point uncompressed vs compressed differently.","solutions":["Regenerate the key pair with crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']) instead of importing hand-made material.","If importing a JWK, verify crv matches the coordinate lengths (P-256: 32-byte x/y, P-384: 48, P-521: 66) and that the point is on-curve using an external tool.","For X25519 keys, do not export via the ECDSA path — X25519 is unsupported for this conversion by design; use raw export.","Report a k6 issue if the error occurs with a key generated inside k6 itself."],"exampleFix":"// before (importing a possibly-corrupt JWK point)\nconst key = await crypto.subtle.importKey('jwk', brokenJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);\nawait crypto.subtle.exportKey('jwk', key);\n\n// after (generate a known-good pair)\nconst pair = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);\nawait crypto.subtle.exportKey('jwk', pair.privateKey);","handlingStrategy":"try-catch","validationCode":"// No script-level pre-check exists; only validate imported JWK coordinate lengths against the curve.\nfunction jwkCoordsMatchCurve(jwk) {\n  const bytes = { 'P-256': 32, 'P-384': 48, 'P-521': 66 }[jwk.crv];\n  return !!bytes && typeof jwk.x === 'string' && typeof jwk.y === 'string'\n    && Math.ceil((jwk.x.length * 3) / 4) === bytes && Math.ceil((jwk.y.length * 3) / 4) === bytes;\n}","typeGuard":"const isP256FamilyJwk = (jwk) => jwk.kty === 'EC' && ['P-256', 'P-384', 'P-521'].includes(jwk.crv);","tryCatchPattern":"try { const jwk = await crypto.subtle.exportKey('jwk', ecdhKey); } catch (e) { if (/unable to convert ECDH public key/.test(e.message)) { /* fall back to raw export for the public key */ const raw = await crypto.subtle.exportKey('raw', ecdhKey.publicKey); } else throw e; }","preventionTips":["Prefer generateKey-produced ECDH keys over imported hand-made material.","Validate imported JWK crv/x/y lengths before importKey.","Avoid X25519 keys on code paths that export to JWK in this module."],"tags":["webcrypto","ecdh","elliptic-curve","jwk","key-export"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}