grafana/k6 · error

keys have different curves {curve1} and {curve2}

Error message

keys have different curves {curve1} and {curve2}

What it means

The second check in ensureKeysUseSameCurve (internal/js/modules/k6/webcrypto/elliptic_curve.go:728) compares the NamedCurve of the two ECDH keys passed to deriveBits/deriveKey. ECDH mathematically requires both keys to be on the same curve; mixing curves (message includes both, e.g. "keys have different curves P-256 and P-384") returns this error before any bits are derived.

Source

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

		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. Make the namedCurve identical on both sides — regenerate or re-import so both use the same curve
  2. Determine the peer's curve first (from its JWK crv or SPKI parameters) and generate your ECDH key with the same namedCurve
  3. Centralize the curve choice in one constant used by both generateKey and importKey paths

Example fix

// before
const mine = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveBits']);
const peerPub = await crypto.subtle.importKey('jwk', serverJwk, { name: 'ECDH', namedCurve: 'P-384' }, true, []);
await crypto.subtle.deriveBits({ name: 'ECDH', public: peerPub }, mine.privateKey, 256);

// after (match the peer's curve)
const peerPub = await crypto.subtle.importKey('jwk', serverJwk, { name: 'ECDH', namedCurve: 'P-384' }, true, []);
const mine = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-384' }, true, ['deriveBits']);
await crypto.subtle.deriveBits({ name: 'ECDH', public: peerPub }, mine.privateKey, 256);
Defensive patterns

Strategy: validation

Validate before calling

const curveOf = (k) => k.algorithm.namedCurve;
if (curveOf(privKey) !== curveOf(publicKey)) {
  throw new Error(`curve mismatch: ${curveOf(privKey)} vs ${curveOf(publicKey)} — regenerate on the same curve`);
}

Type guard

const sameCurve = (a, b) => a.algorithm?.namedCurve != null && a.algorithm.namedCurve === b.algorithm?.namedCurve;

Try / catch

try {
  bits = await crypto.subtle.deriveBits({ name: 'ECDH', public: peerPub }, priv, 256);
} catch (e) {
  if (String(e.message).includes('different curves')) throw new Error(`ECDH keys must share one curve (${e.message})`);
  throw e;
}

Prevention

When it happens

Trigger: `crypto.subtle.deriveBits({ name: 'ECDH', public: pub384 }, priv256, 256)` — private key generated on P-256 while the peer public key (imported from JWK/raw or generated) is on P-384 or P-521.

Common situations: Hard-coded curve in the script ('P-256') while the server/peer publishes keys on P-384; keys imported from different environments (prod vs staging) with different crypto policies; copying example keys from docs generated on another curve.

Related errors


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