| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build !fips140v1.0 |
| |
| package mldsa |
| |
| import ( |
| "crypto" |
| "crypto/internal/fips140/mldsa" |
| "errors" |
| "io" |
| ) |
| |
| // PrivateKey is an in-memory ML-DSA private key. It implements [crypto.Signer] |
| // and the informal extended [crypto.PrivateKey] interface. |
| // |
| // A PrivateKey is safe for concurrent use. |
| type PrivateKey struct { |
| k mldsa.PrivateKey |
| } |
| |
| var errInvalidParameters = errors.New("mldsa: invalid parameters") |
| |
| // GenerateKey generates a new random ML-DSA private key. |
| func GenerateKey(params Parameters) (*PrivateKey, error) { |
| switch params { |
| case MLDSA44(): |
| return &PrivateKey{k: *mldsa.GenerateKey44()}, nil |
| case MLDSA65(): |
| return &PrivateKey{k: *mldsa.GenerateKey65()}, nil |
| case MLDSA87(): |
| return &PrivateKey{k: *mldsa.GenerateKey87()}, nil |
| default: |
| return nil, errInvalidParameters |
| } |
| } |
| |
| // NewPrivateKey decodes an ML-DSA private key from the given seed. |
| // |
| // The seed must be exactly [PrivateKeySize] bytes long. |
| func NewPrivateKey(params Parameters, seed []byte) (*PrivateKey, error) { |
| var err error |
| var k *mldsa.PrivateKey |
| switch params { |
| case MLDSA44(): |
| k, err = mldsa.NewPrivateKey44(seed) |
| case MLDSA65(): |
| k, err = mldsa.NewPrivateKey65(seed) |
| case MLDSA87(): |
| k, err = mldsa.NewPrivateKey87(seed) |
| default: |
| return nil, errInvalidParameters |
| } |
| if err != nil { |
| return nil, err |
| } |
| return &PrivateKey{k: *k}, nil |
| } |
| |
| // Public returns the corresponding [PublicKey] for this private key. |
| // |
| // It implements the [crypto.Signer] interface. |
| func (sk *PrivateKey) Public() crypto.PublicKey { |
| return sk.PublicKey() |
| } |
| |
| // Equal reports whether sk and x are the same key (i.e. they are derived from |
| // the same seed). |
| // |
| // If x is not a *PrivateKey, Equal returns false. |
| func (sk *PrivateKey) Equal(x crypto.PrivateKey) bool { |
| other, ok := x.(*PrivateKey) |
| if !ok || other == nil { |
| 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() { |
| 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 |
| } |
| } |
| |
| // SignDeterministic works like [PrivateKey.Sign], but the signature is |
| // deterministic. |
| func (sk *PrivateKey) SignDeterministic(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.SignDeterministic(&sk.k, message, context) |
| case crypto.MLDSAMu: |
| return mldsa.SignExternalMuDeterministic(&sk.k, message) |
| default: |
| return nil, errInvalidSignerOpts |
| } |
| } |
| |
| // PublicKey is an ML-DSA public key. It implements the informal extended |
| // [crypto.PublicKey] interface. |
| // |
| // A PublicKey is safe for concurrent use. |
| type PublicKey struct { |
| p mldsa.PublicKey |
| } |
| |
| // NewPublicKey creates a new ML-DSA public key from the given encoding. |
| func NewPublicKey(params Parameters, encoding []byte) (*PublicKey, error) { |
| return newPublicKey(&PublicKey{}, params, encoding) |
| } |
| |
| func newPublicKey(pub *PublicKey, params Parameters, encoding []byte) (*PublicKey, error) { |
| var err error |
| var pk *mldsa.PublicKey |
| switch params { |
| case MLDSA44(): |
| pk, err = mldsa.NewPublicKey44(encoding) |
| case MLDSA65(): |
| pk, err = mldsa.NewPublicKey65(encoding) |
| case MLDSA87(): |
| pk, err = mldsa.NewPublicKey87(encoding) |
| default: |
| return nil, errInvalidParameters |
| } |
| if err != nil { |
| return nil, err |
| } |
| pub.p = *pk |
| return pub, nil |
| } |
| |
| // Bytes returns the public key encoding. |
| func (pk *PublicKey) Bytes() []byte { |
| return pk.p.Bytes() |
| } |
| |
| // Equal reports whether pk and x are the same key (i.e. they have the same |
| // encoding). |
| // |
| // If x is not a *PublicKey, Equal returns false. |
| func (pk *PublicKey) Equal(x crypto.PublicKey) bool { |
| other, ok := x.(*PublicKey) |
| if !ok || other == nil { |
| return false |
| } |
| return pk.p.Equal(&other.p) |
| } |
| |
| // Parameters returns the parameters associated with this public key. |
| func (pk *PublicKey) Parameters() Parameters { |
| switch pk.p.Parameters() { |
| case "ML-DSA-44": |
| return MLDSA44() |
| case "ML-DSA-65": |
| return MLDSA65() |
| case "ML-DSA-87": |
| return MLDSA87() |
| default: |
| panic("mldsa: invalid parameters in public key") |
| } |
| } |
| |
| // Verify reports whether signature is a valid signature of message by pk. |
| // If opts is nil, it's equivalent to the zero value of Options. |
| func Verify(pk *PublicKey, message []byte, signature []byte, opts *Options) error { |
| if pk == nil { |
| return errors.New("mldsa: nil public key") |
| } |
| if pk.p == (mldsa.PublicKey{}) { |
| return errors.New("mldsa: zero public key") |
| } |
| if opts == nil { |
| opts = &Options{} |
| } |
| return mldsa.Verify(&pk.p, message, signature, opts.Context) |
| } |