grafana/k6 · error

failed to decode D: %w

Error message

failed to decode D: %w

What it means

When the EC JWK contains a non-empty d field it is treated as a private key, and d (the private scalar) is decoded with base64.RawURLEncoding. This error means d is not valid unpadded base64url — padding characters, wrong alphabet, whitespace, or bad length. It is returned from crypto.subtle.importKey('jwk', ...) for ECDSA.

Source

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

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

	pk := &ecdsa.PublicKey{
		Curve: crv,
		X:     new(big.Int).SetBytes(x),
		Y:     new(big.Int).SetBytes(y),
	}

	// if the key is a public key, return it
	if jwkKey.D == "" {
		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()

View on GitHub (pinned to 93accf6570)

Solutions

  1. Convert d to unpadded base64url before import
  2. Check the decoded scalar length does not exceed the curve size (32/48/66 bytes for P-256/P-384/P-521)
  3. Remove the d field entirely if you only need the public key (import then returns a public CryptoKey)
  4. Re-export the JWK from the originating system with strict base64url encoding

Example fix

// before
const jwk = { kty: 'EC', crv: 'P-256', x, y, d: paddedD };
// after
const toB64u = (s) => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').trim();
const jwk = { kty: 'EC', crv: 'P-256', x, y, d: toB64u(paddedD) };
Defensive patterns

Strategy: validation

Validate before calling

const B64URL = /^[A-Za-z0-9_-]+$/;
if (jwk.d !== undefined && jwk.d !== '') {
  const d = String(jwk.d).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '').trim();
  if (!B64URL.test(d)) throw new Error('d is not valid unpadded base64url');
  jwk = { ...jwk, d };
}

Type guard

function isB64uScalar(s) {
  return typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s) && s.length % 4 !== 1;
}

Try / catch

try {
  key = await crypto.subtle.importKey('jwk', jwk, alg, true, usages);
} catch (e) {
  if (e.message.includes('failed to decode D')) {
    jwk = { ...jwk, d: toB64u(jwk.d) };
    key = await crypto.subtle.importKey('jwk', jwk, alg, true, usages);
  } else throw e;
}

Prevention

When it happens

Trigger: d contains '=' padding, '+' or '/' characters, or whitespace; d truncated or copied incompletely; d supplied as a number instead of a string would instead fail earlier at JSON unmarshal.

Common situations: Private scalars emitted with padded base64url by external key stores; hand-assembled JWKs where d came from a hex or PEM representation; secret-management systems that re-encode values.

Understand the failure class

Related errors


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