diff --git a/src/3rdparty/epee/span.h b/src/3rdparty/epee/span.h new file mode 100644 index 00000000..b355c960 --- /dev/null +++ b/src/3rdparty/epee/span.h @@ -0,0 +1,176 @@ +// Copyright (c) 2017-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include + +namespace epee +{ + /*! + \brief Non-owning sequence of data. Does not deep copy + + Inspired by `gsl::span` and/or `boost::iterator_range`. This class is + intended to be used as a parameter type for functions that need to take a + writable or read-only sequence of data. Most common cases are `span` + and `span`. Using as a class member is only recommended if + clearly documented as not doing a deep-copy. C-arrays are easily convertible + to this type. + + \note Conversion from C string literal to `span` will include + the NULL-terminator. + \note Never allows derived-to-base pointer conversion; an array of derived + types is not an array of base types. + */ + template + class span + { + template + static constexpr bool safe_conversion() noexcept + { + // Allow exact matches or `T*` -> `const T*`. + using with_const = typename std::add_const::type; + return std::is_same() || + (std::is_const() && std::is_same()); + } + + public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = pointer; + using const_iterator = const_pointer; + + constexpr span() noexcept : ptr(nullptr), len(0) {} + constexpr span(std::nullptr_t) noexcept : span() {} + + //! Prevent derived-to-base conversions; invalid in this context. + template()>::type> + constexpr span(U* const src_ptr, const std::size_t count) noexcept + : ptr(src_ptr), len(count) {} + + //! Conversion from C-array. Prevents common bugs with sizeof + arrays. + template + constexpr span(T (&src)[N]) noexcept : span(src, N) {} + + constexpr span(const span&) noexcept = default; + span& operator=(const span&) noexcept = default; + + /*! Try to remove `amount` elements from beginning of span. + \return Number of elements removed. */ + std::size_t remove_prefix(std::size_t amount) noexcept + { + amount = std::min(len, amount); + ptr += amount; + len -= amount; + return amount; + } + + constexpr iterator begin() const noexcept { return ptr; } + constexpr const_iterator cbegin() const noexcept { return ptr; } + + constexpr iterator end() const noexcept { return begin() + size(); } + constexpr const_iterator cend() const noexcept { return cbegin() + size(); } + + constexpr bool empty() const noexcept { return size() == 0; } + constexpr pointer data() const noexcept { return ptr; } + constexpr std::size_t size() const noexcept { return len; } + constexpr std::size_t size_bytes() const noexcept { return size() * sizeof(value_type); } + + T &operator[](size_t idx) noexcept { return ptr[idx]; } + const T &operator[](size_t idx) const noexcept { return ptr[idx]; } + + private: + T* ptr; + std::size_t len; + }; + + //! \return `span` from a STL compatible `src`. + template + constexpr span to_span(const T& src) + { + // compiler provides diagnostic if size() is not size_t. + return {src.data(), src.size()}; + } + + //! \return `span` from a STL compatible `src`. + template + constexpr span to_mut_span(T& src) + { + // compiler provides diagnostic if size() is not size_t. + return {src.data(), src.size()}; + } + + template + constexpr bool has_padding() noexcept + { + return !std::is_standard_layout() || alignof(T) != 1; + } + + //! \return Cast data from `src` as `span`. + template + span to_byte_span(const span src) noexcept + { + static_assert(!has_padding(), "source type may have padding"); + return {reinterpret_cast(src.data()), src.size_bytes()}; + } + + //! \return `span` which represents the bytes at `&src`. + template + span as_byte_span(const T& src) noexcept + { + static_assert(!std::is_empty(), "empty types will not work -> sizeof == 1"); + static_assert(!has_padding(), "source type may have padding"); + return {reinterpret_cast(std::addressof(src)), sizeof(T)}; + } + + //! \return `span` which represents the bytes at `&src`. + template + span as_mut_byte_span(T& src) noexcept + { + static_assert(!std::is_empty(), "empty types will not work -> sizeof == 1"); + static_assert(!has_padding(), "source type may have padding"); + return {reinterpret_cast(std::addressof(src)), sizeof(T)}; + } + + //! make a span from a std::string + template + span strspan(const std::string &s) noexcept + { + static_assert(std::is_same() || std::is_same() || std::is_same() || std::is_same(), "Unexpected type"); + return {reinterpret_cast(s.data()), s.size()}; + } +} diff --git a/src/base/base.cmake b/src/base/base.cmake index 4cf1ab72..3246d6f9 100644 --- a/src/base/base.cmake +++ b/src/base/base.cmake @@ -1,5 +1,5 @@ set(HEADERS_BASE - src/3rdparty/fmt/format.cc + src/3rdparty/epee/span.h src/base/api/interfaces/IApiListener.h src/base/crypto/Algorithm.h src/base/crypto/Coin.h @@ -81,11 +81,13 @@ set(HEADERS_BASE src/base/tools/cryptonote/WalletAddress.h src/base/tools/Cvt.h src/base/tools/Handle.h + src/base/tools/Span.h src/base/tools/String.h src/base/tools/Timer.h ) set(SOURCES_BASE + src/3rdparty/fmt/format.cc src/base/crypto/Algorithm.cpp src/base/crypto/Coin.cpp src/base/crypto/keccak.cpp @@ -130,14 +132,14 @@ set(SOURCES_BASE src/base/net/tools/LineReader.cpp src/base/net/tools/NetBuffer.cpp src/base/tools/Arguments.cpp + src/base/tools/cryptonote/BlockTemplate.cpp + src/base/tools/cryptonote/crypto-ops-data.c + src/base/tools/cryptonote/crypto-ops.c + src/base/tools/cryptonote/Signatures.cpp + src/base/tools/cryptonote/WalletAddress.cpp src/base/tools/Cvt.cpp src/base/tools/String.cpp src/base/tools/Timer.cpp - src/base/tools/cryptonote/BlockTemplate.cpp - src/base/tools/cryptonote/Signatures.cpp - src/base/tools/cryptonote/WalletAddress.cpp - src/base/tools/cryptonote/crypto-ops.c - src/base/tools/cryptonote/crypto-ops-data.c ) diff --git a/src/base/net/stratum/DaemonClient.cpp b/src/base/net/stratum/DaemonClient.cpp index 5cea361d..50a3a232 100644 --- a/src/base/net/stratum/DaemonClient.cpp +++ b/src/base/net/stratum/DaemonClient.cpp @@ -137,21 +137,21 @@ int64_t xmrig::DaemonClient::submit(const JobResult &result) memcpy(data + m_job.nonceOffset() * 2, result.nonce, 8); - if (m_blocktemplate.has_miner_signature && result.sig) { + if (m_blocktemplate.hasMinerSignature() && 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); + memcpy(data + m_blocktemplate.offset(BlockTemplate::TX_PUBKEY_OFFSET) * 2, result.sig_data, 32 * 2); + memcpy(data + m_blocktemplate.offset(BlockTemplate::EPH_PUBLIC_KEY_OFFSET) * 2, result.sig_data + 32 * 2, 32 * 2); } if (result.extra_nonce >= 0) { - Cvt::toHex(data + m_blocktemplate.tx_extra_nonce_index * 2, 8, reinterpret_cast(&result.extra_nonce), 4); + Cvt::toHex(data + m_blocktemplate.offset(BlockTemplate::TX_EXTRA_NONCE_OFFSET) * 2, 8, reinterpret_cast(&result.extra_nonce), 4); } # else Cvt::toHex(data + m_job.nonceOffset() * 2, 8, reinterpret_cast(&result.nonce), 4); - if (m_blocktemplate.has_miner_signature) { + if (m_blocktemplate.hasMinerSignature()) { Cvt::toHex(data + sig_offset * 2, 128, result.minerSignature(), 64); } @@ -376,29 +376,29 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value ¶ms, int *code) String blocktemplate = Json::getString(params, kBlocktemplateBlob); if (blocktemplate.isNull()) { - return jobError("Empty block template received from daemon."); + return jobError("Empty block template received from daemon."); // FIXME } - if (!m_blocktemplate.Init(blocktemplate, m_coin)) { + if (!m_blocktemplate.parse(blocktemplate, m_coin)) { return jobError("Invalid block template received from daemon."); } # ifdef XMRIG_PROXY_PROJECT - const size_t k = m_blocktemplate.miner_tx_prefix_begin_index; + const size_t k = m_blocktemplate.offset(BlockTemplate::MINER_TX_PREFIX_OFFSET); job.setMinerTx( - m_blocktemplate.raw_blob.data() + k, - m_blocktemplate.raw_blob.data() + m_blocktemplate.miner_tx_prefix_end_index, - m_blocktemplate.eph_public_key_index - k, - m_blocktemplate.tx_pubkey_index - k, - m_blocktemplate.tx_extra_nonce_index - k, - m_blocktemplate.tx_extra_nonce_size, - m_blocktemplate.miner_tx_merkle_tree_branch + m_blocktemplate.blob() + k, + m_blocktemplate.blob() + m_blocktemplate.offset(BlockTemplate::MINER_TX_PREFIX_END_OFFSET), + m_blocktemplate.offset(BlockTemplate::EPH_PUBLIC_KEY_OFFSET) - k, + m_blocktemplate.offset(BlockTemplate::TX_PUBKEY_OFFSET) - k, + m_blocktemplate.offset(BlockTemplate::TX_EXTRA_NONCE_OFFSET) - k, + m_blocktemplate.txExtraNonce().size(), + m_blocktemplate.minerTxMerkleTreeBranch() ); # endif m_blockhashingblob = Json::getString(params, "blockhashing_blob"); - if (m_blocktemplate.has_miner_signature) { + if (m_blocktemplate.hasMinerSignature()) { if (m_pool.spendSecretKey().isEmpty()) { return jobError("Secret spend key is not set."); } @@ -429,7 +429,7 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value ¶ms, int *code) } uint8_t derivation[32]; - if (!generate_key_derivation(m_blocktemplate.raw_blob.data() + m_blocktemplate.tx_pubkey_index, secret_viewkey, derivation)) { + if (!generate_key_derivation(m_blocktemplate.blob(BlockTemplate::TX_PUBKEY_OFFSET), secret_viewkey, derivation)) { return jobError("Failed to generate key derivation for miner signature."); } @@ -448,7 +448,7 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value ¶ms, int *code) uint8_t eph_secret_key[32]; derive_secret_key(derivation, 0, secret_spendkey, eph_secret_key); - job.setEphemeralKeys(m_blocktemplate.raw_blob.data() + m_blocktemplate.eph_public_key_index, eph_secret_key); + job.setEphemeralKeys(m_blocktemplate.blob(BlockTemplate::EPH_PUBLIC_KEY_OFFSET), eph_secret_key); # endif } @@ -458,7 +458,7 @@ bool xmrig::DaemonClient::parseJob(const rapidjson::Value ¶ms, int *code) } if (m_coin.isValid()) { - job.setAlgorithm(m_coin.algorithm(m_blocktemplate.major_version)); + job.setAlgorithm(m_coin.algorithm(m_blocktemplate.majorVersion())); } if (!job.setBlob(m_blockhashingblob)) { diff --git a/src/base/net/stratum/Job.cpp b/src/base/net/stratum/Job.cpp index e765978f..81816011 100644 --- a/src/base/net/stratum/Job.cpp +++ b/src/base/net/stratum/Job.cpp @@ -352,16 +352,16 @@ void xmrig::Job::generateHashingBlob(String &blob) const { uint8_t root_hash[32]; const uint8_t* p = m_minerTxPrefix.data(); - BlockTemplate::CalculateRootHash(p, p + m_minerTxPrefix.size(), m_minerTxMerkleTreeBranch, root_hash); + BlockTemplate::calculateRootHash(p, p + m_minerTxPrefix.size(), m_minerTxMerkleTreeBranch, root_hash); uint64_t root_hash_offset = nonceOffset() + nonceSize(); if (m_hasMinerSignature) { - root_hash_offset += BlockTemplate::SIGNATURE_SIZE + 2 /* vote */; + root_hash_offset += BlockTemplate::kSignatureSize + 2 /* vote */; } blob = rawBlob(); - Cvt::toHex(blob.data() + root_hash_offset * 2, 64, root_hash, BlockTemplate::HASH_SIZE); + Cvt::toHex(blob.data() + root_hash_offset * 2, 64, root_hash, BlockTemplate::kHashSize); } @@ -374,7 +374,7 @@ void xmrig::Job::generateMinerSignature(const uint8_t* blob, size_t size, uint8_ memcpy(tmp, blob, size); // Fill signature with zeros - memset(tmp + nonceOffset() + nonceSize(), 0, BlockTemplate::SIGNATURE_SIZE); + memset(tmp + nonceOffset() + nonceSize(), 0, BlockTemplate::kSignatureSize); uint8_t prefix_hash[32]; xmrig::keccak(tmp, static_cast(size), prefix_hash, sizeof(prefix_hash)); diff --git a/src/base/tools/Cvt.cpp b/src/base/tools/Cvt.cpp index 18d5e627..0d1c89fb 100644 --- a/src/base/tools/Cvt.cpp +++ b/src/base/tools/Cvt.cpp @@ -17,7 +17,6 @@ * along with this program. If not, see . */ - #include "base/tools/Cvt.h" #include "3rdparty/rapidjson/document.h" @@ -245,6 +244,12 @@ rapidjson::Value xmrig::Cvt::toHex(const Buffer &data, rapidjson::Document &doc) } +rapidjson::Value xmrig::Cvt::toHex(const Span &data, rapidjson::Document &doc) +{ + return toHex(data.data(), data.size(), doc); +} + + rapidjson::Value xmrig::Cvt::toHex(const std::string &data, rapidjson::Document &doc) { return toHex(reinterpret_cast(data.data()), data.size(), doc); diff --git a/src/base/tools/Cvt.h b/src/base/tools/Cvt.h index e7507aaf..58058127 100644 --- a/src/base/tools/Cvt.h +++ b/src/base/tools/Cvt.h @@ -22,6 +22,7 @@ #include "3rdparty/rapidjson/fwd.h" #include "base/tools/Buffer.h" +#include "base/tools/Span.h" #include "base/tools/String.h" @@ -37,9 +38,11 @@ public: inline static bool fromHex(Buffer &buf, const String &hex) { return fromHex(buf, hex.data(), hex.size()); } inline static Buffer fromHex(const std::string &hex) { return fromHex(hex.data(), hex.size()); } inline static Buffer fromHex(const String &hex) { return fromHex(hex.data(), hex.size()); } - inline static String toHex(const Buffer &data) { return toHex(data.data(), data.size()); } inline static String toHex(const std::string &data) { return toHex(reinterpret_cast(data.data()), data.size()); } + template + inline static String toHex(const T &data) { return toHex(data.data(), data.size()); } + static bool fromHex(Buffer &buf, const char *in, size_t size); static bool fromHex(Buffer &buf, const rapidjson::Value &value); static bool fromHex(std::string &buf, const char *in, size_t size); @@ -49,6 +52,7 @@ public: static Buffer fromHex(const char *in, size_t size); static Buffer randomBytes(size_t size); static rapidjson::Value toHex(const Buffer &data, rapidjson::Document &doc); + static rapidjson::Value toHex(const Span &data, rapidjson::Document &doc); static rapidjson::Value toHex(const std::string &data, rapidjson::Document &doc); static rapidjson::Value toHex(const uint8_t *in, size_t size, rapidjson::Document &doc); static String toHex(const uint8_t *in, size_t size); diff --git a/src/base/tools/Span.h b/src/base/tools/Span.h new file mode 100644 index 00000000..5d8acebc --- /dev/null +++ b/src/base/tools/Span.h @@ -0,0 +1,35 @@ +/* XMRig + * Copyright (c) 2018-2021 SChernykh + * Copyright (c) 2016-2021 XMRig , + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef XMRIG_SPAN_H +#define XMRIG_SPAN_H + + +#include "3rdparty/epee/span.h" + + +namespace xmrig { + + +using Span = epee::span; + + +} /* namespace xmrig */ + + +#endif /* XMRIG_SPAN_H */ diff --git a/src/base/tools/cryptonote/BlobReader.h b/src/base/tools/cryptonote/BlobReader.h index e8be0d65..a45bb972 100644 --- a/src/base/tools/cryptonote/BlobReader.h +++ b/src/base/tools/cryptonote/BlobReader.h @@ -24,11 +24,13 @@ #include #include +#include namespace xmrig { +template class BlobReader { public: @@ -45,7 +47,7 @@ public: inline bool skip(size_t n) { if (m_index + n > m_size) { - return false; + return outOfRange(); } m_index += n; @@ -57,7 +59,7 @@ public: inline bool operator()(uint8_t(&data)[N]) { if (m_index + N > m_size) { - return false; + return outOfRange(); } memcpy(data, m_data + m_index, N); @@ -67,19 +69,23 @@ public: } template - inline void readItems(T &data, size_t count) + inline bool operator()(T &data, size_t n) { - data.resize(count); - for (size_t i = 0; i < count; ++i) { - operator()(data[i]); + if (m_index + n > m_size) { + return outOfRange(); } + + data = { m_data + m_index, n }; + m_index += n; + + return true; } private: inline bool getByte(uint8_t &data) { if (m_index >= m_size) { - return false; + return outOfRange(); } data = m_data[m_index++]; @@ -95,8 +101,9 @@ private: do { if (!getByte(t)) { - return false; + return outOfRange(); } + result |= static_cast(t & 0x7F) << shift; shift += 7; } while (t & 0x80); @@ -106,6 +113,15 @@ private: return true; } + inline bool outOfRange() + { + if (EXCEPTIONS) { + throw std::out_of_range("Blob read out of range"); + } + + return false; + } + const size_t m_size; const uint8_t *m_data; size_t m_index = 0; diff --git a/src/base/tools/cryptonote/BlockTemplate.cpp b/src/base/tools/cryptonote/BlockTemplate.cpp index ede0b19a..db3d0496 100644 --- a/src/base/tools/cryptonote/BlockTemplate.cpp +++ b/src/base/tools/cryptonote/BlockTemplate.cpp @@ -18,177 +18,64 @@ * along with this program. If not, see . */ - -#include "base/crypto/keccak.h" -#include "base/tools/Cvt.h" -#include "base/tools/cryptonote/BlobReader.h" #include "base/tools/cryptonote/BlockTemplate.h" +#include "3rdparty/rapidjson/document.h" +#include "base/crypto/keccak.h" +#include "base/tools/cryptonote/BlobReader.h" +#include "base/tools/Cvt.h" -namespace xmrig { - - -bool BlockTemplate::Init(const String& blockTemplate, Coin coin) +void xmrig::BlockTemplate::calculateMinerTxHash(const uint8_t *prefix_begin, const uint8_t *prefix_end, uint8_t *hash) { - raw_blob = Cvt::fromHex(blockTemplate); - - BlobReader ar(raw_blob.data(), raw_blob.size()); - - // Block header - ar(major_version); - ar(minor_version); - ar(timestamp); - ar(prev_id); - ar(nonce); - - // Wownero block template has miner signature starting from version 18 - has_miner_signature = (coin == Coin::WOWNERO) && (major_version >= 18); - if (has_miner_signature) { - ar(miner_signature); - ar(vote); - } - - // Miner transaction begin - // Prefix begin - miner_tx_prefix_begin_index = ar.index(); - - ar(tx_version); - ar(unlock_time); - ar(num_inputs); - - // must be 1 input - if (num_inputs != 1) - return false; - - ar(input_type); - - // input type must be txin_gen (0xFF) - if (input_type != 0xFF) - return false; - - ar(height); - - ar(num_outputs); - - // must be 1 output - if (num_outputs != 1) - return false; - - ar(amount); - ar(output_type); - - // output type must be txout_to_key (2) - if (output_type != 2) - return false; - - eph_public_key_index = ar.index(); - - ar(eph_public_key); - ar(extra_size); - - const uint64_t tx_extra_index = ar.index(); - - ar.readItems(extra, extra_size); - - BlobReader ar_extra(extra.data(), extra_size); - - tx_extra_nonce_size = 0; - tx_extra_nonce_index = 0; - - while (ar_extra.index() < extra_size) { - uint64_t extra_tag = 0; - ar_extra(extra_tag); - - switch (extra_tag) { - case 0x01: // TX_EXTRA_TAG_PUBKEY - tx_pubkey_index = tx_extra_index + ar_extra.index(); - ar_extra.skip(KEY_SIZE); - break; - - case 0x02: // TX_EXTRA_NONCE - ar_extra(tx_extra_nonce_size); - tx_extra_nonce_index = tx_extra_index + ar_extra.index(); - ar_extra.skip(tx_extra_nonce_size); - break; - - default: - return false; // TODO: handle other tags - } - } - - miner_tx_prefix_end_index = ar.index(); - // Prefix end - - // RCT signatures (empty in miner transaction) - ar(vin_rct_type); - - // must be RCTTypeNull (0) - if (vin_rct_type != 0) - return false; - - const size_t miner_tx_end = ar.index(); - // Miner transaction end - - // Miner transaction must have exactly 1 byte with value 0 after the prefix - if ((miner_tx_end != miner_tx_prefix_end_index + 1) || (raw_blob[miner_tx_prefix_end_index] != 0)) - return false; - - // Other transaction hashes - ar(num_hashes); - -# ifdef XMRIG_PROXY_PROJECT - hashes.resize((num_hashes + 1) * HASH_SIZE); - 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]; - ar(h); - memcpy(hashes.data() + i * HASH_SIZE, h, HASH_SIZE); - } - - CalculateMerkleTreeHash(); -# endif - - return true; -} - - -void BlockTemplate::CalculateMinerTxHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, uint8_t* hash) -{ - uint8_t hashes[HASH_SIZE * 3]; + uint8_t hashes[kHashSize * 3]; // Calculate 3 partial hashes // 1. Prefix - keccak(prefix_begin, static_cast(prefix_end - prefix_begin), hashes, HASH_SIZE); + keccak(prefix_begin, static_cast(prefix_end - prefix_begin), hashes, kHashSize); // 2. Base RCT, single 0 byte in miner tx - static const uint8_t known_second_hash[HASH_SIZE] = { + static const uint8_t known_second_hash[kHashSize] = { 188,54,120,158,122,30,40,20,54,70,66,41,130,143,129,125,102,18,247,180,119,214,101,145,255,150,169,224,100,188,201,138 }; - memcpy(hashes + HASH_SIZE, known_second_hash, HASH_SIZE); + memcpy(hashes + kHashSize, known_second_hash, kHashSize); // 3. Prunable RCT, empty in miner tx - memset(hashes + HASH_SIZE * 2, 0, HASH_SIZE); + memset(hashes + kHashSize * 2, 0, kHashSize); // Calculate miner transaction hash - keccak(hashes, sizeof(hashes), hash, HASH_SIZE); + keccak(hashes, sizeof(hashes), hash, kHashSize); } -void BlockTemplate::CalculateMerkleTreeHash() +void xmrig::BlockTemplate::calculateRootHash(const uint8_t *prefix_begin, const uint8_t *prefix_end, const Buffer &miner_tx_merkle_tree_branch, uint8_t *root_hash) { - miner_tx_merkle_tree_branch.clear(); + calculateMinerTxHash(prefix_begin, prefix_end, root_hash); - const uint64_t count = num_hashes + 1; - uint8_t* h = hashes.data(); + for (size_t i = 0; i < miner_tx_merkle_tree_branch.size(); i += kHashSize) { + uint8_t h[kHashSize * 2]; + + memcpy(h, root_hash, kHashSize); + memcpy(h + kHashSize, miner_tx_merkle_tree_branch.data() + i, kHashSize); + + keccak(h, kHashSize * 2, root_hash, kHashSize); + } +} + + +void xmrig::BlockTemplate::calculateMerkleTreeHash() +{ + m_minerTxMerkleTreeBranch.clear(); + + const uint64_t count = m_numHashes + 1; + const uint8_t *h = m_hashes.data(); if (count == 1) { - memcpy(root_hash, h, HASH_SIZE); + memcpy(m_rootHash, h, kHashSize); } else if (count == 2) { - miner_tx_merkle_tree_branch.insert(miner_tx_merkle_tree_branch.end(), h + HASH_SIZE, h + HASH_SIZE * 2); - keccak(h, HASH_SIZE * 2, root_hash, HASH_SIZE); + m_minerTxMerkleTreeBranch.insert(m_minerTxMerkleTreeBranch.end(), h + kHashSize, h + kHashSize * 2); + keccak(h, kHashSize * 2, m_rootHash, kHashSize); } else { size_t i, j, cnt; @@ -197,64 +84,222 @@ void BlockTemplate::CalculateMerkleTreeHash() cnt >>= 1; - miner_tx_merkle_tree_branch.reserve(HASH_SIZE * (i - 1)); + m_minerTxMerkleTreeBranch.reserve(kHashSize * (i - 1)); - Buffer ints(cnt * HASH_SIZE); - memcpy(ints.data(), h, (cnt * 2 - count) * HASH_SIZE); + Buffer ints(cnt * kHashSize); + memcpy(ints.data(), h, (cnt * 2 - count) * kHashSize); for (i = cnt * 2 - count, j = cnt * 2 - count; j < cnt; i += 2, ++j) { if (i == 0) { - miner_tx_merkle_tree_branch.insert(miner_tx_merkle_tree_branch.end(), h + HASH_SIZE, h + HASH_SIZE * 2); + m_minerTxMerkleTreeBranch.insert(m_minerTxMerkleTreeBranch.end(), h + kHashSize, h + kHashSize * 2); } - keccak(h + i * HASH_SIZE, HASH_SIZE * 2, ints.data() + j * HASH_SIZE, HASH_SIZE); + keccak(h + i * kHashSize, kHashSize * 2, ints.data() + j * kHashSize, kHashSize); } while (cnt > 2) { cnt >>= 1; for (i = 0, j = 0; j < cnt; i += 2, ++j) { if (i == 0) { - miner_tx_merkle_tree_branch.insert(miner_tx_merkle_tree_branch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2); + m_minerTxMerkleTreeBranch.insert(m_minerTxMerkleTreeBranch.end(), ints.data() + kHashSize, ints.data() + kHashSize * 2); } - keccak(ints.data() + i * HASH_SIZE, HASH_SIZE * 2, ints.data() + j * HASH_SIZE, HASH_SIZE); + keccak(ints.data() + i * kHashSize, kHashSize * 2, ints.data() + j * kHashSize, kHashSize); } } - miner_tx_merkle_tree_branch.insert(miner_tx_merkle_tree_branch.end(), ints.data() + HASH_SIZE, ints.data() + HASH_SIZE * 2); - keccak(ints.data(), HASH_SIZE * 2, root_hash, HASH_SIZE); + m_minerTxMerkleTreeBranch.insert(m_minerTxMerkleTreeBranch.end(), ints.data() + kHashSize, ints.data() + kHashSize * 2); + keccak(ints.data(), kHashSize * 2, m_rootHash, kHashSize); } } -void BlockTemplate::CalculateRootHash(const uint8_t* prefix_begin, const uint8_t* prefix_end, const Buffer& miner_tx_merkle_tree_branch, uint8_t* root_hash) +bool xmrig::BlockTemplate::parse(const Buffer &blocktemplate, const Coin &coin, bool hashes) { - 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]; - - memcpy(h, root_hash, HASH_SIZE); - memcpy(h + HASH_SIZE, miner_tx_merkle_tree_branch.data() + i, HASH_SIZE); - - keccak(h, HASH_SIZE * 2, root_hash, HASH_SIZE); + if (blocktemplate.size() < kMinSize) { + return false; } + + m_blob = blocktemplate; + m_coin = coin; + bool rc = false; + + try { + rc = parse(hashes); + } catch (...) {} + + return rc; } -void BlockTemplate::GenerateHashingBlob() +bool xmrig::BlockTemplate::parse(const char *blocktemplate, size_t size, const Coin &coin, bool hashes) { - hashingBlob.clear(); - hashingBlob.reserve(miner_tx_prefix_begin_index + HASH_SIZE + 3); + if (size < (kMinSize * 2) || !Cvt::fromHex(m_blob, blocktemplate, size)) { + return false; + } - hashingBlob.assign(raw_blob.begin(), raw_blob.begin() + miner_tx_prefix_begin_index); - hashingBlob.insert(hashingBlob.end(), root_hash, root_hash + HASH_SIZE); + m_coin = coin; + bool rc = false; - uint64_t k = num_hashes + 1; + try { + rc = parse(hashes); + } catch (...) {} + + return rc; +} + + +bool xmrig::BlockTemplate::parse(const rapidjson::Value &blocktemplate, const Coin &coin, bool hashes) +{ + return blocktemplate.IsString() && parse(blocktemplate.GetString(), blocktemplate.GetStringLength(), coin, hashes); +} + + +bool xmrig::BlockTemplate::parse(const String &blocktemplate, const Coin &coin, bool hashes) +{ + return parse(blocktemplate.data(), blocktemplate.size(), coin, hashes); +} + + +void xmrig::BlockTemplate::generateHashingBlob(Buffer &out) const +{ + out.clear(); + out.reserve(offset(MINER_TX_PREFIX_OFFSET) + kHashSize + 3); + + out.assign(m_blob.begin(), m_blob.begin() + offset(MINER_TX_PREFIX_OFFSET)); + out.insert(out.end(), m_rootHash, m_rootHash + kHashSize); + + uint64_t k = m_numHashes + 1; while (k >= 0x80) { - hashingBlob.emplace_back((static_cast(k) & 0x7F) | 0x80); + out.emplace_back((static_cast(k) & 0x7F) | 0x80); k >>= 7; } - hashingBlob.emplace_back(static_cast(k)); + out.emplace_back(static_cast(k)); } -} /* namespace xmrig */ +bool xmrig::BlockTemplate::parse(bool hashes) +{ + BlobReader ar(m_blob.data(), m_blob.size()); + + // Block header + ar(m_version.first); + ar(m_version.second); + ar(m_timestamp); + ar(m_prevId, kHashSize); + + setOffset(NONCE_OFFSET, ar.index()); + ar.skip(kNonceSize); + + // Wownero block template has miner signature starting from version 18 + if (m_coin == Coin::WOWNERO && majorVersion() >= 18) { + ar(m_minerSignature, kSignatureSize); + ar(m_vote); + } + + // Miner transaction begin + // Prefix begin + setOffset(MINER_TX_PREFIX_OFFSET, ar.index()); + + ar(m_txVersion); + ar(m_unlockTime); + ar(m_numInputs); + + // must be 1 input + if (m_numInputs != 1) { + return false; + } + + ar(m_inputType); + + // input type must be txin_gen (0xFF) + if (m_inputType != 0xFF) { + return false; + } + + ar(m_height); + ar(m_numOutputs); + + // must be 1 output + if (m_numOutputs != 1) { + return false; + } + + ar(m_amount); + ar(m_outputType); + + // output type must be txout_to_key (2) + if (m_outputType != 2) { + return false; + } + + setOffset(EPH_PUBLIC_KEY_OFFSET, ar.index()); + + ar(m_ephPublicKey, kKeySize); + ar(m_extraSize); + + setOffset(TX_EXTRA_OFFSET, ar.index()); + + BlobReader ar_extra(blob(TX_EXTRA_OFFSET), m_extraSize); + ar.skip(m_extraSize); + + while (ar_extra.index() < m_extraSize) { + uint64_t extra_tag = 0; + ar_extra(extra_tag); + + switch (extra_tag) { + case 0x01: // TX_EXTRA_TAG_PUBKEY + setOffset(TX_PUBKEY_OFFSET, offset(TX_EXTRA_OFFSET) + ar_extra.index()); + ar_extra.skip(kKeySize); + break; + + case 0x02: // TX_EXTRA_NONCE + { + uint64_t size = 0; + ar_extra(size); + setOffset(TX_EXTRA_NONCE_OFFSET, offset(TX_EXTRA_OFFSET) + ar_extra.index()); + ar_extra(m_txExtraNonce, size); + } + break; + + default: + return false; // TODO: handle other tags + } + } + + setOffset(MINER_TX_PREFIX_END_OFFSET, ar.index()); + // Prefix end + + // RCT signatures (empty in miner transaction) + uint8_t vin_rct_type = 0; + ar(vin_rct_type); + + // must be RCTTypeNull (0) + if (vin_rct_type != 0) { + return false; + } + + const size_t miner_tx_end = ar.index(); + // Miner transaction end + + // Miner transaction must have exactly 1 byte with value 0 after the prefix + if ((miner_tx_end != offset(MINER_TX_PREFIX_END_OFFSET) + 1) || (*blob(MINER_TX_PREFIX_END_OFFSET) != 0)) { + return false; + } + + // Other transaction hashes + ar(m_numHashes); + + if (hashes) { + m_hashes.resize((m_numHashes + 1) * kHashSize); + calculateMinerTxHash(blob(MINER_TX_PREFIX_OFFSET), blob(MINER_TX_PREFIX_END_OFFSET), m_hashes.data()); + + for (uint64_t i = 1; i <= m_numHashes; ++i) { + Span h; + ar(h, kHashSize); + memcpy(m_hashes.data() + i * kHashSize, h.data(), kHashSize); + } + + calculateMerkleTreeHash(); + } + + return true; +} diff --git a/src/base/tools/cryptonote/BlockTemplate.h b/src/base/tools/cryptonote/BlockTemplate.h index 6effd3fe..11d9e33d 100644 --- a/src/base/tools/cryptonote/BlockTemplate.h +++ b/src/base/tools/cryptonote/BlockTemplate.h @@ -22,71 +22,129 @@ #define XMRIG_BLOCKTEMPLATE_H +#include "3rdparty/rapidjson/fwd.h" #include "base/crypto/Coin.h" #include "base/tools/Buffer.h" #include "base/tools/String.h" +#include "base/tools/Span.h" namespace xmrig { -struct BlockTemplate +class BlockTemplate { - enum { - HASH_SIZE = 32, - KEY_SIZE = 32, - SIGNATURE_SIZE = 64, - NONCE_SIZE = 4, +public: + static constexpr size_t kHashSize = 32; + static constexpr size_t kKeySize = 32; + static constexpr size_t kNonceSize = 4; + static constexpr size_t kSignatureSize = 64; + +# ifdef XMRIG_PROXY_PROJECT + static constexpr bool kCalcHashes = true; +# else + static constexpr bool kCalcHashes = false; +# endif + + enum Offset : uint32_t { + NONCE_OFFSET, + MINER_TX_PREFIX_OFFSET, + MINER_TX_PREFIX_END_OFFSET, + EPH_PUBLIC_KEY_OFFSET, + TX_EXTRA_OFFSET, + TX_PUBKEY_OFFSET, + TX_EXTRA_NONCE_OFFSET, + OFFSET_COUNT }; - Buffer raw_blob; - size_t eph_public_key_index; - size_t tx_pubkey_index; - uint64_t tx_extra_nonce_size; - size_t tx_extra_nonce_index; - size_t miner_tx_prefix_begin_index; - size_t miner_tx_prefix_end_index; + inline const Coin &coin() const { return m_coin; } + inline const uint8_t *blob() const { return m_blob.data(); } + inline const uint8_t *blob(Offset offset) const { return m_blob.data() + m_offsets[offset]; } + inline size_t offset(Offset offset) const { return m_offsets[offset]; } + inline size_t size() const { return m_blob.size(); } // Block header - uint8_t major_version; - uint8_t minor_version; - uint64_t timestamp; - uint8_t prev_id[HASH_SIZE]; - uint8_t nonce[NONCE_SIZE]; + inline uint8_t majorVersion() const { return m_version.first; } + inline uint8_t minorVersion() const { return m_version.second; } + inline uint64_t timestamp() const { return m_timestamp; } + inline const Span &prevId() const { return m_prevId; } + inline const uint8_t *nonce() const { return blob(NONCE_OFFSET); } - bool has_miner_signature; - uint8_t miner_signature[SIGNATURE_SIZE]; - uint8_t vote[2]; + // Wownero miner signature + inline bool hasMinerSignature() const { return !m_minerSignature.empty(); } + inline const Span &minerSignature() const { return m_minerSignature; } + inline const uint8_t *vote() const { return m_vote; } // Miner tx - uint64_t tx_version; - uint64_t unlock_time; - uint64_t num_inputs; - uint8_t input_type; - uint64_t height; - uint64_t num_outputs; - uint64_t amount; - uint8_t output_type; - uint8_t eph_public_key[KEY_SIZE]; - uint64_t extra_size; - Buffer extra; - uint8_t vin_rct_type; + inline uint64_t txVersion() const { return m_txVersion; } + inline uint64_t unlockTime() const { return m_unlockTime; } + inline uint64_t numInputs() const { return m_numInputs; } + inline uint8_t inputType() const { return m_inputType; } + inline uint64_t height() const { return m_height; } + inline uint64_t numOutputs() const { return m_numOutputs; } + inline uint64_t amount() const { return m_amount; } + inline uint64_t outputType() const { return m_outputType; } + inline const Span &ephPublicKey() const { return m_ephPublicKey; } + inline const Span &txExtraNonce() const { return m_txExtraNonce; } // Transaction hashes - uint64_t num_hashes; - Buffer hashes; + inline uint64_t numHashes() const { return m_numHashes; } + inline const Buffer &hashes() const { return m_hashes; } + inline const Buffer &minerTxMerkleTreeBranch() const { return m_minerTxMerkleTreeBranch; } + inline const uint8_t *rootHash() const { return m_rootHash; } - Buffer miner_tx_merkle_tree_branch; - uint8_t root_hash[HASH_SIZE]; + inline Buffer generateHashingBlob() const + { + Buffer out; + generateHashingBlob(out); - Buffer hashingBlob; + return out; + } - bool Init(const String& blockTemplate, Coin coin); + 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); - 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 GenerateHashingBlob(); + bool parse(const Buffer &blocktemplate, const Coin &coin, bool hashes = kCalcHashes); + bool parse(const char *blocktemplate, size_t size, const Coin &coin, bool hashes); + bool parse(const rapidjson::Value &blocktemplate, const Coin &coin, bool hashes = kCalcHashes); + bool parse(const String &blocktemplate, const Coin &coin, bool hashes = kCalcHashes); + void calculateMerkleTreeHash(); + void generateHashingBlob(Buffer &out) const; + +private: + static constexpr size_t kMinSize = 76; + + inline void setOffset(Offset offset, size_t value) { m_offsets[offset] = static_cast(value); } + + bool parse(bool hashes); + + Buffer m_blob; + Coin m_coin; + uint32_t m_offsets[OFFSET_COUNT]{}; + + std::pair m_version; + uint64_t m_timestamp = 0; + Span m_prevId; + + Span m_minerSignature; + uint8_t m_vote[2]{}; + + uint64_t m_txVersion = 0; + uint64_t m_unlockTime = 0; + uint64_t m_numInputs = 0; + uint8_t m_inputType = 0; + uint64_t m_height = 0; + uint64_t m_numOutputs = 0; + uint64_t m_amount = 0; + uint8_t m_outputType = 0; + Span m_ephPublicKey; + uint64_t m_extraSize = 0; + Span m_txExtraNonce; + + uint64_t m_numHashes = 0; + Buffer m_hashes; + Buffer m_minerTxMerkleTreeBranch; + uint8_t m_rootHash[kHashSize]{}; }; diff --git a/src/base/tools/cryptonote/WalletAddress.cpp b/src/base/tools/cryptonote/WalletAddress.cpp index 6b7f5343..8ea2bbd6 100644 --- a/src/base/tools/cryptonote/WalletAddress.cpp +++ b/src/base/tools/cryptonote/WalletAddress.cpp @@ -105,7 +105,7 @@ bool xmrig::WalletAddress::decode(const char *address, size_t size) assert(data.size() == data_size); - BlobReader ar(data.data(), data_size); + BlobReader ar(data.data(), data_size); if (ar(m_tag) && ar(m_publicSpendKey) && ar(m_publicViewKey) && ar.skip(ar.remaining() - sizeof(m_checksum)) && ar(m_checksum)) { uint8_t md[200];