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: | 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))) diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat new file mode 100644 index 000000000..32da1e63a Binary files /dev/null and b/src/blocks/checkpoints.dat differ 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); } 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 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/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) { diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index c393d5ced..23a891bfa 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; @@ -317,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 @@ -349,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; } //----------------------------------------------------------------------------------- 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 000000000..38f596397 Binary files /dev/null and b/tests/data/txs/bpp_tx_e89415.bin differ 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]++) +}