Update BlockTemplate class.

This commit is contained in:
XMRig 2021-08-23 18:32:58 +07:00
parent a28f411339
commit bea2a6cf5b
No known key found for this signature in database
GPG key ID: 446A53638BE94409
11 changed files with 598 additions and 257 deletions

176
src/3rdparty/epee/span.h vendored Normal file
View file

@ -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 <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <type_traits>
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<char>`
and `span<std::uint8_t>`. 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<const char>` 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<typename T>
class span
{
template<typename U>
static constexpr bool safe_conversion() noexcept
{
// Allow exact matches or `T*` -> `const T*`.
using with_const = typename std::add_const<U>::type;
return std::is_same<T, U>() ||
(std::is_const<T>() && std::is_same<T, with_const>());
}
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<typename U, typename = typename std::enable_if<safe_conversion<U>()>::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<std::size_t N>
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<const T::value_type>` from a STL compatible `src`.
template<typename T>
constexpr span<const typename T::value_type> to_span(const T& src)
{
// compiler provides diagnostic if size() is not size_t.
return {src.data(), src.size()};
}
//! \return `span<T::value_type>` from a STL compatible `src`.
template<typename T>
constexpr span<typename T::value_type> to_mut_span(T& src)
{
// compiler provides diagnostic if size() is not size_t.
return {src.data(), src.size()};
}
template<typename T>
constexpr bool has_padding() noexcept
{
return !std::is_standard_layout<T>() || alignof(T) != 1;
}
//! \return Cast data from `src` as `span<const std::uint8_t>`.
template<typename T>
span<const std::uint8_t> to_byte_span(const span<const T> src) noexcept
{
static_assert(!has_padding<T>(), "source type may have padding");
return {reinterpret_cast<const std::uint8_t*>(src.data()), src.size_bytes()};
}
//! \return `span<const std::uint8_t>` which represents the bytes at `&src`.
template<typename T>
span<const std::uint8_t> as_byte_span(const T& src) noexcept
{
static_assert(!std::is_empty<T>(), "empty types will not work -> sizeof == 1");
static_assert(!has_padding<T>(), "source type may have padding");
return {reinterpret_cast<const std::uint8_t*>(std::addressof(src)), sizeof(T)};
}
//! \return `span<std::uint8_t>` which represents the bytes at `&src`.
template<typename T>
span<std::uint8_t> as_mut_byte_span(T& src) noexcept
{
static_assert(!std::is_empty<T>(), "empty types will not work -> sizeof == 1");
static_assert(!has_padding<T>(), "source type may have padding");
return {reinterpret_cast<std::uint8_t*>(std::addressof(src)), sizeof(T)};
}
//! make a span from a std::string
template<typename T>
span<const T> strspan(const std::string &s) noexcept
{
static_assert(std::is_same<T, char>() || std::is_same<T, unsigned char>() || std::is_same<T, int8_t>() || std::is_same<T, uint8_t>(), "Unexpected type");
return {reinterpret_cast<const T*>(s.data()), s.size()};
}
}

View file

@ -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
)

View file

@ -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<const uint8_t*>(&result.extra_nonce), 4);
Cvt::toHex(data + m_blocktemplate.offset(BlockTemplate::TX_EXTRA_NONCE_OFFSET) * 2, 8, reinterpret_cast<const uint8_t*>(&result.extra_nonce), 4);
}
# else
Cvt::toHex(data + m_job.nonceOffset() * 2, 8, reinterpret_cast<const uint8_t*>(&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 &params, 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 &params, 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 &params, 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 &params, 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)) {

View file

@ -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<int>(size), prefix_hash, sizeof(prefix_hash));

View file

@ -17,7 +17,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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<const uint8_t *>(data.data()), data.size(), doc);

View file

@ -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<const uint8_t *>(data.data()), data.size()); }
template<typename T>
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);

35
src/base/tools/Span.h Normal file
View file

@ -0,0 +1,35 @@
/* XMRig
* Copyright (c) 2018-2021 SChernykh <https://github.com/SChernykh>
* Copyright (c) 2016-2021 XMRig <https://github.com/xmrig>, <support@xmrig.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef XMRIG_SPAN_H
#define XMRIG_SPAN_H
#include "3rdparty/epee/span.h"
namespace xmrig {
using Span = epee::span<const uint8_t>;
} /* namespace xmrig */
#endif /* XMRIG_SPAN_H */

View file

@ -24,11 +24,13 @@
#include <cstdint>
#include <cstring>
#include <stdexcept>
namespace xmrig {
template<bool EXCEPTIONS>
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<typename T>
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<uint64_t>(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;

View file

@ -18,177 +18,64 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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<int>(prefix_end - prefix_begin), hashes, HASH_SIZE);
keccak(prefix_begin, static_cast<int>(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<uint8_t>(k) & 0x7F) | 0x80);
out.emplace_back((static_cast<uint8_t>(k) & 0x7F) | 0x80);
k >>= 7;
}
hashingBlob.emplace_back(static_cast<uint8_t>(k));
out.emplace_back(static_cast<uint8_t>(k));
}
} /* namespace xmrig */
bool xmrig::BlockTemplate::parse(bool hashes)
{
BlobReader<true> 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<true> 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;
}

View file

@ -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<uint32_t>(value); }
bool parse(bool hashes);
Buffer m_blob;
Coin m_coin;
uint32_t m_offsets[OFFSET_COUNT]{};
std::pair<uint8_t, uint8_t> 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]{};
};

View file

@ -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<false> 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];