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

  1. Regenerate the key pair with crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey']) instead of importing hand-made material.
  2. 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.
  3. For X25519 keys, do not export via the ECDSA path — X25519 is unsupported for this conversion by design; use raw export.
  4. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/cfeaca086d7d0a3f. Report an issue: GitHub.