Skip to content

proposal: crypto/mldsa: add MuHash #79533

Description

@FiloSottile

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions