grafana/k6 · error

failed to parse input as ECDH key: %w

Error message

failed to parse input as ECDH key: %w

What it means

importECDHJWK implements ECDH JWK import by first running the same ECDSA JWK parser, then converting the result to an ECDH key. This error is the wrapper around any failure of that first parsing stage, so its text ('failed to parse input as ECDH key') is generic; the specific cause (bad JSON, missing fields, bad crv, bad base64url) is in the wrapped %w chain. It is raised from crypto.subtle.importKey('jwk', ..., {name:'ECDH', namedCurve:...}).

Source

Thrown at internal/js/modules/k6/webcrypto/jwk.go:272

		return pk, PublicCryptoKeyType, nil
	}

	d, err := base64URLDecode(jwkKey.D)
	if err != nil {
		return nil, UnknownCryptoKeyType, fmt.Errorf("failed to decode D: %w", err)
	}

	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:

View on GitHub (pinned to 93accf6570)

Solutions

  1. Read the wrapped cause after 'failed to parse input as ECDH key:' to find the real failure
  2. Validate the JWK shape: kty 'EC', crv one of P-256/P-384/P-521, x/y present as unpadded base64url strings
  3. Pass the JWK as an object, not a JSON string
  4. Test the same JWK with {name:'ECDSA'} import to get the more specific error message, then fix that field

Example fix

// before
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDH', namedCurve: 'P-256' }, true, []); // generic failure
// after
// diagnose with the ECDSA importer to get the precise field error, fix it, then import as ECDH:
try { await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, true, []); } catch (e) { console.log(e.message); }
const key = await crypto.subtle.importKey('jwk', fixedJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey', 'deriveBits']);
Defensive patterns

Strategy: try-catch

Validate before calling

const B64URL = /^[A-Za-z0-9_-]+$/;
function isValidEcdhJwk(j) {
  return j && typeof j === 'object' && j.kty === 'EC' &&
    ['P-256','P-384','P-521'].includes(j.crv) &&
    typeof j.x === 'string' && B64URL.test(j.x) &&
    typeof j.y === 'string' && B64URL.test(j.y) &&
    (j.d === undefined || (typeof j.d === 'string' && B64URL.test(j.d)));
}

Type guard

function isEcdhImportableJwk(j) {
  return !!j && typeof j === 'object' && j.kty === 'EC' &&
    ['P-256','P-384','P-521'].includes(j.crv) &&
    /^[A-Za-z0-9_-]+$/.test(j.x || '') && /^[A-Za-z0-9_-]+$/.test(j.y || '');
}

Try / catch

try {
  key = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDH', namedCurve: jwk.crv }, true, ['deriveKey','deriveBits']);
} catch (e) {
  if (e.message.includes('failed to parse input as ECDH key')) {
    // wrapper hides the cause: re-run via the ECDSA importer for the specific field error
    try { await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, true, []); }
    catch (e2) { throw new Error('ECDH JWK root cause: ' + e2.message); }
  }
  throw e;
}

Prevention

When it happens

Trigger: Any of the ECDSA-parse failures occurring while importing with the ECDH algorithm: non-string fields, kty != 'EC', missing crv/x/y, crv outside P-256/P-384/P-521, or x/y/d not in unpadded base64url.

Common situations: Same real-world causes as the ECDSA parse errors (double-encoded JSON, padded base64url, non-canonical curve names), but seen through the ECDH code path where the generic message can mask which field is at fault.

Understand the failure class

Related errors


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