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
- Convert d to unpadded base64url before import
- Check the decoded scalar length does not exceed the curve size (32/48/66 bytes for P-256/P-384/P-521)
- Remove the d field entirely if you only need the public key (import then returns a public CryptoKey)
- 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
- Treat d like x/y: unpadded base64url only
- If you only need verify/encrypt operations, remove d to import a public key
- Store private JWKs verbatim from the exporting system; do not re-encode by hand
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode X coordinate: %w
- failed to decode Y coordinate: %w
- failed to decode private exponent: %w
- failed to decode first prime factor: %w
- failed to decode second prime factor: %w
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/5cd6abffe6010966.
Report an issue: GitHub.