grafana/k6 · error

failed to decode coefficient: %w

Error message

failed to decode coefficient: %w

What it means

Thrown by k6's webcrypto RSA JWK importer when the 'qi' (q inverse, CRT coefficient) member of the imported JWK fails base64url decoding. Like the other CRT fields (dp, dq), qi is mandatory in k6's RSA private-key JWK path and must be unpadded RFC 7515 base64url; the wrapped error carries the exact decoding failure.

Source

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

	pBytes, err := base64URLDecode(jwk.P)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode first prime factor: %w", err)
	}
	qBytes, err := base64URLDecode(jwk.Q)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode second prime factor: %w", err)
	}
	dpBytes, err := base64URLDecode(jwk.Dp)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode first exponent: %w", err)
	}
	dqBytes, err := base64URLDecode(jwk.Dq)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode second exponent: %w", err)
	}
	qiBytes, err := base64URLDecode(jwk.Qi)
	if err != nil {
		return nil, UnknownCryptoKeyType, 0, fmt.Errorf("failed to decode coefficient: %w", err)
	}

	privKey := &rsa.PrivateKey{
		PublicKey: pubKey,
		D:         new(big.Int).SetBytes(dBytes),
		Primes: []*big.Int{
			new(big.Int).SetBytes(pBytes),
			new(big.Int).SetBytes(qBytes),
		},
		Precomputed: rsa.PrecomputedValues{
			Dp:   new(big.Int).SetBytes(dpBytes),
			Dq:   new(big.Int).SetBytes(dqBytes),
			Qinv: new(big.Int).SetBytes(qiBytes),
		},
	}

	err = privKey.Validate()
	if err != nil {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Export a complete JWK from a working key via crypto.subtle.exportKey('jwk', key) and import that verbatim
  2. Re-encode qi as unpadded base64url (translate '+'->'-', '/'->'_', drop '=' padding)
  3. Pre-validate all JWK members with a base64url regex before calling importKey
  4. Prefer PEM ('spki'/'pkcs8') import when you control the key serialization format

Example fix

// before
const priv = await crypto.subtle.importKey('jwk', exported, alg, true, ['sign']);
// exported.qi is hex: '00aa11bb...'

// after
const b64url = hexToB64Url(exported.qiHex); // encode bytes, then base64url without padding
const priv = await crypto.subtle.importKey('jwk', { ...exported, qi: b64url }, alg, true, ['sign']);
Defensive patterns

Strategy: validation

Validate before calling

const b64url = s => s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const NEEDS_FIX = /[+/=]/;
for (const f of ['qi','dp','dq','p','q','d','n','e']) {
  if (typeof jwk[f] === 'string' && NEEDS_FIX.test(jwk[f])) jwk[f] = b64url(jwk[f]);
}
await crypto.subtle.importKey('jwk', jwk, alg, true, usages);

Type guard

const isB64Url = s => typeof s === 'string' && /^[A-Za-z0-9_-]*$/.test(s);
const hasValidQi = j => isB64Url(j.qi);

Try / catch

try { await crypto.subtle.importKey('jwk', jwk, alg, true, usages); }
catch (e) { if (/coefficient/.test(e.message)) console.error('fix jwk.qi encoding'); throw e; }

Prevention

When it happens

Trigger: crypto.subtle.importKey('jwk', jwk, rsaAlg, ...) with jwk.qi containing '+', '/', '=', whitespace, or a length that is not a multiple of 4 base64 chars. All earlier members (n, e, d, p, q, dp, dq) must decode successfully first, otherwise a different 'failed to decode ...' error is returned.

Common situations: Manually assembling a JWK from MPI/hex values produced by another crypto library without converting to base64url; truncating a long JWK line when copying; mixing up field order or pasting hex ('a1b2...') into qi; JWK tools that emit padded base64.

Understand the failure class

Related errors


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