golang/go · error

mldsa: invalid SignerOpts

Error message

mldsa: invalid SignerOpts

What it means

Returned by PrivateKey.Sign when the crypto.SignerOpts passed in has a HashFunc() that returns a value other than 0 (direct message signing) or crypto.MLDSAMu (pre-hashed external-μ signing). ML-DSA supports exactly these two signing modes per FIPS 204 / RFC 9881; any other hash identifier is rejected as unsupported. The error is the package-level var errInvalidSignerOpts.

Source

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

		return false
	}
	return sk.k.Equal(&other.k)
}

// PublicKey returns the corresponding [PublicKey] for this private key.
func (sk *PrivateKey) PublicKey() *PublicKey {
	// Making a copy severs the pointer relationship between the private and
	// public keys, so that keeping the public key around doesn't keep the
	// private key alive. This costs a copy and an allocation.
	return &PublicKey{p: *sk.k.PublicKey()}
}

// 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() {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Pass nil or a *mldsa.Options with the zero-value hash for direct message signing: sk.Sign(rand, message, nil).
  2. For pre-hashed μ signing, use a SignerOpts whose HashFunc() returns crypto.MLDSAMu and pre-compute the μ representative.
  3. If you need to attach a context string, pass &mldsa.Options{Context: "ctx"} with HashFunc left at 0.

Example fix

// before
opts := &rsa.PSSOptions{Hash: crypto.SHA256}
sig, err := sk.Sign(rand.Reader, digest, opts) // errInvalidSignerOpts

// after
sig, err := sk.Sign(rand.Reader, message, nil) // direct signing
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate SignerOpts before calling Sign.
func validMldsaOpts(opts crypto.SignerOpts) bool {
    if opts == nil { return true } // treated as direct signing
    hf := opts.HashFunc()
    return hf == 0 || hf == crypto.MLDSAMu
}
if !validMldsaOpts(opts) { return errInvalidSignerOpts }
sig, err := sk.Sign(nil, msg, opts)

Type guard

func isMldsaApprovedOpts(opts crypto.SignerOpts) bool {
    if opts == nil { return true }
    hf := opts.HashFunc()
    return hf == 0 || hf == crypto.MLDSAMu
}

Try / catch

sig, err := sk.Sign(nil, msg, opts)
if err != nil {
    if errors.Is(err, mldsaErrInvalidSignerOpts) {
        // fix opts to nil or MLDSAMu and retry once
    }
    return err
}

Prevention

When it happens

Trigger: Calling sk.Sign(nil, msg, opts) where opts.HashFunc() returns a conventional hash crypto.Hash (e.g. crypto.SHA256) instead of 0 or crypto.MLDSAMu. Passing a custom SignerOpts whose HashFunc() yields an unrecognized uint value.

Common situations: Adapting existing ECDSA/Ed25519 signing code to ML-DSA and reusing a SHA-256 opts struct. Misunderstanding that ML-DSA is not a pre-hash-signature scheme by default and assuming opts.HashFunc must return a real hash. Forgetting to pass nil or &Options{}.

Related errors


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