mldsa

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 14, 2025 License: MIT Imports: 4 Imported by: 0

README

mldsa

A pure Go implementation of ML-DSA (Module-Lattice Digital Signature Algorithm) as specified in FIPS 204.

ML-DSA is a post-quantum digital signature scheme standardized by NIST, designed to be secure against attacks by quantum computers.

Features

  • Pure Go implementation with no external dependencies (only standard library)
  • Supports all three security levels: ML-DSA-44, ML-DSA-65, and ML-DSA-87
  • Implements crypto.Signer and crypto.MessageSigner (Go 1.25+) interfaces
  • Simple, clean API
  • FIPS 204 compliant (validated against NIST ACVP test vectors)
  • MIT licensed

Installation

go get github.com/KarpelesLab/mldsa

Security Levels

Parameter Set Security Level Public Key Private Key Signature
ML-DSA-44 128-bit 1,312 bytes 2,560 bytes 2,420 bytes
ML-DSA-65 192-bit 1,952 bytes 4,032 bytes 3,309 bytes
ML-DSA-87 256-bit 2,592 bytes 4,896 bytes 4,627 bytes

Usage

Key Generation
package main

import (
    "crypto/rand"
    "fmt"
    "log"

    "github.com/KarpelesLab/mldsa"
)

func main() {
    // Generate a new ML-DSA-65 key pair
    key, err := mldsa.GenerateKey65(rand.Reader)
    if err != nil {
        log.Fatal(err)
    }

    // Get the public key
    publicKey := key.PublicKey()

    fmt.Printf("Public key size: %d bytes\n", len(publicKey.Bytes()))
}
Signing and Verification
package main

import (
    "crypto/rand"
    "fmt"
    "log"

    "github.com/KarpelesLab/mldsa"
)

func main() {
    // Generate key pair
    key, err := mldsa.GenerateKey65(rand.Reader)
    if err != nil {
        log.Fatal(err)
    }

    message := []byte("Hello, post-quantum world!")

    // Sign the message using crypto.Signer interface
    signature, err := key.Sign(rand.Reader, message, nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Signature size: %d bytes\n", len(signature))

    // Verify the signature
    publicKey := key.PublicKey()
    valid := publicKey.Verify(signature, message, nil)
    fmt.Printf("Signature valid: %v\n", valid)
}
Using Context Strings

ML-DSA supports optional context strings (up to 255 bytes) for domain separation:

context := []byte("my-application-v1")

// Sign with context using SignWithContext
signature, err := key.SignWithContext(rand.Reader, message, context)
if err != nil {
    log.Fatal(err)
}

// Or use SignerOpts with the crypto.Signer interface
opts := &mldsa.SignerOpts{Context: context}
signature, err = key.Sign(rand.Reader, message, opts)
if err != nil {
    log.Fatal(err)
}

// Verify with the same context
valid := publicKey.Verify(signature, message, context)
Key Serialization
// Serialize keys
seed := key.Bytes()              // 32-byte seed (can regenerate full key)
privateKeyBytes := key.PrivateKeyBytes()  // Full private key
publicKeyBytes := publicKey.Bytes()       // Public key

// Deserialize keys
key2, err := mldsa.NewKey65(seed)
if err != nil {
    log.Fatal(err)
}

privateKey, err := mldsa.NewPrivateKey65(privateKeyBytes)
if err != nil {
    log.Fatal(err)
}

publicKey2, err := mldsa.NewPublicKey65(publicKeyBytes)
if err != nil {
    log.Fatal(err)
}

API Reference

Key Generation Functions
// ML-DSA-44 (128-bit security)
func GenerateKey44(rand io.Reader) (*Key44, error)
func NewKey44(seed []byte) (*Key44, error)
func NewPrivateKey44(b []byte) (*PrivateKey44, error)
func NewPublicKey44(b []byte) (*PublicKey44, error)

// ML-DSA-65 (192-bit security)
func GenerateKey65(rand io.Reader) (*Key65, error)
func NewKey65(seed []byte) (*Key65, error)
func NewPrivateKey65(b []byte) (*PrivateKey65, error)
func NewPublicKey65(b []byte) (*PublicKey65, error)

// ML-DSA-87 (256-bit security)
func GenerateKey87(rand io.Reader) (*Key87, error)
func NewKey87(seed []byte) (*Key87, error)
func NewPrivateKey87(b []byte) (*PrivateKey87, error)
func NewPublicKey87(b []byte) (*PublicKey87, error)
Key Types

Each security level has three key types:

  • Key* - A full key pair (contains both private and public key, plus the original seed)
  • PrivateKey* - A standalone private key (can sign messages)
  • PublicKey* - A standalone public key (can verify signatures)
Key Methods
// Key pair methods
func (key *Key65) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)
func (key *Key65) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)
func (key *Key65) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)
func (key *Key65) PublicKey() *PublicKey65
func (key *Key65) Bytes() []byte           // Returns 32-byte seed
func (key *Key65) PrivateKeyBytes() []byte // Returns full private key

// Private key methods (implements crypto.Signer and crypto.MessageSigner)
func (sk *PrivateKey65) Public() crypto.PublicKey
func (sk *PrivateKey65) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)
func (sk *PrivateKey65) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)
func (sk *PrivateKey65) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)
func (sk *PrivateKey65) Bytes() []byte

// Public key methods
func (pk *PublicKey65) Verify(sig, message, context []byte) bool
func (pk *PublicKey65) Bytes() []byte
func (pk *PublicKey65) Equal(other crypto.PublicKey) bool
SignerOpts
// SignerOpts implements crypto.SignerOpts for ML-DSA signing operations.
type SignerOpts struct {
    Context []byte // Optional context string (max 255 bytes)
}

func (opts *SignerOpts) HashFunc() crypto.Hash // Returns 0 (ML-DSA signs messages directly)

Constants

const (
    SeedSize = 32  // Size of the seed for key generation

    // ML-DSA-44
    PublicKeySize44  = 1312
    PrivateKeySize44 = 2560
    SignatureSize44  = 2420

    // ML-DSA-65
    PublicKeySize65  = 1952
    PrivateKeySize65 = 4032
    SignatureSize65  = 3309

    // ML-DSA-87
    PublicKeySize87  = 2592
    PrivateKeySize87 = 4896
    SignatureSize87  = 4627
)

License

MIT License - see LICENSE for details.

References

Documentation

Overview

Package mldsa implements ML-DSA (Module-Lattice Digital Signature Algorithm) as specified in FIPS 204.

ML-DSA is a post-quantum digital signature scheme standardized by NIST. This package supports three security levels:

  • ML-DSA-44: NIST security level 2 (comparable to AES-128)
  • ML-DSA-65: NIST security level 3 (comparable to AES-192)
  • ML-DSA-87: NIST security level 5 (comparable to AES-256)

Basic usage:

key, err := mldsa.GenerateKey65(rand.Reader)
if err != nil {
    // handle error
}
sig, err := key.Sign(rand.Reader, message, nil)
if err != nil {
    // handle error
}
valid := key.PublicKey().Verify(sig, message, nil)

Index

Constants

View Source
const (
	PublicKeySize44  = 32 + k44*n*10/8
	PrivateKeySize44 = 32 + 32 + 64 + (k44+l44)*n*3/8 + k44*n*13/8
	SignatureSize44  = lambda128/4 + l44*n*18/8 + omega80 + k44
)

ML-DSA-44 parameters.

View Source
const (
	PublicKeySize65  = 32 + k65*n*10/8
	PrivateKeySize65 = 32 + 32 + 64 + (k65+l65)*n*4/8 + k65*n*13/8
	SignatureSize65  = lambda192/4 + l65*n*20/8 + omega55 + k65
)

ML-DSA-65 parameters.

View Source
const (
	PublicKeySize87  = 32 + k87*n*10/8
	PrivateKeySize87 = 32 + 32 + 64 + (k87+l87)*n*3/8 + k87*n*13/8
	SignatureSize87  = lambda256/4 + l87*n*20/8 + omega75 + k87
)

ML-DSA-87 parameters.

View Source
const (

	// SeedSize is the size of the random seed used for key generation.
	SeedSize = 32
)

Global ML-DSA constants from FIPS 204.

Variables

This section is empty.

Functions

This section is empty.

Types

type Key44

type Key44 struct {
	PrivateKey44
	// contains filtered or unexported fields
}

Key44 is a key pair for ML-DSA-44.

func GenerateKey44

func GenerateKey44(rand io.Reader) (*Key44, error)

GenerateKey44 generates a new ML-DSA-44 key pair.

func NewKey44

func NewKey44(seed []byte) (*Key44, error)

NewKey44 creates a key pair from a seed.

func (*Key44) Bytes

func (key *Key44) Bytes() []byte

Bytes returns the seed.

func (*Key44) PrivateKeyBytes

func (key *Key44) PrivateKeyBytes() []byte

PrivateKeyBytes returns the full encoded private key.

func (*Key44) PublicKey

func (key *Key44) PublicKey() *PublicKey44

PublicKey returns the public key.

func (*Key44) Sign

func (key *Key44) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the key pair's private key. This implements the crypto.Signer interface.

func (*Key44) SignMessage added in v0.1.1

func (key *Key44) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the key pair's private key. This implements the crypto.MessageSigner interface.

func (*Key44) SignWithContext added in v0.1.1

func (key *Key44) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string using the key pair.

type Key65

type Key65 struct {
	PrivateKey65
	// contains filtered or unexported fields
}

Key65 is a key pair for ML-DSA-65, containing both private and public components.

func GenerateKey65

func GenerateKey65(rand io.Reader) (*Key65, error)

GenerateKey65 generates a new ML-DSA-65 key pair.

func NewKey65

func NewKey65(seed []byte) (*Key65, error)

NewKey65 creates a key pair from a seed.

func (*Key65) Bytes

func (key *Key65) Bytes() []byte

Bytes returns the seed (32 bytes).

func (*Key65) PrivateKeyBytes

func (key *Key65) PrivateKeyBytes() []byte

PrivateKeyBytes returns the full encoded private key.

func (*Key65) PublicKey

func (key *Key65) PublicKey() *PublicKey65

PublicKey returns the public key for this key pair.

func (*Key65) Sign

func (key *Key65) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the key pair's private key. This implements the crypto.Signer interface.

func (*Key65) SignMessage added in v0.1.1

func (key *Key65) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the key pair's private key. This implements the crypto.MessageSigner interface.

func (*Key65) SignWithContext added in v0.1.1

func (key *Key65) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string using the key pair.

type Key87

type Key87 struct {
	PrivateKey87
	// contains filtered or unexported fields
}

Key87 is a key pair for ML-DSA-87.

func GenerateKey87

func GenerateKey87(rand io.Reader) (*Key87, error)

GenerateKey87 generates a new ML-DSA-87 key pair.

func NewKey87

func NewKey87(seed []byte) (*Key87, error)

NewKey87 creates a key pair from a seed.

func (*Key87) Bytes

func (key *Key87) Bytes() []byte

Bytes returns the seed.

func (*Key87) PrivateKeyBytes

func (key *Key87) PrivateKeyBytes() []byte

PrivateKeyBytes returns the full encoded private key.

func (*Key87) PublicKey

func (key *Key87) PublicKey() *PublicKey87

PublicKey returns the public key.

func (*Key87) Sign

func (key *Key87) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the key pair's private key. This implements the crypto.Signer interface.

func (*Key87) SignMessage added in v0.1.1

func (key *Key87) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the key pair's private key. This implements the crypto.MessageSigner interface.

func (*Key87) SignWithContext added in v0.1.1

func (key *Key87) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string using the key pair.

type PrivateKey44

type PrivateKey44 struct {
	// contains filtered or unexported fields
}

PrivateKey44 is the private key for ML-DSA-44.

func NewPrivateKey44

func NewPrivateKey44(b []byte) (*PrivateKey44, error)

NewPrivateKey44 parses an encoded private key.

func (*PrivateKey44) Bytes

func (sk *PrivateKey44) Bytes() []byte

Bytes returns the encoded private key.

func (*PrivateKey44) Public added in v0.1.1

func (sk *PrivateKey44) Public() crypto.PublicKey

Public returns the public key corresponding to this private key. This implements the crypto.Signer interface.

func (*PrivateKey44) Sign

func (sk *PrivateKey44) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the private key. This implements the crypto.Signer interface.

For ML-DSA, the digest is the message to be signed (not a hash). If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used.

func (*PrivateKey44) SignMessage added in v0.1.1

func (sk *PrivateKey44) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the private key. This implements the crypto.MessageSigner interface.

If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used. Returns an error if opts specifies a hash function, as ML-DSA signs messages directly.

func (*PrivateKey44) SignWithContext added in v0.1.1

func (sk *PrivateKey44) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string. Context must be at most 255 bytes.

type PrivateKey65

type PrivateKey65 struct {
	// contains filtered or unexported fields
}

PrivateKey65 is the private key for ML-DSA-65.

func NewPrivateKey65

func NewPrivateKey65(b []byte) (*PrivateKey65, error)

NewPrivateKey65 parses an encoded private key.

func (*PrivateKey65) Bytes

func (sk *PrivateKey65) Bytes() []byte

Bytes returns the encoded private key.

func (*PrivateKey65) Public added in v0.1.1

func (sk *PrivateKey65) Public() crypto.PublicKey

Public returns the public key corresponding to this private key. This implements the crypto.Signer interface.

func (*PrivateKey65) Sign

func (sk *PrivateKey65) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the private key. This implements the crypto.Signer interface.

For ML-DSA, the digest is the message to be signed (not a hash). If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used.

func (*PrivateKey65) SignMessage added in v0.1.1

func (sk *PrivateKey65) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the private key. This implements the crypto.MessageSigner interface.

If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used. Returns an error if opts specifies a hash function, as ML-DSA signs messages directly.

func (*PrivateKey65) SignWithContext added in v0.1.1

func (sk *PrivateKey65) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string. Context must be at most 255 bytes.

type PrivateKey87

type PrivateKey87 struct {
	// contains filtered or unexported fields
}

PrivateKey87 is the private key for ML-DSA-87.

func NewPrivateKey87

func NewPrivateKey87(b []byte) (*PrivateKey87, error)

NewPrivateKey87 parses an encoded private key.

func (*PrivateKey87) Bytes

func (sk *PrivateKey87) Bytes() []byte

Bytes returns the encoded private key.

func (*PrivateKey87) Public added in v0.1.1

func (sk *PrivateKey87) Public() crypto.PublicKey

Public returns the public key corresponding to this private key. This implements the crypto.Signer interface.

func (*PrivateKey87) Sign

func (sk *PrivateKey87) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)

Sign signs digest with the private key. This implements the crypto.Signer interface.

For ML-DSA, the digest is the message to be signed (not a hash). If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used.

func (*PrivateKey87) SignMessage added in v0.1.1

func (sk *PrivateKey87) SignMessage(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error)

SignMessage signs msg with the private key. This implements the crypto.MessageSigner interface.

If opts is *SignerOpts, its Context field is used for domain separation. If opts is nil or not *SignerOpts, no context is used. Returns an error if opts specifies a hash function, as ML-DSA signs messages directly.

func (*PrivateKey87) SignWithContext added in v0.1.1

func (sk *PrivateKey87) SignWithContext(rand io.Reader, message, context []byte) ([]byte, error)

SignWithContext signs a message with an optional context string. Context must be at most 255 bytes.

type PublicKey44

type PublicKey44 struct {
	// contains filtered or unexported fields
}

PublicKey44 is the public key for ML-DSA-44.

func NewPublicKey44

func NewPublicKey44(b []byte) (*PublicKey44, error)

NewPublicKey44 parses an encoded public key.

func (*PublicKey44) Bytes

func (pk *PublicKey44) Bytes() []byte

Bytes returns the encoded public key.

func (*PublicKey44) Equal

func (pk *PublicKey44) Equal(other crypto.PublicKey) bool

Equal reports whether pk and other are the same public key.

func (*PublicKey44) Verify

func (pk *PublicKey44) Verify(sig, message, context []byte) bool

Verify checks the signature.

type PublicKey65

type PublicKey65 struct {
	// contains filtered or unexported fields
}

PublicKey65 is the public key for ML-DSA-65.

func NewPublicKey65

func NewPublicKey65(b []byte) (*PublicKey65, error)

NewPublicKey65 parses an encoded public key.

func (*PublicKey65) Bytes

func (pk *PublicKey65) Bytes() []byte

Bytes returns the encoded public key.

func (*PublicKey65) Equal

func (pk *PublicKey65) Equal(other crypto.PublicKey) bool

Equal reports whether pk and other are the same public key.

func (*PublicKey65) Verify

func (pk *PublicKey65) Verify(sig, message, context []byte) bool

Verify checks the signature on message with optional context.

type PublicKey87

type PublicKey87 struct {
	// contains filtered or unexported fields
}

PublicKey87 is the public key for ML-DSA-87.

func NewPublicKey87

func NewPublicKey87(b []byte) (*PublicKey87, error)

NewPublicKey87 parses an encoded public key.

func (*PublicKey87) Bytes

func (pk *PublicKey87) Bytes() []byte

Bytes returns the encoded public key.

func (*PublicKey87) Equal

func (pk *PublicKey87) Equal(other crypto.PublicKey) bool

Equal reports whether pk and other are the same public key.

func (*PublicKey87) Verify

func (pk *PublicKey87) Verify(sig, message, context []byte) bool

Verify checks the signature.

type SignerOpts added in v0.1.1

type SignerOpts struct {
	// Context is an optional context string for domain separation (max 255 bytes).
	// If nil, no context is used.
	Context []byte
}

SignerOpts implements crypto.SignerOpts for ML-DSA signing operations. It allows specifying an optional context string for domain separation.

func (*SignerOpts) HashFunc added in v0.1.1

func (opts *SignerOpts) HashFunc() crypto.Hash

HashFunc returns 0 to indicate that ML-DSA does not use pre-hashing. ML-DSA signs messages directly rather than message digests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL