From c8b4cdffdca796746b830be04a4ea65d4b9a93d7 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Wed, 1 Mar 2023 16:56:17 +0000 Subject: [PATCH 1/9] p2p: avoid spam blocking ipv4 addresses in a blocked subnet --- src/p2p/net_node.inl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index c393d5ced..8f5fb4f3d 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -247,7 +247,23 @@ namespace nodetool if (it == m_blocked_hosts.end()) { m_blocked_hosts[host_str] = limit; - added = true; + + // if the host was already blocked due to being in a blocked subnet, let it be silent + bool matches_blocked_subnet = false; + if (addr.get_type_id() == epee::net_utils::address_type::ipv4) + { + auto ipv4_address = addr.template as(); + for (auto jt = m_blocked_subnets.begin(); jt != m_blocked_subnets.end(); ++jt) + { + if (jt->first.matches(ipv4_address)) + { + matches_blocked_subnet = true; + break; + } + } + } + if (!matches_blocked_subnet) + added = true; } else if (it->second < limit || !add_only) it->second = limit; From 65764dce8cea0c0bf982163b4c153157cea9e9e6 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 9 Mar 2023 17:17:59 +0000 Subject: [PATCH 2/9] p2p: do not log to global when re-blocking a subnet --- src/p2p/net_node.inl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 8f5fb4f3d..23a891bfa 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -333,6 +333,7 @@ namespace nodetool limit = std::numeric_limits::max(); else limit = now + seconds; + const bool added = m_blocked_subnets.find(subnet) == m_blocked_subnets.end(); m_blocked_subnets[subnet] = limit; // drop any connection to that subnet. This should only have to look into @@ -365,7 +366,10 @@ namespace nodetool conns.clear(); } - MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked."); + if (added) + MCLOG_CYAN(el::Level::Info, "global", "Subnet " << subnet.host_str() << " blocked."); + else + MINFO("Subnet " << subnet.host_str() << " blocked."); return true; } //----------------------------------------------------------------------------------- From b1710a4fcc87a2327f0a979017aa5ac36c3228e3 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Mon, 6 Mar 2023 14:40:14 +0100 Subject: [PATCH 3/9] cryptonote_basic: remove unused struct --- src/cryptonote_basic/cryptonote_basic_impl.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index b423573c5..ca991f05e 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -39,15 +39,6 @@ namespace cryptonote { /************************************************************************/ /* */ /************************************************************************/ - template - struct array_hasher: std::unary_function - { - std::size_t operator()(const t_array& val) const - { - return boost::hash_range(&val.data[0], &val.data[sizeof(val.data)]); - } - }; - #pragma pack(push, 1) struct public_address_outer_blob From 12ad6748a4a03d0f1ba64ec2c5f392f94b363318 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 1 Jan 2023 10:38:13 +0000 Subject: [PATCH 4/9] util: make GMT timestamps explicit for clarity For privacy reasons, time functions use GMT, to avoid logs leaking timezones. It'd make more sense to use localtime for wallet output (which are not logged by default), but that adds inconsistencies which can also be confusing. So add a Z suffix for now to make it clear these are not local time. --- src/common/util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/util.cpp b/src/common/util.cpp index 1a7b9b544..b618ffe06 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1067,7 +1067,7 @@ std::string get_nix_version_display_string() time_t tt = ts; struct tm tm; misc_utils::get_gmt_time(tt, tm); - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%SZ", &tm); return std::string(buffer); } From e44d831d7f321d5473054e40a3d77f2c053750a5 Mon Sep 17 00:00:00 2001 From: selsta Date: Sun, 19 Feb 2023 18:33:18 +0100 Subject: [PATCH 5/9] workflows: update dependencies to fix warnings --- .github/workflows/build.yml | 27 ++++++++++++++------------- .github/workflows/depends.yml | 11 ++++++----- .github/workflows/gitian.yml | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2cde753bc..850dbdc9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,10 +27,10 @@ jobs: env: CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: /Users/runner/Library/Caches/ccache key: ccache-${{ runner.os }}-build-${{ github.sha }} @@ -51,15 +51,15 @@ jobs: run: shell: msys2 {0} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: C:\Users\runneradmin\.ccache key: ccache-${{ runner.os }}-build-${{ github.sha }} restore-keys: ccache-${{ runner.os }}-build- - - uses: eine/setup-msys2@v2 + - uses: msys2/setup-msys2@v2 with: update: true install: mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-ccache mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb mingw-w64-x86_64-unbound git @@ -79,10 +79,10 @@ jobs: matrix: os: [ubuntu-22.04, ubuntu-20.04] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.ccache key: ccache-${{ runner.os }}-build-${{ matrix.os }}-${{ github.sha }} @@ -105,10 +105,10 @@ jobs: env: CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.ccache key: ccache-${{ runner.os }}-libwallet-${{ github.sha }} @@ -133,11 +133,11 @@ jobs: env: CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: recursive - name: ccache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.ccache key: ccache-${{ runner.os }}-build-ubuntu-latest-${{ github.sha }} @@ -167,8 +167,9 @@ jobs: source-archive: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: + fetch-depth: 0 submodules: recursive - name: archive run: | @@ -177,7 +178,7 @@ jobs: export OUTPUT="$VERSION.tar" echo "OUTPUT=$OUTPUT" >> $GITHUB_ENV /home/runner/.local/bin/git-archive-all --prefix "$VERSION/" --force-submodules "$OUTPUT" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: ${{ env.OUTPUT }} path: /home/runner/work/monero/monero/${{ env.OUTPUT }} diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index c05f74f9c..710a548a5 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -57,19 +57,20 @@ jobs: packages: "clang-8 gperf cmake python3-zmq libdbus-1-dev libharfbuzz-dev" name: ${{ matrix.toolchain.name }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: + fetch-depth: 0 submodules: recursive # Most volatile cache - name: ccache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.ccache key: ccache-${{ matrix.toolchain.host }}-${{ github.sha }} restore-keys: ccache-${{ matrix.toolchain.host }}- # Less volatile cache - name: depends cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: contrib/depends/built key: depends-${{ matrix.toolchain.host }}-${{ hashFiles('contrib/depends/packages/*') }} @@ -78,7 +79,7 @@ jobs: depends-${{ matrix.toolchain.host }}- # Static cache - name: OSX SDK cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: contrib/depends/sdk-sources key: sdk-${{ matrix.toolchain.host }}-${{ matrix.toolchain.osx_sdk }} @@ -96,7 +97,7 @@ jobs: run: | ${{env.CCACHE_SETTINGS}} make depends target=${{ matrix.toolchain.host }} -j2 - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: ${{ matrix.toolchain.host == 'x86_64-w64-mingw32' || matrix.toolchain.host == 'x86_64-apple-darwin11' || matrix.toolchain.host == 'x86_64-unknown-linux-gnu' }} with: name: ${{ matrix.toolchain.name }} diff --git a/.github/workflows/gitian.yml b/.github/workflows/gitian.yml index 6506e3d46..91e60a88f 100644 --- a/.github/workflows/gitian.yml +++ b/.github/workflows/gitian.yml @@ -42,7 +42,7 @@ jobs: echo \`\`\` >> $GITHUB_STEP_SUMMARY shasum -a256 * >> $GITHUB_STEP_SUMMARY echo \`\`\` >> $GITHUB_STEP_SUMMARY - - uses: actions/upload-artifact@v3.1.0 + - uses: actions/upload-artifact@v3 with: name: ${{ matrix.operating-system.name }} path: | From f8d0f857f632066732535ed21a66f72b23613b52 Mon Sep 17 00:00:00 2001 From: Francois Beutin Date: Fri, 17 Mar 2023 21:27:51 +0100 Subject: [PATCH 6/9] device: Add ledger Stax device id to device detection --- src/device/device_ledger.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index cf1daa6ad..a58489bb0 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -526,6 +526,7 @@ namespace hw { {0x2c97, 0x0001, 0, 0xffa0}, {0x2c97, 0x0004, 0, 0xffa0}, {0x2c97, 0x0005, 0, 0xffa0}, + {0x2c97, 0x0006, 0, 0xffa0}, }; bool device_ledger::connect(void) { From 62ae03bfd3ca87828f557ae63e9ca2ca40812d30 Mon Sep 17 00:00:00 2001 From: Jeffrey Ryan Date: Fri, 13 Jan 2023 20:53:25 -0600 Subject: [PATCH 7/9] verRctNonSemanticsSimpleCached: fix fragility --- src/cryptonote_config.h | 1 + src/cryptonote_core/CMakeLists.txt | 4 +- src/cryptonote_core/blockchain.cpp | 81 +--- src/cryptonote_core/blockchain.h | 22 +- src/cryptonote_core/tx_verification_utils.cpp | 167 +++++++ src/cryptonote_core/tx_verification_utils.h | 78 ++++ src/ringct/rctSigs.cpp | 37 -- src/ringct/rctSigs.h | 1 - src/ringct/rctTypes.h | 8 + tests/data/txs/bpp_tx_e89415.bin | Bin 0 -> 1539 bytes tests/unit_tests/CMakeLists.txt | 1 + .../ver_rct_non_semantics_simple_cached.cpp | 426 ++++++++++++++++++ 12 files changed, 715 insertions(+), 111 deletions(-) create mode 100644 src/cryptonote_core/tx_verification_utils.cpp create mode 100644 src/cryptonote_core/tx_verification_utils.h create mode 100644 tests/data/txs/bpp_tx_e89415.bin create mode 100644 tests/unit_tests/ver_rct_non_semantics_simple_cached.cpp diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 1f59c4bc3..937218a2d 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -265,6 +265,7 @@ namespace config const unsigned char HASH_KEY_MM_SLOT = 'm'; const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS_SEED[] = "multisig_tx_privkeys_seed"; const constexpr char HASH_KEY_MULTISIG_TX_PRIVKEYS[] = "multisig_tx_privkeys"; + const constexpr char HASH_KEY_TXHASH_AND_MIXRING[] = "txhash_and_mixring"; // Multisig const uint32_t MULTISIG_MAX_SIGNERS{16}; diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 69411e379..beead6217 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -31,7 +31,9 @@ set(cryptonote_core_sources cryptonote_core.cpp tx_pool.cpp tx_sanity_check.cpp - cryptonote_tx_utils.cpp) + cryptonote_tx_utils.cpp + tx_verification_utils.cpp +) set(cryptonote_core_headers) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index decf934ee..d40bb0f94 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -57,6 +57,7 @@ #include "common/notify.h" #include "common/varint.h" #include "common/pruning.h" +#include "common/data_cache.h" #include "time_helper.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -98,7 +99,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_difficulty_for_next_block(1), m_btc_valid(false), m_batch_success(true), - m_prepare_height(0) + m_prepare_height(0), + m_rct_ver_cache() { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -3322,7 +3324,7 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } return false; } -bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys) const +bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys) { PERF_TIMER(expand_transaction_2); CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); @@ -3645,6 +3647,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, false, "Transaction spends at least one output which is too young"); } + // Warn that new RCT types are present, and thus the cache is not being used effectively + static constexpr const std::uint8_t RCT_CACHE_TYPE = rct::RCTTypeBulletproofPlus; + if (tx.rct_signatures.type > RCT_CACHE_TYPE) + { + MWARNING("RCT cache is not caching new verification results. Please update RCT_CACHE_TYPE!"); + } + if (tx.version == 1) { if (threads > 1) @@ -3666,12 +3675,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } else { - if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) - { - MERROR_VER("Failed to expand rct signatures!"); - return false; - } - // from version 2, check ringct signatures // obviously, the original and simple rct APIs use a mixRing that's indexes // in opposite orders, because it'd be too simple otherwise... @@ -3690,61 +3693,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeCLSAG: case rct::RCTTypeBulletproofPlus: { - // check all this, either reconstructed (so should really pass), or not - { - if (pubkeys.size() != rv.mixRing.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - for (size_t i = 0; i < pubkeys.size(); ++i) - { - if (pubkeys[i].size() != rv.mixRing[i].size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); - return false; - } - } - - for (size_t n = 0; n < pubkeys.size(); ++n) - { - for (size_t m = 0; m < pubkeys[n].size(); ++m) - { - if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) - { - MERROR_VER("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); - return false; - } - if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) - { - MERROR_VER("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); - return false; - } - } - } - } - - const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); - if (n_sigs != tx.vin.size()) - { - MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); - return false; - } - for (size_t n = 0; n < tx.vin.size(); ++n) - { - bool error; - if (rct::is_rct_clsag(rv.type)) - error = memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); - else - error = rv.p.MGs[n].II.empty() || memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); - if (error) - { - MERROR_VER("Failed to check ringct signatures: mismatched key image"); - return false; - } - } - - if (!rct::verRctNonSemanticsSimpleCached(rv)) + if (!ver_rct_non_semantics_simple_cached(tx, pubkeys, m_rct_ver_cache, RCT_CACHE_TYPE)) { MERROR_VER("Failed to check ringct signatures!"); return false; @@ -3754,6 +3703,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeFull: case rct::RCTTypeFullBulletproof: { + if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) + { + MERROR_VER("Failed to expand rct signatures!"); + return false; + } + // check all this, either reconstructed (so should really pass), or not { bool size_matches = true; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index c61ce4466..42246fca2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -57,6 +57,7 @@ #include "rpc/core_rpc_server_commands_defs.h" #include "cryptonote_basic/difficulty.h" #include "cryptonote_tx_utils.h" +#include "tx_verification_utils.h" #include "cryptonote_basic/verification_context.h" #include "crypto/hash.h" #include "checkpoints/checkpoints.h" @@ -596,6 +597,15 @@ namespace cryptonote */ bool store_blockchain(); + /** + * @brief expands v2 transaction data from blockchain + * + * RingCT transactions do not transmit some of their data if it + * can be reconstituted by the receiver. This function expands + * that implicit data. + */ + static bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys); + /** * @brief validates a transaction's inputs * @@ -1222,6 +1232,9 @@ namespace cryptonote uint64_t m_prepare_nblocks; std::vector *m_prepare_blocks; + // cache for verifying transaction RCT non semantics + mutable rct_ver_cache_t m_rct_ver_cache; + /** * @brief collects the keys for all outputs being "spent" as an input * @@ -1574,15 +1587,6 @@ namespace cryptonote */ void load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints); - /** - * @brief expands v2 transaction data from blockchain - * - * RingCT transactions do not transmit some of their data if it - * can be reconstituted by the receiver. This function expands - * that implicit data. - */ - bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys) const; - /** * @brief invalidates any cached block template */ diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp new file mode 100644 index 000000000..a93ef2f25 --- /dev/null +++ b/src/cryptonote_core/tx_verification_utils.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2023, 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. + +#include "cryptonote_core/blockchain.h" +#include "cryptonote_core/tx_verification_utils.h" +#include "ringct/rctSigs.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "blockchain" + +#define VER_ASSERT(cond, msgexpr) CHECK_AND_ASSERT_MES(cond, false, msgexpr) + +using namespace cryptonote; + +// Do RCT expansion, then do post-expansion sanity checks, then do full non-semantics verification. +static bool expand_tx_and_ver_rct_non_sem(transaction& tx, const rct::ctkeyM& mix_ring) +{ + // Pruned transactions can not be expanded and verified because they are missing RCT data + VER_ASSERT(!tx.pruned, "Pruned transaction will not pass verRctNonSemanticsSimple"); + + // Calculate prefix hash + const crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + + // Expand mixring, tx inputs, tx key images, prefix hash message, etc into the RCT sig + const bool exp_res = Blockchain::expand_transaction_2(tx, tx_prefix_hash, mix_ring); + VER_ASSERT(exp_res, "Failed to expand rct signatures!"); + + const rct::rctSig& rv = tx.rct_signatures; + + // Check that expanded RCT mixring == input mixring + VER_ASSERT(rv.mixRing == mix_ring, "Failed to check ringct signatures: mismatched pubkeys/mixRing"); + + // Check CLSAG/MLSAG size against transaction input + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + VER_ASSERT(n_sigs == tx.vin.size(), "Failed to check ringct signatures: mismatched input sigs/vin sizes"); + + // For each input, check that the key images were copied into the expanded RCT sig correctly + for (size_t n = 0; n < n_sigs; ++n) + { + const crypto::key_image& nth_vin_image = boost::get(tx.vin[n]).k_image; + + if (rct::is_rct_clsag(rv.type)) + { + const bool ki_match = 0 == memcmp(&nth_vin_image, &rv.p.CLSAGs[n].I, 32); + VER_ASSERT(ki_match, "Failed to check ringct signatures: mismatched CLSAG key image"); + } + else + { + const bool mg_nonempty = !rv.p.MGs[n].II.empty(); + VER_ASSERT(mg_nonempty, "Failed to check ringct signatures: missing MLSAG key image"); + const bool ki_match = 0 == memcmp(&nth_vin_image, &rv.p.MGs[n].II[0], 32); + VER_ASSERT(ki_match, "Failed to check ringct signatures: mismatched MLSAG key image"); + } + } + + // Mix ring data is now known to be correctly incorporated into the RCT sig inside tx. + return rct::verRctNonSemanticsSimple(rv); +} + +// Create a unique identifier for pair of tx blob + mix ring +static crypto::hash calc_tx_mixring_hash(const transaction& tx, const rct::ctkeyM& mix_ring) +{ + std::stringstream ss; + + // Start with domain seperation + ss << config::HASH_KEY_TXHASH_AND_MIXRING; + + // Then add TX hash + const crypto::hash tx_hash = get_transaction_hash(tx); + ss.write(tx_hash.data, sizeof(crypto::hash)); + + // Then serialize mix ring + binary_archive ar(ss); + ::do_serialize(ar, const_cast(mix_ring)); + + // Calculate hash of TX hash and mix ring blob + crypto::hash tx_and_mixring_hash; + get_blob_hash(ss.str(), tx_and_mixring_hash); + + return tx_and_mixring_hash; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace cryptonote +{ + +bool ver_rct_non_semantics_simple_cached +( + transaction& tx, + const rct::ctkeyM& mix_ring, + rct_ver_cache_t& cache, + const std::uint8_t rct_type_to_cache +) +{ + // Hello future Monero dev! If you got this assert, read the following carefully: + // + // For this version of RCT, the way we guaranteed that verification caches do not generate false + // positives (and thus possibly enabling double spends) is we take a hash of two things. One, + // we use get_transaction_hash() which gives us a (cryptographically secure) unique + // representation of all "knobs" controlled by the possibly malicious constructor of the + // transaction. Two, we take a hash of all *previously validated* blockchain data referenced by + // this transaction which is required to validate the ring signature. In our case, this is the + // mixring. Future versions of the protocol may differ in this regard, but if this assumptions + // holds true in the future, enable the verification hash by modifying the `untested_tx` + // condition below. + const bool untested_tx = tx.version > 2 || tx.rct_signatures.type > rct::RCTTypeBulletproofPlus; + VER_ASSERT(!untested_tx, "Unknown TX type. Make sure RCT cache works correctly with this type and then enable it in the code here."); + + // Don't cache older (or newer) rctSig types + // This cache only makes sense when it caches data from mempool first, + // so only "current fork version-enabled" RCT types need to be cached + if (tx.rct_signatures.type != rct_type_to_cache) + { + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " skipped"); + return expand_tx_and_ver_rct_non_sem(tx, mix_ring); + } + + // Generate unique hash for tx+mix_ring pair + const crypto::hash tx_mixring_hash = calc_tx_mixring_hash(tx, mix_ring); + + // Search cache for successful verification of same TX + mix ring combination + if (cache.has(tx_mixring_hash)) + { + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " hit"); + return true; + } + + // We had a cache miss, so now we must expand the mix ring and do full verification + MDEBUG("RCT cache: tx " << get_transaction_hash(tx) << " missed"); + if (!expand_tx_and_ver_rct_non_sem(tx, mix_ring)) + { + return false; + } + + // At this point, the TX RCT verified successfully, so add it to the cache and return true + cache.add(tx_mixring_hash); + + return true; +} + +} // namespace cryptonote diff --git a/src/cryptonote_core/tx_verification_utils.h b/src/cryptonote_core/tx_verification_utils.h new file mode 100644 index 000000000..ccd401d2a --- /dev/null +++ b/src/cryptonote_core/tx_verification_utils.h @@ -0,0 +1,78 @@ +// Copyright (c) 2023, 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 "common/data_cache.h" +#include "cryptonote_basic/cryptonote_basic.h" + +namespace cryptonote +{ + +// Modifying this value should not affect consensus. You can adjust it for performance needs +static constexpr const size_t RCT_VER_CACHE_SIZE = 8192; + +using rct_ver_cache_t = ::tools::data_cache<::crypto::hash, RCT_VER_CACHE_SIZE>; + +/** + * @brief Cached version of rct::verRctNonSemanticsSimple + * + * This function will not affect how the transaction is serialized and it will never modify the + * transaction prefix. + * + * The reference to tx is mutable since the transaction's ring signatures may be expanded by + * Blockchain::expand_transaction_2. However, on cache hits, the transaction will not be + * expanded. This means that the caller does not need to call expand_transaction_2 on this + * transaction before passing it; the transaction will not successfully verify with "old" RCT data + * if the transaction has been otherwise modified since the last verification. + * + * But, if cryptonote::get_transaction_hash(tx) returns a "stale" hash, this function is not + * guaranteed to work. So make sure that the cryptonote::transaction passed has not had + * modifications to it since the last time its hash was fetched without properly invalidating the + * hashes. + * + * rct_type_to_cache can be any RCT version value as long as rct::verRctNonSemanticsSimple works for + * this RCT version, but for most applications, it doesn't make sense to not make this version + * the "current" RCT version (i.e. the version that transactions in the mempool are). + * + * @param tx transaction which contains RCT signature to verify + * @param mix_ring mixring referenced by this tx. THIS DATA MUST BE PREVIOUSLY VALIDATED + * @param cache saves tx+mixring hashes used to cache calls + * @param rct_type_to_cache Only RCT sigs with version (e.g. RCTTypeBulletproofPlus) will be cached + * @return true when verRctNonSemanticsSimple() w/ expanded tx.rct_signatures would return true + * @return false when verRctNonSemanticsSimple() w/ expanded tx.rct_signatures would return false + */ +bool ver_rct_non_semantics_simple_cached +( + transaction& tx, + const rct::ctkeyM& mix_ring, + rct_ver_cache_t& cache, + std::uint8_t rct_type_to_cache +); + +} // namespace cryptonote diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 65f107511..63bacb201 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -30,7 +30,6 @@ #include "misc_log_ex.h" #include "misc_language.h" -#include "common/data_cache.h" #include "common/perf_timer.h" #include "common/threadpool.h" #include "common/util.h" @@ -1765,42 +1764,6 @@ namespace rct { } } - bool verRctNonSemanticsSimpleCached(const rctSig & rv) - { - // Hello future Monero dev! If you got this assert, read the following carefully: - // - // RCT cache assumes that this function will serialize and hash all rv's fields used for RingCT verification - // If you're about to add a new RCTType here, first you must check that binary_archive serialization writes all rv's fields to the binary blob - // If it's not the case, rewrite this function to serialize everything, even some "temporary" fields which are not serialized normally - CHECK_AND_ASSERT_MES_L1(rv.type <= RCTTypeBulletproofPlus, false, "Unknown RCT type. Make sure RCT cache works correctly with this type and then enable it in the code here."); - - // Don't cache older (or newer) rctSig types - // This cache only makes sense when it caches data from mempool first, - // so only "current fork version-enabled" RCT types need to be cached - if (rv.type != RCTTypeBulletproofPlus) - return verRctNonSemanticsSimple(rv); - - // Get the hash of rv - std::stringstream ss; - binary_archive ar(ss); - - ::do_serialize(ar, const_cast(rv)); - - crypto::hash h; - cryptonote::get_blob_hash(ss.str(), h); - - static tools::data_cache cache; - - if (cache.has(h)) - return true; - - const bool res = verRctNonSemanticsSimple(rv); - if (res) - cache.add(h); - - return res; - } - //RingCT protocol //genRct: // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index ddd790109..5846cb46e 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -135,7 +135,6 @@ namespace rct { bool verRctSemanticsSimple(const rctSig & rv); bool verRctSemanticsSimple(const std::vector & rv); bool verRctNonSemanticsSimple(const rctSig & rv); - bool verRctNonSemanticsSimpleCached(const rctSig & rv); static inline bool verRctSimple(const rctSig & rv) { return verRctSemanticsSimple(rv) && verRctNonSemanticsSimple(rv); } xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev); xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 8bc3ad704..af9db325f 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -97,6 +97,14 @@ namespace rct { struct ctkey { key dest; key mask; //C here if public + + bool operator==(const ctkey &other) const { + return (dest == other.dest) && (mask == other.mask); + } + + bool operator!=(const ctkey &other) const { + return !(*this == other); + } }; typedef std::vector ctkeyV; typedef std::vector ctkeyM; diff --git a/tests/data/txs/bpp_tx_e89415.bin b/tests/data/txs/bpp_tx_e89415.bin new file mode 100644 index 0000000000000000000000000000000000000000..38f596397b50ec27d53c58dd6234ec393eb3301e GIT binary patch literal 1539 zcmV+e2K@N~009C35SFBh8nL;(0npv90-x&}k=`+!u^rvg8|LaL^05cjVW6u8tI-7F zlmV7g_X+fd00w# z1#HUTcm|~iECH>1A-#6Max)+RdZ1hzOrCV4zO)d}a~8IUf2pPdh@LRJiHVkN#C-;9ZOXz9nRq&d5TVpfAZ( z%V=0S9bC0m0C2SSV36K+K;UQ$2gx=2A0P5#H>G2Bc!^a>#GwueQa}X%3BNhONI36N zag|ehL|HF(tNhq73pS^w_34s`^<0|A(~Klv9piUPIh-GI2J>kI;yqX(;I|cC1iFdZ z;-)WtGDEwo-BY36x9h|5Y^9lvNC*T! zeqk6~V00wVMJY*-sJO}xV1mrpN}bY_3`G?kl5C-EekOGdit*3x#i3>!eG4Z4+YoLw zribYwI?o3sakJAJgUxQOTuZ9JuN+<8m>{}y3W+FaBTJt8N(X$xd}}nUu5jk0eShKj zY0+sK7)NN3ua}*Gm9HpoT-DYNTp}ih34HTVhKF5aY)$!Bg-y|P&KeiP;NLq)$)Q10 zS2QHmxNL-XC<~{uqF=_YlAACFtZy%?6`;4RT@O5%;(h67*u!abho<({j2uAGxIB9~ zBYNpgBYsDMj)=6K_uleVPPn8_pEU>ghSax=SW^?u^N6!&YZ{AlYf0M?O4kEPJV&Q6 zA&9E>cb9#Blu1IKc{X>U_%uL5>t1xY0?<;9THS$s6E9=U=oQH#s_h5I^Ob|60`CzU zDTs7$!=WK5@!S7SAB@;(@1>Vezw6gkiwwyev=FihJ`A_qdk~4k9+S71EOjU}Xz^ny zF9FXnT4^of^V|VT^a%>7{NDmg(A(iMzD1s%=7pv|e5@0y5Hg{w`l%VGH~@sF@nt|9 zI6?;}=$}OMRSl%g{ROWR?=I>k>16;2sm)WmhNs)z^!Qp3 zXBFUZstu1QuBV6H=Y~nWYvUdVkuIsr)7zIveF-R#6M{5>fb*;#hkekmN8a;(UjCGH(X%S_bPP<^$p@VfES-Jf}0>mFgW~%X)dB4C?A$ ze6>}jR*qt7?E`l2+%bvf@H)zU93$D^ZtQGFScBI{{aAspo*XGRE1i4 zwPd>4io@V{93u$!7@kmVhz2k<}xwVioPoV_eS5|x-I{w-<5;xIG?jvni~`W zIStk}YK$Yyaaxy>&O?#gn+?zVvdQ!_njlTX%c&z8yA1ZlR!UsRznJHc9+?6qHIzhT z|A1eAIQ}^674IA2Pz`eC-=F1?v|KW3_2x#)e538dx@*{lEGGS8Ls1jx!~pGR>{=@~ zeQ$1GBBIN$-%wLoO;lqWh&dp)l^0R{%y9})UlnF+3#a6 pC6o+feSYhM{p8yy&%2Jmr-i)srzJ3%r^!M=VXg$hFH6D{WC$9A@qz#V literal 0 HcmV?d00001 diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 55818dc93..2efa931bc 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -91,6 +91,7 @@ set(unit_tests_sources unbound.cpp uri.cpp varint.cpp + ver_rct_non_semantics_simple_cached.cpp ringct.cpp output_selection.cpp vercmp.cpp diff --git a/tests/unit_tests/ver_rct_non_semantics_simple_cached.cpp b/tests/unit_tests/ver_rct_non_semantics_simple_cached.cpp new file mode 100644 index 000000000..118fb7c48 --- /dev/null +++ b/tests/unit_tests/ver_rct_non_semantics_simple_cached.cpp @@ -0,0 +1,426 @@ +// Copyright (c) 2023, 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. + +#include + +#define IN_UNIT_TESTS // To access Blockchain::{expand_transaction_2, verRctNonSemanticsSimpleCached} + +#include "gtest/gtest.h" +#include "unit_tests_utils.h" + +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_core/blockchain.h" +#include "file_io_utils.h" +#include "misc_log_ex.h" +#include "ringct/rctSigs.h" + +namespace cryptonote +{ +// declaration not provided in cryptonote_format_utils.h, but definition is not static ;) +bool expand_transaction_1(transaction &tx, bool base_only); +} + +namespace +{ +/** + * @brief Make rct::ctkey from hex string representation of destionation and mask + * + * @param dest_hex + * @param mask_hex + * @return rct::ctkey + */ +static rct::ctkey make_ctkey(const char* dest_hex, const char* mask_hex) +{ + rct::key dest; + rct::key mask; + CHECK_AND_ASSERT_THROW_MES(epee::from_hex::to_buffer(epee::as_mut_byte_span(dest), dest_hex), "dest bad hex: " << dest_hex); + CHECK_AND_ASSERT_THROW_MES(epee::from_hex::to_buffer(epee::as_mut_byte_span(mask), mask_hex), "mask bad hex: " << mask_hex); + return {dest, mask}; +} + +template +static std::string stringify_with_do_serialize(const T& t) +{ + std::stringstream ss; + binary_archive ar(ss); + CHECK_AND_ASSERT_THROW_MES(ar.good(), "Archiver is not in a good state. This shouldn't happen!"); + ::do_serialize(ar, const_cast(t)); + return ss.str(); +} + +static bool check_tx_is_expanded(const cryptonote::transaction& tx, const rct::ctkeyM& pubkeys) +{ + // Ripped from cryptonote_core/blockchain.cpp + + const rct::rctSig& rv = tx.rct_signatures; + + if (pubkeys.size() != rv.mixRing.size()) + { + MERROR("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + for (size_t i = 0; i < pubkeys.size(); ++i) + { + if (pubkeys[i].size() != rv.mixRing[i].size()) + { + MERROR("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) + { + MERROR("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) + { + MERROR("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } + } + } + + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + if (n_sigs != tx.vin.size()) + { + MERROR("Failed to check ringct signatures: mismatched MGs/vin sizes"); + return false; + } + for (size_t n = 0; n < tx.vin.size(); ++n) + { + bool error; + if (rct::is_rct_clsag(rv.type)) + error = memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); + else + error = rv.p.MGs[n].II.empty() || memcmp(&boost::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); + if (error) + { + MERROR("Failed to check ringct signatures: mismatched key image"); + return false; + } + } + + return true; +} + +/** + * @brief Perform expand_transaction_1 and Blockchain::expand_transaction_2 on a certain transaction + */ +static void expand_transaction_fully(cryptonote::transaction& tx, const rct::ctkeyM& input_pubkeys) +{ + const crypto::hash tx_prefix_hash = cryptonote::get_transaction_prefix_hash(tx); + CHECK_AND_ASSERT_THROW_MES(cryptonote::expand_transaction_1(tx, false), "expand 1 failed"); + CHECK_AND_ASSERT_THROW_MES + ( + cryptonote::Blockchain::expand_transaction_2(tx, tx_prefix_hash, input_pubkeys), + "expand 2 failed" + ); + CHECK_AND_ASSERT_THROW_MES(!memcmp(&tx_prefix_hash, &tx.rct_signatures.message, 32), "message check failed"); + CHECK_AND_ASSERT_THROW_MES(input_pubkeys == tx.rct_signatures.mixRing, "mixring check failed"); + CHECK_AND_ASSERT_THROW_MES(check_tx_is_expanded(tx, input_pubkeys), "tx expansion check 2 failed"); +} + +/** + * @brief Mostly construct transaction from binary file and provided mix ring pubkeys + * + * Most important to us, this should populate the .rct_signatures.message and + * .rct_signatures.mixRings fields of the transaction. + * + * @param file_name relative file path in unit test data directory + * @param input_pubkeys manually retrived input pubkey destination / masks for each ring + * @return cryptonote::transaction the expanded transaction + */ +static cryptonote::transaction expand_transaction_from_bin_file_and_pubkeys +( + const char* file_name, + const rct::ctkeyM& input_pubkeys +) +{ + cryptonote::transaction transaction; + + const boost::filesystem::path tx_json_path = unit_test::data_dir / file_name; + std::string tx_blob; + CHECK_AND_ASSERT_THROW_MES + ( + epee::file_io_utils::load_file_to_string(tx_json_path.string(), tx_blob), + "loading file to string failed" + ); + + CHECK_AND_ASSERT_THROW_MES + ( + cryptonote::parse_and_validate_tx_from_blob(tx_blob, transaction), + "TX blob could not be parsed" + ); + + expand_transaction_fully(transaction, input_pubkeys); + + return transaction; +} + +/** + * @brief Return whether a modification changes blob resulting from do_serialize() + */ +template +static bool modification_changes_do_serialize +( + const T& og_obj, + TModifier& obj_modifier_func, + bool expected_change +) +{ + T modded_obj = og_obj; + obj_modifier_func(modded_obj); + const std::string og_blob = stringify_with_do_serialize(og_obj); + const std::string modded_blob = stringify_with_do_serialize(modded_obj); + const bool did_change = modded_blob != og_blob; + if (did_change != expected_change) + { + const std::string og_hex = epee::to_hex::string(epee::strspan(og_blob)); + const std::string modded_hex = epee::to_hex::string(epee::strspan(modded_blob)); + MERROR("unexpected: modded_blob '" << modded_hex << "' vs og_blob ' << " << og_hex << "'"); + } + return did_change; +} + +// Contains binary representation of mainnet transaction (height 2777777): +// e89415b95564aa7e3587c91422756ba5303e727996e19c677630309a0d52a7ca +static constexpr const char* tx1_file_name = "txs/bpp_tx_e89415.bin"; + +// This contains destination key / mask pairs for each output in the input ring of the above tx +static const rct::ctkeyM tx1_input_pubkeys = +{{ + make_ctkey("e50f476129d40af31e0938743f7f2d60e867aab31294f7acaf6e38f0976f0228", "51e788ddf5c95c124a7314d45a91b52d60db25a0572de9c2b4ec515aca3d4481"), + make_ctkey("804245d067fcfe6cd66376db0571869989bc68b3e22a0f902109c7530df47a59", "c3cc65d3b3a05defaa05213dc3b0496f9b86dbeeefbff28db34b134b6ee3230b"), + make_ctkey("527563a03b498e47732b815f5f0c5875a70e0fb71a37c88123f0f8686349fae4", "04417c03b397cd11e403275ec89cb0ab5b8476bb88470e9ae7208ea63dacf073"), + make_ctkey("bffca8b5c7fe4235ba7136d6b5325f63df343dc147940b677f50217f8953bca6", "5cd8c5e54e07275422c9c5a9f4a7268d26c494ffba419e878b7e873a02ae2e76"), + make_ctkey("1f73385ea74308aa78b5abf585faac14a5e78a6e23f0f68c9c14681108b28ef0", "5c02b3156daaa8ec476d3244439d90efa266f3e51cb9c8eb384d8b9a8efaa024"), + make_ctkey("a2421eae8bb256644b34feeab48c6086c2c9feb40d2643436dc45e303eee8ab2", "787823abffa988b56d4a7b4a834630f71520220fd82fad035955e616ec095788"), + make_ctkey("17d8d8dc1e1c25b7295f2eab44c4ccc08a629b8e8d781bbb6f9a51a9561bcd4c", "db1ea24be6947e03176a297160dba16d65f37751bb0ef2ba71a4590d12b61dfc"), + make_ctkey("2c39348a9ab04dbabe3b5249819b7845ed8aaebd0d8eddd98bda0bf40753a398", "4e6cd25fbd10e2e040be84e3bf8043c612daeef625e66a5e5bcff88c9c46e82c"), + make_ctkey("c4c97157f23b45c7084526aaa9958fe858bebe446a7efa22c491c439b74271b1", "e251db2c86193a11a5bffefffe48c20e3d92a8dc98cb3a2f41704e565bcd860a"), + make_ctkey("d342045525139a8551bcdfa7aa0117d2ac2327cb6cf449ca59420c300e4471a5", "789c11f72060ad80f4cda5d89b24d49f9435bf765598dea7a91776e99f05f87c"), + make_ctkey("9a972ccf2c74f648070b0be839749c98eca87166de401a6c1f59e64b938a46c1", "5444cbed5cec31fb6ed1612f815d292f2bf3d2ff584bbcd8e5201ec59670d414"), + make_ctkey("49ccb806ccf5cbd74bae8d9fb2da8918ab61d0774ee6a6c3a6ccd237db22a088", "0c5db942fb44f29f6ef956e24db91f98a6de6e7288b0b04d01b8f260453d1431"), + make_ctkey("74417e8d1483df2df6fe68c88fc9a72639c35d765b38351b838521addf45dadc", "a1a606d6c4762ef51c1759bcb8b5c88be1d323025400c41fe6885431064b64dc"), + make_ctkey("48c4c349adaf7b3be27656ea70d1c83b93e1511bb0aac987861a4da9689b0e95", "ad14ffd5edac199ea7c5437d558089b0f2f03aa74bde43611322d769968b5a1c"), + make_ctkey("2d2ffade0f85ddd83a036469e49542e93cad94f9bea535f0ea2eb2f56304517e", "bcc48d00bd06dc5439200e749d0caf8a062b072d0c0eb1f78f6a4d8f2373e5f4"), + make_ctkey("4ee857d0ce17f66eca9c81eb326e404ceb50c8198248f2f827c440ee7aa0c0d7", "a8a9d61d4abbfb123630ffd214c834cc45113eaa51dd2f904cc6ae0c3c5d70e3") +}}; +} // anonymous namespace + +TEST(verRctNonSemanticsSimple, tx1_preconditions) +{ + // If this unit test fails, something changed about transaction deserialization / expansion or + // something changed about RingCT signature verification. + + cryptonote::rct_ver_cache_t rct_ver_cache; + + cryptonote::transaction tx = expand_transaction_from_bin_file_and_pubkeys + (tx1_file_name, tx1_input_pubkeys); + const rct::rctSig& rs = tx.rct_signatures; + + const crypto::hash tx_prefix_hash = cryptonote::get_transaction_prefix_hash(tx); + + EXPECT_EQ(1, tx.vin.size()); + EXPECT_EQ(2, tx.vout.size()); + const rct::key expected_sig_msg = rct::hash2rct(tx_prefix_hash); + EXPECT_EQ(expected_sig_msg, rs.message); + EXPECT_EQ(1, rs.mixRing.size()); + EXPECT_EQ(16, rs.mixRing[0].size()); + EXPECT_EQ(0, rs.pseudoOuts.size()); + EXPECT_EQ(0, rs.p.rangeSigs.size()); + EXPECT_EQ(0, rs.p.bulletproofs.size()); + EXPECT_EQ(1, rs.p.bulletproofs_plus.size()); + EXPECT_EQ(2, rs.p.bulletproofs_plus[0].V.size()); + EXPECT_EQ(7, rs.p.bulletproofs_plus[0].L.size()); + EXPECT_EQ(7, rs.p.bulletproofs_plus[0].R.size()); + EXPECT_EQ(0, rs.p.MGs.size()); + EXPECT_EQ(1, rs.p.CLSAGs.size()); + EXPECT_EQ(16, rs.p.CLSAGs[0].s.size()); + EXPECT_EQ(1, rs.p.pseudoOuts.size()); + EXPECT_EQ(tx1_input_pubkeys, rs.mixRing); + EXPECT_EQ(2, rs.outPk.size()); + + EXPECT_TRUE(rct::verRctSemanticsSimple(rs)); + EXPECT_TRUE(rct::verRctNonSemanticsSimple(rs)); + EXPECT_TRUE(rct::verRctSimple(rs)); + EXPECT_TRUE(cryptonote::ver_rct_non_semantics_simple_cached(tx, tx1_input_pubkeys, rct_ver_cache, rct::RCTTypeBulletproofPlus)); + EXPECT_TRUE(cryptonote::ver_rct_non_semantics_simple_cached(tx, tx1_input_pubkeys, rct_ver_cache, rct::RCTTypeBulletproofPlus)); +} + +#define SERIALIZABLE_SIG_CHANGES_SUBTEST(fieldmodifyclause) \ + do { \ + const auto sig_modifier_func = [](rct::rctSig& rs) { rs.fieldmodifyclause; }; \ + EXPECT_TRUE(modification_changes_do_serialize(original_sig, sig_modifier_func, true)); \ + } while (0); \ + +TEST(verRctNonSemanticsSimple, serializable_sig_changes) +{ + // Hello, future visitors. If this unit test fails, then fields of rctSig have been dropped from + // serialization. + + const cryptonote::transaction tx = expand_transaction_from_bin_file_and_pubkeys + (tx1_file_name, tx1_input_pubkeys); + const rct::rctSig& original_sig = tx.rct_signatures; + + // These are the subtests most likely to fail. Fields 'message' and 'mixRing' are not serialized + // when sent over the wire, since they can be reconstructed from transaction data. However, they + // are serialized by ::do_serialize(rctSig). + // How signatures are serialized for the blockchain can be found in the methods + // rct::rctSigBase::serialize_rctsig_base and rct::rctSigPrunable::serialize_rctsig_prunable. + SERIALIZABLE_SIG_CHANGES_SUBTEST(message.bytes[31]++) + SERIALIZABLE_SIG_CHANGES_SUBTEST(mixRing.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(mixRing[0].push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(mixRing[0][8].dest[10]--) + SERIALIZABLE_SIG_CHANGES_SUBTEST(mixRing[0][15].mask[3]--) + + // rctSigBase changes. These subtests are less likely to break + SERIALIZABLE_SIG_CHANGES_SUBTEST(type ^= 23) + SERIALIZABLE_SIG_CHANGES_SUBTEST(pseudoOuts.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(ecdhInfo.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(outPk.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(outPk[0].dest[14]--) + SERIALIZABLE_SIG_CHANGES_SUBTEST(outPk[1].dest[14]--) + SERIALIZABLE_SIG_CHANGES_SUBTEST(outPk[0].mask[14]--) + SERIALIZABLE_SIG_CHANGES_SUBTEST(outPk[1].mask[14]--) + SERIALIZABLE_SIG_CHANGES_SUBTEST(txnFee *= 2023) + + // rctSigPrunable changes + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.rangeSigs.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].A[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].A1[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].B[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].r1[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].s1[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].d1[13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].L.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].L[2][13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].R.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].R[2][13] -= 7) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.MGs.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs[0].s.push_back({})) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs[0].s[15][31] ^= 69) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs[0].c1[0] /= 3) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs[0].D[0] /= 3) + SERIALIZABLE_SIG_CHANGES_SUBTEST(p.pseudoOuts.push_back({})) + + // Uncomment line below to sanity check SERIALIZABLE_SIG_CHANGES_SUBTEST + // SERIALIZABLE_SIG_CHANGES_SUBTEST(message) // should fail +} + +#define UNSERIALIZABLE_SIG_CHANGES_SUBTEST(fieldmodifyclause) \ + do { \ + const auto sig_modifier_func = [](rct::rctSig& rs) { rs.fieldmodifyclause; }; \ + EXPECT_FALSE(modification_changes_do_serialize(original_sig, sig_modifier_func, false)); \ + } while (0); \ + +TEST(verRctNonSemanticsSimple, unserializable_sig_changes) +{ + // Hello, future visitors. If this unit test fails, then congrats! ::do_serialize(rctSig) became + // better at uniquely representing rctSig. + const cryptonote::transaction tx = expand_transaction_from_bin_file_and_pubkeys + (tx1_file_name, tx1_input_pubkeys); + const rct::rctSig& original_sig = tx.rct_signatures; + + UNSERIALIZABLE_SIG_CHANGES_SUBTEST(p.CLSAGs[0].I[14]++) + UNSERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].V.push_back({})) + UNSERIALIZABLE_SIG_CHANGES_SUBTEST(p.bulletproofs_plus[0].V[1][31]--) + + // Uncomment line below to sanity check UNSERIALIZABLE_SIG_CHANGES_SUBTEST_SHORTCUT + // UNSERIALIZABLE_SIG_CHANGES_SUBTEST_SHORTCUT(message[2]++) // should fail +} + +#define SERIALIZABLE_MIXRING_CHANGES_SUBTEST(fieldmodifyclause) \ + do { \ + using mr_mod_func_t = std::function; \ + const mr_mod_func_t mr_modifier_func = [&](rct::ctkeyM& mr) { mr fieldmodifyclause; }; \ + EXPECT_TRUE(modification_changes_do_serialize(original_mixring, mr_modifier_func, true)); \ + } while (0); \ + +TEST(verRctNonSemanticsSimple, serializable_mixring_changes) +{ + // Hello, future Monero devs! If this unit test fails, a huge concensus-related assumption has + // been broken and verRctNonSemanticsSimpleCached needs to be reevalulated for validity. If it + // is not, there may be an exploit which allows for double-spending. See the implementation for + // more comments on the uniqueness of the internal cache hash. + + const rct::ctkeyM original_mixring = tx1_input_pubkeys; + + const size_t mlen = tx1_input_pubkeys.size(); + ASSERT_EQ(1, mlen); + const size_t nlen = tx1_input_pubkeys[0].size(); + ASSERT_EQ(16, nlen); + + SERIALIZABLE_MIXRING_CHANGES_SUBTEST(.clear()) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST(.push_back({})) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([0].clear()) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([0].push_back({})) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([0][0].dest[4]--) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([0][15].mask[31]--) + + // Loop through all bytes of the mixRing and check for serialiable changes + for (size_t i = 0; i < mlen; ++i) + { + for (size_t j = 0; j < nlen; ++j) + { + static_assert(sizeof(rct::key) == 32, "rct::key size wrong"); + for (size_t k = 0; k < sizeof(rct::key); ++k) + { + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([i][j].dest[k]++) + SERIALIZABLE_MIXRING_CHANGES_SUBTEST([i][j].mask[k]++) + } + } + } +} + +#define EXPAND_TRANSACTION_2_FAILURES_SUBTEST(fieldmodifyclause) \ + do { \ + cryptonote::transaction test_tx = original_tx; \ + test_tx.fieldmodifyclause; \ + test_tx.invalidate_hashes(); \ + EXPECT_FALSE(check_tx_is_expanded(test_tx, original_mixring)); \ + } while (0); \ + +TEST(verRctNonSemanticsSimple, expand_transaction_2_failures) +{ + cryptonote::transaction original_tx = expand_transaction_from_bin_file_and_pubkeys + (tx1_file_name, tx1_input_pubkeys); + rct::ctkeyM original_mixring = tx1_input_pubkeys; + + EXPAND_TRANSACTION_2_FAILURES_SUBTEST(rct_signatures.p.CLSAGs[0].I[0]++) + EXPAND_TRANSACTION_2_FAILURES_SUBTEST(rct_signatures.mixRing[0][15].dest[31]++) + EXPAND_TRANSACTION_2_FAILURES_SUBTEST(rct_signatures.mixRing[0][15].mask[31]++) +} From 4578688c7b969b84331a01075b32ccb1138e2857 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 16 Feb 2023 14:21:50 +0100 Subject: [PATCH 8/9] depends: remove unused packages --- contrib/depends/packages/native_biplist.mk | 20 ----- contrib/depends/packages/native_cdrkit.mk | 26 ------ .../depends/packages/native_cmake-unused.mk | 23 ----- contrib/depends/packages/native_ds_store.mk | 17 ---- .../depends/packages/native_libdmg-hfsplus.mk | 22 ----- contrib/depends/packages/native_mac_alias.mk | 21 ----- .../depends/patches/cmake/cmake-1-fixes.patch | 67 --------------- .../patches/native_biplist/sorted_list.patch | 29 ------- .../native_cdrkit/cdrkit-deterministic.patch | 86 ------------------- .../patches/native_mac_alias/python3.patch | 72 ---------------- 10 files changed, 383 deletions(-) delete mode 100644 contrib/depends/packages/native_biplist.mk delete mode 100644 contrib/depends/packages/native_cdrkit.mk delete mode 100644 contrib/depends/packages/native_cmake-unused.mk delete mode 100644 contrib/depends/packages/native_ds_store.mk delete mode 100644 contrib/depends/packages/native_libdmg-hfsplus.mk delete mode 100644 contrib/depends/packages/native_mac_alias.mk delete mode 100644 contrib/depends/patches/cmake/cmake-1-fixes.patch delete mode 100644 contrib/depends/patches/native_biplist/sorted_list.patch delete mode 100644 contrib/depends/patches/native_cdrkit/cdrkit-deterministic.patch delete mode 100644 contrib/depends/patches/native_mac_alias/python3.patch diff --git a/contrib/depends/packages/native_biplist.mk b/contrib/depends/packages/native_biplist.mk deleted file mode 100644 index 3c6e8900f..000000000 --- a/contrib/depends/packages/native_biplist.mk +++ /dev/null @@ -1,20 +0,0 @@ -package=native_biplist -$(package)_version=0.9 -$(package)_download_path=https://pypi.python.org/packages/source/b/biplist -$(package)_file_name=biplist-$($(package)_version).tar.gz -$(package)_sha256_hash=b57cadfd26e4754efdf89e9e37de87885f9b5c847b2615688ca04adfaf6ca604 -$(package)_install_libdir=$(build_prefix)/lib/python/dist-packages -$(package)_patches=sorted_list.patch - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/sorted_list.patch -endef - -define $(package)_build_cmds - python setup.py build -endef - -define $(package)_stage_cmds - mkdir -p $($(package)_install_libdir) && \ - python setup.py install --root=$($(package)_staging_dir) --prefix=$(build_prefix) --install-lib=$($(package)_install_libdir) -endef diff --git a/contrib/depends/packages/native_cdrkit.mk b/contrib/depends/packages/native_cdrkit.mk deleted file mode 100644 index 8243458ec..000000000 --- a/contrib/depends/packages/native_cdrkit.mk +++ /dev/null @@ -1,26 +0,0 @@ -package=native_cdrkit -$(package)_version=1.1.11 -$(package)_download_path=https://distro.ibiblio.org/fatdog/source/600/c -$(package)_file_name=cdrkit-$($(package)_version).tar.bz2 -$(package)_sha256_hash=b50d64c214a65b1a79afe3a964c691931a4233e2ba605d793eb85d0ac3652564 -$(package)_patches=cdrkit-deterministic.patch - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/cdrkit-deterministic.patch -endef - -define $(package)_config_cmds - cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix) -endef - -define $(package)_build_cmds - $(MAKE) genisoimage -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -C genisoimage install -endef - -define $(package)_postprocess_cmds - rm bin/isovfy bin/isoinfo bin/isodump bin/isodebug bin/devdump -endef diff --git a/contrib/depends/packages/native_cmake-unused.mk b/contrib/depends/packages/native_cmake-unused.mk deleted file mode 100644 index c9ab75711..000000000 --- a/contrib/depends/packages/native_cmake-unused.mk +++ /dev/null @@ -1,23 +0,0 @@ -package=native_cmake -$(package)_version=3.14.0 -$(package)_version_dot=v3.14 -$(package)_download_path=https://cmake.org/files/$($(package)_version_dot)/ -$(package)_file_name=cmake-$($(package)_version).tar.gz -$(package)_sha256_hash=aa76ba67b3c2af1946701f847073f4652af5cbd9f141f221c97af99127e75502 - -define $(package)_set_vars -$(package)_config_opts= -endef - -define $(package)_config_cmds - ./bootstrap &&\ - ./configure $($(package)_config_opts) -endef - -define $(package)_build_cmd - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/contrib/depends/packages/native_ds_store.mk b/contrib/depends/packages/native_ds_store.mk deleted file mode 100644 index f0c617659..000000000 --- a/contrib/depends/packages/native_ds_store.mk +++ /dev/null @@ -1,17 +0,0 @@ -package=native_ds_store -$(package)_version=1.1.0 -$(package)_download_path=https://github.com/al45tair/ds_store/archive/ -$(package)_download_file=v$($(package)_version).tar.gz -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=a9f4c0755c6be7224ff7029e188dd262e830bb81e801424841db9eb0780ec8ed -$(package)_install_libdir=$(build_prefix)/lib/python/dist-packages -$(package)_dependencies=native_biplist - -define $(package)_build_cmds - python setup.py build -endef - -define $(package)_stage_cmds - mkdir -p $($(package)_install_libdir) && \ - python setup.py install --root=$($(package)_staging_dir) --prefix=$(build_prefix) --install-lib=$($(package)_install_libdir) -endef diff --git a/contrib/depends/packages/native_libdmg-hfsplus.mk b/contrib/depends/packages/native_libdmg-hfsplus.mk deleted file mode 100644 index a4ffb6046..000000000 --- a/contrib/depends/packages/native_libdmg-hfsplus.mk +++ /dev/null @@ -1,22 +0,0 @@ -package=native_libdmg-hfsplus -$(package)_version=0.1 -$(package)_download_path=https://github.com/theuni/libdmg-hfsplus/archive -$(package)_file_name=libdmg-hfsplus-v$($(package)_version).tar.gz -$(package)_sha256_hash=6569a02eb31c2827080d7d59001869ea14484c281efab0ae7f2b86af5c3120b3 -$(package)_build_subdir=build - -define $(package)_preprocess_cmds - mkdir build -endef - -define $(package)_config_cmds - cmake -DCMAKE_INSTALL_PREFIX:PATH=$(build_prefix)/bin .. -endef - -define $(package)_build_cmds - $(MAKE) -C dmg -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -C dmg install -endef diff --git a/contrib/depends/packages/native_mac_alias.mk b/contrib/depends/packages/native_mac_alias.mk deleted file mode 100644 index 48bd90fb6..000000000 --- a/contrib/depends/packages/native_mac_alias.mk +++ /dev/null @@ -1,21 +0,0 @@ -package=native_mac_alias -$(package)_version=1.1.0 -$(package)_download_path=https://github.com/al45tair/mac_alias/archive/ -$(package)_download_file=v$($(package)_version).tar.gz -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=b10cb44ecb64fc25283fae7a9cf365d2829377d84e37b9c21100aca8757509be -$(package)_install_libdir=$(build_prefix)/lib/python/dist-packages -$(package)_patches=python3.patch - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/python3.patch -endef - -define $(package)_build_cmds - python setup.py build -endef - -define $(package)_stage_cmds - mkdir -p $($(package)_install_libdir) && \ - python setup.py install --root=$($(package)_staging_dir) --prefix=$(build_prefix) --install-lib=$($(package)_install_libdir) -endef diff --git a/contrib/depends/patches/cmake/cmake-1-fixes.patch b/contrib/depends/patches/cmake/cmake-1-fixes.patch deleted file mode 100644 index 062c06767..000000000 --- a/contrib/depends/patches/cmake/cmake-1-fixes.patch +++ /dev/null @@ -1,67 +0,0 @@ -This file is part of MXE. See LICENSE.md for licensing information. - -Contains ad hoc patches for cross building. - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tony Theodore -Date: Fri, 12 Aug 2016 02:01:20 +1000 -Subject: [PATCH 1/3] fix windres invocation options - -windres doesn't recognise various gcc flags like -mms-bitfields, --fopenmp, -mthreads etc. (basically not `-D` or `-I`) - -diff --git a/Modules/Platform/Windows-windres.cmake b/Modules/Platform/Windows-windres.cmake -index 1111111..2222222 100644 ---- a/Modules/Platform/Windows-windres.cmake -+++ b/Modules/Platform/Windows-windres.cmake -@@ -1 +1 @@ --set(CMAKE_RC_COMPILE_OBJECT " -O coff ") -+set(CMAKE_RC_COMPILE_OBJECT " -O coff ") - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tony Theodore -Date: Tue, 25 Jul 2017 20:34:56 +1000 -Subject: [PATCH 2/3] add option to disable -isystem - -taken from (not accepted): -https://gitlab.kitware.com/cmake/cmake/merge_requests/895 - -see also: -https://gitlab.kitware.com/cmake/cmake/issues/16291 -https://gitlab.kitware.com/cmake/cmake/issues/16919 - -diff --git a/Modules/Compiler/GNU.cmake b/Modules/Compiler/GNU.cmake -index 1111111..2222222 100644 ---- a/Modules/Compiler/GNU.cmake -+++ b/Modules/Compiler/GNU.cmake -@@ -42,7 +42,7 @@ macro(__compiler_gnu lang) - string(APPEND CMAKE_${lang}_FLAGS_RELWITHDEBINFO_INIT " -O2 -g -DNDEBUG") - set(CMAKE_${lang}_CREATE_PREPROCESSED_SOURCE " -E > ") - set(CMAKE_${lang}_CREATE_ASSEMBLY_SOURCE " -S -o ") -- if(NOT APPLE OR NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 4) # work around #4462 -+ if(NOT APPLE OR NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 4 AND (NOT MXE_DISABLE_INCLUDE_SYSTEM_FLAG)) # work around #4462 - set(CMAKE_INCLUDE_SYSTEM_FLAG_${lang} "-isystem ") - endif() - endmacro() - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Tony Theodore -Date: Tue, 15 Aug 2017 15:25:06 +1000 -Subject: [PATCH 3/3] add CPACK_NSIS_EXECUTABLE variable - - -diff --git a/Source/CPack/cmCPackNSISGenerator.cxx b/Source/CPack/cmCPackNSISGenerator.cxx -index 1111111..2222222 100644 ---- a/Source/CPack/cmCPackNSISGenerator.cxx -+++ b/Source/CPack/cmCPackNSISGenerator.cxx -@@ -384,7 +384,9 @@ int cmCPackNSISGenerator::InitializeInternal() - } - #endif - -- nsisPath = cmSystemTools::FindProgram("makensis", path, false); -+ this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLE", "makensis"); -+ nsisPath = cmSystemTools::FindProgram( -+ this->GetOption("CPACK_NSIS_EXECUTABLE"), path, false); - - if (nsisPath.empty()) { - cmCPackLogger( diff --git a/contrib/depends/patches/native_biplist/sorted_list.patch b/contrib/depends/patches/native_biplist/sorted_list.patch deleted file mode 100644 index 89abdb1b7..000000000 --- a/contrib/depends/patches/native_biplist/sorted_list.patch +++ /dev/null @@ -1,29 +0,0 @@ ---- a/biplist/__init__.py 2014-10-26 19:03:11.000000000 +0000 -+++ b/biplist/__init__.py 2016-07-19 19:30:17.663521999 +0000 -@@ -541,7 +541,7 @@ - return HashableWrapper(n) - elif isinstance(root, dict): - n = {} -- for key, value in iteritems(root): -+ for key, value in sorted(iteritems(root)): - n[self.wrapRoot(key)] = self.wrapRoot(value) - return HashableWrapper(n) - elif isinstance(root, list): -@@ -616,7 +616,7 @@ - elif isinstance(obj, dict): - size = proc_size(len(obj)) - self.incrementByteCount('dictBytes', incr=1+size) -- for key, value in iteritems(obj): -+ for key, value in sorted(iteritems(obj)): - check_key(key) - self.computeOffsets(key, asReference=True) - self.computeOffsets(value, asReference=True) -@@ -714,7 +714,7 @@ - keys = [] - values = [] - objectsToWrite = [] -- for key, value in iteritems(obj): -+ for key, value in sorted(iteritems(obj)): - keys.append(key) - values.append(value) - for key in keys: diff --git a/contrib/depends/patches/native_cdrkit/cdrkit-deterministic.patch b/contrib/depends/patches/native_cdrkit/cdrkit-deterministic.patch deleted file mode 100644 index 8ab0993dc..000000000 --- a/contrib/depends/patches/native_cdrkit/cdrkit-deterministic.patch +++ /dev/null @@ -1,86 +0,0 @@ ---- cdrkit-1.1.11.old/genisoimage/tree.c 2008-10-21 19:57:47.000000000 -0400 -+++ cdrkit-1.1.11/genisoimage/tree.c 2013-12-06 00:23:18.489622668 -0500 -@@ -1139,8 +1139,9 @@ - scan_directory_tree(struct directory *this_dir, char *path, - struct directory_entry *de) - { -- DIR *current_dir; -+ int current_file; - char whole_path[PATH_MAX]; -+ struct dirent **d_list; - struct dirent *d_entry; - struct directory *parent; - int dflag; -@@ -1164,7 +1165,8 @@ - this_dir->dir_flags |= DIR_WAS_SCANNED; - - errno = 0; /* Paranoia */ -- current_dir = opendir(path); -+ //current_dir = opendir(path); -+ current_file = scandir(path, &d_list, NULL, alphasort); - d_entry = NULL; - - /* -@@ -1173,12 +1175,12 @@ - */ - old_path = path; - -- if (current_dir) { -+ if (current_file >= 0) { - errno = 0; -- d_entry = readdir(current_dir); -+ d_entry = d_list[0]; - } - -- if (!current_dir || !d_entry) { -+ if (current_file < 0 || !d_entry) { - int ret = 1; - - #ifdef USE_LIBSCHILY -@@ -1191,8 +1193,8 @@ - de->isorec.flags[0] &= ~ISO_DIRECTORY; - ret = 0; - } -- if (current_dir) -- closedir(current_dir); -+ if(d_list) -+ free(d_list); - return (ret); - } - #ifdef ABORT_DEEP_ISO_ONLY -@@ -1208,7 +1210,7 @@ - errmsgno(EX_BAD, "use Rock Ridge extensions via -R or -r,\n"); - errmsgno(EX_BAD, "or allow deep ISO9660 directory nesting via -D.\n"); - } -- closedir(current_dir); -+ free(d_list); - return (1); - } - #endif -@@ -1250,13 +1252,13 @@ - * The first time through, skip this, since we already asked - * for the first entry when we opened the directory. - */ -- if (dflag) -- d_entry = readdir(current_dir); -+ if (dflag && current_file >= 0) -+ d_entry = d_list[current_file]; - dflag++; - -- if (!d_entry) -+ if (current_file < 0) - break; -- -+ current_file--; - /* OK, got a valid entry */ - - /* If we do not want all files, then pitch the backups. */ -@@ -1348,7 +1350,7 @@ - insert_file_entry(this_dir, whole_path, d_entry->d_name); - #endif /* APPLE_HYB */ - } -- closedir(current_dir); -+ free(d_list); - - #ifdef APPLE_HYB - /* diff --git a/contrib/depends/patches/native_mac_alias/python3.patch b/contrib/depends/patches/native_mac_alias/python3.patch deleted file mode 100644 index 1a32340be..000000000 --- a/contrib/depends/patches/native_mac_alias/python3.patch +++ /dev/null @@ -1,72 +0,0 @@ -diff -dur a/mac_alias/alias.py b/mac_alias/alias.py ---- a/mac_alias/alias.py 2015-10-19 12:12:48.000000000 +0200 -+++ b/mac_alias/alias.py 2016-04-03 12:13:12.037159417 +0200 -@@ -243,10 +243,10 @@ - alias = Alias() - alias.appinfo = appinfo - -- alias.volume = VolumeInfo (volname.replace('/',':'), -+ alias.volume = VolumeInfo (volname.decode().replace('/',':'), - voldate, fstype, disktype, - volattrs, volfsid) -- alias.target = TargetInfo (kind, filename.replace('/',':'), -+ alias.target = TargetInfo (kind, filename.decode().replace('/',':'), - folder_cnid, cnid, - crdate, creator_code, type_code) - alias.target.levels_from = levels_from -@@ -261,9 +261,9 @@ - b.read(1) - - if tag == TAG_CARBON_FOLDER_NAME: -- alias.target.folder_name = value.replace('/',':') -+ alias.target.folder_name = value.decode().replace('/',':') - elif tag == TAG_CNID_PATH: -- alias.target.cnid_path = struct.unpack(b'>%uI' % (length // 4), -+ alias.target.cnid_path = struct.unpack('>%uI' % (length // 4), - value) - elif tag == TAG_CARBON_PATH: - alias.target.carbon_path = value -@@ -298,9 +298,9 @@ - alias.target.creation_date \ - = mac_epoch + datetime.timedelta(seconds=seconds) - elif tag == TAG_POSIX_PATH: -- alias.target.posix_path = value -+ alias.target.posix_path = value.decode() - elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT: -- alias.volume.posix_path = value -+ alias.volume.posix_path = value.decode() - elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE: - alias.volume.disk_image_alias = Alias.from_bytes(value) - elif tag == TAG_USER_HOME_LENGTH_PREFIX: -@@ -422,13 +422,13 @@ - # (so doing so is ridiculous, and nothing could rely on it). - b.write(struct.pack(b'>h28pI2shI64pII4s4shhI2s10s', - self.target.kind, -- carbon_volname, voldate, -+ carbon_volname, int(voldate), - self.volume.fs_type, - self.volume.disk_type, - self.target.folder_cnid, - carbon_filename, - self.target.cnid, -- crdate, -+ int(crdate), - self.target.creator_code, - self.target.type_code, - self.target.levels_from, -@@ -449,12 +449,12 @@ - - b.write(struct.pack(b'>hhQhhQ', - TAG_HIGH_RES_VOLUME_CREATION_DATE, -- 8, long(voldate * 65536), -+ 8, int(voldate * 65536), - TAG_HIGH_RES_CREATION_DATE, -- 8, long(crdate * 65536))) -+ 8, int(crdate * 65536))) - - if self.target.cnid_path: -- cnid_path = struct.pack(b'>%uI' % len(self.target.cnid_path), -+ cnid_path = struct.pack('>%uI' % len(self.target.cnid_path), - *self.target.cnid_path) - b.write(struct.pack(b'>hh', TAG_CNID_PATH, - len(cnid_path))) From 3341cded57507c48d702ac3399fc2b2af7a787f8 Mon Sep 17 00:00:00 2001 From: wowario Date: Sun, 19 Mar 2023 09:22:58 +0300 Subject: [PATCH 9/9] add checkpoints.dat --- src/blocks/checkpoints.dat | Bin 0 -> 63684 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/blocks/checkpoints.dat diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat new file mode 100644 index 0000000000000000000000000000000000000000..32da1e63a070ebfefc3ccb9e26008b64f1693df3 GIT binary patch literal 63684 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 Jx)ZmS)*CaM>{tK* literal 0 HcmV?d00001