File: src\libraries\Common\src\System\Security\Cryptography\CompositeMLDsaManaged.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Internal.Cryptography;
 
#if NETFRAMEWORK
using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber;
#endif
 
namespace System.Security.Cryptography
{
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
    // System.Security.Cryptography excludes browser at build time, but we need to rely on UnsupportedOSPlatform for Microsoft.Bcl.Cryptography.
    [UnsupportedOSPlatform("browser")]
#endif
    [Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
    internal sealed partial class CompositeMLDsaManaged : CompositeMLDsa
    {
        private static readonly Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> s_algorithmMetadata = CreateAlgorithmMetadata();
        private static readonly ConcurrentDictionary<CompositeMLDsaAlgorithm, bool> s_algorithmSupport = new();
 
        private static ReadOnlySpan<byte> MessageRepresentativePrefix => "CompositeAlgorithmSignatures2025"u8;
 
        private MLDsa _mldsa;
        private ComponentAlgorithm _componentAlgorithm;
 
        private AlgorithmMetadata AlgorithmDetails => field ??= s_algorithmMetadata[Algorithm];
 
        private CompositeMLDsaManaged(CompositeMLDsaAlgorithm algorithm, MLDsa mldsa, ComponentAlgorithm componentAlgorithm)
            : base(algorithm)
        {
            _mldsa = mldsa;
            _componentAlgorithm = componentAlgorithm;
        }
 
        internal static bool SupportsAny() => MLDsaImplementation.SupportsAny();
 
        internal static bool IsAlgorithmSupportedImpl(CompositeMLDsaAlgorithm algorithm)
        {
            AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
 
            return s_algorithmSupport.GetOrAdd(
                algorithm,
                alg => MLDsaImplementation.IsAlgorithmSupported(metadata.MLDsaAlgorithm) && metadata.TraditionalAlgorithm switch
                {
                    RsaAlgorithm rsaAlgorithm => RsaComponent.IsAlgorithmSupported(rsaAlgorithm),
                    ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.IsAlgorithmSupported(ecdsaAlgorithm),
                    _ => false,
                });
        }
 
        internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm)
        {
            Debug.Assert(IsAlgorithmSupportedImpl(algorithm));
 
            AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
 
            // draft-ietf-lamps-pq-composite-sigs-08, 4.1
            // 1. Generate component keys
            //
            //    mldsaSeed = Random(32)
            //    (mldsaPK, mldsaSK) = ML-DSA.KeyGen(mldsaSeed)
            //    (tradPK, tradSK) = Trad.KeyGen()
            //
            // 2. Check for component key gen failure
            //
            //    if NOT (mldsaPK, mldsaSK) or NOT (tradPK, tradSK):
            //      output "Key generation error"
 
            MLDsa mldsaKey = MLDsaImplementation.GenerateKey(metadata.MLDsaAlgorithm);
 
            ComponentAlgorithm? tradKey;
 
            try
            {
                tradKey = metadata.TraditionalAlgorithm switch
                {
                    RsaAlgorithm rsaAlgorithm => RsaComponent.GenerateKey(rsaAlgorithm),
                    ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.GenerateKey(ecdsaAlgorithm),
                    _ => throw FailAndGetException(),
                };
 
                static CryptographicException FailAndGetException()
                {
                    Debug.Fail("Only supported algorithms should reach here.");
                    return new CryptographicException();
                }
            }
            catch (CryptographicException)
            {
                mldsaKey.Dispose();
 
                throw;
            }
 
            // 3. Output the composite public and private keys
            //
            //    pk = SerializePublicKey(mldsaPK, tradPK)
            //    sk = SerializePrivateKey(mldsaSeed, tradSK)
            //    return (pk, sk)
 
            return new CompositeMLDsaManaged(algorithm, mldsaKey, tradKey);
        }
 
        internal static CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
        {
            Debug.Assert(IsAlgorithmSupportedImpl(algorithm));
 
            AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
 
            // draft-ietf-lamps-pq-composite-sigs-08, 5.1
            // 1. Parse each constituent encoded public key.
            //    The length of the mldsaKey is known based on the
            //    size of the ML-DSA component key length specified
            //    by the Object ID.
            //
            //    switch ML-DSA do
            //       case ML-DSA-44:
            //         mldsaPK = bytes[:1312]
            //         tradPK  = bytes[1312:]
            //       case ML-DSA-65:
            //         mldsaPK = bytes[:1952]
            //         tradPK  = bytes[1952:]
            //       case ML-DSA-87:
            //         mldsaPK = bytes[:2592]
            //         tradPK  = bytes[2592:]
            //
            //    Note that while ML-DSA has fixed-length keys, RSA and
            //    ECDSA may not, depending on encoding, so rigorous
            //    length-checking of the overall composite key is not
            //    always possible.
            //
            // 2. Output the component public keys
            //
            //    output (mldsaPK, tradPK)
 
            ReadOnlySpan<byte> mldsaKey = source.Slice(0, metadata.MLDsaAlgorithm.PublicKeySizeInBytes);
            ReadOnlySpan<byte> tradKey = source.Slice(metadata.MLDsaAlgorithm.PublicKeySizeInBytes);
 
            MLDsaImplementation mldsa = MLDsaImplementation.ImportPublicKey(metadata.MLDsaAlgorithm, mldsaKey);
            ComponentAlgorithm componentAlgorithm = metadata.TraditionalAlgorithm switch
            {
                RsaAlgorithm rsaAlgorithm => RsaComponent.ImportPublicKey(rsaAlgorithm, tradKey),
                ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.ImportPublicKey(ecdsaAlgorithm, tradKey),
                _ => throw FailAndGetException(),
            };
 
            static CryptographicException FailAndGetException()
            {
                Debug.Fail("Only supported algorithms should reach here.");
                return new CryptographicException();
            }
 
            return new CompositeMLDsaManaged(algorithm, mldsa, componentAlgorithm);
        }
 
        internal static CompositeMLDsa ImportCompositeMLDsaPrivateKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
        {
            Debug.Assert(IsAlgorithmSupportedImpl(algorithm));
 
            AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
 
            // draft-ietf-lamps-pq-composite-sigs-08, 5.2
            // 1. Parse each constituent encoded key.
            //
            //    mldsaSeed = bytes[:32]
            //    tradSK  = bytes[32:]
            //
            //    Note that while ML-DSA has fixed-length keys, RSA and ECDSA
            //    may not, depending on encoding, so rigorous length-checking
            //    of the overall composite key is not always possible.
            //
            // 2. Output the component private keys
            //
            //    output (mldsaSeed, tradSK)
 
            ReadOnlySpan<byte> mldsaKey = source.Slice(0, metadata.MLDsaAlgorithm.PrivateSeedSizeInBytes);
            ReadOnlySpan<byte> tradKey = source.Slice(metadata.MLDsaAlgorithm.PrivateSeedSizeInBytes);
 
            MLDsaImplementation mldsa = MLDsaImplementation.ImportSeed(metadata.MLDsaAlgorithm, mldsaKey);
            ComponentAlgorithm componentAlgorithm = metadata.TraditionalAlgorithm switch
            {
                RsaAlgorithm rsaAlgorithm => RsaComponent.ImportPrivateKey(rsaAlgorithm, tradKey),
                ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.ImportPrivateKey(ecdsaAlgorithm, tradKey),
                _ => throw FailAndGetException(),
            };
 
            static CryptographicException FailAndGetException()
            {
                Debug.Fail("Only supported algorithms should reach here.");
                return new CryptographicException();
            }
 
            return new CompositeMLDsaManaged(algorithm, mldsa, componentAlgorithm);
        }
 
        protected override int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination)
        {
            // draft-ietf-lamps-pq-composite-sigs-08, 4.2
            // 1. If len(ctx) > 255:
            //     return error
 
            Debug.Assert(context.Length <= 255, $"Caller should have checked context.Length, got {context.Length}");
 
            // 2. Compute the Message representative M'.
            //    As in FIPS 204, len(ctx) is encoded as a single unsigned byte.
            //
            //       M' :=  Prefix || Label || len(ctx) || ctx || PH( M )
 
            byte[] M_prime = GetMessageRepresentative(AlgorithmDetails, context, data);
 
            // 3. Separate the private key into component keys
            //    and re-generate the ML-DSA key from seed.
            //
            //      (mldsaSeed, tradSK) = DeserializePrivateKey(sk)
            //      (_, mldsaSK) = ML-DSA.KeyGen(mldsaSeed)
 
            /* no-op */
 
            // 4. Generate the two component signatures independently by
            //    calculating the signature over M' according to their algorithm
            //    specifications.
            //
            //      mldsaSig = ML-DSA.Sign( mldsaSK, M', ctx=Label )
            //      tradSig = Trad.Sign( tradSK, M' )
 
            //  Note that in step 4 above, both component signature processes are
            //  invoked, and no indication is given about which one failed.This
            //  SHOULD be done in a timing-invariant way to prevent side-channel
            //  attackers from learning which component algorithm failed.
 
            Span<byte> mldsaSig = destination.Slice(0, AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
            Span<byte> tradSig = destination.Slice(AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
 
            bool mldsaSigned = false;
            bool tradSigned = false;
 
            try
            {
                _mldsa.SignData(M_prime, mldsaSig, AlgorithmDetails.Label);
                mldsaSigned = true;
            }
            catch (CryptographicException)
            {
            }
 
            int tradBytesWritten = 0;
 
            try
            {
                tradBytesWritten = _componentAlgorithm.SignData(M_prime, tradSig);
                tradSigned = true;
            }
            catch (CryptographicException)
            {
            }
 
            // 5. If either ML-DSA.Sign() or Trad.Sign() return an error, then
            //    this process MUST return an error.
            //
            //     if NOT mldsaSig or NOT tradSig:
            //       output "Signature generation error"
 
            [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
            static bool Or(bool x, bool y) => x | y;
 
            if (Or(!mldsaSigned, !tradSigned))
            {
                CryptographicOperations.ZeroMemory(destination);
                throw new CryptographicException(SR.Cryptography_CompositeSignDataError);
            }
 
            // 6. Output the encoded composite signature value.
            //
            //     s = SerializeSignatureValue(mldsaSig, tradSig)
            //     return s
 
            return mldsaSig.Length + tradBytesWritten;
        }
 
        protected override bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature)
        {
            // draft-ietf-lamps-pq-composite-sigs-08, 4.3
            // 1. If len(ctx) > 255
            //      return error
 
            Debug.Assert(context.Length <= 255, $"Caller should have checked context.Length, got {context.Length}");
 
            // 2. Separate the keys and signatures
            //
            //    (mldsaPK, tradPK)       = DeserializePublicKey(pk)
            //    (mldsaSig, tradSig)  = DeserializeSignatureValue(s)
            //
            //  If Error during deserialization, or if any of the component
            //  keys or signature values are not of the correct type or
            //  length for the given component algorithm then output
            //  "Invalid signature" and stop.
 
            ReadOnlySpan<byte> mldsaSig = signature.Slice(0, AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
            ReadOnlySpan<byte> tradSig = signature.Slice(AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
 
            // 3. Compute a Hash of the Message.
            //    As in FIPS 204, len(ctx) is encoded as a single unsigned byte.
            //
            //     M' = Prefix || Label || len(ctx) || ctx || PH( M )
 
            byte[] M_prime = GetMessageRepresentative(AlgorithmDetails, context, data);
 
            // 4. Check each component signature individually, according to its
            //    algorithm specification.
            //    If any fail, then the entire signature validation fails.
            //
            //     if not ML-DSA.Verify( mldsaPK, M', mldsaSig, ctx=Label ) then
            //         output "Invalid signature"
            //
            //     if not Trad.Verify( tradPK, M', tradSig ) then
            //         output "Invalid signature"
            //
            //     if all succeeded, then
            //        output "Valid signature"
 
            // We don't short circuit here because we want to avoid revealing which component signature failed.
            // This is not required in the spec, but it is a good practice to avoid timing attacks.
 
            [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
            static bool And(bool x, bool y) => x & y;
 
            return And(_mldsa.VerifyData(M_prime, mldsaSig, AlgorithmDetails.Label), _componentAlgorithm.VerifyData(M_prime, tradSig));
        }
 
        protected override bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten)
        {
            AsnWriter? writer = null;
 
            try
            {
                using (CryptoPoolLease lease = CryptoPoolLease.Rent(Algorithm.MaxPrivateKeySizeInBytes))
                {
                    int privateKeySize = ExportCompositeMLDsaPrivateKeyCore(lease.Span);
 
                    // Add some overhead for the ASN.1 structure.
                    int initialCapacity = 32 + privateKeySize;
 
                    writer = new AsnWriter(AsnEncodingRules.DER, initialCapacity);
 
                    using (writer.PushSequence())
                    {
                        writer.WriteInteger(0); // Version
 
                        using (writer.PushSequence())
                        {
                            writer.WriteObjectIdentifier(Algorithm.Oid);
                        }
 
                        writer.WriteOctetString(lease.Span.Slice(0, privateKeySize));
                    }
 
                    Debug.Assert(writer.GetEncodedLength() <= initialCapacity);
                }
 
                return writer.TryEncode(destination, out bytesWritten);
            }
            finally
            {
                writer?.Reset();
            }
        }
 
        protected override int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination)
        {
            // draft-ietf-lamps-pq-composite-sigs-08, 5.1
            // 1. Combine and output the encoded public key
            //
            //    output mldsaPK || tradPK
 
            int bytesWritten = 0;
 
            _mldsa.ExportMLDsaPublicKey(destination.Slice(0, AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes));
            bytesWritten += AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes;
 
            if (!_componentAlgorithm.TryExportPublicKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PublicKeySizeInBytes), out int componentBytesWritten))
            {
                throw new CryptographicException();
            }
 
            bytesWritten += componentBytesWritten;
 
            return bytesWritten;
        }
 
        protected override int ExportCompositeMLDsaPrivateKeyCore(Span<byte> destination)
        {
            // draft-ietf-lamps-pq-composite-sigs-08, 5.2
            // 1. Combine and output the encoded private key.
            //
            //    output mldsaSeed || tradSK
 
            try
            {
                int bytesWritten = 0;
 
                _mldsa.ExportMLDsaPrivateSeed(destination.Slice(0, AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes));
                bytesWritten += AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes;
 
                if (!_componentAlgorithm.TryExportPrivateKey(destination.Slice(AlgorithmDetails.MLDsaAlgorithm.PrivateSeedSizeInBytes), out int componentBytesWritten))
                {
                    throw new CryptographicException();
                }
 
                bytesWritten += componentBytesWritten;
 
                return bytesWritten;
            }
            catch (CryptographicException)
            {
                CryptographicOperations.ZeroMemory(destination);
                throw;
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _mldsa?.Dispose();
                _mldsa = null!;
 
                _componentAlgorithm?.Dispose();
                _componentAlgorithm = null!;
            }
 
            base.Dispose(disposing);
        }
 
        private static byte[] GetMessageRepresentative(
            AlgorithmMetadata metadata,
            ReadOnlySpan<byte> context,
            ReadOnlySpan<byte> message)
        {
            checked
            {
                // M' = Prefix || Label || len(ctx) || ctx || PH( M )
 
                using (IncrementalHash hash = IncrementalHash.CreateHash(metadata.HashAlgorithmName))
                {
#if NET
                    int hashLength = hash.HashLengthInBytes;
#else
                    int hashLength = hash.GetHashLengthInBytes();
#endif
 
                    int length =
                        MessageRepresentativePrefix.Length +    // Prefix
                        metadata.Label.Length +                 // Label
                        1 +                                     // len(ctx)
                        context.Length +                        // ctx
                        hashLength;                             // PH( M )
 
                    // The representative message will often be < 256 bytes so we can stackalloc with a callback.
                    // That gets a little messy on .NET Framework where by-ref generics aren't supported, so we just allocate.
                    byte[] M_prime = new byte[length];
 
                    int offset = 0;
 
                    // Prefix
                    MessageRepresentativePrefix.CopyTo(M_prime.AsSpan(offset, MessageRepresentativePrefix.Length));
                    offset += MessageRepresentativePrefix.Length;
 
                    // Label
                    metadata.Label.AsSpan().CopyTo(M_prime.AsSpan(offset, metadata.Label.Length));
                    offset += metadata.Label.Length;
 
                    // len(ctx)
                    M_prime[offset] = (byte)context.Length;
                    offset++;
 
                    // ctx
                    context.CopyTo(M_prime.AsSpan(offset, context.Length));
                    offset += context.Length;
 
                    // PH( M )
                    hash.AppendData(message);
#if NET
                    hash.GetHashAndReset(M_prime.AsSpan(offset, hashLength));
#else
                    byte[] hashBytes = hash.GetHashAndReset();
                    hashBytes.CopyTo(M_prime.AsSpan(offset, hashLength));
#endif
                    offset += hashLength;
 
                    Debug.Assert(offset == M_prime.Length);
 
                    return M_prime;
                }
            }
        }
 
#if DESIGNTIMEINTERFACES
        private interface IComponentAlgorithmFactory<TComponentAlgorithm, TAlgorithmDescriptor>
            where TComponentAlgorithm : ComponentAlgorithm, IComponentAlgorithmFactory<TComponentAlgorithm, TAlgorithmDescriptor>
        {
            internal static abstract bool IsAlgorithmSupported(TAlgorithmDescriptor algorithm);
            internal static abstract TComponentAlgorithm GenerateKey(TAlgorithmDescriptor algorithm);
            internal static abstract TComponentAlgorithm ImportPrivateKey(TAlgorithmDescriptor algorithm, ReadOnlySpan<byte> source);
            internal static abstract TComponentAlgorithm ImportPublicKey(TAlgorithmDescriptor algorithm, ReadOnlySpan<byte> source);
        }
#endif
 
        private abstract class ComponentAlgorithm : IDisposable
        {
            private bool _disposed;
 
            internal abstract bool TryExportPublicKey(Span<byte> destination, out int bytesWritten);
            internal abstract bool TryExportPrivateKey(Span<byte> destination, out int bytesWritten);
 
            internal abstract int SignData(
#if NET
                ReadOnlySpan<byte> data,
#else
                byte[] data,
#endif
                Span<byte> destination);
 
            internal abstract bool VerifyData(
#if NET
                ReadOnlySpan<byte> data,
#else
                byte[] data,
#endif
                ReadOnlySpan<byte> signature);
 
            public void Dispose()
            {
                if (!_disposed)
                {
                    _disposed = true;
                    Dispose(true);
                    GC.SuppressFinalize(this);
                }
            }
 
            protected virtual void Dispose(bool disposing)
            {
            }
        }
 
        private static Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> CreateAlgorithmMetadata()
        {
            const int count = 18;
 
            Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> algorithmMetadata = new(count)
            {
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        new RsaAlgorithm(2048, HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
                        [.."COMPSIG-MLDSA44-RSA2048-PSS-SHA256"u8],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        new RsaAlgorithm(2048, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),
                        [.."COMPSIG-MLDSA44-RSA2048-PKCS15-SHA256"u8],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithEd25519,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        new EdDsaAlgorithm(),
                        [.. "COMPSIG-MLDSA44-Ed25519-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithECDsaP256,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256),
                        [.."COMPSIG-MLDSA44-ECDSA-P256-SHA256"u8],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
                        [.."COMPSIG-MLDSA65-RSA3072-PSS-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),
                        [.."COMPSIG-MLDSA65-RSA3072-PKCS15-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pss),
                        [.."COMPSIG-MLDSA65-RSA4096-PSS-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1),
                        [.."COMPSIG-MLDSA65-RSA4096-PKCS15-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaP256,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256),
                        [.."COMPSIG-MLDSA65-P256-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
                        [.."COMPSIG-MLDSA65-P384-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateBrainpoolP256r1(HashAlgorithmName.SHA256),
                        [.."COMPSIG-MLDSA65-BP256-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithEd25519,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new EdDsaAlgorithm(),
                        [.."COMPSIG-MLDSA65-Ed25519-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaP384,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
                        [.."COMPSIG-MLDSA87-P384-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateBrainpoolP384r1(HashAlgorithmName.SHA384),
                        [.."COMPSIG-MLDSA87-BP384-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithEd448,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new EdDsaAlgorithm(),
                        [.."COMPSIG-MLDSA87-Ed448-SHAKE256"u8],
                        new HashAlgorithmName("SHAKE256"))
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithRSA3072Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
                        [.."COMPSIG-MLDSA87-RSA3072-PSS-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithRSA4096Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pss),
                        [.."COMPSIG-MLDSA87-RSA4096-PSS-SHA512"u8],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaP521,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateP521(HashAlgorithmName.SHA512),
                        [.."COMPSIG-MLDSA87-P521-SHA512"u8],
                        HashAlgorithmName.SHA512)
                }
            };
 
            Debug.Assert(count == algorithmMetadata.Count);
 
            return algorithmMetadata;
        }
 
        private sealed class AlgorithmMetadata(
            MLDsaAlgorithm mldsaAlgorithm,
            object traditionalAlgorithm,
            byte[] label,
            HashAlgorithmName hashAlgorithmName)
        {
            internal MLDsaAlgorithm MLDsaAlgorithm { get; } = mldsaAlgorithm;
            internal object TraditionalAlgorithm { get; } = traditionalAlgorithm;
            internal byte[] Label { get; } = label;
            internal HashAlgorithmName HashAlgorithmName { get; } = hashAlgorithmName;
        }
 
        private sealed class RsaAlgorithm(int keySizeInBits, HashAlgorithmName hashAlgorithmName, RSASignaturePadding padding)
        {
            internal int KeySizeInBits { get; } = keySizeInBits;
            internal HashAlgorithmName HashAlgorithmName { get; } = hashAlgorithmName;
            internal RSASignaturePadding Padding { get; } = padding;
        }
 
        private sealed class ECDsaAlgorithm
        {
            internal int KeySizeInBits { get; }
            internal HashAlgorithmName HashAlgorithmName { get; }
 
#if NET || NETSTANDARD
            internal ECCurve Curve { get; }
            internal Oid CurveOid => Curve.Oid;
#else
            internal Oid CurveOid { get; }
            internal KeyBlobMagicNumber PrivateKeyBlobMagicNumber { get; }
            internal KeyBlobMagicNumber PublicKeyBlobMagicNumber { get; }
#endif
 
            internal string CurveOidValue => CurveOid.Value!;
 
            internal int KeySizeInBytes => (KeySizeInBits + 7) / 8;
 
            private ECDsaAlgorithm(
                int keySizeInBits,
#if NET || NETSTANDARD
                ECCurve curve,
#else
                Oid curveOid,
                KeyBlobMagicNumber privateKeyBlobMagicNumber,
                KeyBlobMagicNumber publicKeyBlobMagicNumber,
#endif
                HashAlgorithmName hashAlgorithmName)
            {
                KeySizeInBits = keySizeInBits;
                HashAlgorithmName = hashAlgorithmName;
 
#if NET || NETSTANDARD
                Curve = curve;
#else
                CurveOid = curveOid;
                PrivateKeyBlobMagicNumber = privateKeyBlobMagicNumber;
                PublicKeyBlobMagicNumber = publicKeyBlobMagicNumber;
#endif
 
                Debug.Assert(CurveOid.Value is not null);
            }
 
            internal static ECDsaAlgorithm CreateP256(HashAlgorithmName hashAlgorithmName) =>
                new ECDsaAlgorithm(
                    256,
#if NET || NETSTANDARD
                    ECCurve.NamedCurves.nistP256,
#else
                    new Oid(Oids.secp256r1, "nistP256"),
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P256_MAGIC,
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P256_MAGIC,
#endif
                    hashAlgorithmName);
 
            internal static ECDsaAlgorithm CreateP384(HashAlgorithmName hashAlgorithmName) =>
                new ECDsaAlgorithm(
                    384,
#if NET || NETSTANDARD
                    ECCurve.NamedCurves.nistP384,
#else
                    new Oid(Oids.secp384r1, "nistP384"),
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P384_MAGIC,
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P384_MAGIC,
#endif
                    hashAlgorithmName);
 
            internal static ECDsaAlgorithm CreateP521(HashAlgorithmName hashAlgorithmName) =>
                new ECDsaAlgorithm(
                    521,
#if NET || NETSTANDARD
                    ECCurve.NamedCurves.nistP521,
#else
                    new Oid(Oids.secp521r1, "nistP521"),
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_P521_MAGIC,
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_P521_MAGIC,
#endif
                    hashAlgorithmName);
 
            internal static ECDsaAlgorithm CreateBrainpoolP256r1(HashAlgorithmName hashAlgorithmName) =>
                new ECDsaAlgorithm(
                    256,
#if NET || NETSTANDARD
                    ECCurve.NamedCurves.brainpoolP256r1,
#else
                    new Oid(Oids.brainpoolP256r1, "brainpoolP256r1"),
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC,
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC,
#endif
                    hashAlgorithmName);
 
            internal static ECDsaAlgorithm CreateBrainpoolP384r1(HashAlgorithmName hashAlgorithmName) =>
                new ECDsaAlgorithm(
                    384,
#if NET || NETSTANDARD
                    ECCurve.NamedCurves.brainpoolP384r1,
#else
                    new Oid(Oids.brainpoolP384r1, "brainpoolP384r1"),
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC,
                    KeyBlobMagicNumber.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC,
#endif
                    hashAlgorithmName);
        }
 
        private sealed class EdDsaAlgorithm
        {
        }
    }
}