grafana/k6 · error
failed to convert ECDSA key to ECDH key: %w
Error message
failed to convert ECDSA key to ECDH key: %w
What it means
When importing an ECDH JWK, k6 parses it as an ECDSA private key and then calls Go's (*ecdsa.PrivateKey).ECDH() to obtain the ECDH form. That conversion fails when the public point (x,y) is not on the named curve ('crypto/ecdsa: invalid public key') — the curve itself is already restricted to NIST P-256/P-384/P-521 by the earlier parsing. The error is wrapped with this message and returned from crypto.subtle.importKey('jwk', ...) with {name:'ECDH'}.
Source
Thrown at internal/js/modules/k6/webcrypto/jwk.go:279
return &ecdsa.PrivateKey{
PublicKey: *pk,
D: new(big.Int).SetBytes(d),
}, PrivateCryptoKeyType, nil
}
func importECDHJWK(_ EllipticCurveKind, jsonKeyData []byte) (any, CryptoKeyType, error) {
// first we do try to parse the key as ECDSA key
key, _, err := importECDSAJWK(EllipticCurveKindP256, jsonKeyData)
if err != nil {
return nil, UnknownCryptoKeyType, fmt.Errorf("failed to parse input as ECDH key: %w", err)
}
switch key := key.(type) {
case *ecdsa.PrivateKey:
ecdhKey, err := key.ECDH()
if err != nil {
return nil, UnknownCryptoKeyType, fmt.Errorf("failed to convert ECDSA key to ECDH key: %w", err)
}
return ecdhKey, PrivateCryptoKeyType, nil
case *ecdsa.PublicKey:
ecdhKey, err := key.ECDH()
if err != nil {
return nil, UnknownCryptoKeyType, fmt.Errorf("failed to convert ECDSA key to ECDH key: %w", err)
}
return ecdhKey, PublicCryptoKeyType, nil
default:
return nil, UnknownCryptoKeyType, errors.New("input isn't a valid ECDH key")
}
}
type rsaJWK struct {
Kty string `json:"kty"` // Key Type
N string `json:"n"` // ModulusView on GitHub (pinned to 93accf6570)
Solutions
- Verify the JWK comes from a real exported key pair (export it again from the source)
- Check that decoded x and y each have the byte length of the named curve (32/48/66 bytes) and belong to the same key
- Re-generate the ECDH key pair with crypto.subtle.generateKey and export a fresh JWK
- Use the ECDSA importer on the same JWK to cross-check: ECDSA import does not validate the point, so also verify by attempting a derive operation
Example fix
// before
const badJwk = { kty: 'EC', crv: 'P-256', x: 'AAAA...', y: 'AAAA...', d: 'AAAA...' }; // not on curve
const key = await crypto.subtle.importKey('jwk', badJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits']);
// after
const pair = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey', 'deriveBits']);
const goodJwk = await crypto.subtle.exportKey('jwk', pair.privateKey);
const key = await crypto.subtle.importKey('jwk', goodJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey', 'deriveBits']); Defensive patterns
Strategy: try-catch
Validate before calling
const CURVE_BYTES = { 'P-256': 32, 'P-384': 48, 'P-521': 66 };
function b64uLen(s) { const n = s.length; return n - (n % 4 === 2 ? 1 : n % 4 === 3 ? 2 : 0); } // approx byte count
function coordLengthsPlausible(jwk) {
const want = CURVE_BYTES[jwk.crv];
return want && b64uLen(jwk.x) === want && b64uLen(jwk.y) === want;
}
if (!coordLengthsPlausible(jwk)) throw new Error('x/y lengths do not match crv; key likely corrupted'); Type guard
function hasCurveSizedCoords(jwk) {
const want = { 'P-256': 43, 'P-384': 64, 'P-521': 88 }[jwk.crv];
return !!want && jwk.x.length === want && jwk.y.length === want;
} Try / catch
try {
key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDH', namedCurve: jwk.crv }, true, ['deriveBits']);
} catch (e) {
if (e.message.includes('failed to convert ECDSA key to ECDH key')) {
throw new Error('x/y are not a valid point on ' + jwk.crv + '; re-export the key from its origin');
}
throw e;
} Prevention
- Never hand-craft x/y values; always export real keys
- Check that x and y come from the same key and the same curve as crv
- Coordinate length checks catch most corruption before import
When it happens
Trigger: A JWK whose x/y decode successfully as base64url but do not form a valid point on the curve named by crv: coordinates fabricated, corrupted, swapped, or taken from a different curve than crv claims; a d value paired with mismatched x/y.
Common situations: Hand-crafted test keys with arbitrary coordinate strings; JWKs corrupted in transit or by templating; copying x from one key and y from another; upstream key services emitting inconsistent keys.
Related errors
- failed to convert ECDH key to ECDSA key: %w
- failed to parse elliptic curve: %w
- failed to parse input as ECDH key: %w
- unable to convert ECDH public key to ECDSA public key, curve
- failed to parse input as EC JWK key: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/13767c17e2cf05e5.
Report an issue: GitHub.