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.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-latest (July 7, 2025), 4.1
            //  1.  Generate component keys
            //
            //      mldsaSeed = Random(32)
            //      (mldsaPK, _) = ML-DSA.KeyGen(mldsaSeed)
            //      (tradPK, tradSK) = Trad.KeyGen()
 
            MLDsa? mldsaKey = null;
            ComponentAlgorithm? tradKey = null;
 
            try
            {
                mldsaKey = MLDsaImplementation.GenerateKey(metadata.MLDsaAlgorithm);
            }
            catch (CryptographicException)
            {
            }
 
            try
            {
                tradKey = metadata.TraditionalAlgorithm switch
                {
                    RsaAlgorithm rsaAlgorithm => RsaComponent.GenerateKey(rsaAlgorithm),
                    ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.GenerateKey(ecdsaAlgorithm),
                    _ => FailAndGetNull(),
                };
 
                static ComponentAlgorithm? FailAndGetNull()
                {
                    Debug.Fail("Only supported algorithms should reach here.");
                    return null;
                }
            }
            catch (CryptographicException)
            {
            }
 
            //  2.  Check for component key gen failure
            //
            //      if NOT (mldsaPK, mldsaSK) or NOT (tradPK, tradSK):
            //          output "Key generation error"
 
            [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
            static bool KeyGenFailed([NotNullWhen(false)] MLDsa? mldsaKey, [NotNullWhen(false)] ComponentAlgorithm? tradKey) =>
                (mldsaKey is null) | (tradKey is null);
 
            if (KeyGenFailed(mldsaKey, tradKey))
            {
                try
                {
                    Debug.Assert(mldsaKey is null || tradKey is null);
 
                    mldsaKey?.Dispose();
                    tradKey?.Dispose();
                }
                catch (CryptographicException)
                {
                }
 
                throw new CryptographicException();
            }
 
            //  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-latest (June 20, 2025), 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-latest (June 20, 2025), 5.2
            //  1.  Parse each constituent encoded key.
            //      The length of an ML-DSA private key is always a 32 byte seed
            //      for all parameter sets.
            //
            //      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-latest (June 20, 2025), 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.
            //      Randomize the message representative
            //
            //          r = Random(32)
            //          M' :=  Prefix || Domain || len(ctx) || ctx || r
            //                                              || PH( M )
 
            Span<byte> r = stackalloc byte[CompositeMLDsaAlgorithm.RandomizerSizeInBytes];
            RandomNumberGenerator.Fill(r);
 
            byte[] M_prime = GetMessageRepresentative(AlgorithmDetails, context, r, 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=Domain )
            //          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> randomizer = destination.Slice(0, CompositeMLDsaAlgorithm.RandomizerSizeInBytes);
            Span<byte> mldsaSig = destination.Slice(CompositeMLDsaAlgorithm.RandomizerSizeInBytes, AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
            Span<byte> tradSig = destination.Slice(CompositeMLDsaAlgorithm.RandomizerSizeInBytes + AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
 
            bool mldsaSigned = false;
            bool tradSigned = false;
 
            try
            {
                _mldsa.SignData(M_prime, mldsaSig, AlgorithmDetails.DomainSeparator);
                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(r, mldsaSig, tradSig)
            //          return s
 
            r.CopyTo(randomizer);
            return randomizer.Length + mldsaSig.Length + tradBytesWritten;
        }
 
        protected override bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature)
        {
            // draft-ietf-lamps-pq-composite-sigs-latest (June 20, 2025), 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)
            //          (r, 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> r = signature.Slice(0, CompositeMLDsaAlgorithm.RandomizerSizeInBytes);
            ReadOnlySpan<byte> mldsaSig = signature.Slice(CompositeMLDsaAlgorithm.RandomizerSizeInBytes, AlgorithmDetails.MLDsaAlgorithm.SignatureSizeInBytes);
            ReadOnlySpan<byte> tradSig = signature.Slice(CompositeMLDsaAlgorithm.RandomizerSizeInBytes + 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 || Domain || len(ctx) || ctx || r
            //                                                   || PH( M )
 
            byte[] M_prime = GetMessageRepresentative(AlgorithmDetails, context, r, 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=Domain ) 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.DomainSeparator), _componentAlgorithm.VerifyData(M_prime, tradSig));
        }
 
        protected override bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out int bytesWritten) =>
            throw new PlatformNotSupportedException();
 
        protected override int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination)
        {
            // draft-ietf-lamps-pq-composite-sigs-latest (June 20, 2025), 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-latest (June 20, 2025), 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> r,
            ReadOnlySpan<byte> message)
        {
            checked
            {
                Debug.Assert(r.Length is CompositeMLDsaAlgorithm.RandomizerSizeInBytes);
 
                // M' = Prefix || Domain || len(ctx) || ctx || r || 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.DomainSeparator.Length +       // Domain
                        1 +                                     // len(ctx)
                        context.Length +                        // ctx
                        r.Length +                              // r
                        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;
 
                    // Domain
                    metadata.DomainSeparator.AsSpan().CopyTo(M_prime.AsSpan(offset, metadata.DomainSeparator.Length));
                    offset += metadata.DomainSeparator.Length;
 
                    // len(ctx)
                    M_prime[offset] = (byte)context.Length;
                    offset++;
 
                    // ctx
                    context.CopyTo(M_prime.AsSpan(offset, context.Length));
                    offset += context.Length;
 
                    // r
                    r.CopyTo(M_prime.AsSpan(offset, r.Length));
                    offset += r.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),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x00],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithRSA2048Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        new RsaAlgorithm(2048, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x01],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithEd25519,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        new EdDsaAlgorithm(),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x02],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa44WithECDsaP256,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa44,
                        ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x03],
                        HashAlgorithmName.SHA256)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x04],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA3072Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x05],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pss),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x06],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithRSA4096Pkcs15,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x07],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaP256,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x08],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x09],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        ECDsaAlgorithm.CreateBrainpoolP256r1(HashAlgorithmName.SHA256),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0A],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa65WithEd25519,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa65,
                        new EdDsaAlgorithm(),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0B],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaP384,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0C],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateBrainpoolP384r1(HashAlgorithmName.SHA384),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0D],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithEd448,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new EdDsaAlgorithm(),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0E],
                        new HashAlgorithmName("SHAKE256"))
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithRSA3072Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new RsaAlgorithm(3072, HashAlgorithmName.SHA256, RSASignaturePadding.Pss),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x0F],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithRSA4096Pss,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        new RsaAlgorithm(4096, HashAlgorithmName.SHA384, RSASignaturePadding.Pss),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x10],
                        HashAlgorithmName.SHA512)
                },
                {
                    CompositeMLDsaAlgorithm.MLDsa87WithECDsaP521,
                    new AlgorithmMetadata(
                        MLDsaAlgorithm.MLDsa87,
                        ECDsaAlgorithm.CreateP521(HashAlgorithmName.SHA512),
                        [0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x86, 0xFA, 0x6B, 0x50, 0x09, 0x01, 0x11],
                        HashAlgorithmName.SHA512)
                }
            };
 
            Debug.Assert(count == algorithmMetadata.Count);
 
            return algorithmMetadata;
        }
 
        private sealed class AlgorithmMetadata(
            MLDsaAlgorithm mldsaAlgorithm,
            object traditionalAlgorithm,
            byte[] domainSeparator,
            HashAlgorithmName hashAlgorithmName)
        {
            internal MLDsaAlgorithm MLDsaAlgorithm { get; } = mldsaAlgorithm;
            internal object TraditionalAlgorithm { get; } = traditionalAlgorithm;
            internal byte[] DomainSeparator { get; } = domainSeparator;
            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
        {
        }
    }
}