diff --git a/src/base/net/stratum/Client.cpp b/src/base/net/stratum/Client.cpp
index e4d1d53ae..039ff6930 100644
--- a/src/base/net/stratum/Client.cpp
+++ b/src/base/net/stratum/Client.cpp
@@ -53,6 +53,7 @@
 #include "base/net/tools/NetBuffer.h"
 #include "base/tools/Chrono.h"
 #include "base/tools/Cvt.h"
+#include "base/tools/cryptonote/BlobReader.h"
 #include "net/JobResult.h"
 
 
@@ -83,7 +84,8 @@ static const char *states[] = {
 xmrig::Client::Client(int id, const char *agent, IClientListener *listener) :
     BaseClient(id, listener),
     m_agent(agent),
-    m_sendBuf(1024)
+    m_sendBuf(1024),
+    m_tempBuf(256)
 {
     m_reader.setListener(this);
     m_key = m_storage.add(this);
@@ -198,11 +200,16 @@ int64_t xmrig::Client::submit(const JobResult &result)
     const char *nonce = result.nonce;
     const char *data  = result.result;
 #   else
-    char *nonce = m_sendBuf.data();
-    char *data  = m_sendBuf.data() + 16;
+    char *nonce = m_tempBuf.data();
+    char *data  = m_tempBuf.data() + 16;
+    char *signature = m_tempBuf.data() + 88;
 
     Cvt::toHex(nonce, sizeof(uint32_t) * 2 + 1, reinterpret_cast<const uint8_t *>(&result.nonce), sizeof(uint32_t));
     Cvt::toHex(data, 65, result.result(), 32);
+
+    if (result.minerSignature()) {
+        Cvt::toHex(signature, 129, result.minerSignature(), 64);
+    }
 #   endif
 
     Document doc(kObjectType);
@@ -214,6 +221,12 @@ int64_t xmrig::Client::submit(const JobResult &result)
     params.AddMember("nonce",  StringRef(nonce), allocator);
     params.AddMember("result", StringRef(data), allocator);
 
+#   ifndef XMRIG_PROXY_PROJECT
+    if (result.minerSignature()) {
+        params.AddMember("sig", StringRef(signature), allocator);
+    }
+#   endif
+
     if (has<EXT_ALGO>() && result.algorithm.isValid()) {
         params.AddMember("algo", StringRef(result.algorithm.shortName()), allocator);
     }
@@ -427,6 +440,24 @@ bool xmrig::Client::parseJob(const rapidjson::Value &params, int *code)
         return false;
     }
 
+#   ifndef XMRIG_PROXY_PROJECT
+    uint8_t signatureKeyBuf[32 * 2];
+    if (Cvt::fromHex(signatureKeyBuf, sizeof(signatureKeyBuf), Json::getValue(params, "sig_key"))) {
+        job.setEphemeralKeys(signatureKeyBuf, signatureKeyBuf + 32);
+
+        uint8_t major_version;
+        uint8_t minor_version;
+        uint64_t timestamp;
+
+        CBlobReader ar(job.blob(), job.size());
+        ar(major_version);
+        ar(minor_version);
+        ar(timestamp);
+
+        job.setTimestamp(timestamp);
+    }
+#   endif
+
     m_job.setClientId(m_rpcId);
 
     if (m_job != job) {
diff --git a/src/base/net/stratum/Client.h b/src/base/net/stratum/Client.h
index e6508c656..fdaac9f15 100644
--- a/src/base/net/stratum/Client.h
+++ b/src/base/net/stratum/Client.h
@@ -137,6 +137,7 @@ private:
     std::bitset<EXT_MAX> m_extensions;
     std::shared_ptr<DnsRequest> m_dns;
     std::vector<char> m_sendBuf;
+    std::vector<char> m_tempBuf;
     String m_rpcId;
     Tls *m_tls                  = nullptr;
     uint64_t m_expire           = 0;
diff --git a/src/base/net/stratum/DaemonClient.cpp b/src/base/net/stratum/DaemonClient.cpp
index bb741b336..7b42f8f8b 100644
--- a/src/base/net/stratum/DaemonClient.cpp
+++ b/src/base/net/stratum/DaemonClient.cpp
@@ -102,17 +102,28 @@ int64_t xmrig::DaemonClient::submit(const JobResult &result)
 
     char *data = (m_apiVersion == API_DERO) ? m_blockhashingblob.data() : m_blocktemplateStr.data();
 
+    const size_t sig_offset = m_job.nonceOffset() + m_job.nonceSize();
+
 #   ifdef XMRIG_PROXY_PROJECT
-    memcpy(data + 78, result.nonce, 8);
+
+    memcpy(data + m_job.nonceOffset() * 2, result.nonce, 8);
+
+    if (m_blocktemplate.has_miner_signature && result.sig) {
+        memcpy(data + sig_offset * 2, result.sig, 64 * 2);
+        memcpy(data + m_blocktemplate.tx_pubkey_index * 2, result.sig_data, 32 * 2);
+        memcpy(data + m_blocktemplate.eph_public_key_index * 2, result.sig_data + 32 * 2, 32 * 2);
+    }
+
 #   else
-    Cvt::toHex(data + 78, 8, reinterpret_cast<const uint8_t *>(&result.nonce), 4);
-#   endif
+
+    Cvt::toHex(data + m_job.nonceOffset() * 2, 8, reinterpret_cast<const uint8_t*>(&result.nonce), 4);
 
     if (m_blocktemplate.has_miner_signature) {
-        const size_t sig_offset = m_job.nonceOffset() + m_job.nonceSize();
         Cvt::toHex(data + sig_offset * 2, 128, result.minerSignature(), 64);
     }
 
+#   endif
+
     using namespace rapidjson;
     Document doc(kObjectType);
 
@@ -295,6 +306,16 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value &params, int *code)
             return false;
         }
 
+#       ifdef XMRIG_PROXY_PROJECT
+        job.setSpendSecretKey(secret_spendkey);
+        job.setMinerTx(
+            m_blocktemplate.raw_blob.data() + m_blocktemplate.miner_tx_prefix_begin_index,
+            m_blocktemplate.raw_blob.data() + m_blocktemplate.miner_tx_prefix_end_index,
+            m_blocktemplate.eph_public_key_index - m_blocktemplate.miner_tx_prefix_begin_index,
+            m_blocktemplate.tx_pubkey_index - m_blocktemplate.miner_tx_prefix_begin_index,
+            m_blocktemplate.miner_tx_merkle_tree_branch
+        );
+#       else
         uint8_t secret_viewkey[32];
         derive_view_secret_key(secret_spendkey, secret_viewkey);
 
@@ -306,29 +327,6 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value &params, int *code)
         }
 
         uint8_t derivation[32];
-
-#       ifdef XMRIG_PROXY_PROJECT
-        // Generate unique keys for miner transaction
-        // TODO: make it unique for each connection
-        {
-            uint8_t* eph_public_key = m_blocktemplate.raw_blob.data() + m_blocktemplate.eph_public_key_index;
-            uint8_t* txkey_pub = m_blocktemplate.raw_blob.data() + m_blocktemplate.tx_pubkey_index;
-            uint8_t txkey_sec[32];
-
-            generate_keys(txkey_pub, txkey_sec);
-            generate_key_derivation(public_viewkey, txkey_sec, derivation);
-            derive_public_key(derivation, 0, public_spendkey, eph_public_key);
-
-            Cvt::toHex(blocktemplate.data() + m_blocktemplate.eph_public_key_index * 2, 64, eph_public_key, 32);
-            Cvt::toHex(blocktemplate.data() + m_blocktemplate.tx_pubkey_index * 2, 64, txkey_pub, 32);
-
-            m_blocktemplate.UpdateMinerTxHash();
-            m_blocktemplate.GenerateHashingBlob();
-
-            Cvt::toHex(m_blockhashingblob.data() + m_blocktemplate.miner_tx_prefix_begin_index * 2, 64, m_blocktemplate.root_hash, 32);
-        }
-#       endif
-
         if (!generate_key_derivation(m_blocktemplate.raw_blob.data() + m_blocktemplate.tx_pubkey_index, secret_viewkey, derivation)) {
             LOG_ERR("Failed to generate key derivation for miner signature.");
             *code = 9;
@@ -359,6 +357,7 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value &params, int *code)
 
         job.setEphemeralKeys(m_blocktemplate.raw_blob.data() + m_blocktemplate.eph_public_key_index, eph_secret_key);
         job.setTimestamp(m_blocktemplate.timestamp);
+#       endif
     }
 
     if (m_apiVersion == API_DERO) {
diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp
index a8becb1a2..71d09a4ca 100644
--- a/src/base/net/stratum/Job.cpp
+++ b/src/base/net/stratum/Job.cpp
@@ -32,6 +32,7 @@
 #include "base/net/stratum/Job.h"
 #include "base/tools/Buffer.h"
 #include "base/tools/Cvt.h"
+#include "base/tools/cryptonote/BlockTemplate.h"
 #include "base/tools/cryptonote/Signatures.h"
 #include "base/crypto/keccak.h"
 
@@ -172,12 +173,23 @@ void xmrig::Job::copy(const Job &other)
     m_benchSize = other.m_benchSize;
 #   endif
 
-    m_hasMinerSignature = other.m_hasMinerSignature;
-
+#   ifdef XMRIG_PROXY_PROJECT
+    memcpy(m_spendSecretKey, other.m_spendSecretKey, sizeof(m_spendSecretKey));
+    memcpy(m_viewSecretKey, other.m_viewSecretKey, sizeof(m_viewSecretKey));
+    memcpy(m_spendPublicKey, other.m_spendPublicKey, sizeof(m_spendPublicKey));
+    memcpy(m_viewPublicKey, other.m_viewPublicKey, sizeof(m_viewPublicKey));
+    m_minerTxPrefix = other.m_minerTxPrefix;
+    m_minerTxEphPubKeyOffset = other.m_minerTxEphPubKeyOffset;
+    m_minerTxPubKeyOffset = other.m_minerTxPubKeyOffset;
+    m_minerTxMerkleTreeBranch = other.m_minerTxMerkleTreeBranch;
+#   else
     memcpy(m_ephPublicKey, other.m_ephPublicKey, sizeof(m_ephPublicKey));
     memcpy(m_ephSecretKey, other.m_ephSecretKey, sizeof(m_ephSecretKey));
 
     m_timestamp = other.m_timestamp;
+#   endif
+
+    m_hasMinerSignature = other.m_hasMinerSignature;
 }
 
 
@@ -214,15 +226,83 @@ void xmrig::Job::move(Job &&other)
     m_benchSize = other.m_benchSize;
 #   endif
 
-    m_hasMinerSignature = other.m_hasMinerSignature;
-
+#   ifdef XMRIG_PROXY_PROJECT
+    memcpy(m_spendSecretKey, other.m_spendSecretKey, sizeof(m_spendSecretKey));
+    memcpy(m_viewSecretKey, other.m_viewSecretKey, sizeof(m_viewSecretKey));
+    memcpy(m_spendPublicKey, other.m_spendPublicKey, sizeof(m_spendPublicKey));
+    memcpy(m_viewPublicKey, other.m_viewPublicKey, sizeof(m_viewPublicKey));
+    m_minerTxPrefix = std::move(other.m_minerTxPrefix);
+    m_minerTxEphPubKeyOffset = other.m_minerTxEphPubKeyOffset;
+    m_minerTxPubKeyOffset = other.m_minerTxPubKeyOffset;
+    m_minerTxMerkleTreeBranch = std::move(other.m_minerTxMerkleTreeBranch);
+#   else
     memcpy(m_ephPublicKey, other.m_ephPublicKey, sizeof(m_ephPublicKey));
     memcpy(m_ephSecretKey, other.m_ephSecretKey, sizeof(m_ephSecretKey));
 
     m_timestamp = other.m_timestamp;
+#   endif
+
+    m_hasMinerSignature = other.m_hasMinerSignature;
 }
 
 
+#ifdef XMRIG_PROXY_PROJECT
+
+
+void xmrig::Job::setSpendSecretKey(uint8_t* key)
+{
+    m_hasMinerSignature = true;
+    memcpy(m_spendSecretKey, key, sizeof(m_spendSecretKey));
+    xmrig::derive_view_secret_key(m_spendSecretKey, m_viewSecretKey);
+    xmrig::secret_key_to_public_key(m_spendSecretKey, m_spendPublicKey);
+    xmrig::secret_key_to_public_key(m_viewSecretKey, m_viewPublicKey);
+}
+
+
+void xmrig::Job::setMinerTx(const uint8_t* begin, const uint8_t* end, size_t minerTxEphPubKeyOffset, size_t minerTxPubKeyOffset, const Buffer& minerTxMerkleTreeBranch)
+{
+    m_minerTxPrefix.assign(begin, end);
+    m_minerTxEphPubKeyOffset = minerTxEphPubKeyOffset;
+    m_minerTxPubKeyOffset = minerTxPubKeyOffset;
+    m_minerTxMerkleTreeBranch = minerTxMerkleTreeBranch;
+}
+
+
+void xmrig::Job::generateHashingBlob(String& blob, String& signatureData) const
+{
+    uint8_t* eph_public_key = m_minerTxPrefix.data() + m_minerTxEphPubKeyOffset;
+    uint8_t* txkey_pub = m_minerTxPrefix.data() + m_minerTxPubKeyOffset;
+
+    uint8_t txkey_sec[32];
+
+    generate_keys(txkey_pub, txkey_sec);
+
+    uint8_t derivation[32];
+
+    generate_key_derivation(m_viewPublicKey, txkey_sec, derivation);
+    derive_public_key(derivation, 0, m_spendPublicKey, eph_public_key);
+
+    uint8_t buf[32 * 3] = {};
+    memcpy(buf, txkey_pub, 32);
+    memcpy(buf + 32, eph_public_key, 32);
+
+    generate_key_derivation(txkey_pub, m_viewSecretKey, derivation);
+    derive_secret_key(derivation, 0, m_spendSecretKey, buf + 64);
+
+    signatureData = xmrig::Cvt::toHex(buf, sizeof(buf));
+
+    uint8_t root_hash[32];
+    const uint8_t* p = m_minerTxPrefix.data();
+    xmrig::BlockTemplate::CalculateRootHash(p, p + m_minerTxPrefix.size(), m_minerTxMerkleTreeBranch, root_hash);
+
+    blob = rawBlob();
+    xmrig::Cvt::toHex(blob.data() + (nonceOffset() + nonceSize() + 64) * 2, 64, root_hash, 32);
+}
+
+
+#else
+
+
 void xmrig::Job::generateMinerSignature(uint64_t data, uint8_t* sig) const
 {
     uint8_t sig_data[32];
@@ -237,3 +317,6 @@ void xmrig::Job::generateMinerSignature(uint64_t data, uint8_t* sig) const
 
     xmrig::generate_signature(prefix_hash, m_ephPublicKey, m_ephSecretKey, sig);
 }
+
+
+#endif
diff --git a/src/base/net/stratum/Job.h b/src/base/net/stratum/Job.h
index 047e9b150..701534ea5 100644
--- a/src/base/net/stratum/Job.h
+++ b/src/base/net/stratum/Job.h
@@ -82,7 +82,7 @@ public:
     inline uint32_t backend() const                     { return m_backend; }
     inline uint64_t diff() const                        { return m_diff; }
     inline uint64_t height() const                      { return m_height; }
-    inline uint64_t nonceMask() const                   { return isNicehash() ? 0xFFFFFFULL : (nonceSize() == sizeof(uint64_t) ? (-1ULL  >> (extraNonce().size() * 4)): 0xFFFFFFFFULL); }
+    inline uint64_t nonceMask() const                   { return isNicehash() ? 0xFFFFFFULL : (nonceSize() == sizeof(uint64_t) ? (static_cast<uint64_t>(-1LL) >> (extraNonce().size() * 4)) : 0xFFFFFFFFULL); }
     inline uint64_t target() const                      { return m_target; }
     inline uint8_t *blob()                              { return m_blob; }
     inline uint8_t fixedByte() const                    { return *(m_blob + 42); }
@@ -116,6 +116,11 @@ public:
     inline void setBenchSize(uint32_t size)             { m_benchSize = size; }
 #   endif
 
+#   ifdef XMRIG_PROXY_PROJECT
+    void setSpendSecretKey(uint8_t* key);
+    void setMinerTx(const uint8_t* begin, const uint8_t* end, size_t minerTxEphPubKeyOffset, size_t minerTxPubKeyOffset, const Buffer& minerTxMerkleTreeBranch);
+    void generateHashingBlob(String& blob, String& signatureData) const;
+#   else
     inline const uint8_t* ephSecretKey() const { return m_hasMinerSignature ? m_ephSecretKey : nullptr; }
     inline uint64_t timestamp() const { return m_timestamp; }
 
@@ -128,8 +133,10 @@ public:
 
     inline void setTimestamp(uint64_t timestamp) { m_timestamp = timestamp; }
 
-    inline bool hasMinerSignature() const { return m_hasMinerSignature; }
     void generateMinerSignature(uint64_t data, uint8_t* sig) const;
+#   endif
+
+    inline bool hasMinerSignature() const { return m_hasMinerSignature; }
 
 private:
     void copy(const Job &other);
@@ -154,16 +161,28 @@ private:
     char m_rawBlob[kMaxBlobSize * 2 + 8]{};
     char m_rawTarget[24]{};
     String m_rawSeedHash;
+
+    // Miner signatures
+    uint8_t m_spendSecretKey[32];
+    uint8_t m_viewSecretKey[32];
+    uint8_t m_spendPublicKey[32];
+    uint8_t m_viewPublicKey[32];
+    mutable Buffer m_minerTxPrefix;
+    size_t m_minerTxEphPubKeyOffset = 0;
+    size_t m_minerTxPubKeyOffset = 0;
+    Buffer m_minerTxMerkleTreeBranch;
+#   else
+    // Miner signatures
+    uint8_t m_ephPublicKey[32]{};
+    uint8_t m_ephSecretKey[32]{};
+    uint64_t m_timestamp = 0;
 #   endif
 
+    bool m_hasMinerSignature = false;
+
 #   ifdef XMRIG_FEATURE_BENCHMARK
     uint32_t m_benchSize = 0;
 #   endif
-
-    bool m_hasMinerSignature = false;
-    uint8_t m_ephPublicKey[32]{};
-    uint8_t m_ephSecretKey[32]{};
-    uint64_t m_timestamp = 0;
 };
 
 
diff --git a/src/base/tools/cryptonote/BlockTemplate.cpp b/src/base/tools/cryptonote/BlockTemplate.cpp
index 39bc8b819..f84a668b8 100644
--- a/src/base/tools/cryptonote/BlockTemplate.cpp
+++ b/src/base/tools/cryptonote/BlockTemplate.cpp
@@ -115,7 +115,7 @@ bool BlockTemplate::Init(const String& blockTemplate, Coin coin)
 
 #   ifdef XMRIG_PROXY_PROJECT
     hashes.resize((num_hashes + 1) * HASH_SIZE);
-    CalculateMinerTxHash(hashes.data());
+    CalculateMinerTxHash(raw_blob.data() + miner_tx_prefix_begin_index, raw_blob.data() + miner_tx_prefix_end_index, hashes.data());
 
     for (uint64_t i = 1; i <= num_hashes; ++i) {
         uint8_t h[HASH_SIZE];
@@ -124,21 +124,20 @@ bool BlockTemplate::Init(const String& blockTemplate, Coin coin)
     }
 
     CalculateMerkleTreeHash();
-    GenerateHashingBlob();
 #   endif
 
     return true;
 }
 
 
-void BlockTemplate::CalculateMinerTxHash(uint8_t* hash)
+void BlockTemplate::CalculateMinerTxHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, uint8_t* hash)
 {
     uint8_t hashes[HASH_SIZE * 3];
 
     // Calculate 3 partial hashes
 
     // 1. Prefix
-    keccak(raw_blob.data() + miner_tx_prefix_begin_index, static_cast<int>(miner_tx_prefix_end_index - miner_tx_prefix_begin_index), hashes, HASH_SIZE);
+    keccak(prefix_begin, static_cast<int>(prefix_end - prefix_begin), hashes, HASH_SIZE);
 
     // 2. Base RCT, single 0 byte in miner tx
     static const uint8_t known_second_hash[HASH_SIZE] = {
@@ -203,11 +202,9 @@ void BlockTemplate::CalculateMerkleTreeHash()
 }
 
 
-void BlockTemplate::UpdateMinerTxHash()
+void BlockTemplate::CalculateRootHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, const Buffer& miner_tx_merkle_tree_branch, uint8_t* root_hash)
 {
-    CalculateMinerTxHash(hashes.data());
-
-    memcpy(root_hash, hashes.data(), HASH_SIZE);
+    CalculateMinerTxHash(prefix_begin, prefix_end, root_hash);
 
     for (size_t i = 0; i < miner_tx_merkle_tree_branch.size(); i += HASH_SIZE) {
         uint8_t h[HASH_SIZE * 2];
diff --git a/src/base/tools/cryptonote/BlockTemplate.h b/src/base/tools/cryptonote/BlockTemplate.h
index 079cc021b..8c3e67616 100644
--- a/src/base/tools/cryptonote/BlockTemplate.h
+++ b/src/base/tools/cryptonote/BlockTemplate.h
@@ -80,9 +80,9 @@ struct BlockTemplate
 
     bool Init(const String& blockTemplate, Coin coin);
 
-    void CalculateMinerTxHash(uint8_t* hash);
+    static void CalculateMinerTxHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, uint8_t* hash);
+    static void CalculateRootHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, const Buffer& miner_tx_merkle_tree_branch, uint8_t* root_hash);
     void CalculateMerkleTreeHash();
-    void UpdateMinerTxHash();
     void GenerateHashingBlob();
 };
 
diff --git a/src/net/JobResult.h b/src/net/JobResult.h
index 22fbb3118..614baf14a 100644
--- a/src/net/JobResult.h
+++ b/src/net/JobResult.h
@@ -63,6 +63,7 @@ public:
         }
 
         if (miner_signature) {
+            m_hasMinerSignature = true;
             memcpy(m_minerSignature, miner_signature, sizeof(m_minerSignature));
         }
     }
@@ -84,7 +85,7 @@ public:
     inline const uint8_t *headerHash() const { return m_headerHash; }
     inline const uint8_t *mixHash() const    { return m_mixHash; }
 
-    inline const uint8_t *minerSignature() const { return m_minerSignature; }
+    inline const uint8_t *minerSignature() const { return m_hasMinerSignature ? m_minerSignature : nullptr; }
 
     const Algorithm algorithm;
     const String clientId;
@@ -100,6 +101,7 @@ private:
     uint8_t m_mixHash[32]    = { 0 };
 
     uint8_t m_minerSignature[64] = { 0 };
+    bool m_hasMinerSignature = false;
 };
 
 
diff --git a/src/net/strategies/DonateStrategy.cpp b/src/net/strategies/DonateStrategy.cpp
index a8ecf851a..b288046e4 100644
--- a/src/net/strategies/DonateStrategy.cpp
+++ b/src/net/strategies/DonateStrategy.cpp
@@ -259,7 +259,7 @@ xmrig::IClient *xmrig::DonateStrategy::createProxy()
     const IClient *client = strategy->client();
     m_tls                 = client->hasExtension(IClient::EXT_TLS);
 
-    Pool pool(client->pool().proxy().isValid() ? client->pool().host() : client->ip(), client->pool().port(), m_userId, client->pool().password(), nullptr, 0, true, client->isTLS(), Pool::MODE_POOL);
+    Pool pool(client->pool().proxy().isValid() ? client->pool().host() : client->ip(), client->pool().port(), m_userId, client->pool().password(), client->pool().spendSecretKey(), 0, true, client->isTLS(), Pool::MODE_POOL);
     pool.setAlgo(client->pool().algorithm());
     pool.setProxy(client->pool().proxy());