diff --git a/libi2pd/Crypto.cpp b/libi2pd/Crypto.cpp
index cf8303d1..be2863f4 100644
--- a/libi2pd/Crypto.cpp
+++ b/libi2pd/Crypto.cpp
@@ -1008,86 +1008,5 @@ namespace crypto
 /*		CRYPTO_set_locking_callback (nullptr);
 		m_OpenSSLMutexes.clear ();*/
 	}
-
-#if OPENSSL_PQ	
-	
-	MLKEMKeys::MLKEMKeys (std::string_view name, size_t keyLen, size_t ctLen):
-		m_Name (name), m_KeyLen (keyLen), m_CTLen (ctLen),m_Pkey (nullptr)
-	{
-	}
-	
-	MLKEMKeys::~MLKEMKeys ()
-	{
-		if (m_Pkey) EVP_PKEY_free (m_Pkey);
-	}	
-
-	void MLKEMKeys::GenerateKeys ()
-	{
-		if (m_Pkey) EVP_PKEY_free (m_Pkey);
-		m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ());
-	}
-
-	void MLKEMKeys::GetPublicKey (uint8_t * pub) const
-	{	
-		if (m_Pkey)
-		{	
-			size_t len = m_KeyLen;
-		    EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len);
-		}	
-	}
-
-	void MLKEMKeys::SetPublicKey (const uint8_t * pub)
-	{
-		if (m_Pkey)
-		{	
-			EVP_PKEY_free (m_Pkey);
-			m_Pkey = nullptr;
-		}	
-		OSSL_PARAM params[] =
-		{
-			OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen),
-			OSSL_PARAM_END
-		};		
-		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL);
-		if (ctx)
-		{
-			EVP_PKEY_fromdata_init (ctx);
-			EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
-	}
-
-	void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared)
-	{
-		if (!m_Pkey) return;
-		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-		if (ctx)
-		{	
-			EVP_PKEY_encapsulate_init (ctx, NULL);
-			size_t len = m_CTLen, sharedLen = 32;
-			EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
-	}	
-
-	void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared)
-	{
-		if (!m_Pkey) return;
-		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
-		if (ctx)
-		{	
-			EVP_PKEY_decapsulate_init (ctx, NULL);
-			size_t sharedLen = 32;
-			EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen);
-			EVP_PKEY_CTX_free (ctx);
-		}
-		else
-			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
-	}	
-#endif		
 }
 }
diff --git a/libi2pd/Crypto.h b/libi2pd/Crypto.h
index 10f65b32..6abbd9b2 100644
--- a/libi2pd/Crypto.h
+++ b/libi2pd/Crypto.h
@@ -11,10 +11,7 @@
 
 #include <inttypes.h>
 #include <string>
-#include <string_view>
 #include <vector>
-#include <array>
-#include <tuple>
 #include <openssl/bn.h>
 #include <openssl/dh.h>
 #include <openssl/aes.h>
@@ -279,66 +276,6 @@ namespace crypto
 // init and terminate
 	void InitCrypto (bool precomputation);
 	void TerminateCrypto ();
-
-#if OPENSSL_PQ	
-// Post Quantum
-	
-	class MLKEMKeys
-	{
-		public:
-
-			MLKEMKeys (std::string_view name, size_t keyLen, size_t ctLen);
-			~MLKEMKeys ();
-
-			void GenerateKeys ();
-			void GetPublicKey (uint8_t * pub) const;
-			void SetPublicKey (const uint8_t * pub);
-			void Encaps (uint8_t * ciphertext, uint8_t * shared);
-			void Decaps (const uint8_t * ciphertext, uint8_t * shared);
-			
-		private:
-
-			const std::string m_Name;
-			const size_t m_KeyLen, m_CTLen;
-			EVP_PKEY * m_Pkey;		
-	};
-
-	constexpr size_t MLKEM512_KEY_LENGTH = 800;
-	constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768;
-	constexpr size_t MLKEM768_KEY_LENGTH = 1184;
-	constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088;
-	constexpr size_t MLKEM1024_KEY_LENGTH = 1568;
-	constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568;
-	
-	constexpr std::array<std::tuple<std::string_view, size_t, size_t>, 3> MLKEMS =
-	{
-		std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH),
-		std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH),
-		std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH)
-	};	
-	
-	class MLKEM512Keys: public MLKEMKeys
-	{
-		public:
-
-			MLKEM512Keys (): MLKEMKeys (std::get<0>(MLKEMS[0]), std::get<1>(MLKEMS[0]), std::get<2>(MLKEMS[0])) {}
-	};
-
-	class MLKEM768Keys: public MLKEMKeys
-	{
-		public:
-
-			MLKEM768Keys (): MLKEMKeys (std::get<0>(MLKEMS[1]), std::get<1>(MLKEMS[1]), std::get<2>(MLKEMS[1])) {}
-	};
-
-	class MLKEM1024Keys: public MLKEMKeys
-	{
-		public:
-
-			MLKEM1024Keys (): MLKEMKeys (std::get<0>(MLKEMS[2]), std::get<1>(MLKEMS[2]), std::get<2>(MLKEMS[2])) {}
-	};
-	
-#endif	
 }
 }
 
diff --git a/libi2pd/CryptoKey.h b/libi2pd/CryptoKey.h
index 1e0ea3f2..14ef4fa8 100644
--- a/libi2pd/CryptoKey.h
+++ b/libi2pd/CryptoKey.h
@@ -183,23 +183,6 @@ namespace crypto
 		};	
 		return 0;
 	}
-	
-#if OPENSSL_PQ
-	constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
-		return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
-	}	
-
-	constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type)
-	{
-		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
-		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
-		return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
-	}
-	
-#endif	
 }
 }
 
diff --git a/libi2pd/ECIESX25519AEADRatchetSession.cpp b/libi2pd/ECIESX25519AEADRatchetSession.cpp
index d5b7150d..5d563d97 100644
--- a/libi2pd/ECIESX25519AEADRatchetSession.cpp
+++ b/libi2pd/ECIESX25519AEADRatchetSession.cpp
@@ -11,7 +11,7 @@
 #include "Log.h"
 #include "util.h"
 #include "Crypto.h"
-#include "CryptoKey.h"
+#include "PostQuantum.h"
 #include "Elligator.h"
 #include "Tag.h"
 #include "I2PEndian.h"
diff --git a/libi2pd/ECIESX25519AEADRatchetSession.h b/libi2pd/ECIESX25519AEADRatchetSession.h
index 2077219f..b7704534 100644
--- a/libi2pd/ECIESX25519AEADRatchetSession.h
+++ b/libi2pd/ECIESX25519AEADRatchetSession.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 #include "Identity.h"
 #include "Crypto.h"
+#include "PostQuantum.h"
 #include "Garlic.h"
 #include "Tag.h"
 
@@ -228,7 +229,7 @@ namespace garlic
 			uint8_t m_NSREncodedKey[32], m_NSRH[32], m_NSRKey[32]; // new session reply, for incoming only
 			std::shared_ptr<i2p::crypto::X25519Keys> m_EphemeralKeys;
 #if OPENSSL_PQ	
-			std::unique_ptr<i2p::crypto::MLKEM512Keys> m_PQKeys;
+			std::unique_ptr<i2p::crypto::MLKEMKeys> m_PQKeys;
 			std::unique_ptr<std::vector<uint8_t> > m_NSREncodedPQKey;
 #endif			
 			SessionState m_State = eSessionStateNew;
diff --git a/libi2pd/PostQuantum.cpp b/libi2pd/PostQuantum.cpp
new file mode 100644
index 00000000..abe207d2
--- /dev/null
+++ b/libi2pd/PostQuantum.cpp
@@ -0,0 +1,101 @@
+/*
+* Copyright (c) 2025, The PurpleI2P Project
+*
+* This file is part of Purple i2pd project and licensed under BSD3
+*
+* See full license text in LICENSE file at top of project tree
+*/
+
+#include "Log.h"
+#include "PostQuantum.h"
+
+#if OPENSSL_PQ
+
+#include <openssl/param_build.h>
+#include <openssl/core_names.h>
+
+namespace i2p
+{
+namespace crypto
+{
+	MLKEMKeys::MLKEMKeys (std::string_view name, size_t keyLen, size_t ctLen):
+		m_Name (name), m_KeyLen (keyLen), m_CTLen (ctLen),m_Pkey (nullptr)
+	{
+	}
+	
+	MLKEMKeys::~MLKEMKeys ()
+	{
+		if (m_Pkey) EVP_PKEY_free (m_Pkey);
+	}	
+
+	void MLKEMKeys::GenerateKeys ()
+	{
+		if (m_Pkey) EVP_PKEY_free (m_Pkey);
+		m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ());
+	}
+
+	void MLKEMKeys::GetPublicKey (uint8_t * pub) const
+	{	
+		if (m_Pkey)
+		{	
+			size_t len = m_KeyLen;
+		    EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len);
+		}	
+	}
+
+	void MLKEMKeys::SetPublicKey (const uint8_t * pub)
+	{
+		if (m_Pkey)
+		{	
+			EVP_PKEY_free (m_Pkey);
+			m_Pkey = nullptr;
+		}	
+		OSSL_PARAM params[] =
+		{
+			OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (uint8_t *)pub, m_KeyLen),
+			OSSL_PARAM_END
+		};		
+		EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, m_Name.c_str (), NULL);
+		if (ctx)
+		{
+			EVP_PKEY_fromdata_init (ctx);
+			EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params);
+			EVP_PKEY_CTX_free (ctx);
+		}
+		else
+			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
+	}
+
+	void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared)
+	{
+		if (!m_Pkey) return;
+		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
+		if (ctx)
+		{	
+			EVP_PKEY_encapsulate_init (ctx, NULL);
+			size_t len = m_CTLen, sharedLen = 32;
+			EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen);
+			EVP_PKEY_CTX_free (ctx);
+		}
+		else
+			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
+	}	
+
+	void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared)
+	{
+		if (!m_Pkey) return;
+		auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL);
+		if (ctx)
+		{	
+			EVP_PKEY_decapsulate_init (ctx, NULL);
+			size_t sharedLen = 32;
+			EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen);
+			EVP_PKEY_CTX_free (ctx);
+		}
+		else
+			LogPrint (eLogError, "MLKEM512 can't create PKEY context");
+	}	
+}	
+}	
+
+#endif
\ No newline at end of file
diff --git a/libi2pd/PostQuantum.h b/libi2pd/PostQuantum.h
new file mode 100644
index 00000000..6081f653
--- /dev/null
+++ b/libi2pd/PostQuantum.h
@@ -0,0 +1,97 @@
+/*
+* Copyright (c) 2025, The PurpleI2P Project
+*
+* This file is part of Purple i2pd project and licensed under BSD3
+*
+* See full license text in LICENSE file at top of project tree
+*/
+
+#ifndef POST_QUANTUM_H__
+#define POST_QUANTUM_H__
+
+#include <string_view>
+#include <array>
+#include <tuple>
+#include "Crypto.h"
+#include "Identity.h"
+
+#if OPENSSL_PQ
+
+namespace i2p
+{
+namespace crypto
+{
+	class MLKEMKeys
+	{
+		public:
+
+			MLKEMKeys (std::string_view name, size_t keyLen, size_t ctLen);
+			~MLKEMKeys ();
+
+			void GenerateKeys ();
+			void GetPublicKey (uint8_t * pub) const;
+			void SetPublicKey (const uint8_t * pub);
+			void Encaps (uint8_t * ciphertext, uint8_t * shared);
+			void Decaps (const uint8_t * ciphertext, uint8_t * shared);
+			
+		private:
+
+			const std::string m_Name;
+			const size_t m_KeyLen, m_CTLen;
+			EVP_PKEY * m_Pkey;		
+	};
+
+	constexpr size_t MLKEM512_KEY_LENGTH = 800;
+	constexpr size_t MLKEM512_CIPHER_TEXT_LENGTH = 768;
+	constexpr size_t MLKEM768_KEY_LENGTH = 1184;
+	constexpr size_t MLKEM768_CIPHER_TEXT_LENGTH = 1088;
+	constexpr size_t MLKEM1024_KEY_LENGTH = 1568;
+	constexpr size_t MLKEM1024_CIPHER_TEXT_LENGTH = 1568;
+	
+	constexpr std::array<std::tuple<std::string_view, size_t, size_t>, 3> MLKEMS =
+	{
+		std::make_tuple ("ML-KEM-512", MLKEM512_KEY_LENGTH, MLKEM512_CIPHER_TEXT_LENGTH),
+		std::make_tuple ("ML-KEM-768", MLKEM768_KEY_LENGTH, MLKEM768_CIPHER_TEXT_LENGTH),
+		std::make_tuple ("ML-KEM-1024", MLKEM1024_KEY_LENGTH, MLKEM1024_CIPHER_TEXT_LENGTH)
+	};	
+	
+	class MLKEM512Keys: public MLKEMKeys
+	{
+		public:
+
+			MLKEM512Keys (): MLKEMKeys (std::get<0>(MLKEMS[0]), std::get<1>(MLKEMS[0]), std::get<2>(MLKEMS[0])) {}
+	};
+
+	class MLKEM768Keys: public MLKEMKeys
+	{
+		public:
+
+			MLKEM768Keys (): MLKEMKeys (std::get<0>(MLKEMS[1]), std::get<1>(MLKEMS[1]), std::get<2>(MLKEMS[1])) {}
+	};
+
+	class MLKEM1024Keys: public MLKEMKeys
+	{
+		public:
+
+			MLKEM1024Keys (): MLKEMKeys (std::get<0>(MLKEMS[2]), std::get<1>(MLKEMS[2]), std::get<2>(MLKEMS[2])) {}
+	};
+
+	constexpr size_t GetMLKEMPublicKeyLen (i2p::data::CryptoKeyType type)
+	{
+		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
+		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
+		return std::get<1>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
+	}	
+
+	constexpr size_t GetMLKEMCipherTextLen (i2p::data::CryptoKeyType type)
+	{
+		if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD ||
+		    type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return 0;
+		return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]);
+	}
+}	
+}	
+
+#endif
+
+#endif