compositemldsa

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 16 Imported by: 2

README

go-composite-mldsa

GitHub Release Go Reference License Tests Interoperability Supply chain OpenSSF Scorecard

go-composite-mldsa is a Go library for post-quantum composite ML-DSA signatures as specified in draft-ietf-lamps-pq-composite-sigs-19.

A composite key pairs a post-quantum ML-DSA key with a traditional RSA, ECDSA or Ed25519 key. Every signature carries one signature from each and verifies only when both do. A certificate signed this way stays trustworthy as long as either algorithm remains unbroken, which is the point of a migration period where ML-DSA is still new.

The library has two packages and no dependencies outside the Go standard library:

  • compositemldsa generates, encodes, signs and verifies with the 15 composite algorithms the standard library can build
  • compositex509 creates and verifies certificates, certificate requests and revocation lists signed by composite keys or carrying composite subject keys. Every other key type passes through to crypto/x509

Contents

Install

go get github.com/misiektoja/go-composite-mldsa

The module needs Go 1.27.2 or newer.

import (
	compositemldsa "github.com/misiektoja/go-composite-mldsa"
	"github.com/misiektoja/go-composite-mldsa/compositex509"
)

Algorithms

ML-DSA Traditional component
ML-DSA-44 RSA-2048 PSS, RSA-2048 PKCS #1 v1.5, Ed25519, ECDSA P-256
ML-DSA-65 RSA-3072 PSS, RSA-3072 PKCS #1 v1.5, RSA-4096 PSS, RSA-4096 PKCS #1 v1.5, ECDSA P-256, ECDSA P-384, Ed25519
ML-DSA-87 ECDSA P-384, RSA-3072 PSS, RSA-4096 PSS, ECDSA P-521

The Brainpool and Ed448 combinations of the draft are not supported because the Go standard library does not implement those curves. The algorithm reference lists names, OIDs and sizes.

Every supported algorithm passes the draft's published test vectors and exchanges keys, signatures, certificates, requests and revocation lists with Bouncy Castle in both directions.

Limitations
  • The draft is not yet an RFC. The library follows draft-19 exactly and a later draft or the RFC may change the construction.
  • x509.Certificate.Verify cannot build chains through composite certificates. Check each link with compositex509.CheckSignatureFrom.
  • OCSP responses, CMS and TLS are out of scope. The raw signing API covers any structure that signs DER bytes under an algorithm identifier.

Known limitations explains each one.

Getting started

Sign and verify a message:

key, err := compositemldsa.GenerateKey(compositemldsa.MLDSA65ECDSAP256SHA512)
if err != nil {
	log.Fatal(err)
}
signature, err := key.Sign(nil, message, nil)
if err != nil {
	log.Fatal(err)
}
err = compositemldsa.Verify(key.PublicKey(), message, signature, nil)

Issue certificates from a composite CA with the crypto/x509 templates you already use:

der, err := compositex509.CreateCertificate(rand.Reader, template, caCert, subjectPublicKey, caKey)

examples/hierarchy builds a root CA, a device certificate from a certificate request and a revocation list, verifies them and writes PEM files:

go run ./examples/hierarchy out

Getting started walks through the same steps.

Documentation

Full documentation is at misiektoja.github.io/go-composite-mldsa. The package documentation on pkg.go.dev describes every exported identifier.

Support

SUPPORT.md says where to ask questions and report bugs. SECURITY.md describes how to report a vulnerability privately. CONTRIBUTING.md covers the development checks.

License

Apache-2.0, see LICENSE. The draft's test vectors in testdata keep their own Revised BSD License, see testdata/README.md.

Documentation

Overview

Package compositemldsa implements the composite ML-DSA signatures of draft-ietf-lamps-pq-composite-sigs-19, which pair an ML-DSA key with an RSA, ECDSA or Ed25519 key. A composite signature is valid only when both component signatures are valid, so it stays secure while either algorithm remains unbroken.

Example

Signs a message with a new composite key and verifies the signature.

package main

import (
	"fmt"
	"log"

	compositemldsa "github.com/misiektoja/go-composite-mldsa"
)

func main() {
	key, err := compositemldsa.GenerateKey(compositemldsa.MLDSA65ECDSAP256SHA512)
	if err != nil {
		log.Fatal(err)
	}
	message := []byte("hello, post-quantum world")
	signature, err := key.Sign(nil, message, nil)
	if err != nil {
		log.Fatal(err)
	}
	err = compositemldsa.Verify(key.PublicKey(), message, signature, nil)
	fmt.Println(key.Algorithm(), key.Algorithm().OID(), err == nil)
}
Output:
MLDSA65-ECDSA-P256-SHA512 1.3.6.1.5.5.7.6.45 true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MarshalPKCS8PrivateKey

func MarshalPKCS8PrivateKey(sk *PrivateKey) ([]byte, error)

Encodes the private key as a DER PKCS #8 PrivateKeyInfo with parameters absent.

func MarshalPKIXPublicKey

func MarshalPKIXPublicKey(pk *PublicKey) ([]byte, error)

Encodes the public key as a DER SubjectPublicKeyInfo with parameters absent.

func Verify

func Verify(pk *PublicKey, message, signature []byte, opts *Options) error

Verifies signature over message with both components. It returns nil only when both component signatures are valid.

Types

type Algorithm

type Algorithm int

Identifies one composite ML-DSA signature algorithm. The zero value is not a valid algorithm.

const (
	MLDSA44RSA2048PSSSHA256 Algorithm = iota + 1
	MLDSA44RSA2048PKCS15SHA256
	MLDSA44Ed25519SHA512
	MLDSA44ECDSAP256SHA256
	MLDSA65RSA3072PSSSHA512
	MLDSA65RSA3072PKCS15SHA512
	MLDSA65RSA4096PSSSHA512
	MLDSA65RSA4096PKCS15SHA512
	MLDSA65ECDSAP256SHA512
	MLDSA65ECDSAP384SHA512
	MLDSA65Ed25519SHA512
	MLDSA87ECDSAP384SHA512
	MLDSA87RSA3072PSSSHA512
	MLDSA87RSA4096PSSSHA512
	MLDSA87ECDSAP521SHA512
)

The draft-ietf-lamps-pq-composite-sigs-19 algorithms whose components the Go standard library implements. The brainpool and Ed448 combinations are not supported.

func AlgorithmFromName added in v0.2.0

func AlgorithmFromName(name string) (Algorithm, bool)

Returns the supported algorithm with the draft name that String returns, such as "MLDSA65-ECDSA-P256-SHA512". The match is exact and case-sensitive.

func AlgorithmFromOID

func AlgorithmFromOID(oid asn1.ObjectIdentifier) (Algorithm, bool)

Returns the supported algorithm registered under oid.

func Algorithms

func Algorithms() []Algorithm

Returns every supported algorithm in OID order.

func (Algorithm) MLDSAParameters

func (a Algorithm) MLDSAParameters() mldsa.Parameters

Returns the parameter set of the ML-DSA component.

func (Algorithm) OID

Returns the object identifier of the algorithm. Algorithm identifiers carry no parameters.

func (Algorithm) PreHash

func (a Algorithm) PreHash() crypto.Hash

Returns the hash applied to the message before both components sign it.

func (Algorithm) String

func (a Algorithm) String() string

Returns the algorithm name used in the draft, such as "MLDSA65-ECDSA-P256-SHA512".

type Options

type Options struct {
	// Binds the signature to an application context of at most 255 bytes. X.509 and other PKIX
	// uses keep it empty.
	Context string

	// When not zero, the message is the digest of the real message under this hash, which must be
	// the pre-hash of the algorithm. Section 10.5 of the draft describes this external pre-hashing.
	Hash crypto.Hash
}

Options for signing and verification. A nil *Options means an empty context and an unhashed message.

func (*Options) HashFunc

func (o *Options) HashFunc() crypto.Hash

Returns the hash of an externally pre-hashed message. It is zero when the message is not hashed.

type PrivateKey

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

Holds the ML-DSA seed and traditional private key of one composite key pair. It implements crypto.Signer and crypto.MessageSigner and always signs the complete message.

A PrivateKey is safe for concurrent use.

func GenerateKey

func GenerateKey(alg Algorithm) (*PrivateKey, error)

Generates a fresh composite key pair. Both components are newly generated, as section 3.1 of the draft requires.

func NewPrivateKey

func NewPrivateKey(alg Algorithm, encoding []byte) (*PrivateKey, error)

Decodes a raw composite private key, the 32-byte ML-DSA seed followed by the traditional private key.

func ParsePKCS8PrivateKey

func ParsePKCS8PrivateKey(der []byte) (*PrivateKey, error)

Decodes a DER PKCS #8 PrivateKeyInfo or OneAsymmetricKey that holds a composite private key. An included public key must match the private key. Attributes must be well formed and are discarded. Elements after the public key are rejected.

Example

Stores a key as PKCS #8 and loads it again.

package main

import (
	"fmt"
	"log"

	compositemldsa "github.com/misiektoja/go-composite-mldsa"
)

func main() {
	key, err := compositemldsa.GenerateKey(compositemldsa.MLDSA44Ed25519SHA512)
	if err != nil {
		log.Fatal(err)
	}
	der, err := compositemldsa.MarshalPKCS8PrivateKey(key)
	if err != nil {
		log.Fatal(err)
	}
	loaded, err := compositemldsa.ParsePKCS8PrivateKey(der)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(loaded.Equal(key))
}
Output:
true

func (*PrivateKey) Algorithm

func (sk *PrivateKey) Algorithm() Algorithm

Returns the algorithm of the key.

func (*PrivateKey) Bytes

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

Returns the raw composite private key, the ML-DSA seed followed by the traditional private key.

func (*PrivateKey) Equal

func (sk *PrivateKey) Equal(x crypto.PrivateKey) bool

Reports whether x is a composite private key with the same algorithm and components.

func (*PrivateKey) Public

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

Returns the public key as *PublicKey, as crypto.Signer requires.

func (*PrivateKey) PublicKey

func (sk *PrivateKey) PublicKey() *PublicKey

Returns the public key of the pair.

func (*PrivateKey) Sign

func (sk *PrivateKey) Sign(random io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error)

Signs message with both components.

With a zero opts.HashFunc the complete message is signed. When opts.HashFunc is the pre-hash of the algorithm, message must be its digest. opts may be *Options to set a context. The random source is used by the traditional component. A nil source selects crypto/rand.

func (*PrivateKey) SignMessage

func (sk *PrivateKey) SignMessage(random io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error)

Signs the complete message. A non-zero opts.HashFunc must be the pre-hash of the algorithm, which is then applied here, so the result matches Sign with the digest.

type PublicKey

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

Holds an ML-DSA public key and a traditional public key that are only valid together.

A PublicKey is safe for concurrent use.

func NewPublicKey

func NewPublicKey(alg Algorithm, encoding []byte) (*PublicKey, error)

Decodes a raw composite public key, the ML-DSA public key followed by the traditional public key.

func ParsePKIXPublicKey

func ParsePKIXPublicKey(der []byte) (*PublicKey, error)

Decodes a DER SubjectPublicKeyInfo that holds a composite public key.

func (*PublicKey) Algorithm

func (pk *PublicKey) Algorithm() Algorithm

Returns the algorithm of the key.

func (*PublicKey) Bytes

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

Returns the raw composite public key, the ML-DSA public key followed by the traditional one.

func (*PublicKey) Equal

func (pk *PublicKey) Equal(x crypto.PublicKey) bool

Reports whether x is a composite public key with the same algorithm and encoding.

func (*PublicKey) MLDSAPublicKey

func (pk *PublicKey) MLDSAPublicKey() *mldsa.PublicKey

Returns the ML-DSA component. It must not be used as a standalone key.

func (*PublicKey) TraditionalPublicKey

func (pk *PublicKey) TraditionalPublicKey() crypto.PublicKey

Returns the traditional component as *rsa.PublicKey, *ecdsa.PublicKey or ed25519.PublicKey. It must not be used as a standalone key.

Directories

Path Synopsis
Package compositex509 extends crypto/x509 to composite ML-DSA keys.
Package compositex509 extends crypto/x509 to composite ML-DSA keys.
examples
hierarchy command
Command hierarchy creates a composite ML-DSA root CA, a device certificate issued from a certificate request and an empty revocation list, verifies every signature and writes the results as PEM files.
Command hierarchy creates a composite ML-DSA root CA, a device certificate issued from a certificate request and an empty revocation list, verifies every signature and writes the results as PEM files.

Jump to

Keyboard shortcuts

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