Go version
go1.26.0 darwin/arm64
What did you do?
I called crypto/mldsa.(*PrivateKey).Sign with a typed-nil *mldsa.Options value.
Minimal reproducer:
package main
import (
"crypto/mldsa"
)
func main() {
sk, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
panic(err)
}
var opts *mldsa.Options
_, _ = sk.Sign(nil, []byte("message"), opts)
}
The same issue also applies to SignDeterministic:
var opts *mldsa.Options
_, _ = sk.SignDeterministic([]byte("message"), opts)
What did you see happen?
The ordinary untyped nil case is handled:
if opts == nil {
opts = &Options{}
}
However, a typed-nil (*mldsa.Options)(nil) stored in the crypto.SignerOpts interface is not equal to nil. The code then calls opts.HashFunc(), which succeeds because (*Options).HashFunc does not dereference the receiver. After that, this block type-asserts back to *Options and dereferences the nil pointer:
if opts, ok := opts.(*Options); ok {
context = opts.Context
}
That causes a runtime panic instead of returning an error or treating the typed-nil options value like nil.
What did you expect to see?
I expected typed-nil *mldsa.Options to be handled consistently with nil options, or rejected cleanly without panicking.
For example, either of these behaviors would be reasonable:
var opts *mldsa.Options
sig, err := sk.Sign(nil, []byte("message"), opts)
// behaves like opts == nil
or:
var opts *mldsa.Options
sig, err := sk.Sign(nil, []byte("message"), opts)
// returns a non-nil error
The panic can be avoided by checking the asserted *Options value before reading Context, for example:
if opts, ok := opts.(*Options); ok && opts != nil {
context = opts.Context
}
Go version
go1.26.0 darwin/arm64
What did you do?
I called
crypto/mldsa.(*PrivateKey).Signwith a typed-nil*mldsa.Optionsvalue.Minimal reproducer:
The same issue also applies to
SignDeterministic:What did you see happen?
The ordinary untyped nil case is handled:
However, a typed-nil
(*mldsa.Options)(nil)stored in thecrypto.SignerOptsinterface is not equal to nil. The code then callsopts.HashFunc(), which succeeds because(*Options).HashFuncdoes not dereference the receiver. After that, this block type-asserts back to*Optionsand dereferences the nil pointer:That causes a runtime panic instead of returning an error or treating the typed-nil options value like nil.
What did you expect to see?
I expected typed-nil
*mldsa.Optionsto be handled consistently with nil options, or rejected cleanly without panicking.For example, either of these behaviors would be reasonable:
or:
The panic can be avoided by checking the asserted *Options value before reading Context, for example: