ML-DSA generally requires the full message as input, and then computes and signs a representative μ. μ is H(H(pubkey) || 0x00 || len(context) || context || message). We also support pre-computing and passing μ directly to PrivateKey.Sign, along with a sentinel crypto.Hash value, crypto.MLDSAMu. See “Pre-Hashing (Externalμ-ML-DSA)” in RFC 9881.
This can be useful when the crypto.Signer is backed e.g. by an HSM that doesn't have a high-bandwidth input channel. (Although note that in general, such a crypto.Signer should also support unhashed message inputs, and compute μ internally before dispatching it. crypto.MLDSAMu is more for the case where the system doesn't know the message preimage.)
However, we currently don't provide a way to compute that μ value. The function can't be a simple func() hash.Hash because it depends on the hash of the public key, and on the optional context.
Of course it can still be computed manually using the crypto/sha3 package, but it feels appropriate to provide a higher-level API.
I propose we add a new crypto/mldsa.MuHash type which is instantiated from a PublicKey and implements hash.Hash.
package mldsa
type MuHash struct { /* unexported fields */ }
// NewMuHash returns a new [MuHash] computing μ for messages signed under pk
// with the [Options.Context] from opts. If opts is nil, the empty context is
// used.
//
// NewMuHash returns an error if [Options.Context] is more than 255 bytes long.
func NewMuHash(pk *PublicKey, opts *Options) (*MuHash, error)
// Reset resets the hash state to absorb a new message under the same public
// key and context as the [MuHash] was created with.
func (m *MuHash) Reset()
// Write absorbs message bytes into the hash state. It always returns len(p), nil.
func (m *MuHash) Write(p []byte) (n int, err error)
// Sum appends the 64-byte μ to b and returns the resulting slice. It does not
// change the underlying hash state, so [MuHash.Write] and further [MuHash.Sum]
// calls remain valid.
func (m *MuHash) Sum(b []byte) []byte
// Size returns 64, the length in bytes of the μ message representative.
func (m *MuHash) Size() int { return 64 }
// BlockSize returns the rate of the underlying SHAKE256, in bytes.
func (m *MuHash) BlockSize() int { return 136 }
There is an implementation in a WIP CL: crypto/mldsa: add MuHash.
/cc @golang/security
ML-DSA generally requires the full message as input, and then computes and signs a representative μ. μ is
H(H(pubkey) || 0x00 || len(context) || context || message). We also support pre-computing and passing μ directly to PrivateKey.Sign, along with a sentinel crypto.Hash value, crypto.MLDSAMu. See “Pre-Hashing (Externalμ-ML-DSA)” in RFC 9881.This can be useful when the crypto.Signer is backed e.g. by an HSM that doesn't have a high-bandwidth input channel. (Although note that in general, such a crypto.Signer should also support unhashed message inputs, and compute μ internally before dispatching it. crypto.MLDSAMu is more for the case where the system doesn't know the message preimage.)
However, we currently don't provide a way to compute that μ value. The function can't be a simple
func() hash.Hashbecause it depends on the hash of the public key, and on the optional context.Of course it can still be computed manually using the crypto/sha3 package, but it feels appropriate to provide a higher-level API.
I propose we add a new crypto/mldsa.MuHash type which is instantiated from a PublicKey and implements hash.Hash.
There is an implementation in a WIP CL: crypto/mldsa: add MuHash.
/cc @golang/security