grafana/k6 · error

keys are not valid elliptic curve keys

Error message

keys are not valid elliptic curve keys

What it means

ensureKeysUseSameCurve (internal/js/modules/k6/webcrypto/elliptic_curve.go:721) validates the two keys used in ECDH deriveBits/deriveKey. It first asserts both CryptoKeys carry an EcKeyAlgorithm; if either does not — e.g. one of them is an AES, HMAC, or otherwise non-elliptic key — it returns "keys are not valid elliptic curve keys" before any curve comparison happens.

Source

Thrown at internal/js/modules/k6/webcrypto/elliptic_curve.go:725

	}

	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))
	}

	return nil
}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Ensure both keys are ECDH keys: the base key generated with { name: 'ECDH', namedCurve: ... } and public being the peer's ECDH public key
  2. Log key.algorithm and key.type before deriving to confirm what each variable holds
  3. Regenerate both keys as a matched ECDH pair if provenance is unclear

Example fix

// before
const aesKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt']);
const bits = await crypto.subtle.deriveBits(
  { name: 'ECDH', public: aesKey }, ecdhPrivateKey, 256);

// after
const peerPub = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, []);
const bits = await crypto.subtle.deriveBits(
  { name: 'ECDH', public: peerPub.publicKey }, ecdhPrivateKey, 256);
Defensive patterns

Strategy: validation

Validate before calling

const isEcdhKey = (k) => k && k.algorithm && k.algorithm.name === 'ECDH' && k.algorithm.namedCurve;
if (!isEcdhKey(baseKey) || !isEcdhKey(params.public)) {
  throw new TypeError('both keys passed to deriveBits/deriveKey must be ECDH keys');
}

Type guard

const isEcdhCryptoKey = (k) =>
  !!k && typeof k === 'object' && k.algorithm?.name === 'ECDH' && !!k.algorithm.namedCurve;

Try / catch

try {
  bits = await crypto.subtle.deriveBits({ name: 'ECDH', public: peerPub }, priv, 256);
} catch (e) {
  if (String(e.message).includes('not valid elliptic curve keys')) throw new TypeError('a non-ECDH key was passed to the ECDH derivation');
  throw e;
}

Prevention

When it happens

Trigger: `crypto.subtle.deriveBits({ name: 'ECDH', public: someKey }, baseKey, 256)` where someKey or baseKey is not an elliptic-curve key — for example an AES-GCM key passed as `public`, or a non-ECDH private key as the base key.

Common situations: Variable mix-ups where a symmetric key is passed where the peer's ECDH public key belongs; key-management maps that return the wrong entry; refactors that swap argument order in deriveBits/deriveKey calls.

Related errors


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