golang/go · error

mldsa: zero private key

Error message

mldsa: zero private key

What it means

Returned by PrivateKey.Sign when the underlying mldsa.PrivateKey field sk.k is the zero value, meaning the PrivateKey struct was never populated by key generation or decoding. This guards against signing with an uninitialized key, which would otherwise produce invalid or deterministic garbage. The check sk.k == (mldsa.PrivateKey{}) detects the all-zero internal state.

Source

Thrown at src/crypto/mldsa/mldsa_fips140v1.26.go:107

// Bytes returns the private key seed.
func (sk *PrivateKey) Bytes() []byte {
	return sk.k.Bytes()
}

var errInvalidSignerOpts = errors.New("mldsa: invalid SignerOpts")

// Sign returns a signature of the given message using this private key.
//
// If opts is nil or opts.HashFunc returns zero, the message is signed directly.
// If opts.HashFunc returns [crypto.MLDSAMu], the provided message must be a
// [pre-hashed μ message representative]. opts can be of type *[Options] if a
// context string is desired along with a directly-signed message. The io.Reader
// argument is ignored.
//
// [pre-hashed μ message representative]: https://www.rfc-editor.org/rfc/rfc9881.html#externalmu
func (sk *PrivateKey) Sign(_ io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
	if sk.k == (mldsa.PrivateKey{}) {
		return nil, errors.New("mldsa: zero private key")
	}
	if opts == nil {
		opts = &Options{}
	}
	switch opts.HashFunc() {
	case 0:
		var context string
		if opts, ok := opts.(*Options); ok && opts != nil {
			context = opts.Context
		}
		return mldsa.Sign(&sk.k, message, context)
	case crypto.MLDSAMu:
		return mldsa.SignExternalMu(&sk.k, message)
	default:
		return nil, errInvalidSignerOpts
	}
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Generate the key first: sk, err := mldsa.GenerateKey(rand.Reader); then call sk.Sign(...).
  2. Decode from stored bytes using the documented Unmarshal/Decode functions and check the returned error before signing.
  3. Add a nil/zero-value assertion immediately after key construction in your loading code.

Example fix

// before
var sk mldsa.PrivateKey
sig, err := sk.Sign(nil, msg, nil) // "mldsa: zero private key"

// after
sk, err := mldsa.GenerateKey(rand.Reader)
if err != nil { return err }
sig, err := sk.Sign(nil, msg, nil)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the key is non-zero before signing.
if sk == nil || len(sk.Bytes()) == 0 {
    return errors.New("private key not initialized")
}
sig, err := sk.Sign(nil, msg, nil)

Type guard

func (sk *PrivateKey) isInitialized() bool {
    return sk != nil && len(sk.Bytes()) > 0
}

Try / catch

sig, err := sk.Sign(nil, msg, opts)
if err != nil && strings.Contains(err.Error(), "zero private key") {
    // re-initialize key from source, then retry once
}
return sig, err

Prevention

When it happens

Trigger: Declaring var sk mldsa.PrivateKey (or new(mldsa.PrivateKey)) and calling Sign without running it through GenerateKey or UnmarshalBinary. Loading a key from bytes via a code path that silently failed and left sk.k zero.

Common situations: Deserializing a key from disk/network where the decode error was ignored, leaving the zero-value key. Copying a PrivateKey by value into a new variable and forgetting to populate it. Test scaffolding that instantiates the struct directly.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/adafef97dcbd3bac. Report an issue: GitHub.