Add ML-DSA

Open
#113,502 15 comments 1 reaction 1 assignee View on GitHub

@krwq is already working on this.

Since Mar 17, 2025.

Assessment

This issue has not been assessed yet.

Description

area-System.Security cryptographic-docs-impact

As part of the Post-Quantum Cryptography effort, we should add support for Module-Lattice-Based Digital Signature Algorithm (ML-DSA).

As a signature algorithm, ML-DSA should be available as a primitive, as well as integrated into:

  • X509Certificate2
  • SignedXml
  • COSE
  • TLS (no API required, carried with the certificate)

Based on Elliptic-Curve Digital Signature Algorithm's name of "EC-DSA" becoming ECDsa, the class name for ML-DSA is proposed as MLDsa.

namespace System.Security.Cryptography
{
    [Experimental("SYSLIB5006")]
    public abstract class MLDsa : IDisposable
    {
        public static bool IsSupported { get; }

        protected MLDsa(MLDsaAlgorithm algorithm);

        public MLDsaAlgorithm Algorithm { get; }
        public int SignatureSizeInBytes { get; }
        public void Dispose();

        public int SignData(
            ReadOnlySpan<byte> data,
            Span<byte> destination,
            ReadOnlySpan<byte> context = default);
        public bool VerifyData(
            ReadOnlySpan<byte> data,
            ReadOnlySpan<byte> signature,
            ReadOnlySpan<byte> context = default);

        public int SignPreHash(
            ReadOnlySpan<byte> hash,         
            Span<byte> destination,
            HashAlgorithmName preHashAlgorithm,
            ReadOnlySpan<byte> context = default);
        public bool SignPreHash(
            ReadOnlySpan<byte> hash,         
            ReadOnlySpan<byte> signature,
            HashAlgorithmName preHashAlgorithm,
            ReadOnlySpan<byte> context = default);

        public byte[] ExportSubjectPublicKeyInfo();
        public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
        public string ExportSubjectPublicKeyInfoPem();

        public byte[] ExportPkcs8PrivateKey();
        public bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
        public string ExportPkcs8PrivateKeyPem();

        public byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters);
        public byte[] ExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters);
        public bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten);
        public bool TryExportEncryptedPkcs8PrivateKey(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters,
            Span<byte> destination,
            out int bytesWritten);
        public string ExportEncryptedPkcs8PrivateKeyPem(
            ReadOnlySpan<char> password,
            PbeParameters pbeParameters);
        public string ExportEncryptedPkcs8PrivateKeyPem(
            ReadOnlySpan<byte> passwordBytes,
            PbeParameters pbeParameters);

        public int ExportMLDsaPublicKey(Span<byte> destination);
        public int ExportMLDsaSecretKey(Span<byte> destination);
        public int ExportMLDsaPrivateSeed(Span<byte> destination);

        public static MLDsa GenerateMLDsa44Key();
        public static MLDsa GenerateMLDsa65Key();
        public static MLDsa GenerateMLDsa87Key();

        public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
        public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
        public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
        public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
        public static MLDsa ImportFromPem(ReadOnlySpan<char> source);
        public static MLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
        public static MLDsa ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
        public static MLDsa ImportMLDsaPublicKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static MLDsa ImportMLDsaSecretKey(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
        public static MLDsa ImportMLDsaPrivateSeed(MLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);

        protected void ThrowIfDisposed();
        protected virtual void Dispose(bool disposing);

        protected abstract void SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination);
        protected abstract bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature);
        protected abstract void SignPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, HashAlgorithmName preHashAlgorithm, Span<byte> destination);
        protected abstract bool VerifyPreHashCore(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> context, HashAlgorithmName preHashAlgorithm, ReadOnlySpan<byte> signature);

        protected abstract void ExportMLDsaPublicKeyCore(Span<byte> destination);
        protected abstract void ExportMLDsaSecretKeyCore(Span<byte> destination);
        protected abstract void ExportMLDsaPrivateSeedCore(Span<byte> destination);
    }

    [DebuggerDisplay("{Name,nq}")]
    [Experimental("SYSLIB5006")]
    public sealed class MLDsaAlgorithm
    {
        private MLDsaAlgorithm();

        public static MLDsaAlgorithm MLDsa44 { get; }
        public static MLDsaAlgorithm MLDsa65 { get; }
        public static MLDsaAlgorithm MLDsa87 { get; }

        public string Name { get; }
        public int PublicKeySizeInBytes { get; }
        public int SecretKeySizeInBytes { get; }
        public int SignatureSizeInBytes { get; }
    }

    [Experimental("SYSLIB5006")]
    public class MLDsaCng : MLDsa
    {
         public MLDsaCng(CngKey key);
         // On ECDsaCng this is an allocating property. Changed to a method here.
         public CngKey GetCngKey();
    }

    [Experimental("SYSLIB5006")]
    public class MLDsaOpenSsl : MLDsa
    {
         public MLDsaOpenSsl(SafeEvpPKeyHandle keyHandle);
         public SafeEvpPKeyHandle DuplicateKeyHandle();
    }
}

namespace System.Security.Cryptography.X509Certificates
{
    // Extension class to enable porting to Microsoft.Bcl.Cryptography
    [Experimental("SYSLIB5006")]
    public sealed class MLDsaCertificateExtensions
    {
        public static MLDsa GetMLDsaPublicKey(this X509Certificate2 certificate);
        public static MLDsa GetMLDsaPrivateKey(this X509Certificate2 certificate);
    }

    public partial class CertificateRequest
    {
        [Experimental("SYSLIB5006")]
        public CertificateRequest(string subjectName, MLDsa key);
        [Experimental("SYSLIB5006")]
        public CertificateRequest(X500DistinguishedName subjectName, MLDsa key);
    }

#if NET10_OR_GREATER
    public partial class X509SignatureGenerator
    {
        [Experimental("SYSLIB5006")]
        public static X509SignatureGenerator CreateForMLDsa(MLDsa key);
    }
#endif
}

#if NET10_OR_GREATER
namespace System.Security.Cryptography.Pkcs
{
    public partial class CmsSigner
    {
        [Experimental("SYSLIB5006")]
        public CmsSigner(
            X509Certificate2 certificate,
            MLDsa privateKey,
            SubjectIdentifierType signerIdentifierType = SubjectIdentifierType.SubjectKeyIdentifier);
    }
}
#endif

namespace System.Security.Cryptography.Cose
{
    public partial class CoseSigner
    {
        // As PrivateKey is marked as non-nullable, this will set it to an instance of a non-public type that wraps the key.
        [Experimental("SYSLIB5006")]
        public CoseSigner(MLDsa key);
    }
}
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.