From 6959014a0ec6582bc0fb083532943e35569e1976 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sat, 21 Mar 2026 15:08:23 +0100 Subject: [PATCH] Add Monero and Peya block adapters to shared util --- index.js | 55 +++++++-- src/cryptonote_basic/cryptonote_basic.h | 145 +++++++++++++++++++++++- src/cryptonote_config.h | 1 + src/main.cc | 97 +++++++++++++--- 4 files changed, 275 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index d75f102..3bf50db 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,46 @@ module.exports = require('bindings')('cryptoforknote.node'); -const SHA3 = require('sha3'); -const bignum = require('bignum'); -const bitcoin = require('bitcoinjs-lib'); -const varuint = require('varuint-bitcoin'); const crypto = require('crypto'); -const fastMerkleRoot = require('merkle-lib/fastRoot'); -const rtm = require('cryptoforknote-util/rtm'); +let bitcoin; +let bignum; +let SHA3; +let varuint; +let fastMerkleRoot; +let rtm; + +function getBitcoin() { + if (!bitcoin) bitcoin = require('bitcoinjs-lib'); + return bitcoin; +} + +function getBignum() { + if (!bignum) bignum = require('bignum'); + return bignum; +} + +function getSHA3() { + if (!SHA3) SHA3 = require('sha3'); + return SHA3; +} + +function getVaruint() { + if (!varuint) varuint = require('varuint-bitcoin'); + return varuint; +} + +function getFastMerkleRoot() { + if (!fastMerkleRoot) fastMerkleRoot = require('merkle-lib/fastRoot'); + return fastMerkleRoot; +} + +function getRtm() { + if (!rtm) rtm = require('cryptoforknote-util/rtm'); + return rtm; +} function scriptCompile(addrHash) { + const bitcoin = getBitcoin(); return bitcoin.script.compile([ bitcoin.opcodes.OP_DUP, bitcoin.opcodes.OP_HASH160, @@ -65,6 +96,7 @@ function transaction_hash3(transaction, forWitness) { } function getMerkleRoot(transactions, transaction_hash_func, detectWitness) { + const fastMerkleRoot = getFastMerkleRoot(); if (transactions.length === 0) return Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') const forWitness = detectWitness ? txesHaveWitnessCommit(transactions) : false; const hashes = transactions.map(transaction => transaction_hash_func(transaction, forWitness)); @@ -76,6 +108,7 @@ let last_epoch_number; let last_seed_hash; module.exports.baseDiff = function() { + const bignum = getBignum(); return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); }; @@ -84,6 +117,9 @@ module.exports.baseRavenDiff = function() { }; module.exports.RavenBlockTemplate = function(rpcData, poolAddress) { + const bignum = getBignum(); + const bitcoin = getBitcoin(); + const varuint = getVaruint(); const poolAddrHash = bitcoin.address.fromBase58Check(poolAddress).hash; let txCoinbase = new bitcoin.Transaction(); @@ -147,6 +183,7 @@ module.exports.RavenBlockTemplate = function(rpcData, poolAddress) { const EPOCH_LENGTH = 7500; const epoch_number = Math.floor(rpcData.height / EPOCH_LENGTH); if (last_epoch_number !== epoch_number) { + const SHA3 = getSHA3(); let sha3 = new SHA3.SHA3Hash(256); if (last_epoch_number && last_epoch_number + 1 === epoch_number) { last_seed_hash = sha3.update(last_seed_hash).digest(); @@ -176,6 +213,8 @@ module.exports.RavenBlockTemplate = function(rpcData, poolAddress) { }; function update_merkle_root_hash(offset, payload, blob_in, blob_out, transaction_hash_func, detectWitness) { + const bitcoin = getBitcoin(); + const varuint = getVaruint(); const nTransactions = varuint.decode(blob_in, offset); offset += varuint.decode.bytes; let transactions = []; @@ -214,6 +253,7 @@ module.exports.constructNewDeroBlob = function(blockTemplate, nonceBuff) { }; module.exports.EthBlockTemplate = function(rpcData) { + const bignum = getBignum(); const difficulty = module.exports.baseDiff().div(bignum(rpcData[2].substr(2), 16)).toNumber(); return { hash: rpcData[0].substr(2), @@ -224,6 +264,7 @@ module.exports.EthBlockTemplate = function(rpcData) { }; module.exports.ErgBlockTemplate = function(rpcData) { + const bignum = getBignum(); const difficulty = module.exports.baseDiff().div(bignum(rpcData.b)).toNumber(); return { hash: rpcData.msg, @@ -234,7 +275,7 @@ module.exports.ErgBlockTemplate = function(rpcData) { }; module.exports.RtmBlockTemplate = function(rpcData, poolAddress) { - return rtm.RtmBlockTemplate(rpcData, poolAddress); + return getRtm().RtmBlockTemplate(rpcData, poolAddress); }; module.exports.convertRtmBlob = function(blobBuffer) { diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 8de6868..5caf4d6 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -30,6 +31,16 @@ namespace cryptonote { + inline bool is_salvium_family_blob_type(const BLOB_TYPE blob_type) + { + return blob_type == BLOB_TYPE_CRYPTONOTE_SALVIUM; + } + + inline bool is_monero_family_blob_type(const BLOB_TYPE blob_type) + { + return blob_type == BLOB_TYPE_CRYPTONOTE; + } + struct block; class transaction; class transaction_prefix; @@ -174,6 +185,7 @@ namespace cryptonote }; typedef boost::variant txin_v; + typedef boost::variant monero_txin_v; typedef boost::variant txout_target_v; @@ -188,6 +200,43 @@ namespace cryptonote END_SERIALIZE() }; + struct monero_txout_to_key + { + monero_txout_to_key() { } + explicit monero_txout_to_key(const crypto::public_key &_key) : key(_key) { } + crypto::public_key key; + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + END_SERIALIZE() + }; + + struct monero_txout_to_tagged_key + { + monero_txout_to_tagged_key() { } + monero_txout_to_tagged_key(const crypto::public_key &_key, const crypto::view_tag &_view_tag) : key(_key), view_tag(_view_tag) { } + crypto::public_key key; + crypto::view_tag view_tag; + + BEGIN_SERIALIZE_OBJECT() + FIELD(key) + FIELD(view_tag) + END_SERIALIZE() + }; + + typedef boost::variant monero_txout_target_v; + + struct monero_tx_out + { + uint64_t amount; + monero_txout_target_v target; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(amount) + FIELD(target) + END_SERIALIZE() + }; + class protocol_tx_data_t { public: uint8_t version; @@ -205,6 +254,71 @@ namespace cryptonote END_SERIALIZE() }; + class monero_coinbase_transaction_t + { + public: + size_t version; + uint64_t unlock_time; + + std::vector vin; + std::vector vout; + std::vector extra; + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(version) + if (version != 2) return false; + VARINT_FIELD(unlock_time) + FIELD(vin) + FIELD(vout) + FIELD(extra) + uint8_t rct_signatures_type = 0; + FIELD(rct_signatures_type) + if (rct_signatures_type != 0) return false; + END_SERIALIZE() + + public: + monero_coinbase_transaction_t() { set_null(); } + void set_null() + { + version = 2; + unlock_time = 0; + vin.clear(); + vout.clear(); + extra.clear(); + } + }; + + struct monero_block_header_t + { + uint8_t major_version; + uint8_t minor_version; + uint64_t timestamp; + crypto::hash prev_id; + uint32_t nonce; + + monero_block_header_t(): major_version(0), minor_version(0), timestamp(0), prev_id(cryptonote::null_hash), nonce(0) {} + + BEGIN_SERIALIZE_OBJECT() + VARINT_FIELD(major_version) + VARINT_FIELD(minor_version) + VARINT_FIELD(timestamp) + FIELD(prev_id) + FIELD(nonce) + END_SERIALIZE() + }; + + struct monero_block_t: public monero_block_header_t + { + monero_coinbase_transaction_t miner_tx; + std::vector tx_hashes; + + BEGIN_SERIALIZE_OBJECT() + FIELDS(*static_cast(this)) + FIELD(miner_tx) + FIELD(tx_hashes) + END_SERIALIZE() + }; + struct erc_token_t { uint8_t version; @@ -537,19 +651,46 @@ namespace cryptonote END_SERIALIZE() }; + struct aux_header_t + { + BEGIN_SERIALIZE_OBJECT() + END_SERIALIZE() + }; + struct block: public block_header { transaction miner_tx; transaction protocol_tx; std::vector tx_hashes; + boost::optional aux_header; void set_blob_type(enum BLOB_TYPE bt) { miner_tx.blob_type = protocol_tx.blob_type = blob_type = bt; } BEGIN_SERIALIZE_OBJECT() FIELDS(*static_cast(this)) FIELD(miner_tx) - FIELD(protocol_tx) + if (is_salvium_family_blob_type(blob_type)) + FIELD(protocol_tx) FIELD(tx_hashes) + if (is_salvium_family_blob_type(blob_type) && major_version >= HF_VERSION_MERGE_MINING) + { + uint8_t has_aux_header = !!aux_header; + FIELD(has_aux_header) + if (has_aux_header) + { + if (!typename Archive::is_saving()) + aux_header = aux_header_t(); + FIELD_N("aux_header", *aux_header) + } + else if (!typename Archive::is_saving()) + { + aux_header = boost::none; + } + } + else if (!typename Archive::is_saving()) + { + aux_header = boost::none; + } END_SERIALIZE() }; @@ -615,6 +756,8 @@ VARIANT_TAG(binary_archive, cryptonote::txout_to_scripthash, 0x1); VARIANT_TAG(binary_archive, cryptonote::txout_to_key, 0x2); VARIANT_TAG(binary_archive, cryptonote::txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::txout_to_carrot_v1, 0x4); +VARIANT_TAG(binary_archive, cryptonote::monero_txout_to_key, 0x2); +VARIANT_TAG(binary_archive, cryptonote::monero_txout_to_tagged_key, 0x3); VARIANT_TAG(binary_archive, cryptonote::protocol_tx_data_t, 0x0); VARIANT_TAG(binary_archive, cryptonote::token_metadata_t, 0x0); VARIANT_TAG(binary_archive, cryptonote::erc_token_t, 0x0); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 176a326..b745240 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -2,6 +2,7 @@ #define CURRENT_TRANSACTION_VERSION 5 #define HF_VERSION_ENABLE_N_OUTS 2 +#define HF_VERSION_MERGE_MINING 13 #define TRANSACTION_VERSION_N_OUTS 3 #define TRANSACTION_VERSION_CARROT 4 #define TRANSACTION_VERSION_ENABLE_TOKENS 5 diff --git a/src/main.cc b/src/main.cc index b5a27e9..736ea0d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -20,6 +20,56 @@ using namespace cryptonote; // cryptonote::append_mm_tag_to_extra writes byte with TX_EXTRA_MERGE_MINING_TAG (1 here) and VARINT DEPTH (2 here) const size_t MM_NONCE_SIZE = 1 + 2 + sizeof(crypto::hash); +static bool parse_monero_block_from_blob(const blobdata &input, cryptonote::monero_block_t &b) { + std::stringstream ss; + ss << input; + binary_archive ba(ss); + return ::serialization::serialize(ba, b); +} + +static bool monero_block_to_blob(const cryptonote::monero_block_t &b, blobdata &blob) { + return cryptonote::t_serializable_object_to_blob(b, blob); +} + +static crypto::hash get_monero_coinbase_hash(const cryptonote::monero_coinbase_transaction_t &t) { + crypto::hash hashes[3]; + + std::ostringstream s; + binary_archive a(s); + ::serialization::serialize(a, const_cast(t)); + if (s.str().empty()) { + return cryptonote::null_hash; + } + + crypto::cn_fast_hash(s.str().data(), s.str().size() - 1, hashes[0]); + const blobdata blob = blobdata(1, '\0'); + cryptonote::get_blob_hash(blob, hashes[1]); + hashes[2] = cryptonote::null_hash; + return crypto::cn_fast_hash(hashes, sizeof(hashes)); +} + +static bool get_monero_block_hashing_blob(const cryptonote::monero_block_t &b, blobdata &blob) { + blob = cryptonote::t_serializable_object_to_blob(static_cast(b)); + std::vector tx_ids; + tx_ids.reserve(b.tx_hashes.size() + 1); + tx_ids.push_back(get_monero_coinbase_hash(b.miner_tx)); + tx_ids.insert(tx_ids.end(), b.tx_hashes.begin(), b.tx_hashes.end()); + crypto::hash tree_root_hash = cryptonote::null_hash; + cryptonote::get_tx_tree_hash(tx_ids, tree_root_hash); + blob.append(reinterpret_cast(&tree_root_hash), sizeof(tree_root_hash)); + blob.append(tools::get_varint_data(b.tx_hashes.size() + 1)); + return true; +} + +static bool get_monero_block_hash(const cryptonote::monero_block_t &b, crypto::hash &res) { + blobdata blob; + if (!monero_block_to_blob(b, blob)) { + return false; + } + cryptonote::get_blob_hash(blob, res); + return true; +} + blobdata uint64be_to_blob(uint64_t num) { blobdata res = " "; res[0] = num >> 56 & 0xff; @@ -149,10 +199,16 @@ NAN_METHOD(convert_blob) { // (parentBlockBuffer, cnBlobType) blob_type = static_cast(Nan::To(info[1]).FromMaybe(0)); } - block b = AUTO_VAL_INIT(b); - b.set_blob_type(blob_type); - if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2"); - if (!get_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block"); + if (blob_type == BLOB_TYPE_CRYPTONOTE) { + monero_block_t b = AUTO_VAL_INIT(b); + if (!parse_monero_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2"); + if (!get_monero_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block"); + } else { + block b = AUTO_VAL_INIT(b); + b.set_blob_type(blob_type); + if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block 2"); + if (!get_block_hashing_blob(b, output)) return THROW_ERROR_EXCEPTION("convert_blob: Failed to create mining block"); + } v8::Local returnValue = Nan::CopyBuffer((char*)output.data(), output.size()).ToLocalChecked(); info.GetReturnValue().Set(returnValue); @@ -173,12 +229,17 @@ NAN_METHOD(get_block_id) { blob_type = static_cast(Nan::To(info[1]).FromMaybe(0)); } - block b = AUTO_VAL_INIT(b); - b.set_blob_type(blob_type); - if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); - crypto::hash block_id; - if (!get_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block"); + if (blob_type == BLOB_TYPE_CRYPTONOTE) { + monero_block_t b = AUTO_VAL_INIT(b); + if (!parse_monero_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); + if (!get_monero_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block"); + } else { + block b = AUTO_VAL_INIT(b); + b.set_blob_type(blob_type); + if (!parse_and_validate_block_from_blob(input, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); + if (!get_block_hash(b, block_id)) return THROW_ERROR_EXCEPTION("Failed to calculate hash for block"); + } char *cstr = reinterpret_cast(&block_id); v8::Local returnValue = Nan::CopyBuffer(cstr, 32).ToLocalChecked(); @@ -206,12 +267,18 @@ NAN_METHOD(construct_block_blob) { // (parentBlockTemplateBuffer, nonceBuffer, c blobdata block_template_blob = std::string(Buffer::Data(block_template_buf), Buffer::Length(block_template_buf)); blobdata output = ""; - block b = AUTO_VAL_INIT(b); - b.set_blob_type(blob_type); - if (!parse_and_validate_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); - - b.nonce = nonce; - if (!block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob"); + if (blob_type == BLOB_TYPE_CRYPTONOTE) { + monero_block_t b = AUTO_VAL_INIT(b); + if (!parse_monero_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); + b.nonce = nonce; + if (!monero_block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob"); + } else { + block b = AUTO_VAL_INIT(b); + b.set_blob_type(blob_type); + if (!parse_and_validate_block_from_blob(block_template_blob, b)) return THROW_ERROR_EXCEPTION("Failed to parse block"); + b.nonce = nonce; + if (!block_to_blob(b, output)) return THROW_ERROR_EXCEPTION("Failed to convert block to blob"); + } v8::Local returnValue = Nan::CopyBuffer((char*)output.data(), output.size()).ToLocalChecked(); info.GetReturnValue().Set(returnValue);