grafana/k6 · error
unable to convert ECDH public key to ECDSA public key, curve
Error message
unable to convert ECDH public key to ECDSA public key, curve: %s
What it means
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.
Source
Thrown at internal/js/modules/k6/webcrypto/elliptic_curve.go:711
}, nil
}
func convertPublicECDHtoECDSA(k *ecdh.PublicKey) (*ecdsa.PublicKey, error) {
var crv elliptic.Curve
switch k.Curve() {
case ecdh.P256():
crv = elliptic.P256()
case ecdh.P384():
crv = elliptic.P384()
case ecdh.P521():
crv = elliptic.P521()
default:
return nil, errors.New("curve not supported for converting to ECDSA key")
}
x, y := elliptic.Unmarshal(crv, k.Bytes()) //nolint:staticcheck // we need to use the Unmarshal function
if x == nil {
return nil, fmt.Errorf("unable to convert ECDH public key to ECDSA public key, curve: %s", crv.Params().Name)
}
return &ecdsa.PublicKey{
Curve: crv,
X: x,
Y: y,
}, nil
}
func ensureKeysUseSameCurve(k1, k2 CryptoKey) error {
ecAlg1, ok1 := k1.Algorithm.(EcKeyAlgorithm)
ecAlg2, ok2 := k2.Algorithm.(EcKeyAlgorithm)
if !ok1 || !ok2 {
return errors.New("keys are not valid elliptic curve keys")
}
if ecAlg1.NamedCurve != ecAlg2.NamedCurve {
return errors.New("keys have different curves " + string(ecAlg1.NamedCurve) + " and " + string(ecAlg2.NamedCurve))View on GitHub (pinned to 93accf6570)
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.
Example fix
// before (importing a possibly-corrupt JWK point)
const key = await crypto.subtle.importKey('jwk', brokenJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
await crypto.subtle.exportKey('jwk', key);
// after (generate a known-good pair)
const pair = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']);
await crypto.subtle.exportKey('jwk', pair.privateKey); Defensive patterns
Strategy: try-catch
Validate before calling
// No script-level pre-check exists; only validate imported JWK coordinate lengths against the curve.
function jwkCoordsMatchCurve(jwk) {
const bytes = { 'P-256': 32, 'P-384': 48, 'P-521': 66 }[jwk.crv];
return !!bytes && typeof jwk.x === 'string' && typeof jwk.y === 'string'
&& Math.ceil((jwk.x.length * 3) / 4) === bytes && Math.ceil((jwk.y.length * 3) / 4) === bytes;
} Type guard
const isP256FamilyJwk = (jwk) => jwk.kty === 'EC' && ['P-256', 'P-384', 'P-521'].includes(jwk.crv);
Try / catch
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; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to convert ECDH key to ECDSA key: %w
- failed to convert ECDSA key to ECDH key: %w
- invalid ECDH curve
- curve not supported for converting to ECDSA key
- failed to parse elliptic curve: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/cfeaca086d7d0a3f.
Report an issue: GitHub.