diff options
168 files changed, 7082 insertions, 1873 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3c552dd8..bd2e26484 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,24 +1,30 @@ name: ci/gh-actions/cli -on: [push, pull_request] +on: + push: + pull_request: + paths-ignore: + - 'docs/**' + - '**/README.md' # The below variables reduce repetitions across similar targets env: REMOVE_BUNDLED_BOOST : rm -rf /usr/local/share/boost BUILD_DEFAULT_LINUX: | - ccache --max-size=150M cmake -S . -B build -D ARCH="default" -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release && cmake --build build -j3 APT_INSTALL_LINUX: 'sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler ccache' APT_SET_CONF: | - echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom - echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + CCACHE_SETTINGS: | + ccache --max-size=150M + ccache --set-config=compression=true jobs: build-macos: runs-on: macOS-latest env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - uses: actions/checkout@v1 @@ -30,16 +36,15 @@ jobs: key: ccache-${{ runner.os }}-build-${{ github.sha }} restore-keys: ccache-${{ runner.os }}-build- - name: install dependencies - run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi zmq libpgm miniupnpc ldns expat libunwind-headers protobuf ccache + run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc ldns expat libunwind-headers protobuf ccache - name: build run: | - ccache --max-size=150M + ${{env.CCACHE_SETTINGS}} make -j3 build-windows: runs-on: windows-latest env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: C:\Users\runneradmin\.ccache-temp CCACHE_DIR: C:\Users\runneradmin\.ccache defaults: @@ -60,7 +65,7 @@ jobs: 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 - name: build run: | - ccache --max-size=150M + ${{env.CCACHE_SETTINGS}} make release-static-win64 -j2 # See the OS labels and monitor deprecations here: @@ -69,7 +74,6 @@ jobs: build-ubuntu: runs-on: ${{ matrix.os }} env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: /tmp/.ccache-temp strategy: matrix: @@ -92,12 +96,13 @@ jobs: - name: install monero dependencies run: ${{env.APT_INSTALL_LINUX}} - name: build - run: ${{env.BUILD_DEFAULT_LINUX}} + run: | + ${{env.CCACHE_SETTINGS}} + ${{env.BUILD_DEFAULT_LINUX}} libwallet-ubuntu: runs-on: ubuntu-latest env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - uses: actions/checkout@v1 @@ -118,7 +123,7 @@ jobs: run: ${{env.APT_INSTALL_LINUX}} - name: build run: | - ccache --max-size=150M + ${{env.CCACHE_SETTINGS}} cmake . make wallet_api -j3 @@ -126,7 +131,6 @@ jobs: needs: build-ubuntu runs-on: ubuntu-latest env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - uses: actions/checkout@v1 @@ -152,6 +156,7 @@ jobs: env: CTEST_OUTPUT_ON_FAILURE: ON run: | + ${{env.CCACHE_SETTINGS}} ${{env.BUILD_DEFAULT_LINUX}} cmake --build build --target test diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index 8e4eaf177..9385338de 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -1,12 +1,25 @@ name: ci/gh-actions/depends -on: [push, pull_request] +on: + push: + pull_request: + paths-ignore: + - 'docs/**' + - '**/README.md' + +env: + APT_SET_CONF: | + echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + CCACHE_SETTINGS: | + ccache --max-size=150M + ccache --set-config=compression=true jobs: build-macos: runs-on: ubuntu-18.04 env: - CCACHE_COMPRESS: 1 CCACHE_TEMPDIR: /tmp/.ccache-temp strategy: fail-fast: false @@ -69,10 +82,7 @@ jobs: key: sdk-${{ matrix.toolchain.host }}-${{ matrix.toolchain.osx_sdk }} restore-keys: sdk-${{ matrix.toolchain.host }}-${{ matrix.toolchain.osx_sdk }} - name: set apt conf - run: | - echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom - echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom - echo "Acquire::ftp::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom + run: ${{env.APT_SET_CONF}} - name: install dependencies run: sudo apt update; sudo apt -y install build-essential libtool cmake autotools-dev automake pkg-config bsdmainutils curl git ca-certificates ccache ${{ matrix.toolchain.packages }} - name: prepare apple-darwin11 @@ -88,7 +98,7 @@ jobs: sudo update-alternatives --set ${{ matrix.toolchain.host }}-gcc $(which ${{ matrix.toolchain.host }}-gcc-posix) - name: build run: | - ccache --max-size=150M + ${{env.CCACHE_SETTINGS}} make depends target=${{ matrix.toolchain.host }} -j2 - uses: actions/upload-artifact@v2 if: ${{ matrix.toolchain.host == 'x86_64-w64-mingw32' || matrix.toolchain.host == 'x86_64-apple-darwin11' || matrix.toolchain.host == 'x86_64-unknown-linux-gnu' }} diff --git a/.gitignore b/.gitignore index 049fc2562..cf7da3a04 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,5 @@ nbproject /testnet __pycache__/ +*.pyc +*.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b9ba570d..e21fbb65b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,14 @@ endif() enable_language(C ASM) +# Require C11/C++11 and disable extensions for all targets +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + function (die msg) if (NOT WIN32) string(ASCII 27 Esc) @@ -317,6 +325,16 @@ if(ARCH_ID STREQUAL "s390x") set(S390X 1) endif() +if(ARCH_ID STREQUAL "riscv64") +set(RISCV 1) +set(RISCV64 1) +endif() + +if(ARCH_ID STREQUAL "riscv32") +set(RISCV 1) +set(RISCV32 1) +endif() + if(WIN32 OR ARM OR PPC64LE OR PPC64 OR PPC) set(OPT_FLAGS_RELEASE "-O2") else() @@ -384,18 +402,6 @@ endif() message(STATUS "BOOST_IGNORE_SYSTEM_PATHS defaults to ${BOOST_IGNORE_SYSTEM_PATHS_DEFAULT}") option(BOOST_IGNORE_SYSTEM_PATHS "Ignore boost system paths for local boost installation" ${BOOST_IGNORE_SYSTEM_PATHS_DEFAULT}) - -if (NOT DEFINED ENV{DEVELOPER_LIBUNBOUND_OLD}) - message(STATUS "Could not find DEVELOPER_LIBUNBOUND_OLD in env (not required)") -elseif ("$ENV{DEVELOPER_LIBUNBOUND_OLD}" EQUAL 1) - message(STATUS "Found: env DEVELOPER_LIBUNBOUND_OLD = 1, will use the work around") - add_definitions(-DDEVELOPER_LIBUNBOUND_OLD) -elseif ("$ENV{DEVELOPER_LIBUNBOUND_OLD}" EQUAL 0) - message(STATUS "Found: env DEVELOPER_LIBUNBOUND_OLD = 0") -else() - message(STATUS "Found: env DEVELOPER_LIBUNBOUND_OLD with bad value. Will NOT use the work around") -endif() - set_property(GLOBAL PROPERTY USE_FOLDERS ON) enable_testing() @@ -455,10 +461,7 @@ endif() include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include) if(APPLE) - include_directories(SYSTEM /usr/include/malloc) - if(POLICY CMP0042) - cmake_policy(SET CMP0042 NEW) - endif() + cmake_policy(SET CMP0042 NEW) endif() if(MSVC OR MINGW) @@ -503,6 +506,7 @@ if(STATIC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZMQ_STATIC") endif() +option(SANITIZE "Use ASAN memory sanitizer" OFF) if(SANITIZE) if (MSVC) message(FATAL_ERROR "Cannot sanitize with MSVC") @@ -571,7 +575,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") endif () if (APPLE AND NOT IOS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=default -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=default") if (NOT OPENSSL_ROOT_DIR) EXECUTE_PROCESS(COMMAND brew --prefix openssl OUTPUT_VARIABLE OPENSSL_ROOT_DIR @@ -619,8 +623,6 @@ if (NOT (MSVC OR ARCH)) set_default_arch() endif() -CHECK_C_COMPILER_FLAG(-std=c11 HAVE_C11) - option(COVERAGE "Enable profiling for test coverage report" OFF) if(COVERAGE) message(STATUS "Building with profiling for test coverage report") @@ -692,7 +694,7 @@ if (HIDAPI_FOUND) add_definitions(-DHAVE_HIDAPI) include_directories(${HIDAPI_INCLUDE_DIR}) link_directories(${LIBHIDAPI_LIBRARY_DIRS}) -else (HIDAPI_FOUND) +else() message(STATUS "Could not find HIDAPI") endif() @@ -744,7 +746,7 @@ else() message(STATUS "AES support explicitly disabled") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_AES") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNO_AES") - elseif(NOT ARM AND NOT PPC64LE AND NOT PPC64 AND NOT PPC AND NOT S390X) + elseif(NOT ARM AND NOT PPC64LE AND NOT PPC64 AND NOT PPC AND NOT S390X AND NOT RISCV) message(STATUS "AES support enabled") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maes") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") @@ -752,6 +754,8 @@ else() message(STATUS "AES support not available on POWER") elseif(S390X) message(STATUS "AES support not available on s390x") + elseif(RISCV) + message(STATUS "AES support not available on RISC-V") elseif(ARM6) message(STATUS "AES support not available on ARMv6") elseif(ARM7) @@ -772,7 +776,7 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARCH_FLAG}") set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wundef -Wvla -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-error=unused-variable -Wno-error=undef -Wno-error=uninitialized") - if(CMAKE_C_COMPILER_ID MATCHES "Clang") + if(CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(ARM) set(WARNINGS "${WARNINGS} -Wno-error=inline-asm") endif() @@ -793,14 +797,14 @@ else() endif() set(C_WARNINGS "-Waggregate-return -Wnested-externs -Wold-style-definition -Wstrict-prototypes") set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers") - try_compile(STATIC_ASSERT_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.c" COMPILE_DEFINITIONS "-std=c11") + try_compile(STATIC_ASSERT_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.c" CMAKE_FLAGS -DCMAKE_C_STANDARD=11) if(STATIC_ASSERT_RES) set(STATIC_ASSERT_FLAG "") else() set(STATIC_ASSERT_FLAG "-Dstatic_assert=_Static_assert") endif() - try_compile(STATIC_ASSERT_CPP_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.cpp" COMPILE_DEFINITIONS "-std=c++11") + try_compile(STATIC_ASSERT_CPP_RES "${CMAKE_CURRENT_BINARY_DIR}/static-assert" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/test-static-assert.cpp" CMAKE_FLAGS -DCMAKE_CXX_STANDARD=11) if(STATIC_ASSERT_CPP_RES) set(STATIC_ASSERT_CPP_FLAG "") else() @@ -896,8 +900,8 @@ else() message(STATUS "Using C++ security hardening flags: ${CXX_SECURITY_FLAGS}") message(STATUS "Using linker security hardening flags: ${LD_SECURITY_FLAGS}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${C_WARNINGS} ${PIC_FLAG} ${C_SECURITY_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_CPP_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${PIC_FLAG} ${CXX_SECURITY_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_FLAG} ${WARNINGS} ${C_WARNINGS} ${PIC_FLAG} ${C_SECURITY_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GNU_SOURCE ${MINGW_FLAG} ${STATIC_ASSERT_CPP_FLAG} ${WARNINGS} ${CXX_WARNINGS} ${PIC_FLAG} ${CXX_SECURITY_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LD_SECURITY_FLAGS} ${LD_BACKCOMPAT_FLAGS}") # With GCC 6.1.1 the compiled binary malfunctions due to aliasing. Until that @@ -1001,11 +1005,6 @@ else() endif() set(DEBUG_FLAGS "-g3") - if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND NOT (CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8)) - set(DEBUG_FLAGS "${DEBUG_FLAGS} -Og ") - else() - set(DEBUG_FLAGS "${DEBUG_FLAGS} -O0 ") - endif() # At least some CLANGs default to not enough for monero set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth=900") @@ -1242,10 +1241,6 @@ endif() # when ON - will install libwallet_merged into "lib" option(BUILD_GUI_DEPS "Build GUI dependencies." OFF) -# This is not nice, distribution packagers should not enable this, but depend -# on libunbound shipped with their distribution instead -option(INSTALL_VENDORED_LIBUNBOUND "Install libunbound binary built from source vendored with this repo." OFF) - find_package(PythonInterp) find_program(iwyu_tool_path NAMES iwyu_tool.py iwyu_tool) if (iwyu_tool_path AND PYTHONINTERP_FOUND) diff --git a/Dockerfile b/Dockerfile index f21f74ac3..1c373cbc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,192 +1,45 @@ # Multistage docker build, requires docker 17.05 # builder stage -FROM ubuntu:16.04 as builder +FROM ubuntu:20.04 as builder RUN set -ex && \ apt-get update && \ - apt-get --no-install-recommends --yes install \ + DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends --yes install \ + automake \ + autotools-dev \ + bsdmainutils \ + build-essential \ ca-certificates \ + ccache \ cmake \ - g++ \ - make \ - pkg-config \ - graphviz \ - doxygen \ - git \ curl \ - libtool-bin \ - autoconf \ - automake \ - bzip2 \ - xsltproc \ - gperf \ - unzip - -WORKDIR /usr/local - -ENV CFLAGS='-fPIC' -ENV CXXFLAGS='-fPIC' - -#Cmake -ARG CMAKE_VERSION=3.14.6 -ARG CMAKE_VERSION_DOT=v3.14 -ARG CMAKE_HASH=4e8ea11cabe459308671b476469eace1622e770317a15951d7b55a82ccaaccb9 -RUN set -ex \ - && curl -s -O https://cmake.org/files/${CMAKE_VERSION_DOT}/cmake-${CMAKE_VERSION}.tar.gz \ - && echo "${CMAKE_HASH} cmake-${CMAKE_VERSION}.tar.gz" | sha256sum -c \ - && tar -xzf cmake-${CMAKE_VERSION}.tar.gz \ - && cd cmake-${CMAKE_VERSION} \ - && ./configure \ - && make \ - && make install - -## Boost -ARG BOOST_VERSION=1_70_0 -ARG BOOST_VERSION_DOT=1.70.0 -ARG BOOST_HASH=430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778 -RUN set -ex \ - && curl -s -L -o boost_${BOOST_VERSION}.tar.bz2 https://downloads.sourceforge.net/project/boost/boost/${BOOST_VERSION_DOT}/boost_${BOOST_VERSION}.tar.bz2 \ - && echo "${BOOST_HASH} boost_${BOOST_VERSION}.tar.bz2" | sha256sum -c \ - && tar -xvf boost_${BOOST_VERSION}.tar.bz2 \ - && cd boost_${BOOST_VERSION} \ - && ./bootstrap.sh \ - && ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale threading=multi threadapi=pthread cflags="$CFLAGS" cxxflags="$CXXFLAGS" stage -ENV BOOST_ROOT /usr/local/boost_${BOOST_VERSION} - -# OpenSSL -ARG OPENSSL_VERSION=1.1.1i -ARG OPENSSL_HASH=e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242 -RUN set -ex \ - && curl -s -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \ - && echo "${OPENSSL_HASH} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c \ - && tar -xzf openssl-${OPENSSL_VERSION}.tar.gz \ - && cd openssl-${OPENSSL_VERSION} \ - && ./Configure linux-x86_64 no-shared --static "$CFLAGS" \ - && make build_generated \ - && make libcrypto.a \ - && make install -ENV OPENSSL_ROOT_DIR=/usr/local/openssl-${OPENSSL_VERSION} - -# ZMQ -ARG ZMQ_VERSION=v4.3.2 -ARG ZMQ_HASH=a84ffa12b2eb3569ced199660bac5ad128bff1f0 -RUN set -ex \ - && git clone https://github.com/zeromq/libzmq.git -b ${ZMQ_VERSION} \ - && cd libzmq \ - && test `git rev-parse HEAD` = ${ZMQ_HASH} || exit 1 \ - && ./autogen.sh \ - && ./configure --enable-static --disable-shared \ - && make \ - && make install \ - && ldconfig - -# zmq.hpp -ARG CPPZMQ_VERSION=v4.4.1 -ARG CPPZMQ_HASH=f5b36e563598d48fcc0d82e589d3596afef945ae -RUN set -ex \ - && git clone https://github.com/zeromq/cppzmq.git -b ${CPPZMQ_VERSION} \ - && cd cppzmq \ - && test `git rev-parse HEAD` = ${CPPZMQ_HASH} || exit 1 \ - && mv *.hpp /usr/local/include - -# Readline -ARG READLINE_VERSION=8.0 -ARG READLINE_HASH=e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461 -RUN set -ex \ - && curl -s -O https://ftp.gnu.org/gnu/readline/readline-${READLINE_VERSION}.tar.gz \ - && echo "${READLINE_HASH} readline-${READLINE_VERSION}.tar.gz" | sha256sum -c \ - && tar -xzf readline-${READLINE_VERSION}.tar.gz \ - && cd readline-${READLINE_VERSION} \ - && ./configure \ - && make \ - && make install - -# Sodium -ARG SODIUM_VERSION=1.0.18 -ARG SODIUM_HASH=4f5e89fa84ce1d178a6765b8b46f2b6f91216677 -RUN set -ex \ - && git clone https://github.com/jedisct1/libsodium.git -b ${SODIUM_VERSION} \ - && cd libsodium \ - && test `git rev-parse HEAD` = ${SODIUM_HASH} || exit 1 \ - && ./autogen.sh \ - && ./configure \ - && make \ - && make check \ - && make install - -# Udev -ARG UDEV_VERSION=v3.2.8 -ARG UDEV_HASH=d69f3f28348123ab7fa0ebac63ec2fd16800c5e0 -RUN set -ex \ - && git clone https://github.com/gentoo/eudev -b ${UDEV_VERSION} \ - && cd eudev \ - && test `git rev-parse HEAD` = ${UDEV_HASH} || exit 1 \ - && ./autogen.sh \ - && ./configure --disable-gudev --disable-introspection --disable-hwdb --disable-manpages --disable-shared \ - && make \ - && make install - -# Libusb -ARG USB_VERSION=v1.0.22 -ARG USB_HASH=0034b2afdcdb1614e78edaa2a9e22d5936aeae5d -RUN set -ex \ - && git clone https://github.com/libusb/libusb.git -b ${USB_VERSION} \ - && cd libusb \ - && test `git rev-parse HEAD` = ${USB_HASH} || exit 1 \ - && ./autogen.sh \ - && ./configure --disable-shared \ - && make \ - && make install - -# Hidapi -ARG HIDAPI_VERSION=hidapi-0.8.0-rc1 -ARG HIDAPI_HASH=40cf516139b5b61e30d9403a48db23d8f915f52c -RUN set -ex \ - && git clone https://github.com/signal11/hidapi -b ${HIDAPI_VERSION} \ - && cd hidapi \ - && test `git rev-parse HEAD` = ${HIDAPI_HASH} || exit 1 \ - && ./bootstrap \ - && ./configure --enable-static --disable-shared \ - && make \ - && make install - -# Protobuf -ARG PROTOBUF_VERSION=v3.7.1 -ARG PROTOBUF_HASH=6973c3a5041636c1d8dc5f7f6c8c1f3c15bc63d6 -RUN set -ex \ - && git clone https://github.com/protocolbuffers/protobuf -b ${PROTOBUF_VERSION} \ - && cd protobuf \ - && test `git rev-parse HEAD` = ${PROTOBUF_HASH} || exit 1 \ - && git submodule update --init --recursive \ - && ./autogen.sh \ - && ./configure --enable-static --disable-shared \ - && make \ - && make install \ - && ldconfig + git \ + libtool \ + pkg-config \ + gperf WORKDIR /src COPY . . -ENV USE_SINGLE_BUILDDIR=1 ARG NPROC RUN set -ex && \ git submodule init && git submodule update && \ rm -rf build && \ if [ -z "$NPROC" ] ; \ - then make -j$(nproc) release-static ; \ - else make -j$NPROC release-static ; \ + then make -j$(nproc) depends target=x86_64-linux-gnu ; \ + else make -j$NPROC depends target=x86_64-linux-gnu ; \ fi # runtime stage -FROM ubuntu:16.04 +FROM ubuntu:20.04 RUN set -ex && \ apt-get update && \ apt-get --no-install-recommends --yes install ca-certificates && \ apt-get clean && \ rm -rf /var/lib/apt -COPY --from=builder /src/build/release/bin /usr/local/bin/ +COPY --from=builder /src/build/x86_64-linux-gnu/release/bin /usr/local/bin/ # Create monero user RUN adduser --system --group --disabled-password monero && \ @@ -208,5 +61,6 @@ EXPOSE 18081 # switch to user monero USER monero -ENTRYPOINT ["monerod", "--p2p-bind-ip=0.0.0.0", "--p2p-bind-port=18080", "--rpc-bind-ip=0.0.0.0", "--rpc-bind-port=18081", "--non-interactive", "--confirm-external-bind"] +ENTRYPOINT ["monerod"] +CMD ["--p2p-bind-ip=0.0.0.0", "--p2p-bind-port=18080", "--rpc-bind-ip=0.0.0.0", "--rpc-bind-port=18081", "--non-interactive", "--confirm-external-bind"] @@ -805,7 +805,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */build/* */contrib/depends/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -123,12 +123,12 @@ release-static-linux-armv7: release-static-android-armv7: mkdir -p $(builddir)/release/translations cd $(builddir)/release/translations && cmake ../../../translations && $(MAKE) - cd $(builddir)/release && CC=arm-linux-androideabi-clang CXX=arm-linux-androideabi-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG="android-armv7" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARM_MODE=ON -D CMAKE_ANDROID_ARCH_ABI="armeabi-v7a" ../.. && $(MAKE) + cd $(builddir)/release && CC=arm-linux-androideabi-clang CXX=arm-linux-androideabi-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D BUILD_TAG="android-armv7" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARM_MODE=ON -D CMAKE_ANDROID_ARCH_ABI="armeabi-v7a" ../.. && $(MAKE) release-static-android-armv8: mkdir -p $(builddir)/release/translations cd $(builddir)/release/translations && cmake ../../../translations && $(MAKE) - cd $(builddir)/release && CC=aarch64-linux-android-clang CXX=aarch64-linux-android-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG="android-armv8" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI="arm64-v8a" ../.. && $(MAKE) + cd $(builddir)/release && CC=aarch64-linux-android-clang CXX=aarch64-linux-android-clang++ cmake -D BUILD_TESTS=OFF -D ARCH="armv8-a" -D STATIC=ON -D BUILD_64=ON -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D BUILD_TAG="android-armv8" -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI="arm64-v8a" ../.. && $(MAKE) release-static-linux-armv8: mkdir -p $(builddir)/release @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2021 The Monero Project. +Copyright (c) 2014-2022 The Monero Project. Portions Copyright (c) 2012-2013 The Cryptonote developers. ## Table of Contents @@ -42,12 +42,9 @@ Portions Copyright (c) 2012-2013 The Cryptonote developers. ## Research -The [Monero Research Lab](https://src.getmonero.org/resources/research-lab/) is an open forum where the community coordinates research into Monero cryptography, protocols, fungibility, analysis, and more. We welcome collaboration and contributions from outside researchers! Because not all Lab work and publications are distributed as traditional preprints or articles, they may be easy to miss if you are conducting literature reviews for your own Monero research. You are encouraged to get in touch with our researchers if you have questions, wish to collaborate, or would like guidance to help avoid unnecessarily duplicating earlier or known work. +The [Monero Research Lab](https://src.getmonero.org/resources/research-lab/) is an open forum where the community coordinates research into Monero cryptography, protocols, fungibility, analysis, and more. We welcome collaboration and contributions from outside researchers! Because not all Lab work and publications are distributed as traditional preprints or articles, they may be easy to miss if you are conducting literature reviews for your own Monero research. You are encouraged to get in touch with the Monero research community if you have questions, wish to collaborate, or would like guidance to help avoid unnecessarily duplicating earlier or known work. -Our researchers are available on IRC in [#monero-research-lab on Libera](https://web.libera.chat/#monero-research-lab) or by email: - -- Sarang Noether, Ph.D.: [sarang@getmonero.org](mailto:sarang@getmonero.org) or [sarang.noether@protonmail.com](mailto:sarang.noether@protonmail.com); [research repository](https://github.com/SarangNoether/research-lab) -- Surae Noether (Brandon Goodell), Ph.D.: [surae@getmonero.org](mailto:surae@getmonero.org) or [surae.noether@protonmail.com](mailto:surae.noether@protonmail.com); [research repository](https://github.com/b-g-goodell/research-lab) +The Monero research community is available on IRC in [#monero-research-lab on Libera](https://web.libera.chat/#monero-research-lab), which is also accessible via Matrix. ## Announcements @@ -132,8 +129,8 @@ Dates are provided in the format YYYY-MM-DD. | 1686275 | 2018-10-19 | v9 | v0.13.0.0 | v0.13.0.4 | bulletproofs required | 1788000 | 2019-03-09 | v10 | v0.14.0.0 | v0.14.1.2 | New PoW based on Cryptonight-R, new block weight algorithm, slightly more efficient RingCT format | 1788720 | 2019-03-10 | v11 | v0.14.0.0 | v0.14.1.2 | forbid old RingCT transaction format -| 1978433 | 2019-11-30* | v12 | v0.15.0.0 | v0.16.0.0 | New PoW based on RandomX, only allow >= 2 outputs, change to the block median used to calculate penalty, v1 coinbases are forbidden, rct sigs in coinbase forbidden, 10 block lock time for incoming outputs -| 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.1.1 | New CLSAG transaction format +| 1978433 | 2019-11-30 | v12 | v0.15.0.0 | v0.16.0.0 | New PoW based on RandomX, only allow >= 2 outputs, change to the block median used to calculate penalty, v1 coinbases are forbidden, rct sigs in coinbase forbidden, 10 block lock time for incoming outputs +| 2210000 | 2020-10-17 | v13 | v0.17.0.0 | v0.17.1.1 | New CLSAG transaction format | 2210720 | 2020-10-18 | v14 | v0.17.1.1 | v0.17.1.7 | forbid old MLSAG transaction format | XXXXXXX | XXX-XX-XX | XXX | vX.XX.X.X | vX.XX.X.X | XXX | @@ -160,7 +157,7 @@ library archives (`.a`). | Dep | Min. version | Vendored | Debian/Ubuntu pkg | Arch pkg | Void pkg | Fedora pkg | Optional | Purpose | | ------------ | ------------- | -------- | -------------------- | ------------ | ------------------ | ------------------- | -------- | --------------- | -| GCC | 4.7.3 | NO | `build-essential` | `base-devel` | `base-devel` | `gcc` | NO | | +| GCC | 5 | NO | `build-essential` | `base-devel` | `base-devel` | `gcc` | NO | | | CMake | 3.5 | NO | `cmake` | `cmake` | `cmake` | `cmake` | NO | | | pkg-config | any | NO | `pkg-config` | `base-devel` | `base-devel` | `pkgconf` | NO | | | Boost | 1.58 | NO | `libboost-all-dev` | `boost` | `boost-devel` | `boost-devel` | NO | C++ libraries | @@ -187,7 +184,14 @@ library archives (`.a`). | libudev | ? | No | `libudev-dev` | `systemd` | `eudev-libudev-devel` | `systemd-devel` | YES | Hardware wallet | [1] On Debian/Ubuntu `libgtest-dev` only includes sources and headers. You must -build the library binary manually. This can be done with the following command ```sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv libg* /usr/lib/ ``` +build the library binary manually. This can be done with the following command `sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make` +then: + +* on Debian: + `sudo mv libg* /usr/lib/` +* on Ubuntu: + `sudo mv lib/libg* /usr/lib/` + [2] libnorm-dev is needed if your zmq library was built with libnorm, and not needed otherwise Install all dependencies at once on Debian/Ubuntu: @@ -608,10 +612,10 @@ More info and versions in the [Debian package tracker](https://tracker.debian.or docker build --build-arg NPROC=1 -t monero . # either run in foreground - docker run -it -v /monero/chain:/root/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero + docker run -it -v /monero/chain:/home/monero/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero # or in background - docker run -it -d -v /monero/chain:/root/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero + docker run -it -d -v /monero/chain:/home/monero/.bitmonero -v /monero/wallet:/wallet -p 18080:18080 monero ``` * The build needs 3 GB space. diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake index f26911b26..7a41a6763 100644 --- a/cmake/FindReadline.cmake +++ b/cmake/FindReadline.cmake @@ -23,7 +23,7 @@ find_path(Readline_ROOT_DIR NAMES include/readline/readline.h - PATHS /usr/local/opt/readline/ /opt/local/ /usr/local/ /usr/ + PATHS /usr/local/opt/readline/ /opt/homebrew/opt/readline/ /opt/local/ /usr/local/ /usr/ NO_DEFAULT_PATH ) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index 6a62bcaa3..2839d549f 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -32,7 +32,7 @@ function (get_version_tag_from_git GIT) execute_process(COMMAND "${GIT}" rev-parse --short=9 HEAD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE RET OUTPUT_VARIABLE COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -49,7 +49,7 @@ function (get_version_tag_from_git GIT) # Get all the tags execute_process(COMMAND "${GIT}" tag -l --points-at HEAD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE RET OUTPUT_VARIABLE TAG OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/contrib/depends/funcs.mk b/contrib/depends/funcs.mk index ffda2b5b2..804125990 100644 --- a/contrib/depends/funcs.mk +++ b/contrib/depends/funcs.mk @@ -133,6 +133,11 @@ $(1)_config_env+=$($(1)_config_env_$(host_arch)) $($(1)_config_env_$(host_arch)_ $(1)_config_env+=$($(1)_config_env_$(host_os)) $($(1)_config_env_$(host_os)_$(release_type)) $(1)_config_env+=$($(1)_config_env_$(host_arch)_$(host_os)) $($(1)_config_env_$(host_arch)_$(host_os)_$(release_type)) +$(1)_build_env+=$$($(1)_build_env_$(release_type)) +$(1)_build_env+=$($(1)_build_env_$(host_arch)) $($(1)_build_env_$(host_arch)_$(release_type)) +$(1)_build_env+=$($(1)_build_env_$(host_os)) $($(1)_build_env_$(host_os)_$(release_type)) +$(1)_build_env+=$($(1)_build_env_$(host_arch)_$(host_os)) $($(1)_build_env_$(host_arch)_$(host_os)_$(release_type)) + $(1)_config_env+=PKG_CONFIG_LIBDIR=$($($(1)_type)_prefix)/lib/pkgconfig $(1)_config_env+=PKG_CONFIG_PATH=$($($(1)_type)_prefix)/share/pkgconfig $(1)_config_env+=PATH="$(build_prefix)/bin:$(PATH)" diff --git a/contrib/depends/packages/hidapi.mk b/contrib/depends/packages/hidapi.mk index b76ef1548..8c56f9187 100644 --- a/contrib/depends/packages/hidapi.mk +++ b/contrib/depends/packages/hidapi.mk @@ -1,9 +1,10 @@ package=hidapi -$(package)_version=0.9.0 +$(package)_version=0.11.0 $(package)_download_path=https://github.com/libusb/hidapi/archive $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=630ee1834bdd5c5761ab079fd04f463a89585df8fcae51a7bfe4229b1e02a652 +$(package)_sha256_hash=391d8e52f2d6a5cf76e2b0c079cfefe25497ba1d4659131297081fc0cd744632 $(package)_linux_dependencies=libusb eudev +$(package)_patches=missing_win_include.patch define $(package)_set_vars $(package)_config_opts=--enable-static --disable-shared @@ -16,6 +17,10 @@ $(package)_config_opts_linux+=libusb_CFLAGS=-I$(host_prefix)/include/libusb-1.0 $(package)_config_opts_linux+=--with-pic endef +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/missing_win_include.patch +endef + define $(package)_config_cmds ./bootstrap &&\ $($(package)_autoconf) $($(package)_config_opts) AR_FLAGS=$($(package)_arflags) diff --git a/contrib/depends/packages/openssl.mk b/contrib/depends/packages/openssl.mk index d80775b22..79c94a30b 100644 --- a/contrib/depends/packages/openssl.mk +++ b/contrib/depends/packages/openssl.mk @@ -1,15 +1,14 @@ package=openssl -$(package)_version=1.1.1k +$(package)_version=1.1.1l $(package)_download_path=https://www.openssl.org/source $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 +$(package)_sha256_hash=0b7a3e5e59c34827fe0c3a74b7ec8baef302b98fa80088d7f9153aa16fa76bd1 +$(package)_patches=fix_darwin.patch define $(package)_set_vars $(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" -$(package)_config_env_arm_android=ANDROID_NDK_HOME="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib -$(package)_config_env_aarch64_android=ANDROID_NDK_HOME="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib -$(package)_build_env_arm_android=ANDROID_NDK_HOME="$(host_prefix)/native" -$(package)_build_env_aarch64_android=ANDROID_NDK_HOME="$(host_prefix)/native" +$(package)_config_env_android=ANDROID_NDK_HOME="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib +$(package)_build_env_android=ANDROID_NDK_HOME="$(host_prefix)/native" $(package)_config_opts=--prefix=$(host_prefix) --openssldir=$(host_prefix)/etc/openssl $(package)_config_opts+=no-capieng $(package)_config_opts+=no-dso @@ -51,7 +50,7 @@ endef define $(package)_preprocess_cmds sed -i.old 's|"engines", "apps", "test", "util", "tools", "fuzz"|"engines", "tools"|' Configure && \ - sed -i -e 's|cflags --sysroot.*",|cflags",|' Configurations/15-android.conf + patch -p1 < $($(package)_patch_dir)/fix_darwin.patch endef define $(package)_config_cmds @@ -59,11 +58,11 @@ define $(package)_config_cmds endef define $(package)_build_cmds - $(MAKE) -j1 build_libs libcrypto.pc libssl.pc openssl.pc + $(MAKE) build_libs endef define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -j1 install_sw + $(MAKE) DESTDIR=$($(package)_staging_dir) install_sw endef define $(package)_postprocess_cmds diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk index 56ce425bb..eed9e8ec1 100644 --- a/contrib/depends/packages/packages.mk +++ b/contrib/depends/packages/packages.mk @@ -1,6 +1,9 @@ packages:=boost openssl zeromq libiconv expat ldns unbound +# ccache is useless in gitian builds +ifneq ($(GITIAN),1) native_packages := native_ccache +endif hardware_packages := hidapi protobuf libusb hardware_native_packages := native_protobuf @@ -8,8 +11,8 @@ hardware_native_packages := native_protobuf android_native_packages = android_ndk android_packages = ncurses readline sodium -darwin_native_packages = native_biplist native_ds_store native_mac_alias $(hardware_native_packages) -darwin_packages = sodium ncurses readline $(hardware_packages) +darwin_native_packages = $(hardware_native_packages) +darwin_packages = ncurses readline sodium $(hardware_packages) # not really native... freebsd_native_packages = freebsd_base @@ -31,6 +34,6 @@ mingw32_packages = icu4c sodium $(hardware_packages) mingw32_native_packages = $(hardware_native_packages) ifneq ($(build_os),darwin) -darwin_native_packages += native_cctools native_cdrkit native_libdmg-hfsplus +darwin_native_packages += native_cctools endif diff --git a/contrib/depends/packages/unwind.mk b/contrib/depends/packages/unwind.mk index 826a820c4..c3d190bca 100644 --- a/contrib/depends/packages/unwind.mk +++ b/contrib/depends/packages/unwind.mk @@ -1,8 +1,8 @@ package=unwind -$(package)_version=1.2 +$(package)_version=1.5.0 $(package)_download_path=https://download.savannah.nongnu.org/releases/libunwind $(package)_file_name=lib$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=1de38ffbdc88bd694d10081865871cd2bfbb02ad8ef9e1606aee18d65532b992 +$(package)_sha256_hash=90337653d92d4a13de590781371c604f9031cdb50520366aa1e3a91e1efb1017 $(package)_patches=fix_obj_order.patch define $(package)_preprocess_cmds @@ -12,7 +12,7 @@ endef define $(package)_config_cmds cp -f $(BASEDIR)/config.guess config/config.guess &&\ cp -f $(BASEDIR)/config.sub config/config.sub &&\ - $($(package)_autoconf) --disable-shared --enable-static AR_FLAGS=$($(package)_arflags) + $($(package)_autoconf) --disable-shared --enable-static --disable-tests --disable-documentation AR_FLAGS=$($(package)_arflags) endef define $(package)_build_cmds diff --git a/contrib/depends/packages/zeromq.mk b/contrib/depends/packages/zeromq.mk index 55941e67d..20f2d4bd9 100644 --- a/contrib/depends/packages/zeromq.mk +++ b/contrib/depends/packages/zeromq.mk @@ -1,9 +1,9 @@ package=zeromq -$(package)_version=4.1.7 -$(package)_download_path=https://github.com/zeromq/zeromq4-1/releases/download/v$($(package)_version)/ +$(package)_version=4.3.4 +$(package)_download_path=https://github.com/zeromq/libzmq/releases/download/v$($(package)_version)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=31c383cfcd3be1dc8a66e448c403029e793687e70473b89c4cc0bd626e7da299 -$(package)_patches=9114d3957725acd34aa8b8d011585812f3369411.patch 9e6745c12e0b100cd38acecc16ce7db02905e27c.patch ffe62d3398d5e0191f554f61049aa7ec9fc892ae.patch +$(package)_sha256_hash=c593001a89f5a85dd2ddf564805deb860e02471171b3f204944857336295c3e5 +$(package)_patches=06aba27b04c5822cb88a69677382a0f053367143.patch define $(package)_set_vars $(package)_config_opts=--without-documentation --disable-shared --without-libsodium --disable-curve @@ -13,10 +13,7 @@ define $(package)_set_vars endef define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/9114d3957725acd34aa8b8d011585812f3369411.patch && \ - patch -p1 < $($(package)_patch_dir)/9e6745c12e0b100cd38acecc16ce7db02905e27c.patch && \ - patch -p1 < $($(package)_patch_dir)/ffe62d3398d5e0191f554f61049aa7ec9fc892ae.patch && \ - ./autogen.sh + patch -p1 < $($(package)_patch_dir)/06aba27b04c5822cb88a69677382a0f053367143.patch endef define $(package)_config_cmds @@ -24,7 +21,7 @@ define $(package)_config_cmds endef define $(package)_build_cmds - $(MAKE) libzmq.la + $(MAKE) src/libzmq.la endef define $(package)_stage_cmds diff --git a/contrib/depends/patches/hidapi/missing_win_include.patch b/contrib/depends/patches/hidapi/missing_win_include.patch new file mode 100644 index 000000000..5bbe82def --- /dev/null +++ b/contrib/depends/patches/hidapi/missing_win_include.patch @@ -0,0 +1,21 @@ +From a77b066311da42ed7654e39c0356a3b951b2e296 Mon Sep 17 00:00:00 2001 +From: selsta <selsta@sent.at> +Date: Wed, 10 Nov 2021 02:28:54 +0100 +Subject: [PATCH] windows: add missing include for mingw32 + +--- + windows/hid.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/windows/hid.c b/windows/hid.c +index 24756a4..6d8394c 100644 +--- a/windows/hid.c ++++ b/windows/hid.c +@@ -33,6 +33,7 @@ typedef LONG NTSTATUS; + #endif + + #ifdef __MINGW32__ ++#include <devpropdef.h> + #include <ntdef.h> + #include <winbase.h> + #endif diff --git a/contrib/depends/patches/openssl/fix_darwin.patch b/contrib/depends/patches/openssl/fix_darwin.patch new file mode 100644 index 000000000..a917daa12 --- /dev/null +++ b/contrib/depends/patches/openssl/fix_darwin.patch @@ -0,0 +1,60 @@ +From 96ac8f13f4d0ee96baf5724d9f96c44c34b8606c Mon Sep 17 00:00:00 2001 +From: David Carlier <devnexen@gmail.com> +Date: Tue, 24 Aug 2021 22:40:14 +0100 +Subject: [PATCH] Darwin platform allows to build on releases before + Yosemite/ios 8. + +issue #16407 #16408 + +Reviewed-by: Paul Dale <pauli@openssl.org> +Reviewed-by: Tomas Mraz <tomas@openssl.org> +(Merged from https://github.com/openssl/openssl/pull/16409) +--- + crypto/rand/rand_unix.c | 5 +---- + include/crypto/rand.h | 10 ++++++++++ + 2 files changed, 11 insertions(+), 4 deletions(-) + +diff --git a/crypto/rand/rand_unix.c b/crypto/rand/rand_unix.c +index 43f1069d151d..0f4525106af7 100644 +--- a/crypto/rand/rand_unix.c ++++ b/crypto/rand/rand_unix.c +@@ -34,9 +34,6 @@ + #if defined(__OpenBSD__) + # include <sys/param.h> + #endif +-#if defined(__APPLE__) +-# include <CommonCrypto/CommonRandom.h> +-#endif + + #if defined(OPENSSL_SYS_UNIX) || defined(__DJGPP__) + # include <sys/types.h> +@@ -381,7 +378,7 @@ static ssize_t syscall_random(void *buf, size_t buflen) + if (errno != ENOSYS) + return -1; + } +-# elif defined(__APPLE__) ++# elif defined(OPENSSL_APPLE_CRYPTO_RANDOM) + if (CCRandomGenerateBytes(buf, buflen) == kCCSuccess) + return (ssize_t)buflen; + +diff --git a/include/crypto/rand.h b/include/crypto/rand.h +index 5350d3a93119..674f840fd13c 100644 +--- a/include/crypto/rand.h ++++ b/include/crypto/rand.h +@@ -20,6 +20,16 @@ + + # include <openssl/rand.h> + ++# if defined(__APPLE__) && !defined(OPENSSL_NO_APPLE_CRYPTO_RANDOM) ++# include <Availability.h> ++# if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) || \ ++ (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) ++# define OPENSSL_APPLE_CRYPTO_RANDOM 1 ++# include <CommonCrypto/CommonCryptoError.h> ++# include <CommonCrypto/CommonRandom.h> ++# endif ++# endif ++ + /* forward declaration */ + typedef struct rand_pool_st RAND_POOL; + diff --git a/contrib/depends/patches/unwind/fix_obj_order.patch b/contrib/depends/patches/unwind/fix_obj_order.patch index 374a9f04a..e764f0f3d 100644 --- a/contrib/depends/patches/unwind/fix_obj_order.patch +++ b/contrib/depends/patches/unwind/fix_obj_order.patch @@ -1,9 +1,9 @@ ---- config/ltmain.sh.O 2017-01-13 16:00:54.000000000 +0000 -+++ config/ltmain.sh 2019-11-17 06:46:51.994402494 +0000 -@@ -7957,6 +7957,8 @@ - esac - done +--- config/ltmain.sh.0 2020-11-10 17:25:26.000000000 +0100 ++++ config/ltmain.sh 2021-09-11 19:39:36.000000000 +0200 +@@ -10768,6 +10768,8 @@ fi + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + oldobjs=`for obj in $oldobjs; do echo $obj; done | sort` + oldobjs=" `echo $oldobjs`" eval cmds=\"$old_archive_cmds\" diff --git a/contrib/depends/patches/zeromq/06aba27b04c5822cb88a69677382a0f053367143.patch b/contrib/depends/patches/zeromq/06aba27b04c5822cb88a69677382a0f053367143.patch new file mode 100644 index 000000000..53e18a452 --- /dev/null +++ b/contrib/depends/patches/zeromq/06aba27b04c5822cb88a69677382a0f053367143.patch @@ -0,0 +1,22 @@ +From 06aba27b04c5822cb88a69677382a0f053367143 Mon Sep 17 00:00:00 2001 +From: sabotagebeats <27985126+sabotagebeats@users.noreply.github.com> +Date: Thu, 22 Jul 2021 21:53:19 -0700 +Subject: [PATCH] fix: building libzmq fails with error src/clock.cpp:131:16: + error: unused variable 'nsecs_per_usec' + +--- + src/clock.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/clock.cpp b/src/clock.cpp +index 93da90a8e..63c0100a5 100644 +--- a/src/clock.cpp ++++ b/src/clock.cpp +@@ -195,6 +195,7 @@ uint64_t zmq::clock_t::now_us () + + #else + ++ LIBZMQ_UNUSED (nsecs_per_usec); + // Use POSIX gettimeofday function to get precise time. + struct timeval tv; + int rc = gettimeofday (&tv, NULL); diff --git a/contrib/depends/patches/zeromq/9114d3957725acd34aa8b8d011585812f3369411.patch b/contrib/depends/patches/zeromq/9114d3957725acd34aa8b8d011585812f3369411.patch deleted file mode 100644 index f704b3d94..000000000 --- a/contrib/depends/patches/zeromq/9114d3957725acd34aa8b8d011585812f3369411.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 9114d3957725acd34aa8b8d011585812f3369411 Mon Sep 17 00:00:00 2001 -From: Jeroen Ooms <jeroenooms@gmail.com> -Date: Tue, 20 Oct 2015 13:10:38 +0200 -Subject: [PATCH] enable static libraries on mingw - ---- - configure.ac | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/configure.ac b/configure.ac -index 393505b..e92131a 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -265,7 +265,7 @@ case "${host_os}" in - libzmq_dso_visibility="no" - - if test "x$enable_static" = "xyes"; then -- AC_MSG_ERROR([Building static libraries is not supported under MinGW32]) -+ CPPFLAGS="-DZMQ_STATIC" - fi - - # Set FD_SETSIZE to 1024
\ No newline at end of file diff --git a/contrib/depends/patches/zeromq/9e6745c12e0b100cd38acecc16ce7db02905e27c.patch b/contrib/depends/patches/zeromq/9e6745c12e0b100cd38acecc16ce7db02905e27c.patch deleted file mode 100644 index 9aff2c179..000000000 --- a/contrib/depends/patches/zeromq/9e6745c12e0b100cd38acecc16ce7db02905e27c.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 9e6745c12e0b100cd38acecc16ce7db02905e27c Mon Sep 17 00:00:00 2001 -From: David Millard <dmillard10@gmail.com> -Date: Tue, 10 May 2016 13:53:53 -0700 -Subject: [PATCH] Fix autotools for static MinGW builds - ---- - configure.ac | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/configure.ac b/configure.ac -index 5a0fa14..def6ea7 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -259,7 +259,7 @@ case "${host_os}" in - libzmq_dso_visibility="no" - - if test "x$enable_static" = "xyes"; then -- CPPFLAGS="-DZMQ_STATIC" -+ CPPFLAGS="-DZMQ_STATIC $CPPFLAGS" - fi - - # Set FD_SETSIZE to 1024
\ No newline at end of file diff --git a/contrib/depends/patches/zeromq/ffe62d3398d5e0191f554f61049aa7ec9fc892ae.patch b/contrib/depends/patches/zeromq/ffe62d3398d5e0191f554f61049aa7ec9fc892ae.patch deleted file mode 100644 index a532df1b6..000000000 --- a/contrib/depends/patches/zeromq/ffe62d3398d5e0191f554f61049aa7ec9fc892ae.patch +++ /dev/null @@ -1,38 +0,0 @@ -From ffe62d3398d5e0191f554f61049aa7ec9fc892ae Mon Sep 17 00:00:00 2001 -From: Gregory Lemercier <greglemercier@free.fr> -Date: Sun, 7 Oct 2018 18:06:54 +0200 -Subject: [PATCH] Fix build on arm64 architectures with some strict compilers - -This patch fixes an issue that occurs on 64-bit architetures under -strict compiler rules. The code initially checked that the received -size stored in 'uint64_t' was not bigger than the max value of a -'size_t' variable, which is legitimate on 32-bit architectures where -'size_t' variables are stored on 32 bits. On 64-bit architectures, -this test no longer makes sense since 'uint64_t' and 'size_t' types -have the same size. The issue is fixed by ignoring this portion -of code when built for arm64. ---- - src/v1_decoder.cpp | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/src/v1_decoder.cpp b/src/v1_decoder.cpp -index b002dc9d..2c8c97a7 100644 ---- a/src/v1_decoder.cpp -+++ b/src/v1_decoder.cpp -@@ -114,11 +114,13 @@ int zmq::v1_decoder_t::eight_byte_size_ready () - return -1; - } - -+#ifndef __aarch64__ - // Message size must fit within range of size_t data type. - if (payload_length - 1 > std::numeric_limits <size_t>::max ()) { - errno = EMSGSIZE; - return -1; - } -+#endif - - const size_t msg_size = static_cast <size_t> (payload_length - 1); - --- -2.20.1 - diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in index a87b9c058..c3a0df59b 100644 --- a/contrib/depends/toolchain.cmake.in +++ b/contrib/depends/toolchain.cmake.in @@ -83,7 +83,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") SET(PORT OFF) SET(CMAKE_OSX_SYSROOT "@sdk@/MacOSX10.11.sdk/") SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.08") - SET(CMAKE_CXX_STANDARD 11) + SET(CMAKE_CXX_STANDARD 14) SET(CMAKE_OSX_ARCHITECTURES "x86_64") SET(LLVM_ENABLE_PIC OFF) SET(LLVM_ENABLE_PIE OFF) diff --git a/contrib/epee/include/net/levin_base.h b/contrib/epee/include/net/levin_base.h index df59a6c44..b680691ad 100644 --- a/contrib/epee/include/net/levin_base.h +++ b/contrib/epee/include/net/levin_base.h @@ -48,7 +48,7 @@ namespace levin { uint64_t m_signature; uint64_t m_cb; - bool m_have_to_return_data; + uint8_t m_have_to_return_data; uint32_t m_command; int32_t m_return_code; uint32_t m_reservedA; //probably some flags in future @@ -63,7 +63,7 @@ namespace levin { uint64_t m_signature; uint64_t m_cb; - bool m_have_to_return_data; + uint8_t m_have_to_return_data; uint32_t m_command; int32_t m_return_code; uint32_t m_flags; diff --git a/contrib/epee/include/net/levin_client.inl b/contrib/epee/include/net/levin_client.inl index 2f048b027..177dd8967 100644 --- a/contrib/epee/include/net/levin_client.inl +++ b/contrib/epee/include/net/levin_client.inl @@ -82,7 +82,7 @@ int levin_client_impl::invoke(int command, const epee::span<const uint8_t> in_bu bucket_head head = {0}; head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = SWAP64LE(in_buff.size()); - head.m_have_to_return_data = true; + head.m_have_to_return_data = 1; head.m_command = SWAP32LE(command); if(!m_transport.send(&head, sizeof(head))) return -1; @@ -118,7 +118,7 @@ int levin_client_impl::notify(int command, const std::string& in_buff) bucket_head head = {0}; head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = SWAP64LE(in_buff.size()); - head.m_have_to_return_data = false; + head.m_have_to_return_data = 0; head.m_command = SWAP32LE(command); if(!m_transport.send((const char*)&head, sizeof(head))) @@ -141,7 +141,7 @@ inline bucket_head2 head = {0}; head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = SWAP64LE(in_buff.size()); - head.m_have_to_return_data = true; + head.m_have_to_return_data = 1; head.m_command = SWAP32LE(command); head.m_return_code = SWAP32LE(0); head.m_flags = SWAP32LE(LEVIN_PACKET_REQUEST); @@ -179,7 +179,7 @@ inline bucket_head2 head = {0}; head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = SWAP64LE(in_buff.size()); - head.m_have_to_return_data = false; + head.m_have_to_return_data = 0; head.m_command = SWAP32LE(command); head.m_return_code = SWAP32LE(0); head.m_flags = SWAP32LE(LEVIN_PACKET_REQUEST); diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h index ed92f4b95..067707edf 100644 --- a/contrib/epee/include/net/levin_client_async.h +++ b/contrib/epee/include/net/levin_client_async.h @@ -242,7 +242,7 @@ namespace levin bucket_head head = {0}; head.m_signature = LEVIN_SIGNATURE; head.m_cb = in_buff.size(); - head.m_have_to_return_data = true; + head.m_have_to_return_data = 1; head.m_id = target; #ifdef TRACE_LEVIN_PACKETS_BY_GUIDS ::UuidCreate(&head.m_id); @@ -320,7 +320,7 @@ namespace levin bucket_head head = {0}; head.m_signature = LEVIN_SIGNATURE; head.m_cb = in_buff.size(); - head.m_have_to_return_data = false; + head.m_have_to_return_data = 0; head.m_id = target; #ifdef TRACE_LEVIN_PACKETS_BY_GUIDS ::UuidCreate(&head.m_id); @@ -510,7 +510,7 @@ namespace levin head.m_cb = return_buff.size(); - head.m_have_to_return_data = false; + head.m_have_to_return_data = 0; head.m_protocol_version = LEVIN_PROTOCOL_VER_1; head.m_flags = LEVIN_PACKET_RESPONSE; diff --git a/contrib/epee/include/net/levin_helper.h b/contrib/epee/include/net/levin_helper.h index da926a914..541e0948d 100644 --- a/contrib/epee/include/net/levin_helper.h +++ b/contrib/epee/include/net/levin_helper.h @@ -46,7 +46,7 @@ namespace levin levin::bucket_head& head = *(levin::bucket_head*)(&buff[0]); head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = 0; - head.m_have_to_return_data = true; + head.m_have_to_return_data = 1; head.m_command = SWAP32LE(command_id); head.m_return_code = SWAP32LE(1); head.m_reservedA = rand(); //probably some flags in future @@ -68,7 +68,7 @@ namespace levin levin::bucket_head& head = *(levin::bucket_head*)(&buff[0]); head.m_signature = SWAP64LE(LEVIN_SIGNATURE); head.m_cb = 0; - head.m_have_to_return_data = true; + head.m_have_to_return_data = 1; head.m_command = SWAP32LE(command_id); head.m_return_code = SWAP32LE(1); head.m_reservedA = rand(); //probably some flags in future diff --git a/contrib/epee/include/net/levin_protocol_handler.h b/contrib/epee/include/net/levin_protocol_handler.h index c510cfd79..fa7d1a5ab 100644 --- a/contrib/epee/include/net/levin_protocol_handler.h +++ b/contrib/epee/include/net/levin_protocol_handler.h @@ -156,7 +156,7 @@ namespace levin std::string return_buff; m_current_head.m_return_code = m_config.m_pcommands_handler->invoke(m_current_head.m_command, buff_to_invoke, return_buff, m_conn_context); m_current_head.m_cb = return_buff.size(); - m_current_head.m_have_to_return_data = false; + m_current_head.m_have_to_return_data = 0; return_buff.insert(0, (const char*)&m_current_head, sizeof(m_current_head)); if(!m_psnd_hndlr->do_send(byte_slice{std::move(return_buff)})) diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index 9e7b6ec34..6f081dbc7 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -157,6 +157,18 @@ namespace epee pod_val = CONVERT_POD(pod_val); } + template<> + void throwable_buffer_reader::read<bool>(bool& pod_val) + { + RECURSION_LIMITATION(); + static_assert(std::is_pod<bool>::value, "POD type expected"); + static_assert(sizeof(bool) == sizeof(uint8_t), "We really shouldn't use bool directly in serialization code. Replace it with uint8_t if this assert triggers!"); + uint8_t t; + read(&t, sizeof(t)); + CHECK_AND_ASSERT_THROW_MES(t <= 1, "Invalid bool value " << t); + pod_val = (t != 0); + } + template<class t_type> t_type throwable_buffer_reader::read() { diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 07f119ba4..3fa2f7fb4 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -47,9 +47,7 @@ if (USE_READLINE AND (GNU_READLINE_FOUND OR (DEPENDS AND NOT MINGW))) monero_add_library(epee_readline readline_buffer.cpp) endif() -if(HAVE_C11) -SET_PROPERTY(SOURCE memwipe.c PROPERTY COMPILE_FLAGS -std=c11) -endif() +set_property(SOURCE memwipe.c PROPERTY C_STANDARD 11) # Build and install libepee if we're building for GUI if (BUILD_GUI_DEPS) diff --git a/contrib/epee/src/buffer.cpp b/contrib/epee/src/buffer.cpp index 10ea6de56..cf94fb5d5 100644 --- a/contrib/epee/src/buffer.cpp +++ b/contrib/epee/src/buffer.cpp @@ -26,6 +26,7 @@ // 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 <limits> #include <string.h> #include "net/buffer.h" diff --git a/contrib/epee/src/http_auth.cpp b/contrib/epee/src/http_auth.cpp index 18ee18bd4..9f30e1975 100644 --- a/contrib/epee/src/http_auth.cpp +++ b/contrib/epee/src/http_auth.cpp @@ -209,12 +209,7 @@ namespace } template<typename T> - using quoted_result = boost::joined_range< - const boost::joined_range<const boost::string_ref, const T>, const boost::string_ref - >; - - template<typename T> - quoted_result<T> quoted(const T& arg) + auto quoted_(const T& arg) // avoid ADL selecting C++14 std::quoted { return boost::range::join(boost::range::join(ceref(u8"\""), arg), ceref(u8"\"")); } @@ -242,13 +237,13 @@ namespace { str.append(u8"Digest "); add_first_field(str, u8"algorithm", algorithm); - add_field(str, u8"nonce", quoted(user.server.nonce)); - add_field(str, u8"realm", quoted(user.server.realm)); - add_field(str, u8"response", quoted(response)); - add_field(str, u8"uri", quoted(uri)); - add_field(str, u8"username", quoted(user.credentials.username)); + add_field(str, u8"nonce", quoted_(user.server.nonce)); + add_field(str, u8"realm", quoted_(user.server.realm)); + add_field(str, u8"response", quoted_(response)); + add_field(str, u8"uri", quoted_(uri)); + add_field(str, u8"username", quoted_(user.credentials.username)); if (!user.server.opaque.empty()) - add_field(str, u8"opaque", quoted(user.server.opaque)); + add_field(str, u8"opaque", quoted_(user.server.opaque)); } //! Implements superseded algorithm specified in RFC 2069 @@ -674,8 +669,8 @@ namespace Digest::name, (i == 0 ? boost::string_ref{} : sess_algo) ); add_field(out, u8"algorithm", algorithm); - add_field(out, u8"realm", quoted(auth_realm)); - add_field(out, u8"nonce", quoted(nonce)); + add_field(out, u8"realm", quoted_(auth_realm)); + add_field(out, u8"nonce", quoted_(nonce)); add_field(out, u8"stale", is_stale ? ceref("true") : ceref("false")); fields.push_back(std::make_pair(std::string(server_auth_field), std::move(out))); diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp index 21312d49d..03cb05232 100644 --- a/contrib/epee/src/wipeable_string.cpp +++ b/contrib/epee/src/wipeable_string.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/optional/optional.hpp> +#include <limits> #include <string.h> #include "memwipe.h" #include "misc_log_ex.h" diff --git a/contrib/epee/tests/src/CMakeLists.txt b/contrib/epee/tests/src/CMakeLists.txt index 0c42f87ca..e724b53f4 100644 --- a/contrib/epee/tests/src/CMakeLists.txt +++ b/contrib/epee/tests/src/CMakeLists.txt @@ -1,6 +1,13 @@ cmake_minimum_required(VERSION 3.5) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + set(Boost_USE_MULTITHREADED ON) include_directories(.) @@ -14,8 +21,8 @@ IF (MSVC) include_directories(SYSTEM platform/msvc) ELSE() # set stuff for other systems - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Wall") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wno-reorder") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-reorder") ENDIF() diff --git a/contrib/gitian/DOCKRUN.md b/contrib/gitian/DOCKRUN.md new file mode 100644 index 000000000..7f44b7914 --- /dev/null +++ b/contrib/gitian/DOCKRUN.md @@ -0,0 +1,119 @@ +Quick Gitian building with docker +================================= + +*Setup instructions for a Gitian build of Monero using Docker.* + +Gitian supports other container mechanisms too but if you have a Debian or +Ubuntu-based host the steps can be greatly simplified. + +Preparing the Gitian builder host +--------------------------------- + +The procedure here will create a docker container for build preparation, as well as +for actually running the builds. The only items you must install on your own host +are docker and apt-cacher-ng. With docker installed, you should also give yourself +permission to use docker by adding yourself to the docker group. + +```bash +sudo apt-get install docker.io apt-cacher-ng +sudo usermod -aG docker $USER +su $USER +``` + +The final `su` command is needed to start a new shell with your new group membership, +since the `usermod` command doesn't affect any existing sessions. + +You'll also need to clone the monero repository and navigate to the `contrib/gitian` directory: + +```bash +git clone https://github.com/monero-project/monero.git +cd monero/contrib/gitian +``` + +If you want Mac binaries included in your build, you need to obtain the MacOS SDK: + +```bash +curl -O https://bitcoincore.org/depends-sources/sdks/MacOSX10.11.sdk.tar.gz +``` + +Other User Preparation +---------------------- + +The final step will be to `gpg` sign the results of your build and upload them to GitHub. +Before you can do that, you'll need +* a GitHub account. +If your GitHub account name is different from your local account name, you must +set your GitHub account name for the script to use: + +```bash +export GH_USER=<github account name> +``` + +* PGP keys - if you don't have one already, you can use `gpg --quick-gen-key` to generate it. +* a fork of the [gitian.sigs](https://github.com/monero-project/gitian.sigs/) repo on your GitHub account. +Please follow the directions there for uploading your key first. + +**Note:** Please ensure your gpg public key is available to check signatures by adding it to the [gitian.sigs/gitian-pubkeys/](https://github.com/monero-project/gitian.sigs/tree/master/gitian-pubkeys) directory in a pull request. + + +Building the Binaries +--------------------- + +The dockrun.sh script will do everything to build the binaries. Just specify the +version to build as its only argument, e.g. + +```bash +./dockrun.sh v0.17.3.0 +``` + +The build should run to completion with no errors, and will display the SHA256 checksums +of the resulting binaries. You'll be prompted to check if the sums look good, and if so +then the results will be signed, and the signatures will be pushed to GitHub. + +***Note: In order to publish the signed assertions via this script, you need to have your SSH key uploaded to Github beforehand. See https://docs.github.com/articles/generating-an-ssh-key/ for more info.*** + +You can also look in the [gitian.sigs](https://github.com/monero-project/gitian.sigs/) repo and / or [getmonero.org release checksums](https://web.getmonero.org/downloads/hashes.txt) to see if others got the same checksum for the same version tag. If there is ever a mismatch -- **STOP! Something is wrong**. Contact others on IRC / GitHub to figure out what is going on. + + +Other Options +------------- + +This script just runs the [gitian-build.py](gitian-build.py) inside a container named `gitrun`. +You can set other options for that script by setting the OPT variable when running `dockrun.sh` +e.g. + +```bash +# Run build processes with 8 threads +OPT="-j 8" ./dockrun.sh v0.17.3.0 +``` + +Post-build +---------- + +You can examine the build and install logs by running a shell in the container, e.g. + +```bash +# Tail running logs +docker exec -it gitrun /bin/bash +tail -F builder/var/install.log +tail -F builder/var/build.log + +# Inspect logs, in format install-<OS>.log and build-<OS>.log +docker exec -it gitrun /bin/bash +more builder/var/install-linux.log +more builder/var/build-linux.log +``` + +You can find the compiled archives inside of the container at the following directory (be sure to replace `v0.17.3.0` with the version being built): + +```bash +docker exec -it gitrun /bin/bash +ls -la out/v0.17.3.0/ +``` + +To copy the compiled archives to the local host out of the Docker container, you can run the following (be sure to replace `v0.17.3.0` with the version being built): + +```bash +mkdir out +docker cp gitrun:/home/ubuntu/out/v0.17.3.0 out +``` diff --git a/contrib/gitian/README.md b/contrib/gitian/README.md index 0a40d4608..c922a2373 100644 --- a/contrib/gitian/README.md +++ b/contrib/gitian/README.md @@ -31,6 +31,8 @@ This guide explains how to set up the environment, and how to start the builds. * Gitian gives you the option of using any of 3 different virtualization tools: `kvm`, `docker` or `lxc`. This documentation will only show how to build with `lxc` and `docker` (documentation for `kvm` is welcome). Building with `lxc` is the default, but is more complicated, so we recommend docker your first time. +* For a shortcut using `docker` follow the instructions in [DOCKRUN.md](DOCKRUN.md) instead +of following the rest of this document.. ## Create the gitianuser account diff --git a/contrib/gitian/dockrun.sh b/contrib/gitian/dockrun.sh new file mode 100755 index 000000000..015c411fd --- /dev/null +++ b/contrib/gitian/dockrun.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +if [ $# -ne 1 ]; then + echo "usage: $0 <version>" + exit 1 +fi +VERSION=$1 + +DOCKER=`command -v docker` +CACHER=`command -v apt-cacher-ng` + +if [ -z "$DOCKER" -o -z "$CACHER" ]; then + echo "$0: you must first install docker.io and apt-cacher-ng" + echo " e.g. sudo apt-get install docker.io apt-cacher-ng" + exit 1 +fi + +GH_USER=${GH_USER-$USER} + +TAG=gitrun-bionic +TAG2=base-bionic-amd64 +IMAGE=`docker images | grep $TAG` + +WORKDIR=/home/ubuntu + +if [ -z "$IMAGE" ]; then +GID=`getent group docker` +mkdir -p docker +cd docker + +# container for running gitian-build.py +cat <<EOF > ${TAG}.Dockerfile +FROM ubuntu:bionic + +ENV DEBIAN_FRONTEND=noninteractive +RUN echo 'Acquire::http { Proxy "http://172.17.0.1:3142"; };' > /etc/apt/apt.conf.d/50cacher +RUN echo "$GID" >> /etc/group +RUN apt-get update && apt-get --no-install-recommends -y install lsb-release ruby git make wget docker.io python3 curl + +RUN useradd -ms /bin/bash -U ubuntu -G docker +USER ubuntu:docker +WORKDIR $WORKDIR + +RUN git clone https://github.com/monero-project/gitian.sigs.git sigs; \ + git clone https://github.com/devrandom/gitian-builder.git builder; \ + cd builder; git checkout c0f77ca018cb5332bfd595e0aff0468f77542c23; mkdir -p inputs var; cd inputs; \ + git clone https://github.com/monero-project/monero + +CMD ["sleep", "infinity"] +EOF + +docker build --pull -f ${TAG}.Dockerfile -t $TAG . + +cd .. +docker run -v /var/run/docker.sock:/var/run/docker.sock -d --name gitrun $TAG +if [ -f MacOSX10.11.sdk.tar.gz ]; then + docker cp MacOSX10.11.sdk.tar.gz gitrun:$WORKDIR/builder/inputs/ +else + echo "No MacOS SDK found, Mac builds will be omitted" +fi + +fi + +IMAGE=`docker images | grep $TAG2` +if [ -z "$IMAGE" ]; then +mkdir -p docker +cd docker + +# container for actually running each build +cat <<EOF > ${TAG2}.Dockerfile +FROM ubuntu:bionic + +ENV DEBIAN_FRONTEND=noninteractive +RUN echo 'Acquire::http { Proxy "http://172.17.0.1:3142"; };' > /etc/apt/apt.conf.d/50cacher +RUN apt-get update && apt-get --no-install-recommends -y install build-essential git language-pack-en \ + wget lsb-release curl gcc-7 g++-7 gcc g++ binutils-gold pkg-config autoconf libtool automake faketime \ + bsdmainutils ca-certificates python cmake gperf + +RUN useradd -ms /bin/bash -U ubuntu +USER ubuntu:ubuntu +WORKDIR $WORKDIR + +CMD ["sleep", "infinity"] +EOF + +docker build --pull -f ${TAG2}.Dockerfile -t $TAG2 . + +cd .. + +fi + +RUNNING=`docker ps | grep gitrun` +if [ -z "$RUNNING" ]; then + BUILT=`docker ps -a | grep gitrun` + if [ -z "$BUILT" ]; then + docker run -v /var/run/docker.sock:/var/run/docker.sock -d --name gitrun $TAG + else + docker start gitrun + fi +fi +docker cp gitian-build.py gitrun:$WORKDIR/ +docker exec -t gitrun ./gitian-build.py -d -b -D -n $OPT $GH_USER $VERSION +RC=$? +if [ $RC != 0 ]; then + exit $RC +fi +echo "\nBuild Results:\n" +docker exec gitrun sh -c "sha256sum out/$VERSION/*" +echo "\nIf these results look correct, type \"sign\" to sign them, otherwise ^C to stop now." +read check +if [ "$check" != "sign" ]; then + echo "Not signing, bye." + exit 1 +fi + +if [ ! -d sigs ]; then + git clone https://github.com/monero-project/gitian.sigs.git sigs + cd sigs + git remote add $GH_USER git@github.com:$GH_USER/gitian.sigs.git + cd .. +fi + +DIRS=`docker exec gitrun sh -c "echo sigs/$VERSION-*"` +for i in $DIRS; do + docker cp gitrun:$WORKDIR/$i sigs + gpg --detach-sign $i/$GH_USER/*.assert +done + +cd sigs +git checkout -b $VERSION +git add $VERSION-* +git commit -S -m "Add $GH_USER $VERSION" +git push --set-upstream $GH_USER $VERSION diff --git a/contrib/gitian/gitian-android.yml b/contrib/gitian/gitian-android.yml index b8eaa8af9..23cb7d0e8 100644 --- a/contrib/gitian/gitian-android.yml +++ b/contrib/gitian/gitian-android.yml @@ -24,12 +24,6 @@ packages: - "ca-certificates" - "python" - "cmake" -- "ccache" -- "protobuf-compiler" -- "libdbus-1-dev" -- "libharfbuzz-dev" -- "libprotobuf-dev" -- "python3-zmq" - "unzip" remotes: - "url": "https://github.com/monero-project/monero.git" @@ -52,6 +46,7 @@ script: | if test -n "$GBUILD_CACHE_ENABLED"; then export SOURCES_PATH=${GBUILD_COMMON_CACHE} export BASE_CACHE=${GBUILD_PACKAGE_CACHE} + export GITIAN=1 mkdir -p ${BASE_CACHE} ${SOURCES_PATH} fi @@ -125,8 +120,8 @@ script: | cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake -DCMAKE_BUILD_TYPE=Release make ${MAKEOPTS} chmod 755 bin/* - cp ../LICENSE bin - chmod 644 bin/LICENSE + cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin + chmod 644 bin/LICENSE bin/*.md DISTNAME=monero-${i}-${version} mv bin ${DISTNAME} find ${DISTNAME}/ | sort | tar --no-recursion --owner=0 --group=0 -c -T - | bzip2 -9 > ${OUTDIR}/${DISTNAME}.tar.bz2 diff --git a/contrib/gitian/gitian-freebsd.yml b/contrib/gitian/gitian-freebsd.yml index 36b81c641..134823b95 100644 --- a/contrib/gitian/gitian-freebsd.yml +++ b/contrib/gitian/gitian-freebsd.yml @@ -25,12 +25,6 @@ packages: - "ca-certificates" - "python" - "cmake" -- "ccache" -- "protobuf-compiler" -- "libdbus-1-dev" -- "libharfbuzz-dev" -- "libprotobuf-dev" -- "python3-zmq" remotes: - "url": "https://github.com/monero-project/monero.git" "dir": "monero" @@ -52,6 +46,7 @@ script: | if test -n "$GBUILD_CACHE_ENABLED"; then export SOURCES_PATH=${GBUILD_COMMON_CACHE} export BASE_CACHE=${GBUILD_PACKAGE_CACHE} + export GITIAN=1 mkdir -p ${BASE_CACHE} ${SOURCES_PATH} fi @@ -122,8 +117,8 @@ script: | cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_SKIP_RPATH=ON make ${MAKEOPTS} chmod 755 bin/* - cp ../LICENSE bin - chmod 644 bin/LICENSE + cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin + chmod 644 bin/LICENSE bin/*.md DISTNAME=monero-${i}-${version} mv bin ${DISTNAME} find ${DISTNAME}/ | sort | tar --no-recursion --owner=0 --group=0 -c -T - | bzip2 -9 > ${OUTDIR}/${DISTNAME}.tar.bz2 diff --git a/contrib/gitian/gitian-linux.yml b/contrib/gitian/gitian-linux.yml index 0aac983cc..7ab628fbc 100644 --- a/contrib/gitian/gitian-linux.yml +++ b/contrib/gitian/gitian-linux.yml @@ -36,12 +36,6 @@ packages: - "ca-certificates" - "python" - "cmake" -- "ccache" -- "protobuf-compiler" -- "libdbus-1-dev" -- "libharfbuzz-dev" -- "libprotobuf-dev" -- "python3-zmq" remotes: - "url": "https://github.com/monero-project/monero.git" "dir": "monero" @@ -63,6 +57,7 @@ script: | if test -n "$GBUILD_CACHE_ENABLED"; then export SOURCES_PATH=${GBUILD_COMMON_CACHE} export BASE_CACHE=${GBUILD_PACKAGE_CACHE} + export GITIAN=1 mkdir -p ${BASE_CACHE} ${SOURCES_PATH} fi @@ -167,8 +162,8 @@ script: | cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake -DBACKCOMPAT=ON -DCMAKE_SKIP_RPATH=ON make ${MAKEOPTS} chmod 755 bin/* - cp ../LICENSE bin - chmod 644 bin/LICENSE + cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin + chmod 644 bin/LICENSE bin/*.md DISTNAME=monero-${i}-${version} mv bin ${DISTNAME} find ${DISTNAME}/ | sort | tar --no-recursion --owner=0 --group=0 -c -T - | bzip2 -9 > ${OUTDIR}/${DISTNAME}.tar.bz2 diff --git a/contrib/gitian/gitian-osx.yml b/contrib/gitian/gitian-osx.yml index 9889ca45f..3434861d2 100644 --- a/contrib/gitian/gitian-osx.yml +++ b/contrib/gitian/gitian-osx.yml @@ -41,6 +41,7 @@ script: | if test -n "$GBUILD_CACHE_ENABLED"; then export SOURCES_PATH=${GBUILD_COMMON_CACHE} export BASE_CACHE=${GBUILD_PACKAGE_CACHE} + export GITIAN=1 mkdir -p ${BASE_CACHE} ${SOURCES_PATH} fi @@ -111,8 +112,8 @@ script: | cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake make ${MAKEOPTS} chmod 755 bin/* - cp ../LICENSE bin - chmod 644 bin/LICENSE + cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin + chmod 644 bin/LICENSE bin/*.md DISTNAME=monero-${i}-${version} mv bin ${DISTNAME} find ${DISTNAME}/ | sort | tar --no-recursion --owner=0 --group=0 -c -T - | bzip2 -9 > ${OUTDIR}/${DISTNAME}.tar.bz2 diff --git a/contrib/gitian/gitian-win.yml b/contrib/gitian/gitian-win.yml index c53086144..7d5a249c8 100644 --- a/contrib/gitian/gitian-win.yml +++ b/contrib/gitian/gitian-win.yml @@ -20,7 +20,6 @@ packages: - "zip" - "ca-certificates" - "python" -- "rename" - "cmake" alternatives: - @@ -54,6 +53,7 @@ script: | if test -n "$GBUILD_CACHE_ENABLED"; then export SOURCES_PATH=${GBUILD_COMMON_CACHE} export BASE_CACHE=${GBUILD_PACKAGE_CACHE} + export GITIAN=1 mkdir -p ${BASE_CACHE} ${SOURCES_PATH} fi @@ -127,7 +127,7 @@ script: | mkdir build && cd build cmake .. -DCMAKE_TOOLCHAIN_FILE=${BASEPREFIX}/${i}/share/toolchain.cmake make ${MAKEOPTS} - cp ../LICENSE bin + cp ../LICENSE ../README.md ../docs/ANONYMITY_NETWORKS.md bin DISTNAME=monero-${i}-${version} mv bin ${DISTNAME} find ${DISTNAME}/ | sort | zip -X@ ${OUTDIR}/${DISTNAME}.zip diff --git a/docs/COMPILING_DEBUGGING_TESTING.md b/docs/COMPILING_DEBUGGING_TESTING.md index f5c202303..66555fa80 100644 --- a/docs/COMPILING_DEBUGGING_TESTING.md +++ b/docs/COMPILING_DEBUGGING_TESTING.md @@ -1,7 +1,7 @@ # Compiling, debugging and testing efficiently This document describes ways of compiling, debugging and testing efficiently for various use cases. -The intented audience are developers, who want to leverage newly added tricks to Monero via `CMake`. The document will lower the entry point for these developers. +The intended audience are developers, who want to leverage newly added tricks to Monero via `CMake`. The document will lower the entry point for these developers. Before reading this document, please consult section "Build instructions" in the main README.md. Some information from README.md will be repeated here, but the aim is to go beyond it. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e12295412..d60edc6a6 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -20,7 +20,7 @@ posted to #monero-dev on irc.libera.chat). Patches should be self contained. A good rule of thumb is to have one patch per separate issue, feature, or logical change. Also, no other changes, such as random whitespace changes, reindentation, -or fixing typoes, spelling, or wording, unless user visible. +or fixing typos, spelling, or wording, unless user visible. Following the code style of the particular chunk of code you're modifying is encouraged. Proper squashing should be done (eg, if you're making a buggy patch, then a later patch to fix the bug, diff --git a/docs/LEVIN_PROTOCOL.md b/docs/LEVIN_PROTOCOL.md index 43500fd06..2a5dacb84 100644 --- a/docs/LEVIN_PROTOCOL.md +++ b/docs/LEVIN_PROTOCOL.md @@ -1,5 +1,5 @@ # Levin Protocol -This is a document explaining the current design of the levin protocol, as +This is a document explaining the current design of the Levin protocol, as used by Monero. The protocol is largely inherited from cryptonote, but has undergone some changes. @@ -9,7 +9,7 @@ extensibility. One of the goals of this document is to clearly indicate what is being sent "on the wire" to identify metadata that could de-anonymize users over I2P/Tor. -These issues will be addressed as they are found. See `ANONMITY_NETWORKS.md` in +These issues will be addressed as they are found. See `ANONYMITY_NETWORKS.md` in the `docs` folder for any outstanding issues. > This document does not currently list all data being sent by the monero @@ -75,7 +75,7 @@ An unsigned 32-bit little endian integer representing the Monero specific command being invoked. ### Return Code -A signed 32-bit little integer integer representing the response from the peer +A signed 32-bit little endian integer representing the response from the peer from the last command that was invoked. This is `0` for request messages. ### Flags @@ -131,7 +131,7 @@ be zero. The first fragment has the `B` bit set, neither `B` nor `E` is set for ### Dummy Dummy messages have the `B` and `E` bits set, the `Q` and `S` bits unset, and -the `Expect Reponse` field zeroed. When a message of this type is received, the +the `Expect Response` field zeroed. When a message of this type is received, the contents can be safely ignored. @@ -149,7 +149,7 @@ contents can be safely ignored. #### (`1005` Request) Network State #### (`1005` Response) Network State #### (`1006` Request) Peer ID -#### (`1006` Reponse) Peer ID +#### (`1006` Response) Peer ID #### (`1007` Request) Support Flags #### (`1007` Response) Support Flags diff --git a/docs/ZMQ.md b/docs/ZMQ.md index 9128ff2ad..a7006f486 100644 --- a/docs/ZMQ.md +++ b/docs/ZMQ.md @@ -25,6 +25,8 @@ allows for filtering on: (1) format, (2) context, and (3) event. Includes previously unseen transactions in a block but _not_ the `miner_tx`. Does not "re-publish" after a reorg. Includes `do_not_relay` transactions. + * `miner_data` - provides the necessary data to create a custom block template + Available only in the `full` context. The subscription topics are formatted as `format-context-event`, with prefix matching supported by both Monero and ZMQ. The `format`, `context` and `event` diff --git a/external/easylogging++/CMakeLists.txt b/external/easylogging++/CMakeLists.txt index 9aa4c08bc..462f774bf 100644 --- a/external/easylogging++/CMakeLists.txt +++ b/external/easylogging++/CMakeLists.txt @@ -30,7 +30,10 @@ cmake_minimum_required(VERSION 3.5) project(easylogging CXX) -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + monero_enable_coverage() find_package(Threads) diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index 267770074..a765ee8cc 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -2984,8 +2984,8 @@ void Writer::initializeLogger(Logger *logger, bool needLock) { } void Writer::processDispatch() { - static std::atomic_flag in_dispatch; - if (in_dispatch.test_and_set()) + static __thread bool in_dispatch = false; + if (in_dispatch) { if (m_proceed && m_logger != NULL) { @@ -2994,6 +2994,7 @@ void Writer::processDispatch() { } return; } + in_dispatch = true; #if ELPP_LOGGING_ENABLED if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { bool firstDispatched = false; @@ -3032,7 +3033,7 @@ void Writer::processDispatch() { m_logger->releaseLock(); } #endif // ELPP_LOGGING_ENABLED - in_dispatch.clear(); + in_dispatch = false; } void Writer::triggerDispatch(void) { diff --git a/external/randomx b/external/randomx -Subproject fe4324e8c0c035fec3affd6e4c49241c2e5b995 +Subproject 9efc398c196ef1c50d8e6f5e1f2c5ac02f1f6ce diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index a84a4148d..8e68abbe5 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -241,8 +241,15 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { + rct::key commitment; + if (tx.version > 1) + { + commitment = tx.rct_signatures.outPk[i].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + commitment = rct::scalarmult8(commitment); + } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, tx.unlock_time, - tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL); + tx.version > 1 ? &commitment : NULL); } } add_tx_amount_output_indices(tx_id, amount_output_indices); diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 1d7b10648..de2700ad0 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -467,7 +467,12 @@ void mdb_txn_safe::allow_new_txns() creation_gate.clear(); } -void lmdb_resized(MDB_env *env) +void mdb_txn_safe::increment_txns(int i) +{ + num_active_txns += i; +} + +void lmdb_resized(MDB_env *env, int isactive) { mdb_txn_safe::prevent_new_txns(); @@ -478,7 +483,11 @@ void lmdb_resized(MDB_env *env) mdb_env_info(env, &mei); uint64_t old = mei.me_mapsize; + if (isactive) + mdb_txn_safe::increment_txns(-1); mdb_txn_safe::wait_no_active_txns(); + if (isactive) + mdb_txn_safe::increment_txns(1); int result = mdb_env_set_mapsize(env, 0); if (result) @@ -496,7 +505,7 @@ inline int lmdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB { int res = mdb_txn_begin(env, parent, flags, txn); if (res == MDB_MAP_RESIZED) { - lmdb_resized(env); + lmdb_resized(env, 1); res = mdb_txn_begin(env, parent, flags, txn); } return res; @@ -506,7 +515,7 @@ inline int lmdb_txn_renew(MDB_txn *txn) { int res = mdb_txn_renew(txn); if (res == MDB_MAP_RESIZED) { - lmdb_resized(mdb_txn_env(txn)); + lmdb_resized(mdb_txn_env(txn), 0); res = mdb_txn_renew(txn); } return res; @@ -1267,11 +1276,11 @@ BlockchainLMDB::~BlockchainLMDB() // batch transaction shouldn't be active at this point. If it is, consider it aborted. if (m_batch_active) { - try { batch_abort(); } + try { BlockchainLMDB::batch_abort(); } catch (...) { /* ignore */ } } if (m_open) - close(); + BlockchainLMDB::close(); } BlockchainLMDB::BlockchainLMDB(bool batch_transactions): BlockchainDB() @@ -1569,9 +1578,9 @@ void BlockchainLMDB::close() if (m_batch_active) { LOG_PRINT_L3("close() first calling batch_abort() due to active batch transaction"); - batch_abort(); + BlockchainLMDB::batch_abort(); } - this->sync(); + BlockchainLMDB::sync(); m_tinfo.reset(); // FIXME: not yet thread safe!!! Use with care. @@ -1584,7 +1593,7 @@ void BlockchainLMDB::sync() LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); - if (is_read_only()) + if (BlockchainLMDB::is_read_only()) return; // Does nothing unless LMDB environment was opened with MDB_NOSYNC or in part diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 0e6d70039..d87bc6e49 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -154,6 +154,7 @@ struct mdb_txn_safe static void prevent_new_txns(); static void wait_no_active_txns(); static void allow_new_txns(); + static void increment_txns(int); mdb_threadinfo* m_tinfo; MDB_txn* m_txn; diff --git a/src/common/download.cpp b/src/common/download.cpp index 3dd9c3976..c3dfa43d5 100644 --- a/src/common/download.cpp +++ b/src/common/download.cpp @@ -53,7 +53,7 @@ namespace tools download_thread_control(const std::string &path, const std::string &uri, std::function<void(const std::string&, const std::string&, bool)> result_cb, std::function<bool(const std::string&, const std::string&, size_t, ssize_t)> progress_cb): path(path), uri(uri), result_cb(result_cb), progress_cb(progress_cb), stop(false), stopped(false), success(false) {} - ~download_thread_control() { if (thread.joinable()) thread.detach(); } + ~download_thread_control() { if (thread.joinable()) { thread.detach(); thread = {}; } } }; static void download_thread(download_async_handle control) @@ -293,9 +293,13 @@ namespace tools { boost::lock_guard<boost::mutex> lock(control->mutex); if (control->stopped) + { + control->thread = {}; return true; + } } control->thread.join(); + control->thread = {}; return true; } @@ -305,10 +309,14 @@ namespace tools { boost::lock_guard<boost::mutex> lock(control->mutex); if (control->stopped) + { + control->thread = {}; return true; + } control->stop = true; } control->thread.join(); + control->thread = {}; return true; } } diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 508709280..54ca39775 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -3830,15 +3830,51 @@ int sc_isnonzero(const unsigned char *s) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } -int ge_p3_is_point_at_infinity(const ge_p3 *p) { - // X = 0 and Y == Z - int n; - for (n = 0; n < 10; ++n) +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { + // https://eprint.iacr.org/2008/522 + // X == T == 0 and Y/Z == 1 + // note: convert all pieces to canonical bytes in case rounding is required (i.e. an element is > q) + // note2: even though T = XY/Z is true for valid point representations (implying it isn't necessary to + // test T == 0), the input to this function might NOT be valid, so we must test T == 0 + char result_X_bytes[32]; + fe_tobytes((unsigned char*)&result_X_bytes, p->X); + + // X != 0 + for (int i = 0; i < 32; ++i) { - if (p->X[n] | p->T[n]) + if (result_X_bytes[i]) return 0; - if (p->Y[n] != p->Z[n]) + } + + char result_T_bytes[32]; + fe_tobytes((unsigned char*)&result_T_bytes, p->T); + + // T != 0 + for (int i = 0; i < 32; ++i) + { + if (result_T_bytes[i]) + return 0; + } + + char result_Y_bytes[32]; + char result_Z_bytes[32]; + fe_tobytes((unsigned char*)&result_Y_bytes, p->Y); + fe_tobytes((unsigned char*)&result_Z_bytes, p->Z); + + // Y != Z + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != result_Z_bytes[i]) return 0; } - return 1; + + // is Y nonzero? then Y/Z == 1 + for (int i = 0; i < 32; ++i) + { + if (result_Y_bytes[i] != 0) + return 1; + } + + // Y/Z = 0/0 + return 0; } diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 22f76974b..96da16cbd 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -162,4 +162,4 @@ void fe_add(fe h, const fe f, const fe g); void fe_tobytes(unsigned char *, const fe); void fe_invert(fe out, const fe z); -int ge_p3_is_point_at_infinity(const ge_p3 *p); +int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p); diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 7ddc0150f..599ae4f91 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -64,6 +64,11 @@ namespace crypto { friend class crypto_ops; }; + POD_CLASS public_key_memsafe : epee::mlocked<tools::scrubbed<public_key>> { + public_key_memsafe() = default; + public_key_memsafe(const public_key &original) { memcpy(this->data, original.data, 32); } + }; + using secret_key = epee::mlocked<tools::scrubbed<ec_scalar>>; POD_CLASS public_keyV { @@ -100,7 +105,7 @@ namespace crypto { void random32_unbiased(unsigned char *bytes); static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && - sizeof(public_key) == 32 && sizeof(secret_key) == 32 && + sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && sizeof(signature) == 64, "Invalid structure size"); @@ -310,9 +315,13 @@ namespace crypto { const extern crypto::public_key null_pkey; const extern crypto::secret_key null_skey; + + inline bool operator<(const public_key &p1, const public_key &p2) { return memcmp(&p1, &p2, sizeof(public_key)) < 0; } + inline bool operator>(const public_key &p1, const public_key &p2) { return p2 < p1; } } CRYPTO_MAKE_HASHABLE(public_key) CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key) +CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index 801987e37..247c9032f 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -63,6 +63,7 @@ static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static randomx_dataset *rx_dataset; static int rx_dataset_nomem; +static int rx_dataset_nolp; static uint64_t rx_dataset_height; static THREADV randomx_vm *rx_vm = NULL; @@ -316,10 +317,11 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } - if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES) && !rx_dataset_nolp) { rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); if(rx_vm == NULL) { //large pages failed mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); + rx_dataset_nolp = 1; } } if (rx_vm == NULL) @@ -370,5 +372,6 @@ void rx_stop_mining(void) { randomx_release_dataset(rd); } rx_dataset_nomem = 0; + rx_dataset_nolp = 0; CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 5a773f3cf..38aeeee54 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -51,6 +51,12 @@ #define INIT_SIZE_BLK 8 #define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) +#if defined(_MSC_VER) +#define THREADV __declspec(thread) +#else +#define THREADV __thread +#endif + extern void aesb_single_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); extern void aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); @@ -89,6 +95,28 @@ static inline int use_v4_jit(void) #endif } +#if defined(__x86_64__) || defined(__aarch64__) +static inline int force_software_aes(void) +{ + static int use = -1; + + if (use != -1) + return use; + + const char *env = getenv("MONERO_USE_SOFTWARE_AES"); + if (!env) { + use = 0; + } + else if (!strcmp(env, "0") || !strcmp(env, "no")) { + use = 0; + } + else { + use = 1; + } + return use; +} +#endif + #define VARIANT1_1(p) \ do if (variant == 1) \ { \ @@ -437,12 +465,6 @@ static inline int use_v4_jit(void) _b1 = _b; \ _b = _c; \ -#if defined(_MSC_VER) -#define THREADV __declspec(thread) -#else -#define THREADV __thread -#endif - #pragma pack(push, 1) union cn_slow_hash_state { @@ -498,25 +520,6 @@ STATIC INLINE void xor64(uint64_t *a, const uint64_t b) * @return true if the CPU supports AES, false otherwise */ -STATIC INLINE int force_software_aes(void) -{ - static int use = -1; - - if (use != -1) - return use; - - const char *env = getenv("MONERO_USE_SOFTWARE_AES"); - if (!env) { - use = 0; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use = 0; - } - else { - use = 1; - } - return use; -} STATIC INLINE int check_aes_hw(void) { @@ -1009,6 +1012,44 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int } #elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__)) +#ifdef __aarch64__ +#include <sys/mman.h> +THREADV uint8_t *hp_state = NULL; +THREADV int hp_malloced = 0; + +void cn_slow_hash_allocate_state(void) +{ + if(hp_state != NULL) + return; + +#ifndef MAP_HUGETLB +#define MAP_HUGETLB 0 +#endif + hp_state = mmap(0, MEMORY, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); + + if(hp_state == MAP_FAILED) + hp_state = NULL; + if(hp_state == NULL) + { + hp_malloced = 1; + hp_state = (uint8_t *) malloc(MEMORY); + } +} + +void cn_slow_hash_free_state(void) +{ + if(hp_state == NULL) + return; + + if (hp_malloced) + free(hp_state); + else + munmap(hp_state, MEMORY); + hp_state = NULL; + hp_malloced = 0; +} +#else void cn_slow_hash_allocate_state(void) { // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c @@ -1020,6 +1061,7 @@ void cn_slow_hash_free_state(void) // As above return; } +#endif #if defined(__GNUC__) #define RDATA_ALIGN16 __attribute__ ((aligned(16))) @@ -1060,6 +1102,23 @@ union cn_slow_hash_state * and moving between vector and regular registers stalls the pipeline. */ #include <arm_neon.h> +#ifndef __APPLE__ +#include <sys/auxv.h> +#include <asm/hwcap.h> +#endif + +STATIC INLINE int check_aes_hw(void) +{ +#ifdef __APPLE__ + return 1; +#else + static int supported = -1; + + if(supported < 0) + supported = (getauxval(AT_HWCAP) & HWCAP_AES) != 0; + return supported; +#endif +} #define TOTALBLOCKS (MEMORY / AES_BLOCK_SIZE) @@ -1156,7 +1215,6 @@ __asm__( STATIC INLINE void aes_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey, int nblocks) { const uint8x16_t *k = (const uint8x16_t *)expandedKey, zero = {0}; - uint8x16_t tmp; int i; for (i=0; i<nblocks; i++) @@ -1191,7 +1249,6 @@ STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, const u { const uint8x16_t *k = (const uint8x16_t *)expandedKey; const uint8x16_t *x = (const uint8x16_t *)xor; - uint8x16_t tmp; int i; for (i=0; i<nblocks; i++) @@ -1244,16 +1301,17 @@ STATIC INLINE void aligned_free(void *ptr) } #endif /* FORCE_USE_HEAP */ +STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) +{ + U64(a)[0] ^= U64(b)[0]; + U64(a)[1] ^= U64(b)[1]; +} + void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height) { RDATA_ALIGN16 uint8_t expandedKey[240]; -#ifndef FORCE_USE_HEAP - RDATA_ALIGN16 uint8_t local_hp_state[MEMORY]; -#else - uint8_t *local_hp_state = (uint8_t *)aligned_malloc(MEMORY,16); -#endif - + uint8_t *local_hp_state; uint8_t text[INIT_SIZE_BYTE]; RDATA_ALIGN16 uint64_t a[2]; RDATA_ALIGN16 uint64_t b[4]; @@ -1264,12 +1322,22 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int size_t i, j; uint64_t *p = NULL; + oaes_ctx *aes_ctx = NULL; + int useAes = !force_software_aes() && check_aes_hw(); static void (*const extra_hashes[4])(const void *, size_t, char *) = { hash_extra_blake, hash_extra_groestl, hash_extra_jh, hash_extra_skein }; + // this isn't supposed to happen, but guard against it for now. + if(hp_state == NULL) + cn_slow_hash_allocate_state(); + + // locals to avoid constant TLS dereferencing + local_hp_state = hp_state; + + // locals to avoid constant TLS dereferencing /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ if (prehashed) { @@ -1287,11 +1355,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int * the 2MB large random access buffer. */ - aes_expand_key(state.hs.b, expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + if(useAes) { - aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK); - memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + aes_expand_key(state.hs.b, expandedKey); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + aes_pseudo_round(text, text, expandedKey, INIT_SIZE_BLK); + memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + } + } + else + { + aes_ctx = (oaes_ctx *) oaes_alloc(); + oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + for(j = 0; j < INIT_SIZE_BLK; j++) + aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); + + memcpy(&local_hp_state[i * INIT_SIZE_BYTE], text, INIT_SIZE_BYTE); + } } U64(a)[0] = U64(&state.k[0])[0] ^ U64(&state.k[32])[0]; @@ -1307,13 +1390,26 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int _b = vld1q_u8((const uint8_t *)b); _b1 = vld1q_u8(((const uint8_t *)b) + AES_BLOCK_SIZE); - for(i = 0; i < ITER / 2; i++) + if(useAes) { - pre_aes(); - _c = vaeseq_u8(_c, zero); - _c = vaesmcq_u8(_c); - _c = veorq_u8(_c, _a); - post_aes(); + for(i = 0; i < ITER / 2; i++) + { + pre_aes(); + _c = vaeseq_u8(_c, zero); + _c = vaesmcq_u8(_c); + _c = veorq_u8(_c, _a); + post_aes(); + } + } + else + { + for(i = 0; i < ITER / 2; i++) + { + pre_aes(); + aesb_single_round((uint8_t *) &_c, (uint8_t *) &_c, (uint8_t *) &_a); + post_aes(); + } + } /* CryptoNight Step 4: Sequentially pass through the mixing buffer and use 10 rounds @@ -1322,11 +1418,27 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int memcpy(text, state.init, INIT_SIZE_BYTE); - aes_expand_key(&state.hs.b[32], expandedKey); - for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + if(useAes) + { + aes_expand_key(&state.hs.b[32], expandedKey); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + // add the xor to the pseudo round + aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); + } + } + else { - // add the xor to the pseudo round - aes_pseudo_round_xor(text, text, expandedKey, &local_hp_state[i * INIT_SIZE_BYTE], INIT_SIZE_BLK); + oaes_key_import_data(aes_ctx, &state.hs.b[32], AES_KEY_SIZE); + for(i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) + { + for(j = 0; j < INIT_SIZE_BLK; j++) + { + xor_blocks(&text[j * AES_BLOCK_SIZE], &local_hp_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); + aesb_pseudo_round(&text[AES_BLOCK_SIZE * j], &text[AES_BLOCK_SIZE * j], aes_ctx->key->exp_data); + } + } + oaes_free((OAES_CTX **) &aes_ctx); } /* CryptoNight Step 5: Apply Keccak to the state again, and then @@ -1339,10 +1451,6 @@ void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int memcpy(state.init, text, INIT_SIZE_BYTE); hash_permutation(&state.hs); extra_hashes[state.hs.b[0] & 3](&state, 200, hash); - -#ifdef FORCE_USE_HEAP - aligned_free(local_hp_state); -#endif } #else /* aarch64 && crypto */ diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 36ff41684..9927351a9 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -253,11 +253,6 @@ DISABLE_VS_WARNINGS(4244 4345) return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); } //----------------------------------------------------------------- - void account_base::finalize_multisig(const crypto::public_key &spend_public_key) - { - m_keys.m_account_address.m_spend_public_key = spend_public_key; - } - //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 5288b9b04..96b024c3c 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -82,7 +82,6 @@ namespace cryptonote void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys); - void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; std::string get_public_address_str(network_type nettype) const; std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const; diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 6394a7071..d111f6f32 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -49,6 +49,7 @@ #include "misc_language.h" #include "ringct/rctTypes.h" #include "device/device.hpp" +#include "cryptonote_basic/fwd.h" namespace cryptonote { diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index c6b81b094..24d452083 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -228,6 +228,20 @@ namespace boost } template <class Archive> + inline void serialize(Archive &a, rct::BulletproofPlus &x, const boost::serialization::version_type ver) + { + a & x.V; + a & x.A; + a & x.A1; + a & x.B; + a & x.r1; + a & x.s1; + a & x.d1; + a & x.L; + a & x.R; + } + + template <class Archive> inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { a & x.s0; @@ -305,7 +319,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -321,7 +335,11 @@ namespace boost { a & x.rangeSigs; if (x.rangeSigs.empty()) + { a & x.bulletproofs; + if (ver >= 2u) + a & x.bulletproofs_plus; + } a & x.MGs; if (ver >= 1u) a & x.CLSAGs; @@ -335,7 +353,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG && x.type != rct::RCTTypeBulletproofPlus) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -347,11 +365,15 @@ namespace boost //-------------- a & x.p.rangeSigs; if (x.p.rangeSigs.empty()) + { a & x.p.bulletproofs; + if (ver >= 2u) + a & x.p.bulletproofs_plus; + } a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG) + if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG || x.type == rct::RCTTypeBulletproofPlus) a & x.p.pseudoOuts; } @@ -392,6 +414,6 @@ namespace boost } } -BOOST_CLASS_VERSION(rct::rctSigPrunable, 1) -BOOST_CLASS_VERSION(rct::rctSig, 1) +BOOST_CLASS_VERSION(rct::rctSigPrunable, 2) +BOOST_CLASS_VERSION(rct::rctSig, 2) BOOST_CLASS_VERSION(rct::multisig_out, 1) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 17adcdc35..cedc6f546 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -105,7 +105,9 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { - const uint64_t bp_base = 368; + const rct::rctSig &rv = tx.rct_signatures; + const bool plus = rv.type == rct::RCTTypeBulletproofPlus; + const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) return 0; @@ -113,7 +115,7 @@ namespace cryptonote while ((1u << nlr) < n_padded_outputs) ++nlr; nlr += 6; - const size_t bp_size = 32 * (9 + 2 * nlr); + const size_t bp_size = 32 * ((plus ? 6 : 9) + 2 * nlr); CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); @@ -164,7 +166,32 @@ namespace cryptonote if (!base_only) { const bool bulletproof = rct::is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (bulletproof_plus) + { + if (rv.p.bulletproofs_plus.size() != 1) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus size in tx " << get_transaction_hash(tx)); + return false; + } + if (rv.p.bulletproofs_plus[0].L.size() < 6) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); + return false; + } + const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]); + if (max_outputs < tx.vout.size()) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus max outputs in tx " << get_transaction_hash(tx)); + return false; + } + const size_t n_amounts = tx.vout.size(); + CHECK_AND_ASSERT_MES(n_amounts == rv.outPk.size(), false, "Internal error filling out V"); + rv.p.bulletproofs_plus[0].V.resize(n_amounts); + for (size_t i = 0; i < n_amounts; ++i) + rv.p.bulletproofs_plus[0].V[i] = rv.outPk[i].mask; + } + else if (bulletproof) { if (rv.p.bulletproofs.size() != 1) { @@ -306,7 +333,26 @@ namespace cryptonote { // derive secret key with subaddress - step 1: original CN derivation crypto::secret_key scalar_step1; - hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b + crypto::secret_key spend_skey = crypto::null_skey; + + if (ack.m_multisig_keys.empty()) + { + // if not multisig, use normal spend skey + spend_skey = ack.m_spend_secret_key; + } + else + { + // if multisig, use sum of multisig privkeys (local account's share of aggregate spend key) + for (const auto &multisig_key : ack.m_multisig_keys) + { + sc_add((unsigned char*)spend_skey.data, + (const unsigned char*)multisig_key.data, + (const unsigned char*)spend_skey.data); + } + } + + // computes Hs(a*R || idx) + b + hwdev.derive_secret_key(recv_derivation, real_output_index, spend_skey, scalar_step1); // step 2: add Hs(a || index_major || index_minor) crypto::secret_key subaddr_sk; @@ -400,9 +446,11 @@ namespace cryptonote if (tx.version < 2) return blob_size; const rct::rctSig &rv = tx.rct_signatures; - if (!rct::is_rct_bulletproof(rv.type)) + const bool bulletproof = rct::is_rct_bulletproof(rv.type); + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (!bulletproof && !bulletproof_plus) return blob_size; - const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs); + const size_t n_padded_outputs = bulletproof_plus ? rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus) : rct::n_bulletproof_max_amounts(rv.p.bulletproofs); uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - blob_size, "Weight overflow"); return blob_size + bp_clawback; @@ -412,8 +460,8 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); - CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG, - std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); + CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus, + std::numeric_limits<uint64_t>::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin"); CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin"); @@ -431,12 +479,12 @@ namespace cryptonote while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) ++nrl; nrl += 6; - extra = 32 * (9 + 2 * nrl) + 2; + extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; weight += extra; // calculate deterministic CLSAG/MLSAG data size const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); - if (tx.rct_signatures.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(tx.rct_signatures.type)) extra = tx.vin.size() * (ring_size + 2) * 32; else extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); diff --git a/src/cryptonote_basic/events.h b/src/cryptonote_basic/events.h index 6c6742215..3417ece8c 100644 --- a/src/cryptonote_basic/events.h +++ b/src/cryptonote_basic/events.h @@ -41,6 +41,8 @@ namespace cryptonote { cryptonote::transaction tx; crypto::hash hash; + uint64_t blob_size; + uint64_t weight; bool res; //!< Listeners must ignore `tx` when this is false. }; } diff --git a/src/cryptonote_basic/fwd.h b/src/cryptonote_basic/fwd.h index d54223461..901ad151b 100644 --- a/src/cryptonote_basic/fwd.h +++ b/src/cryptonote_basic/fwd.h @@ -33,4 +33,5 @@ namespace cryptonote struct block; class transaction; struct txpool_event; + struct tx_block_template_backlog_entry; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 915835d1b..ff61fc036 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -182,6 +182,7 @@ #define HF_VERSION_EXACT_COINBASE 13 #define HF_VERSION_CLSAG 13 #define HF_VERSION_DETERMINISTIC_UNLOCK_TIME 13 +#define HF_VERSION_BULLETPROOF_PLUS 15 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 @@ -190,6 +191,7 @@ #define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes #define BULLETPROOF_MAX_OUTPUTS 16 +#define BULLETPROOF_PLUS_MAX_OUTPUTS 16 #define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase #define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved @@ -221,6 +223,8 @@ namespace config // Hash domain separators const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; + const char HASH_KEY_BULLETPROOF_PLUS_EXPONENT[] = "bulletproof_plus"; + const char HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT[] = "bulletproof_plus_transcript"; const char HASH_KEY_RINGDB[] = "ringdsb"; const char HASH_KEY_SUBADDRESS[] = "SubAddr"; const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; @@ -229,6 +233,7 @@ namespace config const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + const unsigned char HASH_KEY_MULTISIG_KEY_AGGREGATION[] = "Multisig_key_agg"; const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2"; const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round"; const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; @@ -236,6 +241,9 @@ namespace config const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; const unsigned char HASH_KEY_MM_SLOT = 'm'; + // Multisig + const uint32_t MULTISIG_MAX_SIGNERS{16}; + namespace testnet { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index f0e6794b9..cd9972d1e 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -588,6 +588,7 @@ block Blockchain::pop_block_from_blockchain() CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block"); + const uint8_t previous_hf_version = get_current_hard_fork_version(); try { m_db->pop_block(popped_block, popped_txs); @@ -650,6 +651,13 @@ block Blockchain::pop_block_from_blockchain() m_tx_pool.on_blockchain_dec(top_block_height, top_block_hash); invalidate_block_template_cache(); + const uint8_t new_hf_version = get_current_hard_fork_version(); + if (new_hf_version != previous_hf_version) + { + MINFO("Validating txpool for v" << (unsigned)new_hf_version); + m_tx_pool.validate(new_hf_version); + } + return popped_block; } //------------------------------------------------------------------ @@ -1238,6 +1246,12 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); + crypto::hash prev_id; + if (!get_block_hash(alt_chain.back().bl, prev_id)) + MERROR("Failed to get block hash of an alternative chain's tip"); + else + send_miner_notifications(prev_id, alt_chain.back().already_generated_coins); + for (const auto& notifier : m_block_notifiers) { std::size_t notify_height = split_height; @@ -1784,6 +1798,30 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce, seed_height, seed_hash); } //------------------------------------------------------------------ +bool Blockchain::get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector<tx_block_template_backlog_entry>& tx_backlog) +{ + prev_id = m_db->top_block_hash(&height); + ++height; + + major_version = m_hardfork->get_ideal_version(height); + + seed_hash = crypto::null_hash; + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + { + uint64_t seed_height, next_height; + crypto::rx_seedheights(height, &seed_height, &next_height); + seed_hash = get_block_id_by_height(seed_height); + } + + difficulty = get_difficulty_for_next_block(); + median_weight = m_current_block_cumul_weight_median; + already_generated_coins = m_db->get_block_already_generated_coins(height - 1); + + m_tx_pool.get_block_template_backlog(tx_backlog); + + return true; +} +//------------------------------------------------------------------ // for an alternate chain, get the timestamps from the main chain to complete // the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) const @@ -3105,6 +3143,32 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } + // from v15, allow bulletproofs plus + if (hf_version < HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); + if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) + { + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + + // from v16, forbid bulletproofs + if (hf_version > HF_VERSION_BULLETPROOF_PLUS) { + if (tx.version >= 2) { + const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); + if (bulletproof) + { + MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(HF_VERSION_BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + return true; } //------------------------------------------------------------------ @@ -3145,7 +3209,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); @@ -3186,7 +3250,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { if (!tx.pruned) { @@ -3478,6 +3542,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: { // check all this, either reconstructed (so should really pass), or not { @@ -3513,7 +3578,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + 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"); @@ -3522,7 +3587,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, for (size_t n = 0; n < tx.vin.size(); ++n) { bool error; - if (rv.type == rct::RCTTypeCLSAG) + if (rct::is_rct_clsag(rv.type)) error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); else error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); @@ -4362,6 +4427,20 @@ leave: get_difficulty_for_next_block(); // just to cache it invalidate_block_template_cache(); + const uint8_t new_hf_version = get_current_hard_fork_version(); + if (new_hf_version != hf_version) + { + // the genesis block is added before everything's setup, and the txpool is empty + // when we start from scratch, so we skip this + const bool is_genesis_block = new_height == 1; + if (!is_genesis_block) + { + MGINFO("Validating txpool for v" << (unsigned)new_hf_version); + m_tx_pool.validate(new_hf_version); + } + } + + send_miner_notifications(id, already_generated_coins); for (const auto& notifier: m_block_notifiers) notifier(new_height - 1, {std::addressof(bl), 1}); @@ -4990,6 +5069,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete unsigned nblocks = batches; if (i < extra) ++nblocks; + if (nblocks == 0) + break; tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, epee::span<const block>(&blocks[thread_height - height], nblocks), std::ref(maps[i])), true); thread_height += nblocks; } @@ -5270,7 +5351,7 @@ void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint m_max_prepare_blocks_threads = maxthreads; } -void Blockchain::add_block_notify(boost::function<void(std::uint64_t, epee::span<const block>)>&& notify) +void Blockchain::add_block_notify(BlockNotifyCallback&& notify) { if (notify) { @@ -5279,6 +5360,15 @@ void Blockchain::add_block_notify(boost::function<void(std::uint64_t, epee::span } } +void Blockchain::add_miner_notify(MinerNotifyCallback&& notify) +{ + if (notify) + { + CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_miner_notifiers.push_back(std::move(notify)); + } +} + void Blockchain::safesyncmode(const bool onoff) { /* all of this is no-op'd if the user set a specific @@ -5531,6 +5621,33 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_ m_btc_valid = true; } +void Blockchain::send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins) +{ + if (m_miner_notifiers.empty()) + return; + + const uint64_t height = m_db->height(); + const uint8_t major_version = m_hardfork->get_ideal_version(height); + const difficulty_type diff = get_difficulty_for_next_block(); + const uint64_t median_weight = m_current_block_cumul_weight_median; + + crypto::hash seed_hash = crypto::null_hash; + if (m_hardfork->get_current_version() >= RX_BLOCK_VERSION) + { + uint64_t seed_height, next_height; + crypto::rx_seedheights(height, &seed_height, &next_height); + seed_hash = get_block_id_by_height(seed_height); + } + + std::vector<tx_block_template_backlog_entry> tx_backlog; + m_tx_pool.get_block_template_backlog(tx_backlog); + + for (const auto& notifier : m_miner_notifiers) + { + notifier(major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog); + } +} + namespace cryptonote { template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::vector<transaction>&, std::vector<crypto::hash>&, bool) const; template bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>&, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>&, std::vector<crypto::hash>&) const; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index a0e7967de..9afbfbc2d 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -90,6 +90,9 @@ namespace cryptonote */ typedef std::function<const epee::span<const unsigned char>(cryptonote::network_type network)> GetCheckpointsCallback; + typedef boost::function<void(uint64_t /* height */, epee::span<const block> /* blocks */)> BlockNotifyCallback; + typedef boost::function<void(uint8_t /* major_version */, uint64_t /* height */, const crypto::hash& /* prev_id */, const crypto::hash& /* seed_hash */, difficulty_type /* diff */, uint64_t /* median_weight */, uint64_t /* already_generated_coins */, const std::vector<tx_block_template_backlog_entry>& /* tx_backlog */)> MinerNotifyCallback; + /************************************************************************/ /* */ /************************************************************************/ @@ -371,6 +374,22 @@ namespace cryptonote bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash); /** + * @brief gets data required to create a block template and start mining on it + * + * @param major_version current hardfork version + * @param height current blockchain height + * @param prev_id hash of the top block + * @param seed_hash seed hash used for RandomX initialization + * @param difficulty current mining difficulty + * @param median_weight current median block weight + * @param already_generated_coins current emission + * @param tx_backlog transactions in mempool ready to be mined + * + * @return true if block template filled in successfully, else false + */ + bool get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector<tx_block_template_backlog_entry>& tx_backlog); + + /** * @brief checks if a block is known about with a given hash * * This function checks the main chain, alternate chains, and invalid blocks @@ -775,7 +794,14 @@ namespace cryptonote * * @param notify the notify object to call at every new block */ - void add_block_notify(boost::function<void(std::uint64_t, epee::span<const block>)> &¬ify); + void add_block_notify(BlockNotifyCallback&& notify); + + /** + * @brief sets a miner notify object to call for every new block + * + * @param notify the notify object to call at every new block + */ + void add_miner_notify(MinerNotifyCallback&& notify); /** * @brief sets a reorg notify object to call for every reorg @@ -1157,7 +1183,8 @@ namespace cryptonote the callable object has a single `std::shared_ptr` or `std::weap_ptr` internally. Whereas, the libstdc++ `std::function` will allocate. */ - std::vector<boost::function<void(std::uint64_t, epee::span<const block>)>> m_block_notifiers; + std::vector<BlockNotifyCallback> m_block_notifiers; + std::vector<MinerNotifyCallback> m_miner_notifiers; std::shared_ptr<tools::Notify> m_reorg_notify; // for prepare_handle_incoming_blocks @@ -1537,5 +1564,13 @@ namespace cryptonote * At some point, may be used to push an update to miners */ void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t seed_height, const crypto::hash &seed_hash, uint64_t pool_cookie); + + /** + * @brief sends new block notifications to ZMQ `miner_data` subscribers + * + * @param prev_id hash of new blockchain tip + * @param already_generated_coins total coins mined by the network so far + */ + void send_miner_notifications(const crypto::hash &prev_id, uint64_t already_generated_coins); }; } // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index ed9f7a28c..1da4e2d41 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -879,6 +879,16 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs) + { + if (proofs.size() != 1) + return false; + const size_t sz = proofs[0].V.size(); + if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- bool core::handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block) { bool ret = true; @@ -943,6 +953,17 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; + case rct::RCTTypeBulletproofPlus: + if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) + { + MERROR_VER("Bulletproof_plus does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; default: MERROR_VER("Unknown rct type: " << rv.type); set_semantics_failed(tx_info[n].tx_hash); @@ -960,7 +981,7 @@ namespace cryptonote { if (!tx_info[n].result) continue; - if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG) + if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus) continue; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures)) { @@ -1065,8 +1086,9 @@ namespace cryptonote if (already_have[i]) continue; - const uint64_t weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size()); - ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, weight, tvc[i], tx_relay, relayed); + results[i].blob_size = it->blob.size(); + results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size()); + ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, results[i].weight, tvc[i], tx_relay, relayed); if(tvc[i].m_verifivation_failed) {MERROR_VER("Transaction verification failed: " << results[i].hash);} @@ -1405,6 +1427,11 @@ namespace cryptonote return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce, seed_height, seed_hash); } //----------------------------------------------------------------------------------------------- + bool core::get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector<tx_block_template_backlog_entry>& tx_backlog) + { + return m_blockchain_storage.get_miner_data(major_version, height, prev_id, seed_hash, difficulty, median_weight, already_generated_coins, tx_backlog); + } + //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, clip_pruned, resp); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 286145031..d2bffdaee 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -237,6 +237,13 @@ namespace cryptonote virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash); /** + * @copydoc Blockchain::get_miner_data + * + * @note see Blockchain::get_miner_data + */ + bool get_miner_data(uint8_t& major_version, uint64_t& height, crypto::hash& prev_id, crypto::hash& seed_hash, difficulty_type& difficulty, uint64_t& median_weight, uint64_t& already_generated_coins, std::vector<tx_block_template_backlog_entry>& tx_backlog); + + /** * @brief called when a transaction is relayed. * @note Should only be invoked from `levin_notify`. */ diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index f41c63a4b..a50ebb550 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -43,7 +43,6 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" -#include "multisig/multisig.h" using namespace crypto; @@ -678,7 +677,7 @@ namespace cryptonote rx_slow_hash(main_height, seed_height, seed_hash.data, bd.data(), bd.size(), res.data, 0, 1); } - bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners) + bool get_block_longhash(const Blockchain *pbc, const blobdata& bd, crypto::hash& res, const uint64_t height, const int major_version, const crypto::hash *seed_hash, const int miners) { // block 202612 bug workaround if (height == 202612) @@ -687,8 +686,7 @@ namespace cryptonote epee::string_tools::hex_to_pod(longhash_202612, res); return true; } - blobdata bd = get_block_hashing_blob(b); - if (b.major_version >= RX_BLOCK_VERSION) + if (major_version >= RX_BLOCK_VERSION) { uint64_t seed_height, main_height; crypto::hash hash; @@ -705,12 +703,18 @@ namespace cryptonote } rx_slow_hash(main_height, seed_height, hash.data, bd.data(), bd.size(), res.data, seed_hash ? 0 : miners, !!seed_hash); } else { - const int pow_variant = b.major_version >= 7 ? b.major_version - 6 : 0; + const int pow_variant = major_version >= 7 ? major_version - 6 : 0; crypto::cn_slow_hash(bd.data(), bd.size(), res, pow_variant, height); } return true; } + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners) + { + blobdata bd = get_block_hashing_blob(b); + return get_block_longhash(pbc, bd, res, height, b.major_version, seed_hash, miners); + } + bool get_block_longhash(const Blockchain *pbc, const block& b, crypto::hash& res, const uint64_t height, const int miners) { return get_block_longhash(pbc, b, res, height, NULL, miners); diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 73cdd31cd..cea4aad17 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -108,6 +108,15 @@ namespace cryptonote }; //--------------------------------------------------------------- + + struct tx_block_template_backlog_entry + { + crypto::hash id; + uint64_t weight; + uint64_t fee; + }; + + //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, rct::multisig_out *msout = NULL, bool shuffle_outs = true); @@ -133,6 +142,8 @@ namespace cryptonote ); class Blockchain; + bool get_block_longhash(const Blockchain *pb, const blobdata& bd, crypto::hash& res, const uint64_t height, + const int major_version, const crypto::hash *seed_hash, const int miners); bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const int miners); bool get_block_longhash(const Blockchain *pb, const block& b, crypto::hash& res, const uint64_t height, const crypto::hash *seed_hash, const int miners); void get_altblock_longhash(const block& b, crypto::hash& res, const uint64_t main_height, const uint64_t height, diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a7e96e23a..6fe5a54ac 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -913,6 +913,32 @@ namespace cryptonote }, false, category); } //------------------------------------------------------------------ + void tx_memory_pool::get_block_template_backlog(std::vector<tx_block_template_backlog_entry>& backlog, bool include_sensitive) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); + txpool_tx_meta_t tmp_meta; + m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ + transaction tx; + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + tx.set_hash(txid); + + tmp_meta = meta; + + if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx)) + backlog.push_back({txid, meta.weight, meta.fee}); + + return true; + }, true, category); + } + //------------------------------------------------------------------ void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -1222,11 +1248,11 @@ namespace cryptonote return ret; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata &txblob, transaction &tx) const + bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata_ref& txblob, transaction &tx) const { - struct transction_parser + struct transaction_parser { - transction_parser(const cryptonote::blobdata &txblob, const crypto::hash &txid, transaction &tx): txblob(txblob), txid(txid), tx(tx), parsed(false) {} + transaction_parser(const cryptonote::blobdata_ref &txblob, const crypto::hash &txid, transaction &tx): txblob(txblob), txid(txid), tx(tx), parsed(false) {} cryptonote::transaction &operator()() { if (!parsed) @@ -1238,7 +1264,7 @@ namespace cryptonote } return tx; } - const cryptonote::blobdata &txblob; + const cryptonote::blobdata_ref &txblob; const crypto::hash &txid; transaction &tx; bool parsed; @@ -1289,6 +1315,11 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata& txblob, transaction &tx) const + { + return is_transaction_ready_to_go(txd, txid, cryptonote::blobdata_ref{txblob.data(), txblob.size()}, tx); + } + //--------------------------------------------------------------------------------- bool tx_memory_pool::have_key_images(const std::unordered_set<crypto::key_image>& k_images, const transaction_prefix& tx) { for(size_t i = 0; i!= tx.vin.size(); i++) @@ -1537,61 +1568,59 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - size_t tx_weight_limit = get_transaction_weight_limit(version); - std::unordered_set<crypto::hash> remove; - m_txpool_weight = 0; - m_blockchain.for_all_txpool_txes([this, &remove, tx_weight_limit](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { - m_txpool_weight += meta.weight; - if (meta.weight > tx_weight_limit) { - LOG_PRINT_L1("Transaction " << txid << " is too big (" << meta.weight << " bytes), removing it from pool"); - remove.insert(txid); - } - else if (m_blockchain.have_tx(txid)) { - LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool"); - remove.insert(txid); - } + MINFO("Validating txpool contents for v" << (unsigned)version); + + LockedTXN lock(m_blockchain.get_db()); + + struct tx_entry_t + { + crypto::hash txid; + txpool_tx_meta_t meta; + }; + + // get all txids + std::vector<tx_entry_t> txes; + m_blockchain.for_all_txpool_txes([&txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) { + if (!meta.pruned) // skip pruned txes + txes.push_back({txid, meta}); return true; }, false, relay_category::all); - size_t n_removed = 0; - if (!remove.empty()) + // take them all out and add them back in, some might fail + size_t added = 0; + for (auto &e: txes) { - LockedTXN lock(m_blockchain.get_db()); - for (const crypto::hash &txid: remove) + try { - try - { - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); - cryptonote::transaction tx; - if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary - { - MERROR("Failed to parse tx from txpool"); - continue; - } - // remove tx from db first - m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= get_transaction_weight(tx, txblob.size()); - remove_transaction_keyimages(tx, txid); - auto sorted_it = find_tx_in_sorted_container(txid); - if (sorted_it == m_txs_by_fee_and_receive_time.end()) - { - LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!"); - } - else - { - m_txs_by_fee_and_receive_time.erase(sorted_it); - } - ++n_removed; - } - catch (const std::exception &e) + size_t weight; + uint64_t fee; + cryptonote::transaction tx; + cryptonote::blobdata blob; + bool relayed, do_not_relay, double_spend_seen, pruned; + if (!take_tx(e.txid, tx, blob, weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) + MERROR("Failed to get tx " << e.txid << " from txpool for re-validation"); + + cryptonote::tx_verification_context tvc{}; + relay_method tx_relay = e.meta.get_relay_method(); + if (!add_tx(tx, e.txid, blob, e.meta.weight, tvc, tx_relay, relayed, version)) { - MERROR("Failed to remove invalid tx from pool"); - // continue + MINFO("Failed to re-validate tx " << e.txid << " for v" << (unsigned)version << ", dropped"); + continue; } + m_blockchain.update_txpool_tx(e.txid, e.meta); + ++added; + } + catch (const std::exception &e) + { + MERROR("Failed to re-validate tx from pool"); + continue; } - lock.commit(); } + + lock.commit(); + + const size_t n_removed = txes.size() - added; if (n_removed > 0) ++m_cookie; return n_removed; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index ab2a57ea2..80b38c51d 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -266,6 +266,15 @@ namespace cryptonote void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) const; /** + * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template + * + * @param backlog return-by-reference that data + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes + * + */ + void get_block_template_backlog(std::vector<tx_block_template_backlog_entry>& backlog, bool include_sensitive = false) const; + + /** * @brief get a summary statistics of all transaction hashes in the pool * * @param stats return-by-reference the pool statistics @@ -540,6 +549,7 @@ namespace cryptonote * * @return true if the transaction is good to go, otherwise false */ + bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata_ref &txblob, transaction&tx) const; bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const crypto::hash &txid, const cryptonote::blobdata &txblob, transaction&tx) const; /** diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 106253082..39d562fd1 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -153,6 +153,7 @@ namespace cryptonote context.m_last_request_time = boost::date_time::not_a_date_time; context.m_expect_response = 0; context.m_expect_height = 0; + context.m_requested_objects.clear(); context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download } else diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 0b065c3c3..53de407b6 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -287,6 +287,12 @@ namespace levin boost::asio::steady_timer next_epoch; boost::asio::steady_timer flush_txs; boost::asio::io_service::strand strand; + struct context_t { + std::vector<cryptonote::blobdata> fluff_txs; + std::chrono::steady_clock::time_point flush_time; + bool m_is_income; + }; + boost::unordered_map<boost::uuids::uuid, context_t> contexts; net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand` std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time @@ -363,14 +369,16 @@ namespace levin const auto now = std::chrono::steady_clock::now(); auto next_flush = std::chrono::steady_clock::time_point::max(); std::vector<std::pair<std::vector<blobdata>, boost::uuids::uuid>> connections{}; - zone_->p2p->foreach_connection([timer_error, now, &next_flush, &connections] (detail::p2p_context& context) + for (auto &e: zone_->contexts) { + auto &id = e.first; + auto &context = e.second; if (!context.fluff_txs.empty()) { if (context.flush_time <= now || timer_error) // flush on canceled timer { context.flush_time = std::chrono::steady_clock::time_point::max(); - connections.emplace_back(std::move(context.fluff_txs), context.m_connection_id); + connections.emplace_back(std::move(context.fluff_txs), id); context.fluff_txs.clear(); } else // not flushing yet @@ -378,8 +386,7 @@ namespace levin } else // nothing to flush context.flush_time = std::chrono::steady_clock::time_point::max(); - return true; - }); + } /* Always send with `fluff` flag, even over i2p/tor. The hidden service will disable the forwarding delay and immediately fluff. The i2p/tor @@ -427,22 +434,21 @@ namespace levin MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing"); - - zone->p2p->foreach_connection([txs, now, &zone, &source, &in_duration, &out_duration, &next_flush] (detail::p2p_context& context) + for (auto &e: zone->contexts) { + auto &id = e.first; + auto &context = e.second; // When i2p/tor, only fluff to outbound connections - if (context.handshake_complete() && source != context.m_connection_id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) + if (source != id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) { if (context.fluff_txs.empty()) context.flush_time = now + (context.m_is_income ? in_duration() : out_duration()); next_flush = std::min(next_flush, context.flush_time); context.fluff_txs.reserve(context.fluff_txs.size() + txs.size()); - for (const blobdata& tx : txs) - context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns) + context.fluff_txs.insert(context.fluff_txs.end(), txs.begin(), txs.end()); } - return true; - }); + } if (next_flush == std::chrono::steady_clock::time_point::max()) MWARNING("Unable to send transaction(s), no available connections"); @@ -749,6 +755,32 @@ namespace levin ); } + void notify::on_handshake_complete(const boost::uuids::uuid &id, bool is_income) + { + if (!zone_) + return; + + auto& zone = zone_; + zone_->strand.dispatch([zone, id, is_income]{ + zone->contexts[id] = { + .fluff_txs = {}, + .flush_time = std::chrono::steady_clock::time_point::max(), + .m_is_income = is_income, + }; + }); + } + + void notify::on_connection_close(const boost::uuids::uuid &id) + { + if (!zone_) + return; + + auto& zone = zone_; + zone_->strand.dispatch([zone, id]{ + zone->contexts.erase(id); + }); + } + void notify::run_epoch() { if (!zone_) diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index abbf9d461..12704746a 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -101,6 +101,9 @@ namespace levin //! Probe for new outbound connection - skips if not needed. void new_out_connection(); + void on_handshake_complete(const boost::uuids::uuid &id, bool is_income); + void on_connection_close(const boost::uuids::uuid &id); + //! Run the logic for the next epoch immediately. Only use in testing. void run_epoch(); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 99430b2b0..3f1885423 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -120,6 +120,7 @@ public: if (shared) { core.get().get_blockchain_storage().add_block_notify(cryptonote::listener::zmq_pub::chain_main{shared}); + core.get().get_blockchain_storage().add_miner_notify(cryptonote::listener::zmq_pub::miner_data{shared}); core.get().set_txpool_listener(cryptonote::listener::zmq_pub::txpool_add{shared}); } } diff --git a/src/device/device_cold.hpp b/src/device/device_cold.hpp index d435b448c..07009b9d2 100644 --- a/src/device/device_cold.hpp +++ b/src/device/device_cold.hpp @@ -162,6 +162,26 @@ namespace hw { * Live refresh process termination */ virtual void live_refresh_finish() =0; + + /** + * Requests public address, uses empty passphrase if asked for. + */ + virtual bool get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) =0; + + /** + * Reset session ID, restart with a new session. + */ + virtual void reset_session() =0; + + /** + * Returns true if device already asked for passphrase entry before (i.e., obviously supports passphrase entry) + */ + virtual bool seen_passphrase_entry_prompt() =0; + + /** + * Uses empty passphrase for all passphrase queries. + */ + virtual void set_use_empty_passphrase(bool always_use_empty_passphrase) =0; }; } diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 5caad3a1a..c082544e8 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -55,10 +55,10 @@ namespace hw { } #define TRACKD MTRACE("hw") - #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(mask))==(ok), \ + #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(msk))==(ok), \ "Wrong Device Status: " << "0x" << std::hex << (sw) << " (" << Status::to_string(sw) << "), " << \ "EXPECTED 0x" << std::hex << (ok) << " (" << Status::to_string(ok) << "), " << \ - "MASK 0x" << std::hex << (mask)); + "MASK 0x" << std::hex << (msk)); #define ASSERT_T0(exp) CHECK_AND_ASSERT_THROW_MES(exp, "Protocol assert failure: "#exp ) ; #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg); @@ -451,13 +451,6 @@ namespace hw { ASSERT_X(this->length_recv>=3, "Communication error, less than three bytes received. Check your application version."); - unsigned int device_version = 0; - device_version = VERSION(this->buffer_recv[0], this->buffer_recv[1], this->buffer_recv[2]); - - ASSERT_X (device_version >= MINIMAL_APP_VERSION, - "Unsupported device application version: " << VERSION_MAJOR(device_version)<<"."<<VERSION_MINOR(device_version)<<"."<<VERSION_MICRO(device_version) << - " At least " << MINIMAL_APP_VERSION_MAJOR<<"."<<MINIMAL_APP_VERSION_MINOR<<"."<<MINIMAL_APP_VERSION_MICRO<<" is required."); - return true; } @@ -470,7 +463,10 @@ namespace hw { this->length_recv -= 2; this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1]; logRESP(); - ASSERT_SW(this->sw,ok,msk); + MDEBUG("Device "<< this->id << " exchange: sw: " << this->sw << " expected: " << ok); + ASSERT_X(sw != SW_CLIENT_NOT_SUPPORTED, "Monero Ledger App doesn't support current monero version. Try to update the Monero Ledger App, at least " << MINIMAL_APP_VERSION_MAJOR<< "." << MINIMAL_APP_VERSION_MINOR << "." << MINIMAL_APP_VERSION_MICRO << " is required."); + ASSERT_X(sw != SW_PROTOCOL_NOT_SUPPORTED, "Make sure no other program is communicating with the Ledger."); + ASSERT_SW(this->sw,ok,mask); return this->sw; } @@ -487,7 +483,7 @@ namespace hw { // cancel on device deny = 1; } else { - ASSERT_SW(this->sw,ok,msk); + ASSERT_SW(this->sw,ok,mask); } logRESP(); diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 3b6cc505f..590ae41b5 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -166,8 +166,6 @@ namespace hw { void send_secret(const unsigned char sec[32], int &offset); void receive_secret(unsigned char sec[32], int &offset); - // hw running mode - device_mode mode; bool tx_in_progress; // map public destination key to ephemeral destination key diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index c2070b0d1..0545f3f26 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -66,8 +66,8 @@ namespace trezor { device_trezor::~device_trezor() { try { - disconnect(); - release(); + device_trezor::disconnect(); + device_trezor::release(); } catch(std::exception const& e){ MWARNING("Could not disconnect and release: " << e.what()); } @@ -178,6 +178,15 @@ namespace trezor { } } + bool device_trezor::get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) { + m_reply_with_empty_passphrase = true; + const auto empty_passphrase_reverter = epee::misc_utils::create_scope_leave_handler([&]() { + m_reply_with_empty_passphrase = false; + }); + + return get_public_address(pubkey); + } + bool device_trezor::get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) { try { MDEBUG("Loading view-only key from the Trezor. Please check the Trezor for a confirmation."); @@ -206,6 +215,18 @@ namespace trezor { get_address(index, payment_id, true); } + void device_trezor::reset_session() { + m_device_session_id.clear(); + } + + bool device_trezor::seen_passphrase_entry_prompt() { + return m_seen_passphrase_entry_message; + } + + void device_trezor::set_use_empty_passphrase(bool always_use_empty_passphrase) { + m_always_use_empty_passphrase = always_use_empty_passphrase; + } + /* ======================================================================= */ /* Helpers */ /* ======================================================================= */ diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index d91d1de3f..15337d2b4 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -205,6 +205,26 @@ namespace trezor { const ::tools::wallet2::unsigned_tx_set & unsigned_tx, ::tools::wallet2::signed_tx_set & signed_tx, hw::tx_aux_data & aux_data) override; + + /** + * Requests public address, uses empty passphrase if asked for. + */ + bool get_public_address_with_no_passphrase(cryptonote::account_public_address &pubkey) override; + + /** + * Reset session ID, restart with a new session. + */ + virtual void reset_session() override; + + /** + * Returns true if device already asked for passphrase entry before (i.e., obviously supports passphrase entry) + */ + bool seen_passphrase_entry_prompt() override; + + /** + * Uses empty passphrase for all passphrase queries. + */ + void set_use_empty_passphrase(bool use_always_empty_passphrase) override; }; #endif diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index b0b4342f5..016eb2816 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -45,7 +45,10 @@ namespace trezor { const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080}; - device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success) { + device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success), + m_reply_with_empty_passphrase(false), + m_always_use_empty_passphrase(false), + m_seen_passphrase_entry_message(false) { #ifdef WITH_TREZOR_DEBUGGING m_debug = false; #endif @@ -155,6 +158,9 @@ namespace trezor { TREZOR_AUTO_LOCK_DEVICE(); m_device_session_id.clear(); m_features.reset(); + m_seen_passphrase_entry_message = false; + m_reply_with_empty_passphrase = false; + m_always_use_empty_passphrase = false; if (m_transport){ try { @@ -476,6 +482,7 @@ namespace trezor { return; } + m_seen_passphrase_entry_message = true; bool on_device = true; if (msg->has__on_device() && !msg->_on_device()){ on_device = false; // do not enter on device, old devices. @@ -491,19 +498,21 @@ namespace trezor { } boost::optional<epee::wipeable_string> passphrase; - TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, on_device); + if (m_reply_with_empty_passphrase || m_always_use_empty_passphrase) { + MDEBUG("Answering passphrase prompt with an empty passphrase, always use empty: " << m_always_use_empty_passphrase); + on_device = false; + passphrase = epee::wipeable_string(""); + } else if (m_passphrase){ + MWARNING("Answering passphrase prompt with a stored passphrase (do not use; passphrase can be seen by a potential malware / attacker)"); + on_device = false; + passphrase = epee::wipeable_string(m_passphrase.get()); + } else { + TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, on_device); + } messages::common::PassphraseAck m; m.set_on_device(on_device); if (!on_device) { - if (!passphrase && m_passphrase) { - passphrase = m_passphrase; - } - - if (m_passphrase) { - m_passphrase = boost::none; - } - if (passphrase) { m.set_allocated_passphrase(new std::string(passphrase->data(), passphrase->size())); } diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 0162b23df..de49397d5 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -101,6 +101,9 @@ namespace trezor { messages::MessageType m_last_msg_type; cryptonote::network_type network_type; + bool m_reply_with_empty_passphrase; + bool m_always_use_empty_passphrase; + bool m_seen_passphrase_entry_message; #ifdef WITH_TREZOR_DEBUGGING std::shared_ptr<trezor_debug_callback> m_debug_callback; diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 4aa21b149..87de3c351 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -95,55 +95,35 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str } // gather the keys - std::vector<crypto::secret_key> sk(total); - std::vector<crypto::public_key> pk(total); + std::vector<std::string> first_round_msgs; + first_round_msgs.reserve(total); for (size_t n = 0; n < total; ++n) { wallets[n]->decrypt_keys(pwd_container->password()); - if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n])) - { - tools::fail_msg_writer() << genms::tr("Failed to verify multisig info"); - return false; - } + + first_round_msgs.emplace_back(wallets[n]->get_multisig_first_kex_msg()); + wallets[n]->encrypt_keys(pwd_container->password()); } // make the wallets multisig - std::vector<std::string> extra_info(total); + std::vector<std::string> kex_msgs_intermediate(total); std::stringstream ss; for (size_t n = 0; n < total; ++n) { std::string name = basename + "-" + std::to_string(n + 1); - std::vector<crypto::secret_key> skn; - std::vector<crypto::public_key> pkn; - for (size_t k = 0; k < total; ++k) - { - if (k != n) - { - skn.push_back(sk[k]); - pkn.push_back(pk[k]); - } - } - extra_info[n] = wallets[n]->make_multisig(pwd_container->password(), skn, pkn, threshold); + + kex_msgs_intermediate[n] = wallets[n]->make_multisig(pwd_container->password(), first_round_msgs, threshold); + ss << " " << name << std::endl; } //exchange keys unless exchange_multisig_keys returns no extra info - while (!extra_info[0].empty()) + while (!kex_msgs_intermediate[0].empty()) { - std::unordered_set<crypto::public_key> pkeys; - std::vector<crypto::public_key> signers(total); - for (size_t n = 0; n < total; ++n) - { - if (!tools::wallet2::verify_extra_multisig_info(extra_info[n], pkeys, signers[n])) - { - tools::fail_msg_writer() << genms::tr("Error verifying multisig extra info"); - return false; - } - } for (size_t n = 0; n < total; ++n) { - extra_info[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), pkeys, signers); + kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate); } } diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 9055b92e3..45db59a67 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -70,6 +70,9 @@ const hardfork_t mainnet_hard_forks[] = { { 13, 2210000, 0, 1598180817 }, { 14, 2210720, 0, 1598180818 }, + + { 15, 8000000, 0, 1608223241 }, // temp so tests test with these consensus rules + { 16, 8000001, 0, 1608223242 }, // temp so tests test with these consensus rules }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index eaa2c6f71..14099e64a 100644 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -27,12 +27,17 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(multisig_sources - multisig.cpp) + multisig.cpp + multisig_account.cpp + multisig_account_kex_impl.cpp + multisig_kex_msg.cpp) set(multisig_headers) set(multisig_private_headers - multisig.h) + multisig.h + multisig_account.h + multisig_kex_msg.h) monero_private_headers(multisig ${multisig_private_headers}) diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 272de73b2..85c45bc31 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2021, The Monero Project // // All rights reserved. // @@ -26,29 +26,34 @@ // 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 <unordered_set> -#include "include_base_utils.h" #include "crypto/crypto.h" -#include "ringct/rctOps.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_format_utils.h" -#include "multisig.h" #include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "ringct/rctOps.h" + +#include <algorithm> +#include <unordered_map> +#include <unordered_set> +#include <vector> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "multisig" -using namespace std; - -namespace cryptonote +namespace multisig { - //----------------------------------------------------------------- + //---------------------------------------------------------------------------------------------------------------------- crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { + CHECK_AND_ASSERT_THROW_MES(key != crypto::null_skey, "Unexpected null secret key (danger!)."); + rct::key multisig_salt; static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size"); memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key)); + // private key = H(key, domain-sep) rct::keyV data; data.reserve(2); data.push_back(rct::sk2rct(key)); @@ -57,134 +62,79 @@ namespace cryptonote memwipe(&data[0], sizeof(rct::key)); return result; } - //----------------------------------------------------------------- - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - // the multisig spend public key is the sum of all spend public keys - multisig_keys.clear(); - const crypto::secret_key spend_secret_key = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(spend_secret_key, (crypto::public_key&)spend_pkey), "Failed to derive public key"); - for (const auto &k: spend_keys) - rct::addKeys(spend_pkey, spend_pkey, rct::pk2rct(k)); - multisig_keys.push_back(spend_secret_key); - spend_skey = rct::sk2rct(spend_secret_key); - } - //----------------------------------------------------------------- - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey) - { - multisig_keys.clear(); - spend_pkey = rct::identity(); - spend_skey = rct::zero(); - - // create all our composite private keys - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: spend_keys) - { - rct::key sk = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - crypto::secret_key msk = get_multisig_blinded_secret_key(rct::rct2sk(sk)); - memwipe(&sk, sizeof(sk)); - multisig_keys.push_back(msk); - sc_add(spend_skey.bytes, spend_skey.bytes, (const unsigned char*)msk.data); - } - } - //----------------------------------------------------------------- - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations) - { - std::vector<crypto::public_key> multisig_keys; - crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(keys.m_spend_secret_key); - for (const auto &k: derivations) - { - rct::key d = rct::scalarmultKey(rct::pk2rct(k), rct::sk2rct(blinded_skey)); - multisig_keys.push_back(rct::rct2pk(d)); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& multisig_keys) - { - rct::key secret_key = rct::zero(); - for (const auto &k: multisig_keys) - { - sc_add(secret_key.bytes, secret_key.bytes, (const unsigned char*)k.data); - } - - return rct::rct2sk(secret_key); - } - //----------------------------------------------------------------- - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations) - { - std::vector<crypto::secret_key> multisig_keys; - multisig_keys.reserve(derivations.size()); - - for (const auto &k: derivations) - { - multisig_keys.emplace_back(get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(k)))); - } - - return multisig_keys; - } - //----------------------------------------------------------------- - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys) - { - crypto::secret_key view_skey = get_multisig_blinded_secret_key(skey); - for (const auto &k: skeys) - sc_add((unsigned char*)&view_skey, rct::sk2rct(view_skey).bytes, rct::sk2rct(k).bytes); - return view_skey; - } - //----------------------------------------------------------------- - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys) - { - rct::key spend_public_key = rct::identity(); - for (const auto &pk: pkeys) - { - rct::addKeys(spend_public_key, spend_public_key, rct::pk2rct(pk)); - } - return rct::rct2pk(spend_public_key); - } - //----------------------------------------------------------------- - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki) { if (multisig_key_index >= keys.m_multisig_keys.size()) return false; crypto::generate_key_image(out_key, keys.m_multisig_keys[multisig_key_index], ki); return true; } - //----------------------------------------------------------------- - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R) + //---------------------------------------------------------------------------------------------------------------------- + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R) { rct::scalarmultBase((rct::key&)L, rct::sk2rct(k)); crypto::generate_key_image(pkey, k, (crypto::key_image&)R); } - //----------------------------------------------------------------- - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki) + //---------------------------------------------------------------------------------------------------------------------- + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki) { + // create a multisig partial key image + // KI_partial = ([view key component] + [subaddress component] + [multisig privkeys]) * Hp(output one-time address) + // - the 'multisig priv keys' here are those held by the local account + // - later, we add in the components held by other participants cryptonote::keypair in_ephemeral; if (!cryptonote::generate_key_image_helper(keys, subaddresses, out_key, tx_public_key, additional_tx_public_keys, real_output_index, in_ephemeral, ki, keys.get_device())) return false; std::unordered_set<crypto::key_image> used; - for (size_t m = 0; m < keys.m_multisig_keys.size(); ++m) + + // create a key image component for each of the local account's multisig private keys + for (std::size_t m = 0; m < keys.m_multisig_keys.size(); ++m) { crypto::key_image pki; - bool r = cryptonote::generate_multisig_key_image(keys, m, out_key, pki); + // pki = keys.m_multisig_keys[m] * Hp(out_key) + // pki = key image component + // out_key = one-time address of an output owned by the multisig group + bool r = generate_multisig_key_image(keys, m, out_key, pki); if (!r) return false; + + // this KI component is 'used' because it was included in the partial key image 'ki' above used.insert(pki); } + + // add the KI components from other participants to the partial KI + // if they not included yet for (const auto &pki: pkis) { if (used.find(pki) == used.end()) { + // ignore components that have already been 'used' used.insert(pki); + + // KI_partial = KI_partial + KI_component[...] rct::addKeys((rct::key&)ki, rct::ki2rct(ki), rct::ki2rct(pki)); } } + + // at the end, 'ki' will hold the true key image for our output if inputs were sufficient + // - if 'pkis' (the other participants' KI components) is missing some components + // then 'ki' will not be complete + return true; } - //----------------------------------------------------------------- - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold) - { - CHECK_AND_ASSERT_THROW_MES(participants >= threshold, "participants must be greater or equal than threshold"); - return participants - threshold + 1; - } -} + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig.h b/src/multisig/multisig.h index eab32187c..e041ea670 100644 --- a/src/multisig/multisig.h +++ b/src/multisig/multisig.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, The Monero Project +// Copyright (c) 2017-2021, The Monero Project // // All rights reserved. // @@ -28,44 +28,42 @@ #pragma once -#include <vector> -#include <unordered_map> #include "crypto/crypto.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "ringct/rctTypes.h" -namespace cryptonote -{ - struct account_keys; +#include <unordered_map> +#include <unordered_set> +#include <vector> - crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); - void generate_multisig_N_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - void generate_multisig_N1_N(const account_keys &keys, const std::vector<crypto::public_key> &spend_keys, std::vector<crypto::secret_key> &multisig_keys, rct::key &spend_skey, rct::key &spend_pkey); - /** - * @brief generate_multisig_derivations performs common DH key derivation. - * Each middle round in M/N scheme is DH exchange of public multisig keys of other participants multiplied by secret spend key of current participant. - * this functions does the following: new multisig key = secret spend * public multisig key - * @param keys - current wallet's keys - * @param derivations - public multisig keys of other participants - * @return new public multisig keys derived from previous round. This data needs to be exchange with other participants - */ - std::vector<crypto::public_key> generate_multisig_derivations(const account_keys &keys, const std::vector<crypto::public_key> &derivations); - crypto::secret_key calculate_multisig_signer_key(const std::vector<crypto::secret_key>& derivations); - /** - * @brief calculate_multisig_keys. Calculates secret multisig keys from others' participants ones as follows: mi = H(Mi) - * @param derivations - others' participants public multisig keys. - * @return vector of current wallet's multisig secret keys - */ - std::vector<crypto::secret_key> calculate_multisig_keys(const std::vector<crypto::public_key>& derivations); - crypto::secret_key generate_multisig_view_secret_key(const crypto::secret_key &skey, const std::vector<crypto::secret_key> &skeys); +namespace cryptonote { struct account_keys; } + +namespace multisig +{ /** - * @brief generate_multisig_M_N_spend_public_key calculates multisig wallet's spend public key by summing all of public multisig keys - * @param pkeys unique public multisig keys - * @return multisig wallet's spend public key - */ - crypto::public_key generate_multisig_M_N_spend_public_key(const std::vector<crypto::public_key> &pkeys); - bool generate_multisig_key_image(const account_keys &keys, size_t multisig_key_index, const crypto::public_key& out_key, crypto::key_image& ki); - void generate_multisig_LR(const crypto::public_key pkey, const crypto::secret_key &k, crypto::public_key &L, crypto::public_key &R); - bool generate_multisig_composite_key_image(const account_keys &keys, const std::unordered_map<crypto::public_key, cryptonote::subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key &tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, const std::vector<crypto::key_image> &pkis, crypto::key_image &ki); - uint32_t multisig_rounds_required(uint32_t participants, uint32_t threshold); -} + * @brief get_multisig_blinded_secret_key - converts an input private key into a blinded multisig private key + * Use 1a: converts account private spend key into multisig private key, which is used for key exchange and message signing + * Use 1b: converts account private view key into ancillary private key share, for the composite multisig private view key + * Use 2: converts DH shared secrets (curve points) into private keys, which are intermediate private keys in multisig key exchange + * @param key - private key to transform + * @return transformed private key + */ + crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key); + + bool generate_multisig_key_image(const cryptonote::account_keys &keys, + std::size_t multisig_key_index, + const crypto::public_key& out_key, + crypto::key_image& ki); + void generate_multisig_LR(const crypto::public_key pkey, + const crypto::secret_key &k, + crypto::public_key &L, + crypto::public_key &R); + bool generate_multisig_composite_key_image(const cryptonote::account_keys &keys, + const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, + const crypto::public_key &out_key, + const crypto::public_key &tx_public_key, + const std::vector<crypto::public_key> &additional_tx_public_keys, + std::size_t real_output_index, + const std::vector<crypto::key_image> &pkis, + crypto::key_image &ki); +} //namespace multisig diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp new file mode 100644 index 000000000..b7298c4b6 --- /dev/null +++ b/src/multisig/multisig_account.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2021, 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 "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" +#include "ringct/rctTypes.h" + +#include <cstdint> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_pubkey{rct::rct2pk(rct::identity())}, + m_common_pubkey{rct::rct2pk(rct::identity())}, + m_kex_rounds_complete{0}, + m_next_round_kex_message{multisig_kex_msg{1, base_privkey, std::vector<crypto::public_key>{}, base_common_privkey}.get_msg()} + { + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_account::multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + kex_origins_map_t kex_origins_map, + std::string next_round_kex_message) : + m_base_privkey{base_privkey}, + m_base_common_privkey{base_common_privkey}, + m_multisig_privkeys{std::move(multisig_privkeys)}, + m_common_privkey{common_privkey}, + m_multisig_pubkey{multisig_pubkey}, + m_common_pubkey{common_pubkey}, + m_kex_rounds_complete{kex_rounds_complete}, + m_kex_keys_to_origins_map{std::move(kex_origins_map)}, + m_next_round_kex_message{std::move(next_round_kex_message)} + { + CHECK_AND_ASSERT_THROW_MES(kex_rounds_complete > 0, "multisig account: can't reconstruct account if its kex wasn't initialized"); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_base_privkey, m_base_pubkey), + "Failed to derive public key"); + set_multisig_config(threshold, std::move(signers)); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::account_is_active() const + { + return m_kex_rounds_complete > 0; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + bool multisig_account::multisig_is_ready() const + { + if (account_is_active()) + return multisig_kex_rounds_required(m_signers.size(), m_threshold) == m_kex_rounds_complete; + else + return false; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers) + { + // validate + CHECK_AND_ASSERT_THROW_MES(threshold > 0 && threshold <= signers.size(), "multisig account: tried to set invalid threshold."); + CHECK_AND_ASSERT_THROW_MES(signers.size() >= 2 && signers.size() <= config::MULTISIG_MAX_SIGNERS, + "multisig account: tried to set invalid number of signers."); + + for (auto signer_it = signers.begin(); signer_it != signers.end(); ++signer_it) + { + // signers should all be unique + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signer_it, *signer_it) == signer_it, + "multisig account: tried to set signers, but found a duplicate signer unexpectedly."); + + // signer pubkeys must be in main subgroup, and not identity + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(*signer_it)) && !(*signer_it == rct::rct2pk(rct::identity())), + "multisig account: tried to set signers, but a signer pubkey is invalid."); + } + + // own pubkey should be in signers list + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), m_base_pubkey) != signers.end(), + "multisig account: tried to set signers, but did not find the account's base pubkey in signer list."); + + // sort signers + std::sort(signers.begin(), signers.end(), + [](const crypto::public_key &key1, const crypto::public_key &key2) -> bool + { + return memcmp(&key1, &key2, sizeof(crypto::public_key)) < 0; + } + ); + + // set + m_threshold = threshold; + m_signers = std::move(signers); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1) + { + CHECK_AND_ASSERT_THROW_MES(!account_is_active(), "multisig account: tried to initialize kex, but already initialized"); + + // only mutate account if update succeeds + multisig_account temp_account{*this}; + temp_account.set_multisig_config(threshold, std::move(signers)); + temp_account.kex_update_impl(expanded_msgs_rnd1); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); + CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); + + multisig_account temp_account{*this}; + temp_account.kex_update_impl(expanded_msgs); + *this = std::move(temp_account); + } + //---------------------------------------------------------------------------------------------------------------------- + // EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) + { + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, "num_signers must be >= threshold"); + CHECK_AND_ASSERT_THROW_MES(threshold >= 1, "threshold must be >= 1"); + return num_signers - threshold + 1; + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h new file mode 100644 index 000000000..b01ae6c88 --- /dev/null +++ b/src/multisig/multisig_account.h @@ -0,0 +1,246 @@ +// Copyright (c) 2021, 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 "crypto/crypto.h" +#include "multisig_kex_msg.h" + +#include <cstdint> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + + +namespace multisig +{ + /** + * multisig account: + * + * - handles account keys for an M-of-N multisig participant (M <= N; M >= 1; N >= 2) + * - encapsulates multisig account construction process (via key exchange [kex]) + * - TODO: encapsulates key preparation for aggregation-style signing + * + * :: multisig pubkey: the private key is split, M group participants are required to reassemble (e.g. to sign something) + * - in cryptonote, this is the multisig spend key + * :: multisig common pubkey: the private key is known to all participants (e.g. for authenticating as a group member) + * - in cryptonote, this is the multisig view key + * + * + * multisig key exchange: + * + * An 'M-of-N' (M <= N; M >= 1; N >= 2) multisignature key is a public key where at least 'M' out of 'N' + * possible co-signers must collaborate in order to create a signature. + * + * Constructing a multisig key involves a series of Diffie-Hellman exchanges between participants. + * At the end of key exchange (kex), each participant will hold a number of private keys. Each private + * key is shared by a group of (N - M + 1) participants. This way if (N - M) co-signers are missing, every + * private key will be held by at least one of the remaining M people. + * + * Note on MULTISIG_MAX_SIGNERS: During key exchange, participants will have up to '(N - 1) choose (N - M)' + * key shares. If N is large, then the max number of key shares (when M = (N-1)/2) can be huge. A limit of N <= 16 was + * arbitrarily chosen as a power of 2 that can accomodate the vast majority of practical use-cases. To increase the + * limit, FROST-style key aggregation should be used instead (it is more efficient than DH-based key generation + * when N - M > 1). + * + * - Further reading + * - MRL-0009: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * - MuSig2: https://eprint.iacr.org/2020/1261 + * - ZtM2: https://web.getmonero.org/library/Zero-to-Monero-2-0-0.pdf Ch. 9, especially Section 9.6.3 + * - FROST: https://eprint.iacr.org/2018/417 + */ + class multisig_account final + { + public: + //member types + using kex_origins_map_t = std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>>; + + //constructors + // default constructor + multisig_account() = default; + + /** + * construct from base privkeys + * + * - prepares a kex msg for the first round of multisig key construction. + * - the local account's kex msgs are signed with the base_privkey + * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey + */ + multisig_account(const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey); + + // reconstruct from full account details (not recommended) + multisig_account(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const crypto::secret_key &base_privkey, + const crypto::secret_key &base_common_privkey, + std::vector<crypto::secret_key> multisig_privkeys, + const crypto::secret_key &common_privkey, + const crypto::public_key &multisig_pubkey, + const crypto::public_key &common_pubkey, + const std::uint32_t kex_rounds_complete, + kex_origins_map_t kex_origins_map, + std::string next_round_kex_message); + + // copy constructor: default + + //destructor: default + ~multisig_account() = default; + + //overloaded operators: none + + //getters + // get threshold + std::uint32_t get_threshold() const { return m_threshold; } + // get signers + const std::vector<crypto::public_key>& get_signers() const { return m_signers; } + // get base privkey + const crypto::secret_key& get_base_privkey() const { return m_base_privkey; } + // get base pubkey + const crypto::public_key& get_base_pubkey() const { return m_base_pubkey; } + // get base common privkey + const crypto::secret_key& get_base_common_privkey() const { return m_base_common_privkey; } + // get multisig privkeys + const std::vector<crypto::secret_key>& get_multisig_privkeys() const { return m_multisig_privkeys; } + // get common privkey + const crypto::secret_key& get_common_privkey() const { return m_common_privkey; } + // get multisig pubkey + const crypto::public_key& get_multisig_pubkey() const { return m_multisig_pubkey; } + // get common pubkey + const crypto::public_key& get_common_pubkey() const { return m_common_pubkey; } + // get kex rounds complete + std::uint32_t get_kex_rounds_complete() const { return m_kex_rounds_complete; } + // get kex keys to origins map + const kex_origins_map_t& get_kex_keys_to_origins_map() const { return m_kex_keys_to_origins_map; } + // get the kex msg for the next round + const std::string& get_next_kex_round_msg() const { return m_next_round_kex_message; } + + //account status functions + // account has been intialized, and the account holder can use the 'common' key + bool account_is_active() const; + // account is ready to make multisig signatures + bool multisig_is_ready() const; + + //account helpers + private: + // set the threshold (M) and signers (N) + void set_multisig_config(const std::size_t threshold, std::vector<crypto::public_key> signers); + + //account mutators: key exchange to set up account + public: + /** + * brief: initialize_kex - initialize key exchange + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + */ + void initialize_kex(const std::uint32_t threshold, + std::vector<crypto::public_key> signers, + const std::vector<multisig_kex_msg> &expanded_msgs_rnd1); + /** + * brief: kex_update - Complete the 'in progress' kex round and set the kex message for the next round. + * - Updates the account with a 'transactional' model. This account will only be mutated if the update succeeds. + * - The main interface for multisig key exchange, this handles all the work of processing input messages, + * creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete. + * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round + */ + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs); + + private: + // implementation of kex_update() (non-transactional) + void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs); + /** + * brief: initialize_kex_update - Helper for kex_update_impl() + * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key + * if appropriate. + * param: expanded_msgs - set of multisig kex messages to process + * param: rounds_required - number of rounds required for kex + * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + */ + void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out); + /** + * brief: finalize_kex_update - Helper for kex_update_impl() + * param: rounds_required - number of rounds required for kex + * param: result_keys_to_origins_map - map between keys for the next round and the other participants they correspond to + * inoutparam: temp_account_inout - account to perform last update steps on + */ + void finalize_kex_update(const std::uint32_t rounds_required, + kex_origins_map_t result_keys_to_origins_map); + + //member variables + private: + /// misc. account details + // [M] minimum number of co-signers to sign a message with the aggregate pubkey + std::uint32_t m_threshold{0}; + // [N] base keys of all participants in the multisig (used to initiate key exchange, and as participant ids for msg signing) + std::vector<crypto::public_key> m_signers; + + /// local participant's personal keys + // base keypair of the participant + // - used for signing messages, as the initial base key for key exchange, and to make DH derivations for key exchange + crypto::secret_key m_base_privkey; + crypto::public_key m_base_pubkey; + // common base privkey, used to produce the aggregate common privkey + crypto::secret_key m_base_common_privkey; + + /// core multisig account keys + // the account's private key shares of the multisig address + // TODO: also record which other signers have these privkeys, to enable aggregation signing (instead of round-robin) + std::vector<crypto::secret_key> m_multisig_privkeys; + // a privkey owned by all multisig participants (e.g. a cryptonote view key) + crypto::secret_key m_common_privkey; + // the multisig public key (e.g. a cryptonote spend key) + crypto::public_key m_multisig_pubkey; + // the common public key (e.g. a view spend key) + crypto::public_key m_common_pubkey; + + /// kex variables + // number of key exchange rounds that have been completed (all messages for the round collected and processed) + std::uint32_t m_kex_rounds_complete{0}; + // this account's pubkeys for the in-progress key exchange round + // - either DH derivations (intermediate rounds), H(derivation)*G (final round), empty (when kex is done) + kex_origins_map_t m_kex_keys_to_origins_map; + // the account's message for the in-progress key exchange round + std::string m_next_round_kex_message; + }; + + /** + * brief: multisig_kex_rounds_required - The number of key exchange rounds required to produce an M-of-N shared key. + * - Key exchange (kex) is a synchronous series of 'rounds'. In an 'active round', participants send messages + * to each other. + * - A participant considers a round 'complete' when they have collected sufficient messages + * from other participants, processed those messages, and updated their multisig account state. + * - Typically (as implemented in this module), completing a round coincides with making a message for the next round. + * param: num_signers - number of participants in multisig (N) + * param: threshold - threshold of multisig (M) + * return: number of kex rounds required + */ + std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); +} //namespace multisig diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp new file mode 100644 index 000000000..0a0ca7bdc --- /dev/null +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -0,0 +1,726 @@ +// Copyright (c) 2021, 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 "multisig_account.h" + +#include "crypto/crypto.h" +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "multisig.h" +#include "multisig_kex_msg.h" +#include "ringct/rctOps.h" + +#include <boost/math/special_functions/binomial.hpp> + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <limits> +#include <memory> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: calculate_multisig_keypair_from_derivation - wrapper on calculate_multisig_keypair() for an input public key + * Converts an input public key into a crypto private key (type cast, does not change serialization), + * then passes it to get_multisig_blinded_secret_key(). + * + * Result: + * - privkey = H(derivation) + * - pubkey = privkey * G + * param: derivation - a curve point + * outparam: derived_pubkey_out - public key of the resulting privkey + * return: multisig private key + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::secret_key calculate_multisig_keypair_from_derivation(const crypto::public_key_memsafe &derivation, + crypto::public_key &derived_pubkey_out) + { + crypto::secret_key blinded_skey = get_multisig_blinded_secret_key(rct::rct2sk(rct::pk2rct(derivation))); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(blinded_skey, derived_pubkey_out), "Failed to derive public key"); + + return blinded_skey; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: make_multisig_common_privkey - Create the 'common' multisig privkey, owned by all multisig participants. + * - common privkey = H(sorted base common privkeys) + * param: participant_base_common_privkeys - Base common privkeys contributed by multisig participants. + * outparam: common_privkey_out - result + */ + //---------------------------------------------------------------------------------------------------------------------- + static void make_multisig_common_privkey(std::vector<crypto::secret_key> participant_base_common_privkeys, + crypto::secret_key &common_privkey_out) + { + // sort the privkeys for consistency + //TODO: need a constant-time operator< for sorting secret keys + std::sort(participant_base_common_privkeys.begin(), participant_base_common_privkeys.end(), + [](const crypto::secret_key &key1, const crypto::secret_key &key2) -> bool + { + return memcmp(&key1, &key2, sizeof(crypto::secret_key)) < 0; + } + ); + + // privkey = H(sorted ancillary base privkeys) + crypto::hash_to_scalar(participant_base_common_privkeys.data(), + participant_base_common_privkeys.size()*sizeof(crypto::secret_key), + common_privkey_out); + + CHECK_AND_ASSERT_THROW_MES(common_privkey_out != crypto::null_skey, "Unexpected null secret key (danger!)."); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: compute_multisig_aggregation_coefficient - creates aggregation coefficient for a specific public key in a set + * of public keys + * + * WARNING: The coefficient will only be deterministic if... + * 1) input keys are pre-sorted + * - tested here + * 2) input keys are in canonical form (compressed points in the prime-order subgroup of Ed25519) + * - untested here for performance + * param: sorted_keys - set of component public keys that will be merged into a multisig public spend key + * param: aggregation_key - one of the component public keys + * return: aggregation coefficient + */ + //---------------------------------------------------------------------------------------------------------------------- + static rct::key compute_multisig_aggregation_coefficient(const std::vector<crypto::public_key> &sorted_keys, + const crypto::public_key &aggregation_key) + { + CHECK_AND_ASSERT_THROW_MES(std::is_sorted(sorted_keys.begin(), sorted_keys.end()), + "Keys for aggregation coefficient aren't sorted."); + + // aggregation key must be in sorted_keys + CHECK_AND_ASSERT_THROW_MES(std::find(sorted_keys.begin(), sorted_keys.end(), aggregation_key) != sorted_keys.end(), + "Aggregation key expected to be in input keyset."); + + // aggregation coefficient salt + rct::key salt = rct::zero(); + static_assert(sizeof(rct::key) >= sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION), "Hash domain separator is too big."); + memcpy(salt.bytes, config::HASH_KEY_MULTISIG_KEY_AGGREGATION, sizeof(config::HASH_KEY_MULTISIG_KEY_AGGREGATION)); + + // coeff = H(aggregation_key, sorted_keys, domain-sep) + rct::keyV data; + data.reserve(sorted_keys.size() + 2); + data.push_back(rct::pk2rct(aggregation_key)); + for (const auto &key : sorted_keys) + data.push_back(rct::pk2rct(key)); + data.push_back(salt); + + // note: coefficient is considered public knowledge, no need to memwipe data + return rct::hash_to_scalar(data); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: generate_multisig_aggregate_key - generates a multisig public spend key via key aggregation + * Key aggregation via aggregation coefficients prevents key cancellation attacks. + * See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf + * param: final_keys - address components (public keys) obtained from other participants (not shared with local) + * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference) + * return: final multisig public spend key for the account + */ + //---------------------------------------------------------------------------------------------------------------------- + static crypto::public_key generate_multisig_aggregate_key(std::vector<crypto::public_key> final_keys, + std::vector<crypto::secret_key> &privkeys_inout) + { + // collect all public keys that will go into the spend key (these don't need to be memsafe) + final_keys.reserve(final_keys.size() + privkeys_inout.size()); + + // 1. convert local multisig private keys to pub keys + // 2. insert to final keyset if not there yet + // 3. save the corresponding index of input priv key set for later reference + std::unordered_map<crypto::public_key, std::size_t> own_keys_mapping; + + for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index) + { + crypto::public_key pubkey; + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key"); + + own_keys_mapping[pubkey] = multisig_keys_index; + + final_keys.push_back(pubkey); + } + + // sort input final keys for computing aggregation coefficients (lowest to highest) + // note: input should be sanitized (no duplicates) + std::sort(final_keys.begin(), final_keys.end()); + CHECK_AND_ASSERT_THROW_MES(std::adjacent_find(final_keys.begin(), final_keys.end()) == final_keys.end(), + "Unexpected duplicate found in input list."); + + // key aggregation + rct::key aggregate_key = rct::identity(); + + for (const crypto::public_key &key : final_keys) + { + // get aggregation coefficient + rct::key coeff = compute_multisig_aggregation_coefficient(final_keys, key); + + // convert private key if possible + // note: retain original priv key index in input list, in case order matters upstream + auto found_key = own_keys_mapping.find(key); + if (found_key != own_keys_mapping.end()) + { + // k_agg = coeff*k_base + sc_mul((unsigned char*)&(privkeys_inout[found_key->second]), + coeff.bytes, + (const unsigned char*)&(privkeys_inout[found_key->second])); + + CHECK_AND_ASSERT_THROW_MES(privkeys_inout[found_key->second] != crypto::null_skey, + "Multisig privkey with aggregation coefficient unexpectedly null."); + } + + // convert public key (pre-merge operation) + // K_agg = coeff*K_base + rct::key converted_pubkey = rct::scalarmultKey(rct::pk2rct(key), coeff); + + // build aggregate key (merge operation) + rct::addKeys(aggregate_key, aggregate_key, converted_pubkey); + } + + return rct::rct2pk(aggregate_key); + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_make_next_msg - Construct a kex msg for any round > 1 of multisig key construction. + * - Involves DH exchanges with pubkeys provided by other participants. + * - Conserves mapping [pubkey -> DH derivation] : [origin keys of participants that share this secret with you]. + * param: base_privkey - account's base private key, for performing DH exchanges and signing messages + * param: round - the round of the message that should be produced + * param: threshold - threshold for multisig (M in M-of-N) + * param: num_signers - number of participants in multisig (N) + * param: pubkey_origins_map - map between pubkeys to produce DH derivations with and identity keys of + * participants who will share each derivation with you + * outparam: derivation_origins_map_out - map between DH derivations (shared secrets) and identity keys + * - If msg is not for the last round, then these derivations are also stored in the output message + * so they can be sent to other participants, who will make more DH derivations for the next kex round. + * - If msg is for the last round, then these derivations won't be sent to other participants. + * Instead, they are converted to share secrets (i.e. s = H(derivation)) and multiplied by G. + * The keys s*G are sent to other participants in the message, so they can be used to produce the final + * multisig key via generate_multisig_spend_public_key(). + * - The values s are the local account's shares of the final multisig key's private key. The caller can + * compute those values with calculate_multisig_keypair_from_derivation() (or compute them directly). + * return: multisig kex message for the specified round + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_kex_msg multisig_kex_make_next_msg(const crypto::secret_key &base_privkey, + const std::uint32_t round, + const std::uint32_t threshold, + const std::uint32_t num_signers, + const std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &pubkey_origins_map, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &derivation_origins_map_out) + { + CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(num_signers >= threshold, + "Multisig threshold may not be larger than number of signers."); + CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); + CHECK_AND_ASSERT_THROW_MES(round > 1, "Round for next msg must be > 1."); + CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold), + "Trying to make key exchange message for an invalid round."); + + // make shared secrets with input pubkeys + std::vector<crypto::public_key> msg_pubkeys; + msg_pubkeys.reserve(pubkey_origins_map.size()); + derivation_origins_map_out.clear(); + + for (const auto &pubkey_and_origins : pubkey_origins_map) + { + // D = 8 * k_base * K_pubkey + // note: must be mul8 (cofactor), otherwise it is possible to leak to a malicious participant if the local + // base_privkey is a multiple of 8 or not + // note2: avoid making temporaries that won't be memwiped + rct::key derivation_rct; + auto a_wiper = epee::misc_utils::create_scope_leave_handler([&]{ + memwipe(&derivation_rct, sizeof(rct::key)); + }); + + rct::scalarmultKey(derivation_rct, rct::pk2rct(pubkey_and_origins.first), rct::sk2rct(base_privkey)); + rct::scalarmultKey(derivation_rct, derivation_rct, rct::EIGHT); + + crypto::public_key_memsafe derivation{rct::rct2pk(derivation_rct)}; + + // retain mapping between pubkey's origins and the DH derivation + // note: if msg for last round, then caller must know how to handle these derivations properly + derivation_origins_map_out[derivation] = pubkey_and_origins.second; + + // if the last round, convert derivations to public keys for the output message + if (round == multisig_kex_rounds_required(num_signers, threshold)) + { + // derived_pubkey = H(derivation)*G + crypto::public_key derived_pubkey; + calculate_multisig_keypair_from_derivation(derivation, derived_pubkey); + msg_pubkeys.push_back(derived_pubkey); + } + // otherwise, put derivations in message directly, so other signers can in turn create derivations (shared secrets) + // with them for the next round + else + msg_pubkeys.push_back(derivation); + } + + return multisig_kex_msg{round, base_privkey, std::move(msg_pubkeys)}; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages. + * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key, + * ignores messages signed by the local account, ignores keys found in input 'exclusion set', + * constructs map of pubkey:origins. + * - Requires that all input msgs have the same round number. + * + * origins = all the signing pubkeys that recommended a given pubkey found in input msgs + * + * - If the messages' round numbers are all '1', then only the message signing pubkey is considered + * 'recommended'. Furthermore, the 'exclusion set' is ignored. + * param: own_pubkey - local account's signing key (key used to sign multisig messages) + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - pubkeys to exclude from output set + * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins + * return: round number shared by all input msgs + */ + //---------------------------------------------------------------------------------------------------------------------- + static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &sanitized_pubkeys_out) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input message expected."); + + std::uint32_t round = expanded_msgs[0].get_round(); + sanitized_pubkeys_out.clear(); + + // get all pubkeys from input messages, add them to pubkey:origins map + // - origins = all the signing pubkeys that recommended a given msg pubkey + for (const auto &expanded_msg : expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msg.get_round() == round, "All messages must have the same kex round number."); + + // ignore messages from self + if (expanded_msg.get_signing_pubkey() == own_pubkey) + continue; + + // in round 1, only the signing pubkey is treated as a msg pubkey + if (round == 1) + { + // note: ignores duplicates + sanitized_pubkeys_out[expanded_msg.get_signing_pubkey()].insert(expanded_msg.get_signing_pubkey()); + } + // in other rounds, only the msg pubkeys are treated as msg pubkeys + else + { + // copy all pubkeys from message into list + for (const auto &pubkey : expanded_msg.get_msg_pubkeys()) + { + // ignore own pubkey + if (pubkey == own_pubkey) + continue; + + // ignore pubkeys in 'ignore' set + if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end()) + continue; + + // note: ignores duplicates + sanitized_pubkeys_out[pubkey].insert(expanded_msg.get_signing_pubkey()); + } + } + } + + return round; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round. + * - Sanitizes input msgs. + * - Require uniqueness in: 'signers', 'exclude_pubkeys'. + * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers. + * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be + * shared by (N - M + 1) signers. + * - Requires that msgs are signed by only keys in 'signers'. + * - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations each signer recommends for round 'expected_round', excluding derivations shared + * with the local account. + * - Requires that 'exclude_pubkeys' has [num_signers - 1 CHOOSE (expected_round - 1)] pubkeys. + * - These should be derivations the local account has corresponding to round 'expected_round'. + * param: base_privkey - multisig account's base private key + * param: expected_round - expected kex round of input messages + * param: threshold - threshold for multisig (M in M-of-N) + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round' + * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message + */ + //---------------------------------------------------------------------------------------------------------------------- + static std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluate_multisig_kex_round_msgs( + const crypto::public_key &base_pubkey, + const std::uint32_t expected_round, + const std::uint32_t threshold, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(signers.size() > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(signers.size() <= config::MULTISIG_MAX_SIGNERS, + "Too many multisig signers specified (limit = 16 to prevent dangerous combinatorial explosion during key exchange)."); + CHECK_AND_ASSERT_THROW_MES(signers.size() >= threshold, "Multisig threshold may not be larger than number of signers."); + CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); + CHECK_AND_ASSERT_THROW_MES(expected_round > 0, "Expected round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(expected_round <= multisig_kex_rounds_required(signers.size(), threshold), + "Expecting key exchange messages for an invalid round."); + + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> pubkey_origins_map; + + // leave early in the last round of 1-of-N, where all signers share a key so the local signer doesn't care about + // recommendations from other signers + if (threshold == 1 && expected_round == multisig_kex_rounds_required(signers.size(), threshold)) + return pubkey_origins_map; + + // exclude_pubkeys should all be unique + for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) + { + CHECK_AND_ASSERT_THROW_MES(std::find(exclude_pubkeys.begin(), it, *it) == it, + "Found duplicate pubkeys for exclusion unexpectedly."); + } + + // sanitize input messages + std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map); + CHECK_AND_ASSERT_THROW_MES(round == expected_round, + "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + + // evaluate pubkeys collected + std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; + + // 1. each pubkey should be recommended by a precise number of signers + for (const auto &pubkey_and_origins : pubkey_origins_map) + { + // expected amount = round_num + // With each successive round, pubkeys are shared by incrementally larger groups, + // starting at 1 in round 1 (i.e. the local multisig key to start kex with). + CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // map (sanitized) pubkeys back to origins + for (const auto &origin : pubkey_and_origins.second) + origin_pubkeys_map[origin].insert(pubkey_and_origins.first); + } + + // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer) + CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1, + "Number of unique other signers does not equal number of other signers that recommended pubkeys."); + + // 3. each origin should recommend a precise number of pubkeys + + // TODO: move to a 'math' library, with unit tests + auto n_choose_k_f = + [](const std::uint32_t n, const std::uint32_t k) -> std::uint32_t + { + static_assert(std::numeric_limits<std::int32_t>::digits <= std::numeric_limits<double>::digits, + "n_choose_k requires no rounding issues when converting between int32 <-> double."); + + if (n < k) + return 0; + + double fp_result = boost::math::binomial_coefficient<double>(n, k); + + if (fp_result < 0) + return 0; + + if (fp_result > std::numeric_limits<std::int32_t>::max()) // note: std::round() returns std::int32_t + return 0; + + return static_cast<std::uint32_t>(std::round(fp_result)); + }; + + // other signers: (N - 2) choose (msg_round_num - 1) + // - Each signer recommends keys they share with other signers. + // - In each round, a signer shares a key with 'round num - 1' other signers. + // - Since 'origins pubkey map' excludes keys shared with the local account, + // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers). + // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). + // - Each origin should have a shared key with each group of size 'round - 1'. + // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may + // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). + std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); + + // local: (N - 1) choose (msg_round_num - 1) + std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1); + + // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case + CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, + "Bad num signers or round num (possibly numerical limits exceeded)."); + + // check that local account recommends expected number of keys + CHECK_AND_ASSERT_THROW_MES(exclude_pubkeys.size() == expected_recommendations_self, + "Local account did not recommend expected number of multisig keys."); + + // check that other signers recommend expected number of keys + for (const auto &origin_and_pubkeys : origin_pubkeys_map) + { + CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others, + "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + + // 2 (continued). only expected signers should be recommending keys + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(), + "Multisig kex message with unexpected signer encountered."); + } + + // note: above tests implicitly detect if the total number of recommended keys is correct or not + return pubkey_origins_map; + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * + * brief: multisig_kex_process_round - Process kex messages for the active kex round. + * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg(). + * - In other words, evaluate the input messages and try to make a message for the next round. + * - Note: Must be called on the final round's msgs to evaluate the final key components + * recommended by other participants. + * param: base_privkey - multisig account's base private key + * param: current_round - round of kex the input messages should be designed for + * param: threshold - threshold for multisig (M in M-of-N) + * param: signers - expected participants in multisig kex + * param: expanded_msgs - set of multisig kex messages to process + * param: exclude_pubkeys - keys held by the local account corresponding to round 'current_round' + * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. + * outparam: keys_to_origins_map_out - map between round keys and identity keys + * - If in the final round, these are key shares recommended by other signers for the final aggregate key. + * - Otherwise, these are the local account's DH derivations for the next round. + * - See multisig_kex_make_next_msg() for an explanation. + * return: multisig kex message for next round, or empty message if 'current_round' is the final round + */ + //---------------------------------------------------------------------------------------------------------------------- + static multisig_kex_msg multisig_kex_process_round(const crypto::secret_key &base_privkey, + const crypto::public_key &base_pubkey, + const std::uint32_t current_round, + const std::uint32_t threshold, + const std::vector<crypto::public_key> &signers, + const std::vector<multisig_kex_msg> &expanded_msgs, + const std::vector<crypto::public_key> &exclude_pubkeys, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> &keys_to_origins_map_out) + { + // evaluate messages + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> evaluated_pubkeys = + evaluate_multisig_kex_round_msgs(base_pubkey, current_round, threshold, signers, expanded_msgs, exclude_pubkeys); + + // produce message for next round (if there is one) + if (current_round < multisig_kex_rounds_required(signers.size(), threshold)) + { + return multisig_kex_make_next_msg(base_privkey, + current_round + 1, + threshold, + signers.size(), + evaluated_pubkeys, + keys_to_origins_map_out); + } + else + { + // no more rounds, so collect the key shares recommended by other signers for the final aggregate key + keys_to_origins_map_out.clear(); + keys_to_origins_map_out = std::move(evaluated_pubkeys); + + return multisig_kex_msg{}; + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t rounds_required, + std::vector<crypto::public_key> &exclude_pubkeys_out) + { + if (m_kex_rounds_complete == 0) + { + // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys + + // collect participants' base common privkey shares + // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers + // will be blocked by duplicate-signer errors after this function is called + std::vector<crypto::secret_key> participant_base_common_privkeys; + participant_base_common_privkeys.reserve(expanded_msgs.size() + 1); + + // add local ancillary base privkey + participant_base_common_privkeys.emplace_back(m_base_common_privkey); + + // add other signers' base common privkeys + for (const auto &expanded_msg : expanded_msgs) + { + if (expanded_msg.get_signing_pubkey() != m_base_pubkey) + { + participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey()); + } + } + + // make common privkey + make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); + + // set common pubkey + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey), + "Failed to derive public key"); + + // if N-of-N, then the base privkey will be used directly to make the account's share of the final key + if (rounds_required == 1) + { + m_multisig_privkeys.clear(); + m_multisig_privkeys.emplace_back(m_base_privkey); + } + + // exclude all keys the local account recommends + // - in the first round, only the local pubkey is recommended by the local signer + exclude_pubkeys_out.emplace_back(m_base_pubkey); + } + else + { + // in other rounds, kex msgs will contain participants' shared keys + + // ignore shared keys the account helped create for this round + for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map) + { + exclude_pubkeys_out.emplace_back(shared_key_with_origins.first); + } + } + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::finalize_kex_update(const std::uint32_t rounds_required, + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map) + { + // prepare for next round (or complete the multisig account fully) + if (rounds_required == m_kex_rounds_complete + 1) + { + // finished (have set of msgs to complete address) + + // when 'completing the final round', result keys are other signers' shares of the final key + std::vector<crypto::public_key> result_keys; + result_keys.reserve(result_keys_to_origins_map.size()); + + for (const auto &result_key_and_origins : result_keys_to_origins_map) + { + result_keys.emplace_back(result_key_and_origins.first); + } + + // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied + m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys); + + // no longer need the account's pubkeys saved for this round (they were only used to build exclude_pubkeys) + // TODO: record [pre-aggregation pubkeys : origins] map for aggregation-style signing + m_kex_keys_to_origins_map.clear(); + } + else if (rounds_required == m_kex_rounds_complete + 2) + { + // one more round (must send/receive one more set of kex msgs) + // - at this point, have local signer's pre-aggregation private key shares of the final address + + // result keys are the local signer's DH derivations for the next round + + // derivations are shared secrets between each group of N - M + 1 signers of which the local account is a member + // - convert them to private keys: multisig_key = H(derivation) + // - note: shared key = multisig_key[i]*G is recorded in the kex msg for sending to other participants + // instead of the original 'derivation' value (which MUST be kept secret!) + m_multisig_privkeys.clear(); + m_multisig_privkeys.reserve(result_keys_to_origins_map.size()); + + m_kex_keys_to_origins_map.clear(); + + for (const auto &derivation_and_origins : result_keys_to_origins_map) + { + // multisig_privkey = H(derivation) + // derived pubkey = multisig_key * G + crypto::public_key_memsafe derived_pubkey; + m_multisig_privkeys.push_back( + calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)); + + // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key] + m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second); + } + } + else + { + // next round is an 'intermediate' key exchange round, so there is nothing special to do here + + // save the account's kex keys for this round [DH derivation : other signers who will have the same derivation] + m_kex_keys_to_origins_map = std::move(result_keys_to_origins_map); + } + + // a full set of msgs has been collected and processed, so the 'round is complete' + ++m_kex_rounds_complete; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) + { + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "No key exchange messages passed in."); + + const std::uint32_t rounds_required = multisig_kex_rounds_required(m_signers.size(), m_threshold); + CHECK_AND_ASSERT_THROW_MES(rounds_required > 0, "Multisig kex rounds required unexpectedly 0."); + + // initialize account update + std::vector<crypto::public_key> exclude_pubkeys; + initialize_kex_update(expanded_msgs, rounds_required, exclude_pubkeys); + + // evaluate messages and get this account's kex msg for the next round + std::unordered_map<crypto::public_key_memsafe, std::unordered_set<crypto::public_key>> result_keys_to_origins_map; + + m_next_round_kex_message = multisig_kex_process_round( + m_base_privkey, + m_base_pubkey, + m_kex_rounds_complete + 1, + m_threshold, + m_signers, + expanded_msgs, + exclude_pubkeys, + result_keys_to_origins_map).get_msg(); + + // finish account update + finalize_kex_update(rounds_required, std::move(result_keys_to_origins_map)); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.cpp b/src/multisig/multisig_kex_msg.cpp new file mode 100644 index 000000000..2bbceb19d --- /dev/null +++ b/src/multisig/multisig_kex_msg.cpp @@ -0,0 +1,290 @@ +// Copyright (c) 2021, 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 "multisig_kex_msg.h" +#include "multisig_kex_msg_serialization.h" + +#include "common/base58.h" +#include "crypto/crypto.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "include_base_utils.h" +#include "ringct/rctOps.h" +#include "serialization/binary_archive.h" +#include "serialization/serialization.h" + +#include <boost/utility/string_ref.hpp> + +#include <sstream> +#include <utility> +#include <vector> + + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "multisig" + +const boost::string_ref MULTISIG_KEX_V1_MAGIC{"MultisigV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V1_MAGIC{"MultisigxV1"}; +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_1{"MultisigxV2R1"}; //round 1 +const boost::string_ref MULTISIG_KEX_MSG_V2_MAGIC_N{"MultisigxV2Rn"}; //round n > 1 + +namespace multisig +{ + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey) : + m_kex_round{round} + { + CHECK_AND_ASSERT_THROW_MES(round > 0, "Kex round must be > 0."); + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&signing_privkey) == 0 && + signing_privkey != crypto::null_skey, "Invalid msg signing key."); + + if (round == 1) + { + CHECK_AND_ASSERT_THROW_MES(sc_check((const unsigned char*)&msg_privkey) == 0 && + msg_privkey != crypto::null_skey, "Invalid msg privkey."); + + m_msg_privkey = msg_privkey; + } + else + { + for (const auto &pubkey : msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey for message was invalid."); + CHECK_AND_ASSERT_THROW_MES((rct::scalarmultKey(rct::pk2rct(pubkey), rct::curveOrder()) == rct::identity()), + "Pubkey for message was not in prime subgroup."); + } + + m_msg_pubkeys = std::move(msg_pubkeys); + } + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signing_privkey, m_signing_pubkey), + "Failed to derive public key"); + + // sets message and signing pub key + construct_msg(signing_privkey); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + multisig_kex_msg::multisig_kex_msg(std::string msg) : m_msg{std::move(msg)} + { + parse_and_validate_msg(); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + crypto::hash multisig_kex_msg::get_msg_to_sign() const + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + /// + + std::string data; + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + data.reserve(MULTISIG_KEX_MSG_V2_MAGIC_1.size() + 4 + 32*(1 + (m_kex_round == 1 ? 1 : 0) + m_msg_pubkeys.size())); + + // versioning domain-sep + if (m_kex_round == 1) + data.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + else + data.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + // kex_round as little-endian bytes + for (std::size_t i{0}; i < 4; ++i) + { + data += static_cast<char>(m_kex_round >> i*8); + } + + // signing pubkey + data.append((const char *)&m_signing_pubkey, sizeof(crypto::public_key)); + + // add msg privkey if kex_round == 1 + if (m_kex_round == 1) + data.append((const char *)&m_msg_privkey, sizeof(crypto::secret_key)); + else + { + // only add pubkeys if not round 1 + + // msg pubkeys + for (const auto &key : m_msg_pubkeys) + data.append((const char *)&key, sizeof(crypto::public_key)); + } + + // message to sign + crypto::hash hash; + crypto::cn_fast_hash(data.data(), data.size(), hash); + + return hash; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::construct_msg(const crypto::secret_key &signing_privkey) + { + //// + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // sign_msg = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | serialize(msg_content | crypto_sig[signing_privkey](sign_msg)) + /// + + // sign the message + crypto::signature msg_signature; + crypto::hash msg_to_sign{get_msg_to_sign()}; + crypto::generate_signature(msg_to_sign, m_signing_pubkey, signing_privkey, msg_signature); + + // assemble the message + m_msg.clear(); + + std::stringstream serialized_msg_ss; + binary_archive<true> b_archive(serialized_msg_ss); + + if (m_kex_round == 1) + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_1.data(), MULTISIG_KEX_MSG_V2_MAGIC_1.size()); + + multisig_kex_msg_serializable_round1 msg_serializable; + msg_serializable.msg_privkey = m_msg_privkey; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + else + { + m_msg.append(MULTISIG_KEX_MSG_V2_MAGIC_N.data(), MULTISIG_KEX_MSG_V2_MAGIC_N.size()); + + multisig_kex_msg_serializable_general msg_serializable; + msg_serializable.kex_round = m_kex_round; + msg_serializable.msg_pubkeys = m_msg_pubkeys; + msg_serializable.signing_pubkey = m_signing_pubkey; + msg_serializable.signature = msg_signature; + + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(b_archive, msg_serializable), + "Failed to serialize multisig kex msg"); + } + + m_msg.append(tools::base58::encode(serialized_msg_ss.str())); + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_kex_msg: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_kex_msg::parse_and_validate_msg() + { + // check message type + CHECK_AND_ASSERT_THROW_MES(m_msg.size() > 0, "Kex message unexpectedly empty."); + CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_V1_MAGIC.size()) != MULTISIG_KEX_V1_MAGIC, + "V1 multisig kex messages are deprecated (unsafe)."); + CHECK_AND_ASSERT_THROW_MES(m_msg.substr(0, MULTISIG_KEX_MSG_V1_MAGIC.size()) != MULTISIG_KEX_MSG_V1_MAGIC, + "V1 multisig kex messages are deprecated (unsafe)."); + + // deserialize the message + std::string msg_no_magic; + CHECK_AND_ASSERT_THROW_MES(MULTISIG_KEX_MSG_V2_MAGIC_1.size() == MULTISIG_KEX_MSG_V2_MAGIC_N.size(), + "Multisig kex msg magic inconsistency."); + CHECK_AND_ASSERT_THROW_MES(tools::base58::decode(m_msg.substr(MULTISIG_KEX_MSG_V2_MAGIC_1.size()), msg_no_magic), + "Multisig kex msg decoding error."); + binary_archive<false> b_archive{epee::strspan<std::uint8_t>(msg_no_magic)}; + crypto::signature msg_signature; + + if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_1.size()) == MULTISIG_KEX_MSG_V2_MAGIC_1) + { + // try round 1 message + multisig_kex_msg_serializable_round1 kex_msg_rnd1; + + if (::serialization::serialize(b_archive, kex_msg_rnd1)) + { + // in round 1 the message stores a private ancillary key component for the multisig account + // that will be shared by all participants (e.g. a shared private view key) + m_kex_round = 1; + m_msg_privkey = kex_msg_rnd1.msg_privkey; + m_signing_pubkey = kex_msg_rnd1.signing_pubkey; + msg_signature = kex_msg_rnd1.signature; + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else if (m_msg.substr(0, MULTISIG_KEX_MSG_V2_MAGIC_N.size()) == MULTISIG_KEX_MSG_V2_MAGIC_N) + { + // try general message + multisig_kex_msg_serializable_general kex_msg_general; + + if (::serialization::serialize(b_archive, kex_msg_general)) + { + m_kex_round = kex_msg_general.kex_round; + m_msg_privkey = crypto::null_skey; + m_msg_pubkeys = std::move(kex_msg_general.msg_pubkeys); + m_signing_pubkey = kex_msg_general.signing_pubkey; + msg_signature = kex_msg_general.signature; + + CHECK_AND_ASSERT_THROW_MES(m_kex_round > 1, "Invalid kex message round (must be > 1 for the general msg type)."); + } + else + { + CHECK_AND_ASSERT_THROW_MES(false, "Deserializing kex msg failed."); + } + } + else + { + // unknown message type + CHECK_AND_ASSERT_THROW_MES(false, "Only v2 multisig kex messages are supported."); + } + + // checks + for (const auto &pubkey: m_msg_pubkeys) + { + CHECK_AND_ASSERT_THROW_MES(pubkey != crypto::null_pkey && pubkey != rct::rct2pk(rct::identity()), + "Pubkey from message was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(pubkey)), + "Pubkey from message was not in prime subgroup."); + } + + CHECK_AND_ASSERT_THROW_MES(m_signing_pubkey != crypto::null_pkey && m_signing_pubkey != rct::rct2pk(rct::identity()), + "Message signing key was invalid."); + CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(m_signing_pubkey)), + "Message signing key was not in prime subgroup."); + + // validate signature + crypto::hash signed_msg{get_msg_to_sign()}; + CHECK_AND_ASSERT_THROW_MES(crypto::check_signature(signed_msg, m_signing_pubkey, msg_signature), + "Multisig kex msg signature invalid."); + } + //---------------------------------------------------------------------------------------------------------------------- +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg.h b/src/multisig/multisig_kex_msg.h new file mode 100644 index 000000000..23e3042f2 --- /dev/null +++ b/src/multisig/multisig_kex_msg.h @@ -0,0 +1,109 @@ +// Copyright (c) 2021, 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 "crypto/crypto.h" + +#include <cstdint> +#include <vector> + + +namespace multisig +{ + //// + // multisig key exchange message + // - can parse and validate an input message + // - can construct and sign a new message + // + // msg_content = kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + // msg_to_sign = versioning-domain-sep | msg_content + // msg = versioning-domain-sep | b58(msg_content | crypto_sig[signing_privkey](msg_to_sign)) + // + // note: round 1 messages will contain a private key (e.g. for the aggregate multisig private view key) + /// + class multisig_kex_msg final + { + //member types: none + + //constructors + public: + // default constructor + multisig_kex_msg() = default; + + // construct from info + multisig_kex_msg(const std::uint32_t round, + const crypto::secret_key &signing_privkey, + std::vector<crypto::public_key> msg_pubkeys, + const crypto::secret_key &msg_privkey = crypto::null_skey); + + // construct from string + multisig_kex_msg(std::string msg); + + // copy constructor: default + + //destructor: default + ~multisig_kex_msg() = default; + + //overloaded operators: none + + //member functions + // get msg string + const std::string& get_msg() const { return m_msg; } + // get kex round + std::uint32_t get_round() const { return m_kex_round; } + // get msg pubkeys + const std::vector<crypto::public_key>& get_msg_pubkeys() const { return m_msg_pubkeys; } + // get msg privkey + const crypto::secret_key& get_msg_privkey() const { return m_msg_privkey; } + // get msg signing pubkey + const crypto::public_key& get_signing_pubkey() const { return m_signing_pubkey; } + + private: + // msg_to_sign = versioning-domain-sep | kex_round | signing_pubkey | expand(msg_pubkeys) | OPTIONAL msg_privkey + crypto::hash get_msg_to_sign() const; + // set: msg string based on msg contents, signing pubkey based on input privkey + void construct_msg(const crypto::secret_key &signing_privkey); + // parse msg string into parts, validate contents and signature + void parse_and_validate_msg(); + + //member variables + private: + // message as string + std::string m_msg; + + // key exchange round this msg was produced for + std::uint32_t m_kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> m_msg_pubkeys; + // privkey stored in msg (if kex round 1) + crypto::secret_key m_msg_privkey; + // pubkey used to sign this msg + crypto::public_key m_signing_pubkey; + }; +} //namespace multisig diff --git a/src/multisig/multisig_kex_msg_serialization.h b/src/multisig/multisig_kex_msg_serialization.h new file mode 100644 index 000000000..9c7b993a7 --- /dev/null +++ b/src/multisig/multisig_kex_msg_serialization.h @@ -0,0 +1,78 @@ +// Copyright (c) 2021, 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 "crypto/crypto.h" +#include "serialization/containers.h" +#include "serialization/crypto.h" +#include "serialization/serialization.h" + +#include <cstdint> +#include <vector> + + +namespace multisig +{ + /// round 1 kex message + struct multisig_kex_msg_serializable_round1 + { + // privkey stored in msg + crypto::secret_key msg_privkey; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; + + BEGIN_SERIALIZE() + FIELD(msg_privkey) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; + + /// general kex message (if round > 1) + struct multisig_kex_msg_serializable_general + { + // key exchange round this msg was produced for + std::uint32_t kex_round; + // pubkeys stored in msg + std::vector<crypto::public_key> msg_pubkeys; + // pubkey used to sign this msg + crypto::public_key signing_pubkey; + // message signature + crypto::signature signature; + + BEGIN_SERIALIZE() + VARINT_FIELD(kex_round) + FIELD(msg_pubkeys) + FIELD(signing_pubkey) + FIELD(signature) + END_SERIALIZE() + }; +} //namespace multisig diff --git a/src/net/socks.cpp b/src/net/socks.cpp index c2330bd41..6463e669e 100644 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -321,8 +321,9 @@ namespace socks { if (self && self->proxy_.is_open()) { - self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - self->proxy_.close(); + boost::system::error_code ec; + self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + self->proxy_.close(ec); } }); } diff --git a/src/net/socks.h b/src/net/socks.h index 739c972ab..506b53195 100644 --- a/src/net/socks.h +++ b/src/net/socks.h @@ -201,6 +201,13 @@ namespace socks std::shared_ptr<client> self_; void operator()(boost::system::error_code error = boost::system::error_code{}); }; + + //! Calls `async_close` on `self` at destruction. NOP if `nullptr`. + struct close_on_exit + { + std::shared_ptr<client> self; + ~close_on_exit() { async_close{std::move(self)}(); } + }; }; template<typename Handler> diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index c951db085..d9050200a 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -342,6 +342,7 @@ namespace nodetool } }; + net::socks::client::close_on_exit close_client{}; boost::unique_future<client_result> socks_result{}; { boost::promise<client_result> socks_promise{}; @@ -350,6 +351,7 @@ namespace nodetool auto client = net::socks::make_connect_client( boost::asio::ip::tcp::socket{service}, net::socks::version::v4a, notify{std::move(socks_promise)} ); + close_client.self = client; if (!start_socks(std::move(client), proxy, remote)) return boost::none; } @@ -371,7 +373,10 @@ namespace nodetool { auto result = socks_result.get(); if (!result.first) + { + close_client.self.reset(); return {std::move(result.second)}; + } MERROR("Failed to make socks connection to " << remote.str() << " (via " << proxy << "): " << result.first.message()); } diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 9e64121be..ac815a100 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -111,15 +111,11 @@ namespace nodetool struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { p2p_connection_context_t() - : fluff_txs(), - flush_time(std::chrono::steady_clock::time_point::max()), - peer_id(0), + : peer_id(0), support_flags(0), m_in_timedsync(false) {} - std::vector<cryptonote::blobdata> fluff_txs; - std::chrono::steady_clock::time_point flush_time; peerid_type peer_id; uint32_t support_flags; bool m_in_timedsync; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index ac65a57c1..199601d00 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -696,14 +696,14 @@ namespace nodetool { full_addrs.insert("212.83.175.67:28080"); full_addrs.insert("212.83.172.165:28080"); - full_addrs.insert("192.110.160.146:28080"); + full_addrs.insert("176.9.0.187:28080"); full_addrs.insert("88.99.173.38:28080"); full_addrs.insert("51.79.173.165:28080"); } else if (m_nettype == cryptonote::STAGENET) { full_addrs.insert("162.210.173.150:38080"); - full_addrs.insert("192.110.160.146:38080"); + full_addrs.insert("176.9.0.187:38080"); full_addrs.insert("88.99.173.38:38080"); full_addrs.insert("51.79.173.165:38080"); } @@ -714,10 +714,10 @@ namespace nodetool { full_addrs.insert("212.83.175.67:18080"); full_addrs.insert("212.83.172.165:18080"); - full_addrs.insert("192.110.160.146:18080"); + full_addrs.insert("176.9.0.187:18080"); full_addrs.insert("88.198.163.90:18080"); full_addrs.insert("95.217.25.101:18080"); - full_addrs.insert("209.250.243.248:18080"); + full_addrs.insert("136.244.105.131:18080"); full_addrs.insert("104.238.221.81:18080"); full_addrs.insert("66.85.74.134:18080"); full_addrs.insert("88.99.173.38:18080"); @@ -1429,6 +1429,7 @@ namespace nodetool ape.first_seen = first_seen_stamp ? first_seen_stamp : time(nullptr); zone.m_peerlist.append_with_peer_anchor(ape); + zone.m_notifier.on_handshake_complete(con->m_connection_id, con->m_is_income); zone.m_notifier.new_out_connection(); LOG_DEBUG_CC(*con, "CONNECTION HANDSHAKED OK."); @@ -2543,6 +2544,8 @@ namespace nodetool return 1; } + zone.m_notifier.on_handshake_complete(context.m_connection_id, context.m_is_income); + if(has_too_many_connections(context.m_remote_address)) { LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address"); @@ -2669,6 +2672,9 @@ namespace nodetool zone.m_peerlist.remove_from_peer_anchor(na); } + if (!zone.m_net_server.is_stop_signal_sent()) { + zone.m_notifier.on_connection_close(context.m_connection_id); + } m_payload_handler.on_connection_close(context); MINFO("["<< epee::net_utils::print_connection_context(context) << "] CLOSE CONNECTION"); diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index 40b2dfd55..32da0f5f5 100644 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -31,13 +31,15 @@ set(ringct_basic_sources rctTypes.cpp rctCryptoOps.c multiexp.cc - bulletproofs.cc) + bulletproofs.cc + bulletproofs_plus.cc) set(ringct_basic_private_headers rctOps.h rctTypes.h multiexp.h - bulletproofs.h) + bulletproofs.h + bulletproofs_plus.h) monero_private_headers(ringct_basic ${crypto_private_headers}) diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index a6e12c9b3..1689e5463 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -70,13 +70,12 @@ static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS; -static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr<straus_cached_data> straus_HiGi_cache; static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; -static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; -static const rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; -static const rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; +static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; +static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; +static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); static const rct::key ip12 = inner_product(oneN, twoN); @@ -100,8 +99,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT); - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_EXPONENT + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -121,10 +119,10 @@ static void init_exponents() data.reserve(maxN*maxM*2); for (size_t i = 0; i < maxN*maxM; ++i) { - Hi[i] = get_exponent(rct::H, i * 2); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed"); - Gi[i] = get_exponent(rct::H, i * 2 + 1); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Hi = get_exponent(rct::H, i * 2); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Gi = get_exponent(rct::H, i * 2 + 1); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed"); data.push_back({rct::zero(), Gi_p3[i]}); data.push_back({rct::zero(), Hi_p3[i]}); @@ -133,11 +131,10 @@ static void init_exponents() straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); - MINFO("Hi/Gi cache size: " << (sizeof(Hi)+sizeof(Gi))/1024 << " kB"); MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB"); MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB"); MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB"); - size_t cache_size = (sizeof(Hi)+sizeof(Hi_p3))*2 + straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); + size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Total cache size: " << cache_size/1024 << "kB"); init_done = true; } @@ -895,7 +892,8 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); - const std::vector<rct::key> inverses = invert(to_invert); + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert); // setup weighted aggregates diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc new file mode 100644 index 000000000..3d27849c1 --- /dev/null +++ b/src/ringct/bulletproofs_plus.cc @@ -0,0 +1,1121 @@ +// Copyright (c) 2017-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Implements the Bulletproofs+ prover and verifier algorithms +// +// Preprint: https://eprint.iacr.org/2020/735, version 17 Jun 2020 +// +// NOTE ON NOTATION: +// In the signature constructions used in Monero, commitments to zero are treated as +// public keys against the curve group generator `G`. This means that amount +// commitments must use another generator `H` for values in order to show balance. +// The result is that the roles of `g` and `h` in the preprint are effectively swapped +// in this code, taking on the roles of `H` and `G`, respectively. Read carefully! + +#include <stdlib.h> +#include <boost/thread/mutex.hpp> +#include <boost/thread/lock_guard.hpp> +#include "misc_log_ex.h" +#include "span.h" +#include "cryptonote_config.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "rctOps.h" +#include "multiexp.h" +#include "bulletproofs_plus.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "bulletproof_plus" + +#define STRAUS_SIZE_LIMIT 232 +#define PIPPENGER_SIZE_LIMIT 0 + +namespace rct +{ + // Vector functions + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n); + + // Proof bounds + static constexpr size_t maxN = 64; // maximum number of bits in range + static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + + // Cached public generators + static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; + static std::shared_ptr<straus_cached_data> straus_HiGi_cache; + static std::shared_ptr<pippenger_cached_data> pippenger_HiGi_cache; + + // Useful scalar constants + static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 + static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 + static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 + static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 + static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) + static rct::key TWO_SIXTY_FOUR_MINUS_ONE; // 2**64 - 1 + + // Initial transcript hash + static rct::key initial_transcript; + + static boost::mutex init_mutex; + + // Use the generator caches to compute a multiscalar multiplication + static inline rct::key multiexp(const std::vector<MultiexpData> &data, size_t HiGi_size) + { + if (HiGi_size > 0) + { + static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT"); + return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size())); + } + else + { + return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size())); + } + } + + // Confirm that a scalar is properly reduced + static inline bool is_reduced(const rct::key &scalar) + { + return sc_check(scalar.bytes) == 0; + } + + // Use hashed values to produce indexed public generators + static ge_p3 get_exponent(const rct::key &base, size_t idx) + { + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + rct::key generator; + ge_p3 generator_p3; + rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); + ge_p3_tobytes(generator.bytes, &generator_p3); + CHECK_AND_ASSERT_THROW_MES(!(generator == rct::identity()), "Exponent is point at infinity"); + return generator_p3; + } + + // Construct public generators + static void init_exponents() + { + boost::lock_guard<boost::mutex> lock(init_mutex); + + // Only needs to be done once + static bool init_done = false; + if (init_done) + return; + + std::vector<MultiexpData> data; + data.reserve(maxN*maxM*2); + for (size_t i = 0; i < maxN*maxM; ++i) + { + Hi_p3[i] = get_exponent(rct::H, i * 2); + Gi_p3[i] = get_exponent(rct::H, i * 2 + 1); + + data.push_back({rct::zero(), Gi_p3[i]}); + data.push_back({rct::zero(), Hi_p3[i]}); + } + + straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); + pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); + + // Compute 2**64 - 1 for later use in simplifying verification + TWO_SIXTY_FOUR_MINUS_ONE = TWO; + for (size_t i = 0; i < 6; i++) + { + sc_mul(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes); + } + sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); + + // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs + const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + ge_p3 initial_transcript_p3; + rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); + ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); + + init_done = true; + } + + // Given two scalar arrays, construct a vector pre-commitment: + // + // a = (a_0, ..., a_{n-1}) + // b = (b_0, ..., b_{n-1}) + // + // Outputs a_0*Gi_0 + ... + a_{n-1}*Gi_{n-1} + + // b_0*Hi_0 + ... + b_{n-1}*Hi_{n-1} + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(a.size()*2); + for (size_t i = 0; i < a.size(); ++i) + { + multiexp_data.emplace_back(a[i], Gi_p3[i]); + multiexp_data.emplace_back(b[i], Hi_p3[i]); + } + return multiexp(multiexp_data, 2 * a.size()); + } + + // Helper function used to compute the L and R terms used in the inner-product round function + static rct::key compute_LR(size_t size, const rct::key &y, const std::vector<ge_p3> &G, size_t G0, const std::vector<ge_p3> &H, size_t H0, const rct::keyV &a, size_t a0, const rct::keyV &b, size_t b0, const rct::key &c, const rct::key &d) + { + CHECK_AND_ASSERT_THROW_MES(size + G0 <= G.size(), "Incompatible size for G"); + CHECK_AND_ASSERT_THROW_MES(size + H0 <= H.size(), "Incompatible size for H"); + CHECK_AND_ASSERT_THROW_MES(size + a0 <= a.size(), "Incompatible size for a"); + CHECK_AND_ASSERT_THROW_MES(size + b0 <= b.size(), "Incompatible size for b"); + CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large"); + + std::vector<MultiexpData> multiexp_data; + multiexp_data.resize(size*2 + 2); + rct::key temp; + for (size_t i = 0; i < size; ++i) + { + sc_mul(temp.bytes, a[a0+i].bytes, y.bytes); + sc_mul(multiexp_data[i*2].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + multiexp_data[i*2].point = G[G0+i]; + + sc_mul(multiexp_data[i*2+1].scalar.bytes, b[b0+i].bytes, INV_EIGHT.bytes); + multiexp_data[i*2+1].point = H[H0+i]; + } + + sc_mul(multiexp_data[2*size].scalar.bytes, c.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + multiexp_data[2*size].point = H_p3; + + sc_mul(multiexp_data[2*size+1].scalar.bytes, d.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + multiexp_data[2*size+1].point = G_p3; + + return multiexp(multiexp_data, 0); + } + + // Given a scalar, construct a vector of its powers: + // + // Output (1,x,x**2,...,x**{n-1}) + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::keyV res(n); + res[0] = rct::identity(); + if (n == 1) + return res; + res[1] = x; + for (size_t i = 2; i < n; ++i) + { + sc_mul(res[i].bytes, res[i-1].bytes, x.bytes); + } + return res; + } + + // Given a scalar, construct the sum of its powers from 2 to n (where n is a power of 2): + // + // Output x**2 + x**4 + x**6 + ... + x**n + static rct::key sum_of_even_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES((n & (n - 1)) == 0, "Need n to be a power of 2"); + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key x1 = copy(x); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + + rct::key res = copy(x1); + while (n > 2) + { + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + n /= 2; + } + + return res; + } + + // Given a scalar, return the sum of its powers from 1 to n + // + // Output x**1 + x**2 + x**3 + ... + x**n + static rct::key sum_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key res = ONE; + if (n == 1) + return x; + + n += 1; + rct::key x1 = copy(x); + + const bool is_power_of_2 = (n & (n - 1)) == 0; + if (is_power_of_2) + { + sc_add(res.bytes, res.bytes, x1.bytes); + while (n > 2) + { + sc_mul(x1.bytes, x1.bytes, x1.bytes); + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + n /= 2; + } + } + else + { + rct::key prev = x1; + for (size_t i = 1; i < n; ++i) + { + if (i > 1) + sc_mul(prev.bytes, prev.bytes, x1.bytes); + sc_add(res.bytes, res.bytes, prev.bytes); + } + } + sc_sub(res.bytes, res.bytes, ONE.bytes); + + return res; + } + + // Given two scalar arrays, construct the weighted inner product against another scalar + // + // Output a_0*b_0*y**1 + a_1*b_1*y**2 + ... + a_{n-1}*b_{n-1}*y**n + static rct::key weighted_inner_product(const epee::span<const rct::key> &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::key res = rct::zero(); + rct::key y_power = ONE; + rct::key temp; + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(temp.bytes, a[i].bytes, b[i].bytes); + sc_mul(y_power.bytes, y_power.bytes, y.bytes); + sc_muladd(res.bytes, temp.bytes, y_power.bytes, res.bytes); + } + return res; + } + + static rct::key weighted_inner_product(const rct::keyV &a, const epee::span<const rct::key> &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + return weighted_inner_product(epee::to_span(a), b, y); + } + + // Fold inner-product point vectors + static void hadamard_fold(std::vector<ge_p3> &v, const rct::key &a, const rct::key &b) + { + CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even"); + const size_t sz = v.size() / 2; + for (size_t n = 0; n < sz; ++n) + { + ge_dsmp c[2]; + ge_dsm_precomp(c[0], &v[n]); + ge_dsm_precomp(c[1], &v[sz + n]); + ge_double_scalarmult_precomp_vartime2_p3(&v[n], a.bytes, c[0], b.bytes, c[1]); + } + v.resize(sz); + } + + // Add vectors componentwise + static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b[i].bytes); + } + return res; + } + + // Add a scalar to all elements of a vector + static rct::keyV vector_add(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Subtract a scalar from all elements of a vector + static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_sub(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Multiply a scalar by all elements of a vector + static rct::keyV vector_scalar(const epee::span<const rct::key> &a, const rct::key &x) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(res[i].bytes, a[i].bytes, x.bytes); + } + return res; + } + + // Inversion helper function + static rct::key sm(rct::key y, int n, const rct::key &x) + { + while (n--) + sc_mul(y.bytes, y.bytes, y.bytes); + sc_mul(y.bytes, y.bytes, x.bytes); + return y; + } + + // Compute the inverse of a nonzero + static rct::key invert(const rct::key &x) + { + CHECK_AND_ASSERT_THROW_MES(!(x == ZERO), "Cannot invert zero!"); + rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111; + + _1 = x; + sc_mul(_10.bytes, _1.bytes, _1.bytes); + sc_mul(_100.bytes, _10.bytes, _10.bytes); + sc_mul(_11.bytes, _10.bytes, _1.bytes); + sc_mul(_101.bytes, _10.bytes, _11.bytes); + sc_mul(_111.bytes, _10.bytes, _101.bytes); + sc_mul(_1001.bytes, _10.bytes, _111.bytes); + sc_mul(_1011.bytes, _10.bytes, _1001.bytes); + sc_mul(_1111.bytes, _100.bytes, _1011.bytes); + + rct::key inv; + sc_mul(inv.bytes, _1111.bytes, _1.bytes); + + inv = sm(inv, 123 + 3, _101); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 4, _1001); + inv = sm(inv, 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 3, _101); + inv = sm(inv, 3 + 3, _101); + inv = sm(inv, 3, _111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 2 + 3, _111); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 2 + 4, _1011); + inv = sm(inv, 6 + 4, _1001); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 1 + 4, _1001); + inv = sm(inv, 1 + 3, _111); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 3, _101); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 3, _101); + inv = sm(inv, 1 + 2, _11); + + return inv; + } + + // Invert a batch of scalars, all of which _must_ be nonzero + static rct::keyV invert(rct::keyV x) + { + rct::keyV scratch; + scratch.reserve(x.size()); + + rct::key acc = rct::identity(); + for (size_t n = 0; n < x.size(); ++n) + { + CHECK_AND_ASSERT_THROW_MES(!(x[n] == ZERO), "Cannot invert zero!"); + scratch.push_back(acc); + if (n == 0) + acc = x[0]; + else + sc_mul(acc.bytes, acc.bytes, x[n].bytes); + } + + acc = invert(acc); + + rct::key tmp; + for (int i = x.size(); i-- > 0; ) + { + sc_mul(tmp.bytes, acc.bytes, x[i].bytes); + sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes); + acc = tmp; + } + + return x; + } + + // Compute the slice of a vector + static epee::span<const rct::key> slice(const rct::keyV &a, size_t start, size_t stop) + { + CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index"); + CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index"); + CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices"); + return epee::span<const rct::key>(&a[start], stop - start); + } + + // Update the transcript + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0) + { + rct::key data[2]; + data[0] = transcript; + data[1] = update_0; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0, const rct::key &update_1) + { + rct::key data[3]; + data[0] = transcript; + data[1] = update_0; + data[2] = update_1; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + // Given a value v [0..2**N) and a mask gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma) + { + return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); + } + + BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma) + { + return bulletproof_plus_PROVE(std::vector<uint64_t>(1, v), rct::keyV(1, gamma)); + } + + // Given a set of values v [0..2**N) and masks gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma) + { + // Sanity check on inputs + CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma"); + CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty"); + for (const rct::key &sve: sv) + CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input"); + for (const rct::key &g: gamma) + CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input"); + + init_exponents(); + + // Useful proof bounds + // + // N: number of bits in each range (here, 64) + // logN: base-2 logarithm + // M: first power of 2 greater than or equal to the number of range proofs to aggregate + // logM: base-2 logarithm + constexpr size_t logN = 6; // log2(64) + constexpr size_t N = 1<<logN; + size_t M, logM; + for (logM = 0; (M = 1<<logM) <= maxM && M < sv.size(); ++logM); + CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv/gamma are too large"); + const size_t logMN = logM + logN; + const size_t MN = M * N; + + rct::keyV V(sv.size()); + rct::keyV aL(MN), aR(MN); + rct::keyV aL8(MN), aR8(MN); + rct::key temp; + rct::key temp2; + + // Prepare output commitments and offset by a factor of 8**(-1) + // + // This offset is applied to other group elements as well; + // it allows us to apply a multiply-by-8 operation in the verifier efficiently + // to ensure that the resulting group elements are in the prime-order point subgroup + // and avoid much more constly multiply-by-group-order operations. + for (size_t i = 0; i < sv.size(); ++i) + { + rct::key gamma8, sv8; + sc_mul(gamma8.bytes, gamma[i].bytes, INV_EIGHT.bytes); + sc_mul(sv8.bytes, sv[i].bytes, INV_EIGHT.bytes); + rct::addKeys2(V[i], gamma8, sv8, rct::H); + } + + // Decompose values + // + // Note that this effectively pads the set to a power of 2, which is required for the inner-product argument later. + for (size_t j = 0; j < M; ++j) + { + for (size_t i = N; i-- > 0; ) + { + if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8)))) + { + aL[j*N+i] = rct::identity(); + aL8[j*N+i] = INV_EIGHT; + aR[j*N+i] = aR8[j*N+i] = rct::zero(); + } + else + { + aL[j*N+i] = aL8[j*N+i] = rct::zero(); + aR[j*N+i] = MINUS_ONE; + aR8[j*N+i] = MINUS_INV_EIGHT; + } + } + } + +try_again: + // This is a Fiat-Shamir transcript + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(V)); + + // A + rct::key alpha = rct::skGen(); + rct::key pre_A = vector_exponent(aL8, aR8); + rct::key A; + sc_mul(temp.bytes, alpha.bytes, INV_EIGHT.bytes); + rct::addKeys(A, pre_A, rct::scalarmultBase(temp)); + + // Challenges + rct::key y = transcript_update(transcript, A); + if (y == rct::zero()) + { + MINFO("y is 0, trying again"); + goto try_again; + } + rct::key z = transcript = rct::hash_to_scalar(y); + if (z == rct::zero()) + { + MINFO("z is 0, trying again"); + goto try_again; + } + rct::key z_squared; + sc_mul(z_squared.bytes, z.bytes, z.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + // + // We compute this iteratively in order to reduce scalar operations. + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_mul(d[i].bytes, d[i-1].bytes, TWO.bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + rct::keyV y_powers = vector_of_scalar_powers(y, MN+2); + + // Prepare inner product terms + rct::keyV aL1 = vector_subtract(aL, z); + + rct::keyV aR1 = vector_add(aR, z); + rct::keyV d_y(MN); + for (size_t i = 0; i < MN; i++) + { + sc_mul(d_y[i].bytes, d[i].bytes, y_powers[MN-i].bytes); + } + aR1 = vector_add(aR1, d_y); + + rct::key alpha1 = alpha; + temp = ONE; + for (size_t j = 0; j < sv.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes); + sc_mul(temp2.bytes, temp2.bytes, gamma[j].bytes); + sc_add(alpha1.bytes, alpha1.bytes, temp2.bytes); + } + + // These are used in the inner product rounds + size_t nprime = MN; + std::vector<ge_p3> Gprime(MN); + std::vector<ge_p3> Hprime(MN); + rct::keyV aprime(MN); + rct::keyV bprime(MN); + + const rct::key yinv = invert(y); + rct::keyV yinvpow(MN); + yinvpow[0] = ONE; + for (size_t i = 0; i < MN; ++i) + { + Gprime[i] = Gi_p3[i]; + Hprime[i] = Hi_p3[i]; + if (i > 0) + { + sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes); + } + aprime[i] = aL1[i]; + bprime[i] = aR1[i]; + } + rct::keyV L(logMN); + rct::keyV R(logMN); + int round = 0; + + // Inner-product rounds + while (nprime > 1) + { + nprime /= 2; + + rct::key cL = weighted_inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()), y); + rct::key cR = weighted_inner_product(vector_scalar(slice(aprime, nprime, aprime.size()), y_powers[nprime]), slice(bprime, 0, nprime), y); + + rct::key dL = rct::skGen(); + rct::key dR = rct::skGen(); + + L[round] = compute_LR(nprime, yinvpow[nprime], Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, cL, dL); + R[round] = compute_LR(nprime, y_powers[nprime], Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, cR, dR); + + const rct::key challenge = transcript_update(transcript, L[round], R[round]); + if (challenge == rct::zero()) + { + MINFO("challenge is 0, trying again"); + goto try_again; + } + + const rct::key challenge_inv = invert(challenge); + + sc_mul(temp.bytes, yinvpow[nprime].bytes, challenge.bytes); + hadamard_fold(Gprime, challenge_inv, temp); + hadamard_fold(Hprime, challenge, challenge_inv); + + sc_mul(temp.bytes, challenge_inv.bytes, y_powers[nprime].bytes); + aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), challenge), vector_scalar(slice(aprime, nprime, aprime.size()), temp)); + bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), challenge_inv), vector_scalar(slice(bprime, nprime, bprime.size()), challenge)); + + rct::key challenge_squared; + sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes); + rct::key challenge_squared_inv = invert(challenge_squared); + sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes); + sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes); + + ++round; + } + + // Final round computations + rct::key r = rct::skGen(); + rct::key s = rct::skGen(); + rct::key d_ = rct::skGen(); + rct::key eta = rct::skGen(); + + std::vector<MultiexpData> A1_data; + A1_data.reserve(4); + A1_data.resize(4); + + sc_mul(A1_data[0].scalar.bytes, r.bytes, INV_EIGHT.bytes); + A1_data[0].point = Gprime[0]; + + sc_mul(A1_data[1].scalar.bytes, s.bytes, INV_EIGHT.bytes); + A1_data[1].point = Hprime[0]; + + sc_mul(A1_data[2].scalar.bytes, d_.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + A1_data[2].point = G_p3; + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, bprime[0].bytes); + sc_mul(temp2.bytes, s.bytes, y.bytes); + sc_mul(temp2.bytes, temp2.bytes, aprime[0].bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(A1_data[3].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + A1_data[3].point = H_p3; + + rct::key A1 = multiexp(A1_data, 0); + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, s.bytes); + sc_mul(temp.bytes, temp.bytes, INV_EIGHT.bytes); + sc_mul(temp2.bytes, eta.bytes, INV_EIGHT.bytes); + rct::key B; + rct::addKeys2(B, temp2, temp, rct::H); + + rct::key e = transcript_update(transcript, A1, B); + if (e == rct::zero()) + { + MINFO("e is 0, trying again"); + goto try_again; + } + rct::key e_squared; + sc_mul(e_squared.bytes, e.bytes, e.bytes); + + rct::key r1; + sc_muladd(r1.bytes, aprime[0].bytes, e.bytes, r.bytes); + + rct::key s1; + sc_muladd(s1.bytes, bprime[0].bytes, e.bytes, s.bytes); + + rct::key d1; + sc_muladd(d1.bytes, d_.bytes, e.bytes, eta.bytes); + sc_muladd(d1.bytes, alpha1.bytes, e_squared.bytes, d1.bytes); + + return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R)); + } + + BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma) + { + CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma"); + + // vG + gammaH + rct::keyV sv(v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + sv[i] = rct::d2h(v[i]); + } + return bulletproof_plus_PROVE(sv, gamma); + } + + struct bp_plus_proof_data_t + { + rct::key y, z, e; + std::vector<rct::key> challenges; + size_t logM, inv_offset; + }; + + // Given a batch of range proofs, determine if they are all valid + bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs) + { + init_exponents(); + + const size_t logN = 6; + const size_t N = 1 << logN; + + // Set up + size_t max_length = 0; // size of each of the longest proof's inner-product vectors + size_t nV = 0; // number of output commitments across all proofs + size_t inv_offset = 0; + size_t max_logM = 0; + + std::vector<bp_plus_proof_data_t> proof_data; + proof_data.reserve(proofs.size()); + + // We'll perform only a single batch inversion across all proofs in the batch, + // since batch inversion requires only one scalar inversion operation. + std::vector<rct::key> to_invert; + to_invert.reserve(11 * proofs.size()); // maximal size, given the aggregation limit + + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + + // Sanity checks + CHECK_AND_ASSERT_MES(is_reduced(proof.r1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.s1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.d1), false, "Input scalar not in range"); + + CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element"); + CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes"); + CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof"); + + max_length = std::max(max_length, proof.L.size()); + nV += proof.V.size(); + + proof_data.push_back({}); + bp_plus_proof_data_t &pd = proof_data.back(); + + // Reconstruct the challenges + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(proof.V)); + pd.y = transcript_update(transcript, proof.A); + CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0"); + pd.z = transcript = rct::hash_to_scalar(pd.y); + CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0"); + + // Determine the number of inner-product rounds based on proof size + size_t M; + for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < proof.V.size(); ++pd.logM); + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + max_logM = std::max(pd.logM, max_logM); + + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + // The inner-product challenges are computed per round + pd.challenges.resize(rounds); + for (size_t j = 0; j < rounds; ++j) + { + pd.challenges[j] = transcript_update(transcript, proof.L[j], proof.R[j]); + CHECK_AND_ASSERT_MES(!(pd.challenges[j] == rct::zero()), false, "challenges[j] == 0"); + } + + // Final challenge + pd.e = transcript_update(transcript,proof.A1,proof.B); + CHECK_AND_ASSERT_MES(!(pd.e == rct::zero()), false, "e == 0"); + + // Batch scalar inversions + pd.inv_offset = inv_offset; + for (size_t j = 0; j < rounds; ++j) + to_invert.push_back(pd.challenges[j]); + to_invert.push_back(pd.y); + inv_offset += rounds + 1; + } + CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large"); + size_t maxMN = 1u << max_length; + + rct::key temp; + rct::key temp2; + + // Final batch proof data + std::vector<MultiexpData> multiexp_data; + multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN); + multiexp_data.resize(2 * maxMN); + + const std::vector<rct::key> inverses = invert(std::move(to_invert)); + to_invert.clear(); + + // Weights and aggregates + // + // The idea is to take the single multiscalar multiplication used in the verification + // of each proof in the batch and weight it using a random weighting factor, resulting + // in just one multiscalar multiplication check to zero for the entire batch. + // We can further simplify the verifier complexity by including common group elements + // only once in this single multiscalar multiplication. + // Common group elements' weighted scalar sums are tracked across proofs for this reason. + // + // To build a multiscalar multiplication for each proof, we use the method described in + // Section 6.1 of the preprint. Note that the result given there does not account for + // the construction of the inner-product inputs that are produced in the range proof + // verifier algorithm; we have done so here. + rct::key G_scalar = rct::zero(); + rct::key H_scalar = rct::zero(); + rct::keyV Gi_scalars(maxMN, rct::zero()); + rct::keyV Hi_scalars(maxMN, rct::zero()); + + int proof_data_index = 0; + rct::keyV challenges_cache; + std::vector<ge_p3> proof8_V, proof8_L, proof8_R; + + // Process each proof and add to the weighted batch + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + const bp_plus_proof_data_t &pd = proof_data[proof_data_index++]; + + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + const size_t M = 1 << pd.logM; + const size_t MN = M*N; + + // Random weighting factor must be nonzero, which is exceptionally unlikely! + rct::key weight = ZERO; + while (weight == ZERO) + { + weight = rct::skGen(); + } + + // Rescale previously offset proof elements + // + // This ensures that all such group elements are in the prime-order subgroup. + proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]); + proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]); + proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]); + ge_p3 proof8_A1; + ge_p3 proof8_B; + ge_p3 proof8_A; + rct::scalarmult8(proof8_A1, proof.A1); + rct::scalarmult8(proof8_B, proof.B); + rct::scalarmult8(proof8_A, proof.A); + + // Compute necessary powers of the y-challenge + rct::key y_MN = copy(pd.y); + rct::key y_MN_1; + size_t temp_MN = MN; + while (temp_MN > 1) + { + sc_mul(y_MN.bytes, y_MN.bytes, y_MN.bytes); + temp_MN /= 2; + } + sc_mul(y_MN_1.bytes, y_MN.bytes, pd.y.bytes); + + // V_j: -e**2 * z**(2*j+1) * y**(MN+1) * weight + rct::key e_squared; + sc_mul(e_squared.bytes, pd.e.bytes, pd.e.bytes); + + rct::key z_squared; + sc_mul(z_squared.bytes, pd.z.bytes, pd.z.bytes); + + sc_sub(temp.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(temp.bytes, temp.bytes, y_MN_1.bytes); + sc_mul(temp.bytes, temp.bytes, weight.bytes); + for (size_t j = 0; j < proof8_V.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + multiexp_data.emplace_back(temp, proof8_V[j]); + } + + // B: -weight + sc_mul(temp.bytes, MINUS_ONE.bytes, weight.bytes); + multiexp_data.emplace_back(temp, proof8_B); + + // A1: -weight*e + sc_mul(temp.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(temp, proof8_A1); + + // A: -weight*e*e + rct::key minus_weight_e_squared; + sc_mul(minus_weight_e_squared.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(minus_weight_e_squared, proof8_A); + + // G: weight*d1 + sc_muladd(G_scalar.bytes, weight.bytes, proof.d1.bytes, G_scalar.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_add(d[i].bytes, d[i-1].bytes, d[i-1].bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + // More efficient computation of sum(d) + rct::key sum_d; + sc_mul(sum_d.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, sum_of_even_powers(pd.z, 2*M).bytes); + + // H: weight*( r1*y*s1 + e**2*( y**(MN+1)*z*sum(d) + (z**2-z)*sum(y) ) ) + rct::key sum_y = sum_of_scalar_powers(pd.y, MN); + sc_sub(temp.bytes, z_squared.bytes, pd.z.bytes); + sc_mul(temp.bytes, temp.bytes, sum_y.bytes); + + sc_mul(temp2.bytes, y_MN_1.bytes, pd.z.bytes); + sc_mul(temp2.bytes, temp2.bytes, sum_d.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(temp.bytes, temp.bytes, e_squared.bytes); + sc_mul(temp2.bytes, proof.r1.bytes, pd.y.bytes); + sc_mul(temp2.bytes, temp2.bytes, proof.s1.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_muladd(H_scalar.bytes, temp.bytes, weight.bytes, H_scalar.bytes); + + // Compute the number of rounds for the inner-product argument + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + const rct::key *challenges_inv = &inverses[pd.inv_offset]; + const rct::key yinv = inverses[pd.inv_offset + rounds]; + + // Compute challenge products + challenges_cache.resize(1<<rounds); + challenges_cache[0] = challenges_inv[0]; + challenges_cache[1] = pd.challenges[0]; + for (size_t j = 1; j < rounds; ++j) + { + const size_t slots = 1<<(j+1); + for (size_t s = slots; s-- > 0; --s) + { + sc_mul(challenges_cache[s].bytes, challenges_cache[s/2].bytes, pd.challenges[j].bytes); + sc_mul(challenges_cache[s-1].bytes, challenges_cache[s/2].bytes, challenges_inv[j].bytes); + } + } + + // Gi and Hi + rct::key e_r1_w_y; + sc_mul(e_r1_w_y.bytes, pd.e.bytes, proof.r1.bytes); + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, weight.bytes); + rct::key e_s1_w; + sc_mul(e_s1_w.bytes, pd.e.bytes, proof.s1.bytes); + sc_mul(e_s1_w.bytes, e_s1_w.bytes, weight.bytes); + rct::key e_squared_z_w; + sc_mul(e_squared_z_w.bytes, e_squared.bytes, pd.z.bytes); + sc_mul(e_squared_z_w.bytes, e_squared_z_w.bytes, weight.bytes); + rct::key minus_e_squared_z_w; + sc_sub(minus_e_squared_z_w.bytes, ZERO.bytes, e_squared_z_w.bytes); + rct::key minus_e_squared_w_y; + sc_sub(minus_e_squared_w_y.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, weight.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, y_MN.bytes); + for (size_t i = 0; i < MN; ++i) + { + rct::key g_scalar = copy(e_r1_w_y); + rct::key h_scalar; + + // Use the binary decomposition of the index + sc_muladd(g_scalar.bytes, g_scalar.bytes, challenges_cache[i].bytes, e_squared_z_w.bytes); + sc_muladd(h_scalar.bytes, e_s1_w.bytes, challenges_cache[(~i) & (MN-1)].bytes, minus_e_squared_z_w.bytes); + + // Complete the scalar derivation + sc_add(Gi_scalars[i].bytes, Gi_scalars[i].bytes, g_scalar.bytes); + sc_muladd(h_scalar.bytes, minus_e_squared_w_y.bytes, d[i].bytes, h_scalar.bytes); + sc_add(Hi_scalars[i].bytes, Hi_scalars[i].bytes, h_scalar.bytes); + + // Update iterated values + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, yinv.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, yinv.bytes); + } + + // L_j: -weight*e*e*challenges[j]**2 + // R_j: -weight*e*e*challenges[j]**(-2) + for (size_t j = 0; j < rounds; ++j) + { + sc_mul(temp.bytes, pd.challenges[j].bytes, pd.challenges[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_L[j]); + + sc_mul(temp.bytes, challenges_inv[j].bytes, challenges_inv[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_R[j]); + } + } + + // Verify all proofs in the weighted batch + multiexp_data.emplace_back(G_scalar, rct::G); + multiexp_data.emplace_back(H_scalar, rct::H); + for (size_t i = 0; i < maxMN; ++i) + { + multiexp_data[i * 2] = {Gi_scalars[i], Gi_p3[i]}; + multiexp_data[i * 2 + 1] = {Hi_scalars[i], Hi_p3[i]}; + } + if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity())) + { + MERROR("Verification failure"); + return false; + } + + return true; + } + + bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs) + { + std::vector<const BulletproofPlus*> proof_pointers; + proof_pointers.reserve(proofs.size()); + for (const BulletproofPlus &proof: proofs) + proof_pointers.push_back(&proof); + return bulletproof_plus_VERIFY(proof_pointers); + } + + bool bulletproof_plus_VERIFY(const BulletproofPlus &proof) + { + std::vector<const BulletproofPlus*> proofs; + proofs.push_back(&proof); + return bulletproof_plus_VERIFY(proofs); + } +} diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h new file mode 100644 index 000000000..d9084075a --- /dev/null +++ b/src/ringct/bulletproofs_plus.h @@ -0,0 +1,49 @@ +// Copyright (c) 2017-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#ifndef BULLETPROOFS_PLUS_H +#define BULLETPROOFS_PLUS_H + +#include "rctTypes.h" + +namespace rct +{ + +BulletproofPlus bulletproof_plus_PROVE(const rct::key &v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &v, const rct::keyV &gamma); +BulletproofPlus bulletproof_plus_PROVE(const std::vector<uint64_t> &v, const rct::keyV &gamma); +bool bulletproof_plus_VERIFY(const BulletproofPlus &proof); +bool bulletproof_plus_VERIFY(const std::vector<const BulletproofPlus*> &proofs); +bool bulletproof_plus_VERIFY(const std::vector<BulletproofPlus> &proofs); + +} + +#endif diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index 784c90a4e..f256325a1 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -235,7 +235,7 @@ rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data) heap.reserve(points); for (size_t n = 0; n < points; ++n) { - if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity(&data[n].point)) + if (!(data[n].scalar == rct::zero()) && !ge_p3_is_point_at_infinity_vartime(&data[n].point)) heap.push_back(n); } points = heap.size(); @@ -457,7 +457,7 @@ rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<str MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000)); std::vector<uint8_t> skip(data.size()); for (size_t i = 0; i < data.size(); ++i) - skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity(&data[i].point); + skip[i] = data[i].scalar == rct::zero() || ge_p3_is_point_at_infinity_vartime(&data[i].point); MULTIEXP_PERF(PERF_TIMER_STOP(skip)); #endif diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index f5950c53c..d7883baac 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -35,6 +35,7 @@ #include "common/util.h" #include "rctSigs.h" #include "bulletproofs.h" +#include "bulletproofs_plus.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_config.h" @@ -78,6 +79,36 @@ namespace return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I}; } + rct::BulletproofPlus make_dummy_bulletproof_plus(const std::vector<uint64_t> &outamounts, rct::keyV &C, rct::keyV &masks) + { + const size_t n_outs = outamounts.size(); + const rct::key I = rct::identity(); + size_t nrl = 0; + while ((1u << nrl) < n_outs) + ++nrl; + nrl += 6; + + C.resize(n_outs); + masks.resize(n_outs); + for (size_t i = 0; i < n_outs; ++i) + { + masks[i] = I; + rct::key sv8, sv; + sv = rct::zero(); + sv.bytes[0] = outamounts[i] & 255; + sv.bytes[1] = (outamounts[i] >> 8) & 255; + sv.bytes[2] = (outamounts[i] >> 16) & 255; + sv.bytes[3] = (outamounts[i] >> 24) & 255; + sv.bytes[4] = (outamounts[i] >> 32) & 255; + sv.bytes[5] = (outamounts[i] >> 40) & 255; + sv.bytes[6] = (outamounts[i] >> 48) & 255; + sv.bytes[7] = (outamounts[i] >> 56) & 255; + sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); + rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); + } + + return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)}; + } } namespace rct { @@ -107,6 +138,32 @@ namespace rct { catch (...) { return false; } } + BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev) + { + CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + masks.resize(amounts.size()); + for (size_t i = 0; i < masks.size(); ++i) + masks[i] = hwdev.genCommitmentMask(sk[i]); + BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks); + CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); + C = proof.V; + return proof; + } + + bool verBulletproofPlus(const BulletproofPlus &proof) + { + try { return bulletproof_plus_VERIFY(proof); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + + bool verBulletproofPlus(const std::vector<const BulletproofPlus*> &proofs) + { + try { return bulletproof_plus_VERIFY(proofs); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + //Borromean (c.f. gmax/andytoshi's paper) boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { key64 L[2], alpha; @@ -611,6 +668,25 @@ namespace rct { kv.push_back(p.t); } } + else if (rv.type == RCTTypeBulletproofPlus) + { + kv.reserve((6*2+6) * rv.p.bulletproofs_plus.size()); + for (const auto &p: rv.p.bulletproofs_plus) + { + // V are not hashed as they're expanded from outPk.mask + // (and thus hashed as part of rctSigBase above) + kv.push_back(p.A); + kv.push_back(p.A1); + kv.push_back(p.B); + kv.push_back(p.r1); + kv.push_back(p.s1); + kv.push_back(p.d1); + for (size_t n = 0; n < p.L.size(); ++n) + kv.push_back(p.L[n]); + for (size_t n = 0; n < p.R.size(); ++n) + kv.push_back(p.R[n]); + } + } else { kv.reserve((64*3+1) * rv.p.rangeSigs.size()); @@ -1031,7 +1107,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1063,7 +1139,7 @@ namespace rct { //RCT simple //for post-rct only rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<multisig_kLRki> *kLRki, multisig_out *msout, const std::vector<unsigned int> & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { - const bool bulletproof = rct_config.range_proof_type != RangeProofBorromean; + const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofBorromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -1079,11 +1155,14 @@ namespace rct { } rctSig rv; - if (bulletproof) + if (bulletproof_or_plus) { switch (rct_config.bp_version) { case 0: + case 4: + rv.type = RCTTypeBulletproofPlus; + break; case 3: rv.type = RCTTypeCLSAG; break; @@ -1102,7 +1181,7 @@ namespace rct { rv.message = message; rv.outPk.resize(destinations.size()); - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); @@ -1114,17 +1193,19 @@ namespace rct { //add destination to sig rv.outPk[i].dest = copy(destinations[i]); //compute range proof - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG - if (!bulletproof) + if (!bulletproof_or_plus) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif } rv.p.bulletproofs.clear(); - if (bulletproof) + rv.p.bulletproofs_plus.clear(); + if (bulletproof_or_plus) { + const bool plus = is_rct_bulletproof_plus(rv.type); size_t n_amounts = outamounts.size(); size_t amounts_proved = 0; if (rct_config.range_proof_type == RangeProofPaddedBulletproof) @@ -1133,19 +1214,31 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[0], amount_keys.size()}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < outamounts.size(); ++i) { - rv.outPk[i].mask = rct::scalarmult8(C[i]); + if (plus) + rv.outPk[i].mask = C[i]; + else + rv.outPk[i].mask = rct::scalarmult8(C[i]); outSk[i].mask = masks[i]; } } @@ -1153,7 +1246,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofMultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? BULLETPROOF_PLUS_MAX_OUTPUTS : BULLETPROOF_MAX_OUTPUTS)) batch_size *= 2; rct::keyV C, masks; std::vector<uint64_t> batch_amounts(batch_size); @@ -1162,19 +1255,31 @@ namespace rct { if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); } else { const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < batch_size; ++i) { - rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); + if (plus) + rv.outPk[i + amounts_proved].mask = C[i]; + else + rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); outSk[i + amounts_proved].mask = masks[i]; } amounts_proved += batch_size; @@ -1189,7 +1294,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(outamounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); } //set txn fee @@ -1197,9 +1302,9 @@ namespace rct { // TODO: unused ?? // key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts; pseudoOuts.resize(inamounts.size()); - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) rv.p.CLSAGs.resize(inamounts.size()); else rv.p.MGs.resize(inamounts.size()); @@ -1218,11 +1323,11 @@ namespace rct { if (msout) { msout->c.resize(inamounts.size()); - msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0); + msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0); } for (i = 0 ; i < inamounts.size(); i++) { - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) { rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev); } @@ -1328,20 +1433,25 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results; - std::vector<const Bulletproof*> proofs; + std::vector<const Bulletproof*> bp_proofs; + std::vector<const BulletproofPlus*> bpp_proofs; size_t max_non_bp_proofs = 0, offset = 0; for (const rctSig *rvp: rvv) { CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); const rctSig &rv = *rvp; - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + if (bulletproof || bulletproof_plus) { - CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); - if (rv.type == RCTTypeCLSAG) + if (bulletproof_plus) + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus"); + else + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); + if (is_rct_clsag(rv.type)) { CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG"); CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs"); @@ -1361,7 +1471,7 @@ namespace rct { } CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - if (!bulletproof) + if (!bulletproof && !bulletproof_plus) max_non_bp_proofs += rv.p.rangeSigs.size(); } @@ -1371,11 +1481,15 @@ namespace rct { const rctSig &rv = *rvp; const bool bulletproof = is_rct_bulletproof(rv.type); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { - masks[i] = rv.outPk[i].mask; + if (bulletproof_plus) + masks[i] = rct::scalarmult8(rv.outPk[i].mask); + else + masks[i] = rv.outPk[i].mask; } key sumOutpks = addKeys(masks); DP(sumOutpks); @@ -1391,10 +1505,15 @@ namespace rct { return false; } - if (bulletproof) + if (bulletproof_plus) + { + for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++) + bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]); + } + else if (bulletproof) { for (size_t i = 0; i < rv.p.bulletproofs.size(); i++) - proofs.push_back(&rv.p.bulletproofs[i]); + bp_proofs.push_back(&rv.p.bulletproofs[i]); } else { @@ -1403,9 +1522,18 @@ namespace rct { offset += rv.p.rangeSigs.size(); } } - if (!proofs.empty() && !verBulletproof(proofs)) + if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) + { + LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; + return false; + } + if (!bp_proofs.empty() && !verBulletproof(bp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; return false; } @@ -1445,11 +1573,12 @@ namespace rct { { PERF_TIMER(verRctNonSemanticsSimple); - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet - if (bulletproof) + if (bulletproof || bulletproof_plus) CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing"); else CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); @@ -1460,7 +1589,7 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter(tpool); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; const key message = get_pre_mlsag_hash(rv, hw::get_device("default")); @@ -1468,10 +1597,8 @@ namespace rct { results.resize(rv.mixRing.size()); for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { tpool.submit(&waiter, [&, i] { - if (rv.type == RCTTypeCLSAG) - { + if (is_rct_clsag(rv.type)) results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]); - } else results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]); }); @@ -1518,10 +1645,12 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1542,16 +1671,19 @@ namespace rct { } xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus, + false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG || rv.type == RCTTypeBulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1574,6 +1706,7 @@ namespace rct { bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, false, "unsupported rct type"); + CHECK_AND_ASSERT_MES(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function"); CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); @@ -1598,7 +1731,7 @@ namespace rct { } bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTTypeCLSAG, false, "unsupported rct type"); + CHECK_AND_ASSERT_MES(is_rct_clsag(rv.type), false, "unsupported rct type"); CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); @@ -1620,7 +1753,7 @@ namespace rct { } bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - if (rv.type == RCTTypeCLSAG) + if (is_rct_clsag(rv.type)) return signMultisigCLSAG(rv, indices, k, msout, secret_key); else return signMultisigMLSAG(rv, indices, k, msout, secret_key); diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 1f674056d..c22b0524f 100644 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -196,6 +196,7 @@ namespace rct { case RCTTypeBulletproof: case RCTTypeBulletproof2: case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: return true; default: return false; @@ -215,6 +216,17 @@ namespace rct { } } + bool is_rct_bulletproof_plus(int type) + { + switch (type) + { + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + bool is_rct_borromean(int type) { switch (type) @@ -227,19 +239,34 @@ namespace rct { } } - size_t n_bulletproof_amounts(const Bulletproof &proof) + bool is_rct_clsag(int type) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + switch (type) + { + case RCTTypeCLSAG: + case RCTTypeBulletproofPlus: + return true; + default: + return false; + } + } + + static size_t n_bulletproof_amounts_base(const size_t L_size, const size_t R_size, const size_t V_size, const size_t max_outputs) + { + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() > 0, 0, "Empty bulletproof"); - return proof.V.size(); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(V_size <= (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size * 2 > (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size > 0, 0, "Empty bulletproof"); + return V_size; } + size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs) { size_t n = 0; @@ -254,15 +281,31 @@ namespace rct { return n; } - size_t n_bulletproof_max_amounts(const Bulletproof &proof) + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + + static size_t n_bulletproof_max_amounts_base(size_t L_size, size_t R_size, size_t max_outputs) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - return 1 << (proof.L.size() - 6); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + return 1 << (L_size - 6); } + size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_PLUS_MAX_OUTPUTS); } size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs) { @@ -278,4 +321,18 @@ namespace rct { return n; } + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_max_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits<uint32_t>::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 278ff4164..59ed4d6a6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -238,11 +238,48 @@ namespace rct { END_SERIALIZE() }; + struct BulletproofPlus + { + rct::keyV V; + rct::key A, A1, B; + rct::key r1, s1, d1; + rct::keyV L, R; + + BulletproofPlus() {} + BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + + bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; } + + BEGIN_SERIALIZE_OBJECT() + // Commitments aren't saved, they're restored via outPk + // FIELD(V) + FIELD(A) + FIELD(A1) + FIELD(B) + FIELD(r1) + FIELD(s1) + FIELD(d1) + FIELD(L) + FIELD(R) + + if (L.empty() || L.size() != R.size()) + return false; + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector<Bulletproof> &proofs); size_t n_bulletproof_max_amounts(const std::vector<Bulletproof> &proofs); + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_amounts(const std::vector<BulletproofPlus> &proofs); + size_t n_bulletproof_plus_max_amounts(const std::vector<BulletproofPlus> &proofs); + //A container to hold all signatures necessary for RingCT // rangeSigs holds all the rangeproof data of a transaction // MG holds the MLSAG signature of a transaction @@ -257,6 +294,7 @@ namespace rct { RCTTypeBulletproof = 3, RCTTypeBulletproof2 = 4, RCTTypeCLSAG = 5, + RCTTypeBulletproofPlus = 6, }; enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof }; struct RCTConfig { @@ -285,7 +323,7 @@ namespace rct { FIELD(type) if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; VARINT_FIELD(txnFee) // inputs/outputs not saved, only here for serialization help @@ -314,7 +352,7 @@ namespace rct { return false; for (size_t i = 0; i < outputs; ++i) { - if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.begin_object(); if (!typename Archive<W>::is_saving()) @@ -360,6 +398,7 @@ namespace rct { struct rctSigPrunable { std::vector<rangeSig> rangeSigs; std::vector<Bulletproof> bulletproofs; + std::vector<BulletproofPlus> bulletproofs_plus; std::vector<mgSig> MGs; // simple rct has N, full has 1 std::vector<clsag> CLSAGs; keyV pseudoOuts; //C - for simple rct @@ -376,9 +415,28 @@ namespace rct { return false; if (type == RCTTypeNull) return ar.good(); - if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG) + if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG && type != RCTTypeBulletproofPlus) return false; - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproofPlus) + { + uint32_t nbp = bulletproofs_plus.size(); + VARINT_FIELD(nbp) + ar.tag("bpp"); + ar.begin_array(); + if (nbp > outputs) + return false; + PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + for (size_t i = 0; i < nbp; ++i) + { + FIELDS(bulletproofs_plus[i]) + if (nbp - i > 1) + ar.delimit_array(); + } + if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) + return false; + ar.end_array(); + } + else if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) { uint32_t nbp = bulletproofs.size(); if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) @@ -416,7 +474,7 @@ namespace rct { ar.end_array(); } - if (type == RCTTypeCLSAG) + if (type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("CLSAGs"); ar.begin_array(); @@ -507,7 +565,7 @@ namespace rct { } ar.end_array(); } - if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG) + if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus) { ar.tag("pseudoOuts"); ar.begin_array(); @@ -528,6 +586,7 @@ namespace rct { BEGIN_SERIALIZE_OBJECT() FIELD(rangeSigs) FIELD(bulletproofs) + FIELD(bulletproofs_plus) FIELD(MGs) FIELD(CLSAGs) FIELD(pseudoOuts) @@ -538,12 +597,12 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts; + return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG || type == RCTTypeBulletproofPlus ? p.pseudoOuts : pseudoOuts; } BEGIN_SERIALIZE_OBJECT() @@ -655,7 +714,9 @@ namespace rct { bool is_rct_simple(int type); bool is_rct_bulletproof(int type); + bool is_rct_bulletproof_plus(int type); bool is_rct_borromean(int type); + bool is_rct_clsag(int type); static inline const rct::key &pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; } static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; } @@ -711,6 +772,7 @@ VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof"); VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki"); VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out"); VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag"); +VARIANT_TAG(debug_archive, rct::BulletproofPlus, "rct::bulletproof_plus"); VARIANT_TAG(binary_archive, rct::key, 0x90); VARIANT_TAG(binary_archive, rct::key64, 0x91); @@ -728,6 +790,7 @@ VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c); VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d); VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e); VARIANT_TAG(binary_archive, rct::clsag, 0x9f); +VARIANT_TAG(binary_archive, rct::BulletproofPlus, 0xa0); VARIANT_TAG(json_archive, rct::key, "rct_key"); VARIANT_TAG(json_archive, rct::key64, "rct_key64"); @@ -745,5 +808,6 @@ VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof"); VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR"); VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out"); VARIANT_TAG(json_archive, rct::clsag, "rct_clsag"); +VARIANT_TAG(json_archive, rct::BulletproofPlus, "rct_bulletproof_plus"); #endif /* RCTTYPES_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d42938e2d..40b1b1000 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -535,6 +535,7 @@ namespace cryptonote res.version = restricted ? "" : MONERO_VERSION_FULL; res.synchronized = check_core_ready(); res.busy_syncing = m_p2p.get_payload_object().is_busy_syncing(); + res.restricted = restricted; res.status = CORE_RPC_STATUS_OK; return true; @@ -975,14 +976,26 @@ namespace cryptonote LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); } - std::vector<std::string>::const_iterator txhi = req.txs_hashes.begin(); - std::vector<crypto::hash>::const_iterator vhi = vh.begin(); + CHECK_AND_ASSERT_MES(txs.size() + missed_txs.size() == vh.size(), false, "mismatched number of txs"); + + auto txhi = req.txs_hashes.cbegin(); + auto vhi = vh.cbegin(); + auto missedi = missed_txs.cbegin(); + for(auto& tx: txs) { res.txs.push_back(COMMAND_RPC_GET_TRANSACTIONS::entry()); COMMAND_RPC_GET_TRANSACTIONS::entry &e = res.txs.back(); + while (missedi != missed_txs.end() && *missedi == *vhi) + { + ++vhi; + ++txhi; + ++missedi; + } + crypto::hash tx_hash = *vhi++; + CHECK_AND_ASSERT_MES(tx_hash == std::get<0>(tx), false, "mismatched tx hash"); e.tx_hash = *txhi++; e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx)); if (req.split || req.prune || std::get<3>(tx).empty()) @@ -1872,6 +1885,80 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_getminerdata(const COMMAND_RPC_GETMINERDATA::request& req, COMMAND_RPC_GETMINERDATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + if(!check_core_ready()) + { + error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; + error_resp.message = "Core is busy"; + return false; + } + + crypto::hash prev_id, seed_hash; + difficulty_type difficulty; + + std::vector<tx_block_template_backlog_entry> tx_backlog; + if (!m_core.get_miner_data(res.major_version, res.height, prev_id, seed_hash, difficulty, res.median_weight, res.already_generated_coins, tx_backlog)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to get miner data"; + LOG_ERROR("Failed to get miner data"); + return false; + } + + res.tx_backlog.clear(); + res.tx_backlog.reserve(tx_backlog.size()); + + for (const auto& entry : tx_backlog) + { + res.tx_backlog.emplace_back(COMMAND_RPC_GETMINERDATA::response::tx_backlog_entry{string_tools::pod_to_hex(entry.id), entry.weight, entry.fee}); + } + + res.prev_id = string_tools::pod_to_hex(prev_id); + res.seed_hash = string_tools::pod_to_hex(seed_hash); + res.difficulty = cryptonote::hex(difficulty); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_calcpow(const COMMAND_RPC_CALCPOW::request& req, COMMAND_RPC_CALCPOW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(calcpow); + + blobdata blockblob; + if(!string_tools::parse_hexstr_to_binbuff(req.block_blob, blockblob)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Wrong block blob"; + return false; + } + if(!m_core.check_incoming_block_size(blockblob)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE; + error_resp.message = "Block blob size is too big, rejecting block"; + return false; + } + crypto::hash seed_hash, pow_hash; + std::string buf; + if(req.seed_hash.size()) + { + if (!string_tools::parse_hexstr_to_binbuff(req.seed_hash, buf) || + buf.size() != sizeof(crypto::hash)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Wrong seed hash"; + return false; + } + buf.copy(reinterpret_cast<char *>(&seed_hash), sizeof(crypto::hash)); + } + + cryptonote::get_block_longhash(&(m_core.get_blockchain_storage()), blockblob, pow_hash, req.height, + req.major_version, req.seed_hash.size() ? &seed_hash : NULL, 0); + res = string_tools::pod_to_hex(pow_hash); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(add_aux_pow); @@ -2870,7 +2957,7 @@ namespace cryptonote { if (req.limit_down != -1) { - res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + res.status = "Invalid parameter"; return true; } epee::net_utils::connection_basic::set_rate_down_limit(nodetool::default_limit_down); @@ -2884,7 +2971,7 @@ namespace cryptonote { if (req.limit_up != -1) { - res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + res.status = "Invalid parameter"; return true; } epee::net_utils::connection_basic::set_rate_up_limit(nodetool::default_limit_up); @@ -3137,6 +3224,14 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::JON_RPC, "get_output_distribution", req, res, r)) return r; + const bool restricted = m_restricted && ctx; + if (restricted && req.amounts != std::vector<uint64_t>(1, 0)) + { + error_resp.code = CORE_RPC_ERROR_CODE_RESTRICTED; + error_resp.message = "Restricted RPC can only get output distribution for rct outputs. Use your own node."; + return false; + } + size_t n_0 = 0, n_non0 = 0; for (uint64_t amount: req.amounts) if (amount) ++n_non0; else ++n_0; @@ -3178,6 +3273,13 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::BIN, "/get_output_distribution.bin", req, res, r)) return r; + const bool restricted = m_restricted && ctx; + if (restricted && req.amounts != std::vector<uint64_t>(1, 0)) + { + res.status = "Restricted RPC can only get output distribution for rct outputs. Use your own node."; + return false; + } + size_t n_0 = 0, n_non0 = 0; for (uint64_t amount: req.amounts) if (amount) ++n_non0; else ++n_0; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index db1429ab1..664af3686 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -148,6 +148,8 @@ namespace cryptonote MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("get_block_template", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) + MAP_JON_RPC_WE("get_miner_data", on_getminerdata, COMMAND_RPC_GETMINERDATA) + MAP_JON_RPC_WE_IF("calc_pow", on_calcpow, COMMAND_RPC_CALCPOW, !m_restricted) MAP_JON_RPC_WE("add_aux_pow", on_add_aux_pow, COMMAND_RPC_ADD_AUX_POW) MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) @@ -229,6 +231,8 @@ namespace cryptonote bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx = NULL); bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_getminerdata(const COMMAND_RPC_GETMINERDATA::request& req, COMMAND_RPC_GETMINERDATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_calcpow(const COMMAND_RPC_CALCPOW::request& req, COMMAND_RPC_CALCPOW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ff8c98b98..d25196841 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 7 +#define CORE_RPC_VERSION_MINOR 10 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -689,6 +689,7 @@ namespace cryptonote bool busy_syncing; std::string version; bool synchronized; + bool restricted; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -730,6 +731,7 @@ namespace cryptonote KV_SERIALIZE(busy_syncing) KV_SERIALIZE(version) KV_SERIALIZE(synchronized) + KV_SERIALIZE(restricted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -940,6 +942,78 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_GETMINERDATA + { + struct request_t: public rpc_request_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_response_base + { + uint8_t major_version; + uint64_t height; + std::string prev_id; + std::string seed_hash; + std::string difficulty; + uint64_t median_weight; + uint64_t already_generated_coins; + + struct tx_backlog_entry + { + std::string id; + uint64_t weight; + uint64_t fee; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(id) + KV_SERIALIZE(weight) + KV_SERIALIZE(fee) + END_KV_SERIALIZE_MAP() + }; + + std::vector<tx_backlog_entry> tx_backlog; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(major_version) + KV_SERIALIZE(height) + KV_SERIALIZE(prev_id) + KV_SERIALIZE(seed_hash) + KV_SERIALIZE(difficulty) + KV_SERIALIZE(median_weight) + KV_SERIALIZE(already_generated_coins) + KV_SERIALIZE(tx_backlog) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_CALCPOW + { + struct request_t: public rpc_request_base + { + uint8_t major_version; + uint64_t height; + blobdata block_blob; + std::string seed_hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(major_version) + KV_SERIALIZE(height) + KV_SERIALIZE(block_blob) + KV_SERIALIZE(seed_hash) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + typedef std::string response; + }; + struct COMMAND_RPC_ADD_AUX_POW { struct aux_pow_t diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 0966fb6d2..b2d019ae7 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -247,12 +247,6 @@ namespace cryptonote auto access_control_origins_input = command_line::get_arg(vm, arg.rpc_access_control_origins); if (!access_control_origins_input.empty()) { - if (!config.login) - { - LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RPC server password --") << arg.rpc_login.name << tr(" cannot be empty")); - return boost::none; - } - std::vector<std::string> access_control_origins; boost::split(access_control_origins, access_control_origins_input, boost::is_any_of(",")); std::for_each(access_control_origins.begin(), access_control_origins.end(), std::bind(&boost::trim<std::string>, std::placeholders::_1, std::locale::classic())); diff --git a/src/rpc/zmq_pub.cpp b/src/rpc/zmq_pub.cpp index eac530968..074b55207 100644 --- a/src/rpc/zmq_pub.cpp +++ b/src/rpc/zmq_pub.cpp @@ -48,6 +48,8 @@ #include "cryptonote_basic/events.h" #include "misc_log_ex.h" #include "serialization/json_object.h" +#include "ringct/rctTypes.h" +#include "cryptonote_core/cryptonote_tx_utils.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.zmq" @@ -57,6 +59,7 @@ namespace constexpr const char txpool_signal[] = "tx_signal"; using chain_writer = void(epee::byte_stream&, std::uint64_t, epee::span<const cryptonote::block>); + using miner_writer = void(epee::byte_stream&, uint8_t, uint64_t, const crypto::hash&, const crypto::hash&, cryptonote::difficulty_type, uint64_t, uint64_t, const std::vector<cryptonote::tx_block_template_backlog_entry>&); using txpool_writer = void(epee::byte_stream&, epee::span<const cryptonote::txpool_event>); template<typename F> @@ -116,13 +119,30 @@ namespace const epee::span<const cryptonote::block> blocks; }; + //! Object for miner data serialization + struct miner_data + { + uint8_t major_version; + uint64_t height; + const crypto::hash& prev_id; + const crypto::hash& seed_hash; + cryptonote::difficulty_type diff; + uint64_t median_weight; + uint64_t already_generated_coins; + const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog; + }; + //! Object for "minimal" tx serialization struct minimal_txpool { const cryptonote::transaction& tx; + crypto::hash hash; + uint64_t blob_size; + uint64_t weight; + uint64_t fee; }; - void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_chain self) + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_chain& self) { namespace adapt = boost::adaptors; @@ -143,19 +163,27 @@ namespace dest.EndObject(); } - void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_txpool self) + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const miner_data& self) { - crypto::hash id{}; - std::size_t blob_size = 0; - if (!get_transaction_hash(self.tx, id, blob_size)) - { - MERROR("ZMQ/Pub failure: get_transaction_hash"); - return; - } + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, major_version, self.major_version); + INSERT_INTO_JSON_OBJECT(dest, height, self.height); + INSERT_INTO_JSON_OBJECT(dest, prev_id, self.prev_id); + INSERT_INTO_JSON_OBJECT(dest, seed_hash, self.seed_hash); + INSERT_INTO_JSON_OBJECT(dest, difficulty, cryptonote::hex(self.diff)); + INSERT_INTO_JSON_OBJECT(dest, median_weight, self.median_weight); + INSERT_INTO_JSON_OBJECT(dest, already_generated_coins, self.already_generated_coins); + INSERT_INTO_JSON_OBJECT(dest, tx_backlog, self.tx_backlog); + dest.EndObject(); + } + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const minimal_txpool& self) + { dest.StartObject(); - INSERT_INTO_JSON_OBJECT(dest, id, id); - INSERT_INTO_JSON_OBJECT(dest, blob_size, blob_size); + INSERT_INTO_JSON_OBJECT(dest, id, self.hash); + INSERT_INTO_JSON_OBJECT(dest, blob_size, self.blob_size); + INSERT_INTO_JSON_OBJECT(dest, weight, self.weight); + INSERT_INTO_JSON_OBJECT(dest, fee, self.fee); dest.EndObject(); } @@ -169,6 +197,11 @@ namespace json_pub(buf, minimal_chain{height, blocks}); } + void json_miner_data(epee::byte_stream& buf, uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, cryptonote::difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<cryptonote::tx_block_template_backlog_entry>& tx_backlog) + { + json_pub(buf, miner_data{major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog}); + } + // boost::adaptors are in place "views" - no copy/move takes place // moving transactions (via sort, etc.), is expensive! @@ -187,7 +220,7 @@ namespace namespace adapt = boost::adaptors; const auto to_minimal_tx = [](const cryptonote::txpool_event& event) { - return minimal_txpool{event.tx}; + return minimal_txpool{event.tx, event.hash, event.blob_size, event.weight, cryptonote::get_tx_fee(event.tx)}; }; json_pub(buf, (txes | adapt::filtered(is_valid{}) | adapt::transformed(to_minimal_tx))); } @@ -198,6 +231,11 @@ namespace {u8"json-minimal-chain_main", json_minimal_chain} }}; + constexpr const std::array<context<miner_writer>, 1> miner_contexts = + {{ + {u8"json-full-miner_data", json_miner_data}, + }}; + constexpr const std::array<context<txpool_writer>, 2> txpool_contexts = {{ {u8"json-full-txpool_add", json_full_txpool}, @@ -321,6 +359,7 @@ namespace cryptonote { namespace listener zmq_pub::zmq_pub(void* context) : relay_(), chain_subs_{{0}}, + miner_subs_{{0}}, txpool_subs_{{0}}, sync_() { @@ -328,6 +367,7 @@ zmq_pub::zmq_pub(void* context) throw std::logic_error{"ZMQ context cannot be NULL"}; verify_sorted(chain_contexts, "chain_contexts"); + verify_sorted(miner_contexts, "miner_contexts"); verify_sorted(txpool_contexts, "txpool_contexts"); relay_.reset(zmq_socket(context, ZMQ_PAIR)); @@ -348,22 +388,25 @@ bool zmq_pub::sub_request(boost::string_ref message) message.remove_prefix(1); const auto chain_range = get_range(chain_contexts, message); + const auto miner_range = get_range(miner_contexts, message); const auto txpool_range = get_range(txpool_contexts, message); - if (!chain_range.empty() || !txpool_range.empty()) + if (!chain_range.empty() || !miner_range.empty() || !txpool_range.empty()) { MDEBUG("Client " << (tag ? "subscribed" : "unsubscribed") << " to " << - chain_range.size() << " chain topic(s) and " << txpool_range.size() << " txpool topic(s)"); + chain_range.size() << " chain topic(s), " << miner_range.size() << " miner topic(s) and " << txpool_range.size() << " txpool topic(s)"); const boost::lock_guard<boost::mutex> lock{sync_}; switch (tag) { case 0: remove_subscriptions(chain_subs_, chain_range, chain_contexts.begin()); + remove_subscriptions(miner_subs_, miner_range, miner_contexts.begin()); remove_subscriptions(txpool_subs_, txpool_range, txpool_contexts.begin()); return true; case 1: add_subscriptions(chain_subs_, chain_range, chain_contexts.begin()); + add_subscriptions(miner_subs_, miner_range, miner_contexts.begin()); add_subscriptions(txpool_subs_, txpool_range, txpool_contexts.begin()); return true; default: @@ -436,6 +479,25 @@ std::size_t zmq_pub::send_chain_main(const std::uint64_t height, const epee::spa return 0; } +std::size_t zmq_pub::send_miner_data(uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<tx_block_template_backlog_entry>& tx_backlog) +{ + boost::unique_lock<boost::mutex> guard{sync_}; + + const auto subs_copy = miner_subs_; + guard.unlock(); + + for (const std::size_t sub : subs_copy) + { + if (sub) + { + auto messages = make_pubs(subs_copy, miner_contexts, major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog); + guard.lock(); + return send_messages(relay_.get(), messages); + } + } + return 0; +} + std::size_t zmq_pub::send_txpool_add(std::vector<txpool_event> txes) { if (txes.empty()) @@ -466,6 +528,15 @@ void zmq_pub::chain_main::operator()(const std::uint64_t height, epee::span<cons MERROR("Unable to send ZMQ/Pub - ZMQ server destroyed"); } +void zmq_pub::miner_data::operator()(uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<tx_block_template_backlog_entry>& tx_backlog) const +{ + const std::shared_ptr<zmq_pub> self = self_.lock(); + if (self) + self->send_miner_data(major_version, height, prev_id, seed_hash, diff, median_weight, already_generated_coins, tx_backlog); + else + MERROR("Unable to send ZMQ/Pub - ZMQ server destroyed"); +} + void zmq_pub::txpool_add::operator()(std::vector<cryptonote::txpool_event> txes) const { const std::shared_ptr<zmq_pub> self = self_.lock(); diff --git a/src/rpc/zmq_pub.h b/src/rpc/zmq_pub.h index 02e6b8103..c636e1d7b 100644 --- a/src/rpc/zmq_pub.h +++ b/src/rpc/zmq_pub.h @@ -39,6 +39,7 @@ #include "cryptonote_basic/fwd.h" #include "net/zmq.h" #include "span.h" +#include "cryptonote_basic/difficulty.h" namespace cryptonote { namespace listener { @@ -59,6 +60,7 @@ class zmq_pub net::zmq::socket relay_; std::deque<std::vector<txpool_event>> txes_; std::array<std::size_t, 2> chain_subs_; + std::array<std::size_t, 1> miner_subs_; std::array<std::size_t, 2> txpool_subs_; boost::mutex sync_; //!< Synchronizes counts in `*_subs_` arrays. @@ -88,6 +90,11 @@ class zmq_pub \return Number of ZMQ messages sent to relay. */ std::size_t send_chain_main(std::uint64_t height, epee::span<const cryptonote::block> blocks); + /*! Send a `ZMQ_PUB` notification for a new miner data. + Thread-safe. + \return Number of ZMQ messages sent to relay. */ + std::size_t send_miner_data(uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<tx_block_template_backlog_entry>& tx_backlog); + /*! Send a `ZMQ_PUB` notification for new tx(es) being added to the local pool. Thread-safe. \return Number of ZMQ messages sent to relay. */ @@ -100,6 +107,13 @@ class zmq_pub void operator()(std::uint64_t height, epee::span<const cryptonote::block> blocks) const; }; + //! Callable for `send_miner_data` with weak ownership to `zmq_pub` object. + struct miner_data + { + std::weak_ptr<zmq_pub> self_; + void operator()(uint8_t major_version, uint64_t height, const crypto::hash& prev_id, const crypto::hash& seed_hash, difficulty_type diff, uint64_t median_weight, uint64_t already_generated_coins, const std::vector<tx_block_template_backlog_entry>& tx_backlog) const; + }; + //! Callable for `send_txpool_add` with weak ownership to `zmq_pub` object. struct txpool_add { diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index 28e207ff2..bd715dcfd 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -34,6 +34,7 @@ #include <type_traits> #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_core/cryptonote_tx_utils.h" // drop macro from windows.h #ifdef GetObject @@ -299,7 +300,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) } const auto& rsig = tx.rct_signatures; - if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) + if (!cryptonote::is_coinbase(tx) && rsig.p.bulletproofs.empty() && rsig.p.bulletproofs_plus.empty() && rsig.p.rangeSigs.empty() && rsig.p.MGs.empty() && rsig.get_pseudo_outs().empty() && sigs == val.MemberEnd()) tx.pruned = true; } @@ -1099,13 +1100,14 @@ void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::rctSig& } // prunable - if (!sig.p.bulletproofs.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) + if (!sig.p.bulletproofs.empty() || !sig.p.bulletproofs_plus.empty() || !sig.p.rangeSigs.empty() || !sig.p.MGs.empty() || !sig.get_pseudo_outs().empty()) { dest.Key("prunable"); dest.StartObject(); INSERT_INTO_JSON_OBJECT(dest, range_proofs, sig.p.rangeSigs); INSERT_INTO_JSON_OBJECT(dest, bulletproofs, sig.p.bulletproofs); + INSERT_INTO_JSON_OBJECT(dest, bulletproofs_plus, sig.p.bulletproofs_plus); INSERT_INTO_JSON_OBJECT(dest, mlsags, sig.p.MGs); INSERT_INTO_JSON_OBJECT(dest, pseudo_outs, sig.get_pseudo_outs()); @@ -1140,6 +1142,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) GET_FROM_JSON_OBJECT(prunable->value, sig.p.rangeSigs, range_proofs); GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs, bulletproofs); + GET_FROM_JSON_OBJECT(prunable->value, sig.p.bulletproofs_plus, bulletproofs_plus); GET_FROM_JSON_OBJECT(prunable->value, sig.p.MGs, mlsags); GET_FROM_JSON_OBJECT(prunable->value, pseudo_outs, pseudo_outs); @@ -1149,6 +1152,7 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) { sig.p.rangeSigs.clear(); sig.p.bulletproofs.clear(); + sig.p.bulletproofs_plus.clear(); sig.p.MGs.clear(); sig.get_pseudo_outs().clear(); } @@ -1257,6 +1261,41 @@ void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p) GET_FROM_JSON_OBJECT(val, p.t, t); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, V, p.V); + INSERT_INTO_JSON_OBJECT(dest, A, p.A); + INSERT_INTO_JSON_OBJECT(dest, A1, p.A1); + INSERT_INTO_JSON_OBJECT(dest, B, p.B); + INSERT_INTO_JSON_OBJECT(dest, r1, p.r1); + INSERT_INTO_JSON_OBJECT(dest, s1, p.s1); + INSERT_INTO_JSON_OBJECT(dest, d1, p.d1); + INSERT_INTO_JSON_OBJECT(dest, L, p.L); + INSERT_INTO_JSON_OBJECT(dest, R, p.R); + + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, p.V, V); + GET_FROM_JSON_OBJECT(val, p.A, A); + GET_FROM_JSON_OBJECT(val, p.A1, A1); + GET_FROM_JSON_OBJECT(val, p.B, B); + GET_FROM_JSON_OBJECT(val, p.r1, r1); + GET_FROM_JSON_OBJECT(val, p.s1, s1); + GET_FROM_JSON_OBJECT(val, p.d1, d1); + GET_FROM_JSON_OBJECT(val, p.L, L); + GET_FROM_JSON_OBJECT(val, p.R, R); +} + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig) { dest.StartObject(); @@ -1411,6 +1450,27 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribu GET_FROM_JSON_OBJECT(val, dist.data.base, base); } +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_block_template_backlog_entry& entry) +{ + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, id, entry.id); + INSERT_INTO_JSON_OBJECT(dest, weight, entry.weight); + INSERT_INTO_JSON_OBJECT(dest, fee, entry.fee); + dest.EndObject(); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_block_template_backlog_entry& entry) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, entry.id, id); + GET_FROM_JSON_OBJECT(val, entry.weight, weight); + GET_FROM_JSON_OBJECT(val, entry.fee, fee); +} + } // namespace json } // namespace cryptonote diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index 35ea990b3..4514ad568 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -292,6 +292,9 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::Bulletproof& p); void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::BulletproofPlus& p); +void fromJsonValue(const rapidjson::Value& val, rct::BulletproofPlus& p); + void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const rct::boroSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); @@ -304,6 +307,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& inf void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::rpc::output_distribution& dist); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribution& dist); +void toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const cryptonote::tx_block_template_backlog_entry& entry); +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_block_template_backlog_entry& entry); + template <typename Map> typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Writer<epee::byte_stream>& dest, const Map& map); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index dc031b36c..827ac3ff5 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -235,7 +235,6 @@ namespace const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); - const char* USAGE_FINALIZE_MULTISIG("finalize_multisig <string> [<string>...]"); const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); @@ -1021,7 +1020,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); - std::string multisig_info = m_wallet->get_multisig_info(); + std::string multisig_info = m_wallet->get_multisig_first_kex_msg(); success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig <threshold> <info1> [<info2>...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); @@ -1122,58 +1121,6 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo return true; } -bool simple_wallet::finalize_multisig(const std::vector<std::string> &args) -{ - bool ready; - if (m_wallet->key_on_device()) - { - fail_msg_writer() << tr("command not supported by HW wallet"); - return true; - } - - const auto pwd_container = get_and_verify_password(); - if(pwd_container == boost::none) - { - fail_msg_writer() << tr("Your original password was incorrect."); - return true; - } - - if (!m_wallet->multisig(&ready)) - { - fail_msg_writer() << tr("This wallet is not multisig"); - return true; - } - if (ready) - { - fail_msg_writer() << tr("This wallet is already finalized"); - return true; - } - - LOCK_IDLE_SCOPE(); - - if (args.size() < 2) - { - PRINT_USAGE(USAGE_FINALIZE_MULTISIG); - return true; - } - - try - { - if (!m_wallet->finalize_multisig(pwd_container->password(), args)) - { - fail_msg_writer() << tr("Failed to finalize multisig"); - return true; - } - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("Failed to finalize multisig: ") << e.what(); - return true; - } - - return true; -} - bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { exchange_multisig_keys_main(args, false); @@ -3041,6 +2988,19 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->show_wallet_name_when_locked(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + bool simple_wallet::set_inactivity_lock_timeout(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { #ifdef _WIN32 @@ -3484,6 +3444,8 @@ simple_wallet::simple_wallet() " Whether to automatically start mining for RPC payment if the daemon requires it.\n" "credits-target <unsigned int>\n" " The RPC payment credits balance to target (0 for default).\n " + "show-wallet-name-when-locked <1|0>\n " + " Set this if you would like to display the wallet name when locked.\n " "inactivity-lock-timeout <unsigned int>\n " " How many seconds to wait before locking the wallet (0 to disable).")); m_cmd_binder.set_handler("encrypted_seed", @@ -3627,10 +3589,6 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("make_multisig", boost::bind(&simple_wallet::on_command, this, &simple_wallet::make_multisig, _1), tr(USAGE_MAKE_MULTISIG), tr("Turn this wallet into a multisig wallet")); - m_cmd_binder.set_handler("finalize_multisig", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::finalize_multisig, _1), - tr(USAGE_FINALIZE_MULTISIG), - tr("Turn this wallet into a multisig wallet, extra step for N-1/N wallets")); m_cmd_binder.set_handler("exchange_multisig_keys", boost::bind(&simple_wallet::on_command, this, &simple_wallet::exchange_multisig_keys, _1), tr(USAGE_EXCHANGE_MULTISIG_KEYS), @@ -3893,6 +3851,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; success_msg_writer() << "device-name = " << m_wallet->device_name(); success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); + success_msg_writer() << "show-wallet-name-when-locked = " << m_wallet->show_wallet_name_when_locked(); success_msg_writer() << "inactivity-lock-timeout = " << m_wallet->inactivity_lock_timeout() #ifdef _WIN32 << " (disabled on Windows)" @@ -3960,6 +3919,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); @@ -6061,6 +6021,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args auto local_args = args; LOCK_IDLE_SCOPE(); + std::set<uint32_t> subaddr_indices; bool filter = false; bool available = false; bool verbose = false; @@ -6086,6 +6047,11 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args verbose = true; else if (local_args[0] == "uses") uses = true; + else if (local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + } else { fail_msg_writer() << tr("Invalid keyword: ") << local_args.front(); @@ -6098,14 +6064,6 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args PAUSE_READLINE(); - std::set<uint32_t> subaddr_indices; - if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") - { - if (!parse_subaddress_indices(local_args[0], subaddr_indices)) - return true; - local_args.erase(local_args.begin()); - } - if (local_args.size() > 0) { PRINT_USAGE(USAGE_INCOMING_TRANSFERS); @@ -6522,6 +6480,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) { const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); + + const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); + if (show_wallet_name) + { + tools::msg_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + tools::msg_writer() << tr("Network type: ") << ( + m_wallet->nettype() == cryptonote::TESTNET ? tr("Testnet") : + m_wallet->nettype() == cryptonote::STAGENET ? tr("Stagenet") : tr("Mainnet") + ); + } try { if (get_and_verify_password()) @@ -10973,8 +10941,8 @@ void simple_wallet::mms_init(const std::vector<std::string> &args) std::vector<std::string> numbers; boost::split(numbers, mn, boost::is_any_of("/")); bool mn_ok = (numbers.size() == 2) - && get_number_from_arg(numbers[1], num_authorized_signers, 2, 100) - && get_number_from_arg(numbers[0], num_required_signers, 2, num_authorized_signers); + && get_number_from_arg(numbers[1], num_authorized_signers, 2, config::MULTISIG_MAX_SIGNERS) + && get_number_from_arg(numbers[0], num_required_signers, 1, num_authorized_signers); if (!mn_ok) { fail_msg_writer() << tr("Error in the number of required signers and/or authorized signers"); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 8780bee1d..643270735 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -148,6 +148,7 @@ namespace cryptonote bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>()); bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); @@ -233,7 +234,6 @@ namespace cryptonote bool prepare_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool make_multisig(const std::vector<std::string>& args); bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); - bool finalize_multisig(const std::vector<std::string> &args); bool exchange_multisig_keys(const std::vector<std::string> &args); bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 0afbda705..87242b79c 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -450,7 +450,7 @@ WalletImpl::~WalletImpl() LOG_PRINT_L1(__FUNCTION__); m_wallet->callback(NULL); // Pause refresh thread - prevents refresh from starting again - pauseRefresh(); + WalletImpl::pauseRefresh(); // Call the method directly (not polymorphically) to protect against UB in destructor. // Close wallet - stores cache and stops ongoing refresh operation close(false); // do not store wallet as part of the closing activities // Stop refresh thread @@ -1332,7 +1332,7 @@ MultisigState WalletImpl::multisig() const { string WalletImpl::getMultisigInfo() const { try { clearStatus(); - return m_wallet->get_multisig_info(); + return m_wallet->get_multisig_first_kex_msg(); } catch (const exception& e) { LOG_ERROR("Error on generating multisig info: " << e.what()); setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); @@ -1341,7 +1341,7 @@ string WalletImpl::getMultisigInfo() const { return string(); } -string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { +string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) { try { clearStatus(); @@ -1366,30 +1366,12 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info); } catch (const exception& e) { LOG_ERROR("Error on exchanging multisig keys: " << e.what()); - setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what()); } return string(); } -bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { - try { - clearStatus(); - checkMultisigWalletNotReady(m_wallet); - - if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { - return true; - } - - setStatusError(tr("Failed to finalize multisig wallet creation")); - } catch (const exception& e) { - LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what()); - setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); - } - - return false; -} - bool WalletImpl::exportMultisigImages(string& images) { try { clearStatus(); @@ -1760,6 +1742,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str extra_size, m_wallet->use_fork_rules(8, 0), m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), + m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), m_wallet->get_base_fee(), m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), m_wallet->get_fee_quantization_mask()); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 67fc2c08a..7e1e62081 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -147,7 +147,6 @@ public: std::string getMultisigInfo() const override; std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; std::string exchangeMultisigKeys(const std::vector<std::string> &info) override; - bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index f9c421a93..6da547054 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -790,7 +790,7 @@ struct Wallet /** * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call - * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @param threshold - number of required signers to make valid transaction. Must be <= number of participants * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info */ virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; @@ -801,12 +801,6 @@ struct Wallet */ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0; /** - * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation - * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call - * @return true if success - */ - virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; - /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images * @return true if success diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 417a27db5..f5d5e2168 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -49,6 +49,11 @@ namespace epee { namespace Monero { +WalletManagerImpl::WalletManagerImpl() +{ + tools::set_strict_default_file_permissions(true); +} + Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds) { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index cf3056a17..1e8cff877 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -95,7 +95,7 @@ public: bool setProxy(const std::string &address) override; private: - WalletManagerImpl() {} + WalletManagerImpl(); friend struct WalletManagerFactory; net::http::client m_http_client; std::string m_errorString; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index a576c267c..148f957eb 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -306,7 +306,12 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, boo m_rpc_payment_seed_height = resp_t.seed_height; m_rpc_payment_cookie = resp_t.cookie; - if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43) + if (m_rpc_payment_diff == 0) + { + // If no payment required daemon doesn't give us back a hashing blob + m_rpc_payment_blob.clear(); + } + else if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43) { MERROR("Invalid hashing blob: " << resp_t.hashing_blob); return std::string("Invalid hashing blob"); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5a4cafc32..f2795b50f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <algorithm> #include <numeric> #include <tuple> #include <queue> @@ -59,6 +60,8 @@ using namespace epee; #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "multisig/multisig.h" +#include "multisig/multisig_account.h" +#include "multisig/multisig_kex_msg.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" @@ -146,10 +149,9 @@ using namespace cryptonote; #define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12 #define DEFAULT_UNLOCK_TIME (CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE * DIFFICULTY_TARGET_V2) -#define RECENT_SPEND_WINDOW (50 * DIFFICULTY_TARGET_V2) +#define RECENT_SPEND_WINDOW (15 * DIFFICULTY_TARGET_V2) static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; -static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; @@ -167,42 +169,6 @@ namespace return dir.string(); } - std::string pack_multisignature_keys(const std::string& prefix, const std::vector<crypto::public_key>& keys, const crypto::secret_key& signer_secret_key) - { - std::string data; - crypto::public_key signer; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(signer_secret_key, signer), "Failed to derive public spend key"); - data += std::string((const char *)&signer, sizeof(crypto::public_key)); - - for (const auto &key: keys) - { - data += std::string((const char *)&key, sizeof(crypto::public_key)); - } - - data.resize(data.size() + sizeof(crypto::signature)); - - crypto::hash hash; - crypto::cn_fast_hash(data.data(), data.size() - sizeof(crypto::signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, signer, signer_secret_key, signature); - - return MULTISIG_EXTRA_INFO_MAGIC + tools::base58::encode(data); - } - - std::vector<crypto::public_key> secret_keys_to_public_keys(const std::vector<crypto::secret_key>& keys) - { - std::vector<crypto::public_key> public_keys; - public_keys.reserve(keys.size()); - - std::transform(keys.begin(), keys.end(), std::back_inserter(public_keys), [] (const crypto::secret_key& k) -> crypto::public_key { - crypto::public_key p; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(k, p), "Failed to derive public spend key"); - return p; - }); - - return public_keys; - } - bool keys_intersect(const std::unordered_set<crypto::public_key>& s1, const std::unordered_set<crypto::public_key>& s2) { if (s1.empty() || s2.empty()) @@ -314,7 +280,6 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, { keys_file = file_path; wallet_file = file_path; - boost::system::error_code e; if(string_tools::get_extension(keys_file) == "keys") {//provided keys file name wallet_file = string_tools::cut_off_extension(wallet_file); @@ -816,7 +781,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { size_t size = 0; @@ -840,12 +805,12 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra size += 1; // rangeSigs - if (bulletproof) + if (bulletproof || bulletproof_plus) { size_t log_padded_outputs = 0; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; - size += (2 * (6 + log_padded_outputs) + 4 + 5) * 32 + 3; + size += (2 * (6 + log_padded_outputs) + (bulletproof_plus ? 6 : (4 + 5))) * 32 + 3; } else size += (2*64*32+32+64*32) * n_outputs; @@ -868,29 +833,29 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra // txnFee size += 4; - LOG_PRINT_L2("estimated " << (bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + LOG_PRINT_L2("estimated " << (bulletproof_plus ? "bulletproof plus" : bulletproof ? "bulletproof" : "borromean") << " rct tx size for " << n_inputs << " inputs with ring size " << (mixin+1) << " and " << n_outputs << " outputs: " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); return size; } -size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { if (use_rct) - return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); + return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); else return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size; } -uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag) +uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus) { - size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); - if (use_rct && bulletproof && n_outputs > 2) + size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + if (use_rct && (bulletproof || bulletproof_plus) && n_outputs > 2) { - const uint64_t bp_base = 368; + const uint64_t bp_base = (32 * ((bulletproof_plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) size_t log_padded_outputs = 2; while ((1<<log_padded_outputs) < n_outputs) ++log_padded_outputs; uint64_t nlr = 2 * (6 + log_padded_outputs); - const uint64_t bp_size = 32 * (9 + nlr); + const uint64_t bp_size = 32 * ((bulletproof_plus ? 6 : 9) + nlr); const uint64_t bp_clawback = (bp_base * (1<<log_padded_outputs) - bp_size) * 4 / 5; MDEBUG("clawback on size " << size << ": " << bp_clawback); size += bp_clawback; @@ -903,6 +868,11 @@ uint8_t get_bulletproof_fork() return 8; } +uint8_t get_bulletproof_plus_fork() +{ + return HF_VERSION_BULLETPROOF_PLUS; +} + uint8_t get_clsag_fork() { return HF_VERSION_CLSAG; @@ -1024,13 +994,7 @@ gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets, double shap end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); - THROW_WALLET_EXCEPTION_IF(outputs_to_consider == 0, error::wallet_internal_error, "No rct outputs to consider"); - average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range - if (average_output_time == 0) { - // TODO: apply this to all cases; do so alongside a hard fork, where all clients will update at the same time, preventing anonymity puddle formation - average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); - } - THROW_WALLET_EXCEPTION_IF(average_output_time == 0, error::wallet_internal_error, "Average seconds per output cannot be 0."); + average_output_time = DIFFICULTY_TARGET_V2 * blocks_to_consider / static_cast<double>(outputs_to_consider); // this assumes constant target over the whole rct range }; gamma_picker::gamma_picker(const std::vector<uint64_t> &rct_offsets): gamma_picker(rct_offsets, GAMMA_SHAPE, GAMMA_SCALE) {} @@ -1209,6 +1173,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ignore_outputs_above(MONEY_SUPPLY), m_ignore_outputs_below(0), m_track_uses(false), + m_show_wallet_name_when_locked(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), m_persistent_rpc_client_id(false), @@ -1235,8 +1200,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), - m_encrypt_keys_after_refresh(boost::none), - m_decrypt_keys_lockers(0), m_unattended(unattended), m_devices_registered(false), m_device_last_key_image_sync(0), @@ -1859,6 +1822,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation & case rct::RCTTypeBulletproof: case rct::RCTTypeBulletproof2: case rct::RCTTypeCLSAG: + case rct::RCTTypeBulletproofPlus: return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev); case rct::RCTTypeFull: return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev); @@ -1888,8 +1852,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received"); THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero")); THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero")); - decrypt_keys(*pwd); - m_encrypt_keys_after_refresh = *pwd; + m_encrypt_keys_after_refresh.reset(new wallet_keys_unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, *pwd)); } } @@ -3021,11 +2984,7 @@ void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, MTRACE("update_pool_state start"); auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { - if (m_encrypt_keys_after_refresh) - { - encrypt_keys(*m_encrypt_keys_after_refresh); - m_encrypt_keys_after_refresh = boost::none; - } + m_encrypt_keys_after_refresh.reset(); }); // get the pool state @@ -3456,11 +3415,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo start_height = 0; auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() { - if (m_encrypt_keys_after_refresh) - { - encrypt_keys(*m_encrypt_keys_after_refresh); - m_encrypt_keys_after_refresh = boost::none; - } + m_encrypt_keys_after_refresh.reset(); }); auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);}); @@ -4018,6 +3973,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); + json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); + value2.SetInt(m_inactivity_lock_timeout); json.AddMember("inactivity_lock_timeout", value2, json.GetAllocator()); @@ -4202,6 +4160,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_above = MONEY_SUPPLY; m_ignore_outputs_below = 0; m_track_uses = false; + m_show_wallet_name_when_locked = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; m_setup_background_mining = BackgroundMiningMaybe; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; @@ -4376,6 +4335,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_below = field_ignore_outputs_below; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, show_wallet_name_when_locked, int, Int, false, false); + m_show_wallet_name_when_locked = field_show_wallet_name_when_locked; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT); m_inactivity_lock_timeout = field_inactivity_lock_timeout; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); @@ -4467,7 +4428,26 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_account.set_device(hwdev); account_public_address device_account_public_address; - THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + bool fetch_device_address = true; + + ::hw::device_cold* dev_cold = nullptr; + if (m_key_device_type == hw::device::device_type::TREZOR && (dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev)) != nullptr) { + THROW_WALLET_EXCEPTION_IF(!dev_cold->get_public_address_with_no_passphrase(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + if (device_account_public_address == m_account.get_keys().m_account_address) { + LOG_PRINT_L0("Wallet opened with an empty passphrase"); + fetch_device_address = false; + dev_cold->set_use_empty_passphrase(true); + } else { + fetch_device_address = true; + LOG_PRINT_L0("Wallet opening with an empty passphrase failed. Retry again: " << fetch_device_address); + dev_cold->reset_session(); + } + } + + if (fetch_device_address) { + THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address"); + } + THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. If the device uses the passphrase feature, please check whether the passphrase was entered correctly (it may have been misspelled - different passphrases generate different wallets, passphrase is case-sensitive). " "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) + ", wallet address: " + m_account.get_public_address_str(m_nettype)); @@ -4581,18 +4561,12 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip void wallet2::encrypt_keys(const crypto::chacha_key &key) { - boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); - if (--m_decrypt_keys_lockers) // another lock left ? - return; m_account.encrypt_keys(key); m_account.decrypt_viewkey(key); } void wallet2::decrypt_keys(const crypto::chacha_key &key) { - boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); - if (m_decrypt_keys_lockers++) // already unlocked ? - return; m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -4768,7 +4742,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); - m_account.finalize_multisig(spend_public_key); // Not possible to restore a multisig wallet that is able to activate the MMS // (because the original keys are not (yet) part of the restore info), so @@ -4983,24 +4956,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p store(); } } - +//---------------------------------------------------------------------------------------------------- std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold) + const std::vector<std::string> &initial_kex_msgs, + const std::uint32_t threshold) { - CHECK_AND_ASSERT_THROW_MES(!view_keys.empty(), "empty view keys"); - CHECK_AND_ASSERT_THROW_MES(view_keys.size() == spend_keys.size(), "Mismatched view/spend key sizes"); - CHECK_AND_ASSERT_THROW_MES(threshold > 1 && threshold <= spend_keys.size() + 1, "Invalid threshold"); - - std::string extra_multisig_info; - std::vector<crypto::secret_key> multisig_keys; - rct::key spend_pkey = rct::identity(); - rct::key spend_skey; - auto wiper = epee::misc_utils::create_scope_leave_handler([&](){memwipe(&spend_skey, sizeof(spend_skey));}); - std::vector<crypto::public_key> multisig_signers; - - // decrypt keys + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5008,104 +4969,89 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() + { + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); + } + ); } - // In common multisig scheme there are 4 types of key exchange rounds: - // 1. First round is exchange of view secret keys and public spend keys. - // 2. Middle round is exchange of derivations: Ki = b * Mj, where b - spend secret key, - // M - public multisig key (in first round it equals to public spend key), K - new public multisig key. - // 3. Secret spend establishment round sets your secret multisig keys as follows: kl = H(Ml), where M - is *your* public multisig key, - // k - secret multisig key used to sign transactions. k and M are sets of keys, of course. - // And secret spend key as the sum of all participant's secret multisig keys - // 4. Last round establishes multisig wallet's public spend key. Participants exchange their public multisig keys - // and calculate common spend public key as sum of all unique participants' public multisig keys. - // Note that N/N scheme has only first round. N-1/N has 2 rounds: first and last. Common M/N has all 4 rounds. - - // IMPORTANT: wallet's public spend key is not equal to secret_spend_key * G! - // Wallet's public spend key is the sum of unique public multisig keys of all participants. - // secret_spend_key * G = public signer key - - if (threshold == spend_keys.size() + 1) - { - // In N / N case we only need to do one round and calculate secret multisig keys and new secret spend key - MINFO("Creating spend key..."); + // create multisig account + multisig::multisig_account multisig_account{ + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - // Calculates all multisig keys and spend key - cryptonote::generate_multisig_N_N(get_account().get_keys(), spend_keys, multisig_keys, spend_skey, spend_pkey); + // open initial kex messages, validate them, extract signers + std::vector<multisig::multisig_kex_msg> expanded_msgs; + std::vector<crypto::public_key> signers; + expanded_msgs.reserve(initial_kex_msgs.size()); + signers.reserve(initial_kex_msgs.size() + 1); - // Our signer key is b * G, where b is secret spend key. - multisig_signers = spend_keys; - multisig_signers.push_back(get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key)); - } - else + for (const auto &msg : initial_kex_msgs) { - // We just got public spend keys of all participants and deriving multisig keys (set of Mi = b * Bi). - // note that derivations are public keys as DH exchange suppose it to be - auto derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), spend_keys); + expanded_msgs.emplace_back(msg); - spend_pkey = rct::identity(); - multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey); + // validate each message + // 1. must be 'round 1' + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1, + "Trying to make multisig with message that has invalid multisig kex round (should be '1')."); - if (threshold == spend_keys.size()) - { - // N - 1 / N case + // 2. duplicate signers not allowed + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(), + "Duplicate signers not allowed when converting a wallet to multisig."); - // We need an extra step, so we package all the composite public keys - // we know about, and make a signed string out of them - MINFO("Creating spend key..."); + // add signer (skip self for now) + if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey()) + signers.push_back(expanded_msgs.back().get_signing_pubkey()); + } - // Calculating set of our secret multisig keys as follows: mi = H(Mi), - // where mi - secret multisig key, Mi - others' participants public multisig key - multisig_keys = cryptonote::calculate_multisig_keys(derivations); + // add self to signers + signers.push_back(multisig_account.get_base_pubkey()); - // calculating current participant's spend secret key as sum of all secret multisig keys for current participant. - // IMPORTANT: participant's secret spend key is not an entire wallet's secret spend! - // Entire wallet's secret spend is sum of all unique secret multisig keys - // among all of participants and is not held by anyone! - spend_skey = rct::sk2rct(cryptonote::calculate_multisig_signer_key(multisig_keys)); + // intialize key exchange + multisig_account.initialize_kex(threshold, signers, expanded_msgs); + CHECK_AND_ASSERT_THROW_MES(multisig_account.account_is_active(), "Failed to activate multisig account."); - // Preparing data for the last round to calculate common public spend key. The data contains public multisig keys. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), rct::rct2sk(spend_skey)); - } - else - { - // M / N case - MINFO("Preparing keys for next exchange round..."); - - // Preparing data for middle round - packing new public multisig keys to exchage with others. - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, derivations, m_account.get_keys().m_spend_secret_key); - spend_skey = rct::sk2rct(m_account.get_keys().m_spend_secret_key); - - // Need to store middle keys to be able to proceed in case of wallet shutdown. - m_multisig_derivations = derivations; - } - } - + // update wallet state if (!m_original_keys_available) { // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages // (making a wallet multisig overwrites those keys, see account_base::make_multisig) - m_original_address = m_account.get_keys().m_account_address; - m_original_view_secret_key = m_account.get_keys().m_view_secret_key; + m_original_address = get_account().get_keys().m_account_address; + m_original_view_secret_key = get_account().get_keys().m_view_secret_key; m_original_keys_available = true; } clear(); - MINFO("Creating view key..."); - crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(get_account().get_keys().m_view_secret_key, view_keys); + // account base MINFO("Creating multisig address..."); - CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(view_skey, rct::rct2sk(spend_skey), rct::rct2pk(spend_pkey), multisig_keys), - "Failed to create multisig wallet due to bad keys"); - memwipe(&spend_skey, sizeof(rct::key)); + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to create multisig wallet account due to bad keys"); init_type(hw::device::device_type::SOFTWARE); m_original_keys_available = true; m_multisig = true; m_multisig_threshold = threshold; - m_multisig_signers = multisig_signers; - ++m_multisig_rounds_passed; + m_multisig_signers = signers; + m_multisig_rounds_passed = 1; + + // derivations stored (should be empty in last round) + // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5118,42 +5064,18 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, if (!m_wallet_file.empty()) store(); - return extra_multisig_info; -} - -std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info) -{ - THROW_WALLET_EXCEPTION_IF(info.empty(), - error::wallet_internal_error, "Empty multisig info"); - - if (info[0].substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - THROW_WALLET_EXCEPTION_IF(false, - error::wallet_internal_error, "Unsupported info string"); - } - - std::vector<crypto::public_key> signers; - std::unordered_set<crypto::public_key> pkeys; - - THROW_WALLET_EXCEPTION_IF(!unpack_extra_multisig_info(info, signers, pkeys), - error::wallet_internal_error, "Bad extra multisig info"); - - return exchange_multisig_keys(password, pkeys, signers); + return multisig_account.get_next_kex_round_msg(); } - +//---------------------------------------------------------------------------------------------------- std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> derivations, - std::vector<crypto::public_key> signers) + const std::vector<std::string> &kex_messages) { - CHECK_AND_ASSERT_THROW_MES(!derivations.empty(), "empty pkeys"); - CHECK_AND_ASSERT_THROW_MES(!signers.empty(), "empty signers"); - - bool ready = false; + bool ready{false}; CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig"); CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished"); + CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); - // keys are decrypted + // decrypt account keys epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { @@ -5161,37 +5083,72 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); m_account.encrypt_viewkey(chacha_key); m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); }); - } - - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 1) - { - // the last round is passed and we have to calculate spend public key - // add ours if not included - crypto::public_key local_signer = get_multisig_signer_public_key(); - - if (std::find(signers.begin(), signers.end(), local_signer) == signers.end()) - { - signers.push_back(local_signer); - for (const auto &msk: get_account().get_multisig_keys()) + keys_reencryptor = epee::misc_utils::create_scope_leave_handler( + [&, this, chacha_key]() { - derivations.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(msk)))); + m_account.encrypt_keys(chacha_key); + m_account.decrypt_viewkey(chacha_key); } - } + ); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const auto &msg : kex_messages) + expanded_msgs.emplace_back(msg); + + // reconstruct multisig account + crypto::public_key dummy; + multisig::multisig_account::kex_origins_map_t kex_origins_map; + + for (const auto &derivation : m_multisig_derivations) + kex_origins_map[derivation]; + + multisig::multisig_account multisig_account{ + m_multisig_threshold, + m_multisig_signers, + get_account().get_keys().m_spend_secret_key, + crypto::null_skey, //base common privkey: not used + get_account().get_keys().m_multisig_keys, + get_account().get_keys().m_view_secret_key, + m_account_public_address.m_spend_public_key, + dummy, //common pubkey: not used + m_multisig_rounds_passed, + std::move(kex_origins_map), + "" + }; - CHECK_AND_ASSERT_THROW_MES(signers.size() == m_multisig_signers.size(), "Bad signers size"); + // update multisig kex + multisig_account.kex_update(expanded_msgs); - // Summing all of unique public multisig keys to calculate common public spend key - crypto::public_key spend_public_key = cryptonote::generate_multisig_M_N_spend_public_key(std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - m_account_public_address.m_spend_public_key = spend_public_key; - m_account.finalize_multisig(spend_public_key); + // update wallet state - m_multisig_signers = signers; - std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)) < 0; }); + // address + m_account_public_address.m_spend_public_key = multisig_account.get_multisig_pubkey(); - ++m_multisig_rounds_passed; - m_multisig_derivations.clear(); + // account base + CHECK_AND_ASSERT_THROW_MES(m_account.make_multisig(multisig_account.get_common_privkey(), + multisig_account.get_base_privkey(), + multisig_account.get_multisig_pubkey(), + multisig_account.get_multisig_privkeys()), + "Failed to update multisig wallet account due to bad keys"); + + // derivations stored (should be empty in last round) + // TODO: make use of the origins map for aggregation-style signing (instead of round-robin) + m_multisig_derivations.clear(); + m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); + + for (const auto &key_to_origins : multisig_account.get_kex_keys_to_origins_map()) + m_multisig_derivations.push_back(key_to_origins.first); + // rounds passed + m_multisig_rounds_passed = multisig_account.get_kex_rounds_complete(); + + // why is this necessary? who knows... + if (multisig_account.multisig_is_ready()) + { // keys are encrypted again keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); @@ -5213,270 +5170,28 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor if (!m_wallet_file.empty()) store(); - - return {}; } - // Below are either middle or secret spend key establishment rounds - - for (const auto& key: m_multisig_derivations) - derivations.erase(key); - - // Deriving multisig keys (set of Mi = b * Bi) according to DH from other participants' multisig keys. - auto new_derivations = cryptonote::generate_multisig_derivations(get_account().get_keys(), std::vector<crypto::public_key>(derivations.begin(), derivations.end())); - - std::string extra_multisig_info; - if (m_multisig_rounds_passed == multisig_rounds_required(m_multisig_signers.size(), m_multisig_threshold) - 2) // next round is last - { - // Next round is last therefore we are performing secret spend establishment round as described above. - MINFO("Creating spend key..."); - - // Calculating our secret multisig keys by hashing our public multisig keys. - auto multisig_keys = cryptonote::calculate_multisig_keys(std::vector<crypto::public_key>(new_derivations.begin(), new_derivations.end())); - // And summing it to get personal secret spend key - crypto::secret_key spend_skey = cryptonote::calculate_multisig_signer_key(multisig_keys); - - m_account.make_multisig(m_account.get_keys().m_view_secret_key, spend_skey, rct::rct2pk(rct::identity()), multisig_keys); - - // Packing public multisig keys to exchange with others and calculate common public spend key in the last round - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, secret_keys_to_public_keys(multisig_keys), spend_skey); - } - else - { - // This is just middle round - MINFO("Preparing keys for next exchange round..."); - extra_multisig_info = pack_multisignature_keys(MULTISIG_EXTRA_INFO_MAGIC, new_derivations, m_account.get_keys().m_spend_secret_key); - m_multisig_derivations = new_derivations; - } - - ++m_multisig_rounds_passed; - + // wallet/file relationship if (!m_wallet_file.empty()) create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); - return extra_multisig_info; -} - -void wallet2::unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const -{ - // parse all multisig info - public_keys.resize(info.size()); - secret_keys.resize(info.size()); - for (size_t i = 0; i < info.size(); ++i) - { - THROW_WALLET_EXCEPTION_IF(!verify_multisig_info(info[i], secret_keys[i], public_keys[i]), - error::wallet_internal_error, "Bad multisig info: " + info[i]); - } - - // remove duplicates - for (size_t i = 0; i < secret_keys.size(); ++i) - { - for (size_t j = i + 1; j < secret_keys.size(); ++j) - { - if (rct::sk2rct(secret_keys[i]) == rct::sk2rct(secret_keys[j])) - { - MDEBUG("Duplicate key found, ignoring"); - secret_keys[j] = secret_keys.back(); - public_keys[j] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --j; - } - } - } - - // people may include their own, weed it out - const crypto::secret_key local_skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key local_pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - for (size_t i = 0; i < secret_keys.size(); ++i) - { - if (secret_keys[i] == local_skey) - { - MDEBUG("Local key is present, ignoring"); - secret_keys[i] = secret_keys.back(); - public_keys[i] = public_keys.back(); - secret_keys.pop_back(); - public_keys.pop_back(); - --i; - } - else - { - THROW_WALLET_EXCEPTION_IF(public_keys[i] == local_pkey, error::wallet_internal_error, - "Found local spend public key, but not local view secret key - something very weird"); - } - } -} - -std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold) -{ - std::vector<crypto::secret_key> secret_keys(info.size()); - std::vector<crypto::public_key> public_keys(info.size()); - unpack_multisig_info(info, public_keys, secret_keys); - return make_multisig(password, secret_keys, public_keys, threshold); -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers) -{ - bool ready; - uint32_t threshold, total; - if (!multisig(&ready, &threshold, &total)) - { - MERROR("This is not a multisig wallet"); - return false; - } - if (ready) - { - MERROR("This multisig wallet is already finalized"); - return false; - } - if (threshold + 1 != total) - { - MERROR("finalize_multisig should only be used for N-1/N wallets, use exchange_multisig_keys instead"); - return false; - } - exchange_multisig_keys(password, pkeys, signers); - return true; -} - -bool wallet2::unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const -{ - // parse all multisig info - signers.resize(info.size(), crypto::null_pkey); - for (size_t i = 0; i < info.size(); ++i) - { - if (!verify_extra_multisig_info(info[i], pkeys, signers[i])) - { - return false; - } - } - - return true; -} - -bool wallet2::finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info) -{ - std::unordered_set<crypto::public_key> public_keys; - std::vector<crypto::public_key> signers; - if (!unpack_extra_multisig_info(info, signers, public_keys)) - { - MERROR("Bad multisig info"); - return false; - } - - return finalize_multisig(password, public_keys, signers); -} - -std::string wallet2::get_multisig_info() const -{ - // It's a signed package of private view key and public spend key - const crypto::secret_key skey = cryptonote::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key); - const crypto::public_key pkey = get_multisig_signer_public_key(get_account().get_keys().m_spend_secret_key); - crypto::hash hash; - - std::string data; - data += std::string((const char *)&skey, sizeof(crypto::secret_key)); - data += std::string((const char *)&pkey, sizeof(crypto::public_key)); - - data.resize(data.size() + sizeof(crypto::signature)); - crypto::cn_fast_hash(data.data(), data.size() - sizeof(signature), hash); - crypto::signature &signature = *(crypto::signature*)&data[data.size() - sizeof(crypto::signature)]; - crypto::generate_signature(hash, pkey, get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), signature); - - return std::string("MultisigV1") + tools::base58::encode(data); -} - -bool wallet2::verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey) -{ - const size_t header_len = strlen("MultisigV1"); - if (data.size() < header_len || data.substr(0, header_len) != "MultisigV1") - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(header_len), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() != sizeof(crypto::secret_key) + sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - size_t offset = 0; - skey = *(const crypto::secret_key*)(decoded.data() + offset); - offset += sizeof(skey); - pkey = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(pkey); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, pkey, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - return true; + return multisig_account.get_next_kex_round_msg(); } - -bool wallet2::verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer) +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_multisig_first_kex_msg() const { - if (data.size() < MULTISIG_EXTRA_INFO_MAGIC.size() || data.substr(0, MULTISIG_EXTRA_INFO_MAGIC.size()) != MULTISIG_EXTRA_INFO_MAGIC) - { - MERROR("Multisig info header check error"); - return false; - } - std::string decoded; - if (!tools::base58::decode(data.substr(MULTISIG_EXTRA_INFO_MAGIC.size()), decoded)) - { - MERROR("Multisig info decoding error"); - return false; - } - if (decoded.size() < sizeof(crypto::public_key) + sizeof(crypto::signature)) - { - MERROR("Multisig info is corrupt"); - return false; - } - if ((decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) % sizeof(crypto::public_key)) - { - MERROR("Multisig info is corrupt"); - return false; - } - - const size_t n_keys = (decoded.size() - (sizeof(crypto::public_key) + sizeof(crypto::signature))) / sizeof(crypto::public_key); - size_t offset = 0; - signer = *(const crypto::public_key*)(decoded.data() + offset); - offset += sizeof(signer); - const crypto::signature &signature = *(const crypto::signature*)(decoded.data() + offset + n_keys * sizeof(crypto::public_key)); - - crypto::hash hash; - crypto::cn_fast_hash(decoded.data(), decoded.size() - sizeof(signature), hash); - if (!crypto::check_signature(hash, signer, signature)) - { - MERROR("Multisig info signature is invalid"); - return false; - } - - for (size_t n = 0; n < n_keys; ++n) - { - crypto::public_key mspk = *(const crypto::public_key*)(decoded.data() + offset); - pkeys.insert(mspk); - offset += sizeof(mspk); - } + // create multisig account + multisig::multisig_account multisig_account{ + // k_base = H(normal private spend key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), + // k_view = H(normal private view key) + multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + }; - return true; + return multisig_account.get_next_kex_round_msg(); } - +//---------------------------------------------------------------------------------------------------- bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const { if (!m_multisig) @@ -5489,7 +5204,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())); return true; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_multisig_partial_key_images() const { if (!m_multisig) @@ -5499,7 +5214,7 @@ bool wallet2::has_multisig_partial_key_images() const return true; return false; } - +//---------------------------------------------------------------------------------------------------- bool wallet2::has_unknown_key_images() const { for (const auto &td: m_transfers) @@ -5714,13 +5429,14 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); //keys loaded ok! - //try to load wallet file. but even if we failed, it is not big problem - if (use_fs && (!boost::filesystem::exists(m_wallet_file, e) || e)) + //try to load wallet cache. but even if we failed, it is not big problem + bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty(); + if (cache_missing) { - LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); + LOG_PRINT_L0("wallet cache missing: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; } - else if (use_fs || !cache_buf.empty()) + else { wallet2::cache_file_data cache_file_data; std::string cache_file_buf; @@ -6130,6 +5846,19 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo amount_per_subaddr[0] = utx.second.m_change; else found->second += utx.second.m_change; + + // add transfers to same wallet + for (const auto &dest: utx.second.m_dests) { + auto index = get_subaddress_index(dest.addr); + if (index && (*index).major == index_major) + { + auto found = amount_per_subaddr.find((*index).minor); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[(*index).minor] = dest.amount; + else + found->second += dest.amount; + } + } } } @@ -7077,7 +6806,6 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func) { std::string s = signed_tx_st; - boost::system::error_code errcode; signed_tx_set signed_txs; const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1; @@ -7501,16 +7229,16 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const { if (use_per_byte_fee) { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); } else { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag); + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); } } @@ -9231,8 +8959,8 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; ptx.construction_data.rct_config = { - tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, - use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1 + rct::RangeProofPaddedBulletproof, + use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4 : 3 }; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs @@ -9927,10 +9655,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool use_rct = use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { - bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0 + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; const uint64_t base_fee = get_base_fee(); @@ -9966,7 +9695,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // early out if we know we can't make it anyway // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through - const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag)); + const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus)); uint64_t balance_subtotal = 0; uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) @@ -9984,8 +9713,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Candidate subaddress index for spending: " << i); // determine threshold for fractional amount - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -10082,7 +9811,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. - uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); + uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { @@ -10195,7 +9924,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) + while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << @@ -10212,7 +9941,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp ++original_output_index; } - if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && + estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); @@ -10250,7 +9980,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus); try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit); } @@ -10261,7 +9991,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); auto try_carving_from_partial_payment = [&](uint64_t needed_fee, uint64_t available_for_fee) { @@ -10521,11 +10251,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below // determine threshold for fractional amount const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); - const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag); - const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag); + const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); + const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag, bulletproof_plus); THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!"); const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring; const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024); @@ -10631,10 +10362,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE); const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); const rct::RCTConfig rct_config { - bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean, - bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0, + rct::RangeProofPaddedBulletproof, + bulletproof_plus ? 4 : 3 }; const uint64_t base_fee = get_base_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm()); @@ -10663,7 +10395,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton uint64_t fee_dust_threshold; if (use_fork_rules(HF_VERSION_PER_BYTE_FEE)) { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag, bulletproof_plus); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask); } else @@ -10694,7 +10426,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_weight_limit); - const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag, bulletproof_plus); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit)); if (try_tx) { @@ -10702,7 +10434,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers); - needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); + needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, bulletproof_plus, base_fee, fee_multiplier, fee_quantization_mask); // add N - 1 outputs for correct initial fee estimation for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) @@ -11564,8 +11296,10 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::secret_key scalar1; crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); - const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + rct::key C = tx.rct_signatures.outPk[n].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + C = rct::scalarmult8(C); rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount"); @@ -12217,7 +11951,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::secret_key shared_secret; crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); amount = rct::h2d(ecdh_info.amount); } total += amount; @@ -13323,13 +13057,6 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) return imported_outputs; } //---------------------------------------------------------------------------------------------------- -crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const -{ - crypto::public_key pkey; - crypto::secret_key_to_public_key(get_multisig_blinded_secret_key(spend_skey), pkey); - return pkey; -} -//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_multisig_signer_public_key() const { CHECK_AND_ASSERT_THROW_MES(m_multisig, "Wallet is not multisig"); @@ -13373,7 +13100,7 @@ rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) con CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index"); rct::multisig_kLRki kLRki; kLRki.k = k; - cryptonote::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); + multisig::generate_multisig_LR(m_transfers[n].get_public_key(), rct::rct2sk(kLRki.k), (crypto::public_key&)kLRki.L, (crypto::public_key&)kLRki.R); kLRki.ki = rct::ki2rct(m_transfers[n].m_key_image); return kLRki; } @@ -13420,7 +13147,7 @@ crypto::key_image wallet2::get_multisig_composite_key_image(size_t n) const for (const auto &info: td.m_multisig_info) for (const auto &pki: info.m_partial_key_images) pkis.push_back(pki); - bool r = cryptonote::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); + bool r = multisig::generate_multisig_composite_key_image(get_account().get_keys(), m_subaddresses, td.get_public_key(), tx_key, additional_tx_keys, td.m_internal_output_index, pkis, ki); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); return ki; } @@ -13443,7 +13170,7 @@ cryptonote::blobdata wallet2::export_multisig() for (size_t m = 0; m < get_account().get_multisig_keys().size(); ++m) { // we want to export the partial key image, not the full one, so we can't use td.m_key_image - bool r = generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); + bool r = multisig::generate_multisig_key_image(get_account().get_keys(), m, td.get_public_key(), ki); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate key image"); info[n].m_partial_key_images.push_back(ki); } @@ -13734,12 +13461,8 @@ std::string wallet2::make_uri(const std::string &address, const std::string &pay if (!payment_id.empty()) { - crypto::hash pid32; - if (!wallet2::parse_long_payment_id(payment_id, pid32)) - { - error = "Invalid payment id"; - return std::string(); - } + error = "Standalone payment id deprecated, use integrated address instead"; + return std::string(); } std::string uri = "monero:" + address; @@ -14362,9 +14085,10 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i n_outputs = 2; // extra dummy output const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + const bool bulletproof_plus = use_fork_rules(get_bulletproof_plus_fork(), 0); const bool clsag = use_fork_rules(get_clsag_fork(), 0); - size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); - uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag, bulletproof_plus); return std::make_pair(size, weight); } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index facf9878d..ccf9a96a3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -757,45 +757,20 @@ private: * to other participants */ std::string make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &info, - uint32_t threshold); + const std::vector<std::string> &kex_messages, + const std::uint32_t threshold); /*! - * \brief Creates a multisig wallet + * \brief Increment the multisig key exchange round * \return empty if done, non empty if we need to send another string * to other participants */ - std::string make_multisig(const epee::wipeable_string &password, - const std::vector<crypto::secret_key> &view_keys, - const std::vector<crypto::public_key> &spend_keys, - uint32_t threshold); std::string exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &info); - /*! - * \brief Any but first round of keys exchange - */ - std::string exchange_multisig_keys(const epee::wipeable_string &password, - std::unordered_set<crypto::public_key> pkeys, - std::vector<crypto::public_key> signers); - /*! - * \brief Finalizes creation of a multisig wallet - */ - bool finalize_multisig(const epee::wipeable_string &password, const std::vector<std::string> &info); - /*! - * \brief Finalizes creation of a multisig wallet - */ - bool finalize_multisig(const epee::wipeable_string &password, const std::unordered_set<crypto::public_key> &pkeys, std::vector<crypto::public_key> signers); - /*! - * Get a packaged multisig information string - */ - std::string get_multisig_info() const; - /*! - * Verifies and extracts keys from a packaged multisig information string - */ - static bool verify_multisig_info(const std::string &data, crypto::secret_key &skey, crypto::public_key &pkey); + const std::vector<std::string> &kex_messages); /*! - * Verifies and extracts keys from a packaged multisig information string + * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called) + * \return string to send to other participants */ - static bool verify_extra_multisig_info(const std::string &data, std::unordered_set<crypto::public_key> &pkeys, crypto::public_key &signer); + std::string get_multisig_first_kex_msg() const; /*! * Export multisig info * This will generate and remember new k values @@ -1229,6 +1204,8 @@ private: void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } + void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } uint32_t inactivity_lock_timeout() const { return m_inactivity_lock_timeout; } @@ -1411,7 +1388,7 @@ private: std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels); std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees); - uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; + uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, bool bulletproof_plus, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_base_fee(); uint64_t get_fee_quantization_mask(); @@ -1477,7 +1454,6 @@ private: void set_attribute(const std::string &key, const std::string &value); bool get_attribute(const std::string &key, std::string &value) const; - crypto::public_key get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const; crypto::public_key get_multisig_signer_public_key() const; crypto::public_key get_multisig_signing_public_key(size_t idx) const; crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; @@ -1641,12 +1617,6 @@ private: bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); uint64_t get_segregation_fork_height() const; - void unpack_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &public_keys, - std::vector<crypto::secret_key> &secret_keys) const; - bool unpack_extra_multisig_info(const std::vector<std::string>& info, - std::vector<crypto::public_key> &signers, - std::unordered_set<crypto::public_key> &pkeys) const; void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const; @@ -1749,6 +1719,7 @@ private: uint64_t m_ignore_outputs_above; uint64_t m_ignore_outputs_below; bool m_track_uses; + bool m_show_wallet_name_when_locked; uint32_t m_inactivity_lock_timeout; BackgroundMiningSetupType m_setup_background_mining; bool m_persistent_rpc_client_id; @@ -1798,9 +1769,7 @@ private: crypto::secret_key m_original_view_secret_key; crypto::chacha_key m_cache_key; - boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; - boost::mutex m_decrypt_keys_lock; - unsigned int m_decrypt_keys_lockers; + std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh; bool m_unattended; bool m_devices_registered; diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h index 35714db03..6f50b6727 100644 --- a/src/wallet/wallet_rpc_helpers.h +++ b/src/wallet/wallet_rpc_helpers.h @@ -28,6 +28,7 @@ #pragma once +#include <limits> #include <type_traits> namespace diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 4655e24cd..a173b5a50 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3938,7 +3938,7 @@ namespace tools return false; } - res.multisig_info = m_wallet->get_multisig_info(); + res.multisig_info = m_wallet->get_multisig_first_kex_msg(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -4069,7 +4069,7 @@ namespace tools catch (const std::exception &e) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling import_multisig"; + er.message = std::string{"Error calling import_multisig: "} + e.what(); return false; } @@ -4094,53 +4094,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { - if (!m_wallet) return not_open(er); - if (m_restricted) - { - er.code = WALLET_RPC_ERROR_CODE_DENIED; - er.message = "Command unavailable in restricted mode."; - return false; - } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) - { - er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; - er.message = "This wallet is not multisig"; - return false; - } - if (ready) - { - er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; - er.message = "This wallet is multisig, and already finalized"; - return false; - } - - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) - { - er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; - er.message = "Needs multisig info from more participants"; - return false; - } - - try - { - if (!m_wallet->finalize_multisig(req.password, req.multisig_info)) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Error calling finalize_multisig"; - return false; - } - } - catch (const std::exception &e) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = std::string("Error calling finalize_multisig: ") + e.what(); - return false; - } - res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - - return true; + return false; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx) @@ -4168,7 +4122,7 @@ namespace tools return false; } - if (req.multisig_info.size() < 1 || req.multisig_info.size() > total) + if (req.multisig_info.size() + 1 < total) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Needs multisig info from more participants"; @@ -4426,7 +4380,11 @@ namespace tools return false; } - if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) + boost::optional<epee::net_utils::http::login> daemon_login{}; + if (!req.username.empty() || !req.password.empty()) + daemon_login.emplace(req.username, req.password); + + if (!m_wallet->set_daemon(req.address, daemon_login, req.trusted, std::move(ssl_options))) { er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; er.message = std::string("Unable to set daemon"); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 248d31aa4..867ea54bd 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 23 +#define WALLET_RPC_VERSION_MINOR 24 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -2504,24 +2504,17 @@ namespace wallet_rpc struct COMMAND_RPC_FINALIZE_MULTISIG { + // NOP struct request_t { - std::string password; - std::vector<std::string> multisig_info; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(password) - KV_SERIALIZE(multisig_info) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; struct response_t { - std::string address; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2664,6 +2657,8 @@ namespace wallet_rpc struct request_t { std::string address; + std::string username; + std::string password; bool trusted; std::string ssl_support; // disabled, enabled, autodetect std::string ssl_private_key_path; @@ -2674,6 +2669,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) + KV_SERIALIZE(username) + KV_SERIALIZE(password) KV_SERIALIZE_OPT(trusted, false) KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect") KV_SERIALIZE(ssl_private_key_path) diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index 7455639ca..c8583c476 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -44,6 +44,7 @@ set(core_tests_sources v2_tests.cpp rct.cpp bulletproofs.cpp + bulletproof_plus.cpp rct2.cpp wallet_tools.cpp) @@ -65,6 +66,7 @@ set(core_tests_headers v2_tests.h rct.h bulletproofs.h + bulletproof_plus.h rct2.h wallet_tools.h) diff --git a/tests/core_tests/bulletproof_plus.cpp b/tests/core_tests/bulletproof_plus.cpp new file mode 100644 index 000000000..c3879e646 --- /dev/null +++ b/tests/core_tests/bulletproof_plus.cpp @@ -0,0 +1,373 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "ringct/rctSigs.h" +#include "ringct/bulletproofs_plus.h" +#include "chaingen.h" +#include "bulletproof_plus.h" +#include "device/device.hpp" + +using namespace epee; +using namespace crypto; +using namespace cryptonote; + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +bool gen_bpp_tx_validation_base::generate_with(std::vector<test_event_entry>& events, + size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version, + const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx, + const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + + // create 12 miner accounts, and have them mine the next 12 blocks + cryptonote::account_base miner_accounts[12]; + const cryptonote::block *prev_block = &blk_0; + cryptonote::block blocks[12 + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW]; + for (size_t n = 0; n < 12; ++n) { + miner_accounts[n].generate(); + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n], + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, + 2, 2, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2), + false, "Failed to generate block"); + events.push_back(blocks[n]); + prev_block = blocks + n; + } + + // rewind + cryptonote::block blk_r, blk_last; + { + blk_last = blocks[11]; + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[12+i], blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, + 2, 2, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2), + false, "Failed to generate block"); + events.push_back(blocks[12+i]); + blk_last = blocks[12+i]; + } + blk_r = blk_last; + } + + // create 4 txes from these miners in another block, to generate some rct outputs + std::vector<transaction> rct_txes; + cryptonote::block blk_txes; + std::vector<crypto::hash> starting_rct_tx_hashes; + uint64_t fees = 0; + static const uint64_t input_amounts_available[] = {5000000000000, 30000000000000, 100000000000, 80000000000}; + for (size_t n = 0; n < n_txes; ++n) + { + std::vector<tx_source_entry> sources; + + sources.resize(1); + tx_source_entry& src = sources.back(); + + const uint64_t needed_amount = input_amounts_available[n]; + src.amount = input_amounts_available[n]; + size_t real_index_in_tx = 0; + for (size_t m = 0; m <= mixin; ++m) { + size_t index_in_tx = 0; + for (size_t i = 0; i < blocks[m].miner_tx.vout.size(); ++i) + if (blocks[m].miner_tx.vout[i].amount == needed_amount) + index_in_tx = i; + CHECK_AND_ASSERT_MES(blocks[m].miner_tx.vout[index_in_tx].amount == needed_amount, false, "Expected amount not found"); + src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount); + if (m == n) + real_index_in_tx = index_in_tx; + } + src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx); + src.real_output = n; + src.real_output_in_tx_index = real_index_in_tx; + src.mask = rct::identity(); + src.rct = false; + + //fill outputs entry + tx_destination_entry td; + td.addr = miner_accounts[n].get_keys().m_account_address; + std::vector<tx_destination_entry> destinations; + for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o) + { + td.amount = amounts_paid[o]; + destinations.push_back(td); + } + + if (pre_tx && !pre_tx(sources, destinations, n)) + { + MDEBUG("pre_tx returned failure"); + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; + rct_txes.resize(rct_txes.size() + 1); + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + + if (post_tx && !post_tx(rct_txes.back(), n)) + { + MDEBUG("post_tx returned failure"); + return false; + } + + //events.push_back(rct_txes.back()); + starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes.back())); + LOG_PRINT_L0("Test tx: " << obj_to_json_str(rct_txes.back())); + + for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o) + { + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(destinations[o].addr.m_view_public_key, tx_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); + crypto::secret_key amount_key; + crypto::derivation_to_scalar(derivation, o, amount_key); + rct::key rct_tx_mask; + const uint8_t type = rct_txes.back().rct_signatures.type; + if (rct::is_rct_simple(type)) + rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); + else + rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); + } + + while (amounts_paid[0] != (size_t)-1) + ++amounts_paid; + ++amounts_paid; + + uint64_t fee = 0; + get_tx_fee(rct_txes.back(), fee); + fees += fee; + } + if (!valid) + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(rct_txes); + + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs | test_generator::bf_tx_fees, + hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, hf_version, fees), + false, "Failed to generate block"); + if (!valid) + DO_CALLBACK(events, "mark_invalid_block"); + events.push_back(blk_txes); + blk_last = blk_txes; + + return true; +} + +bool gen_bpp_tx_validation_base::check_bpp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const +{ + DEFINE_TESTS_ERROR_CONTEXT(context); + CHECK_TEST_CONDITION(tx.version >= 2); + CHECK_TEST_CONDITION(rct::is_rct_bulletproof_plus(tx.rct_signatures.type)); + size_t n_sizes = 0, n_amounts = 0; + for (size_t n = 0; n < tx_idx; ++n) + { + while (sizes[0] != (size_t)-1) + ++sizes; + ++sizes; + } + while (sizes[n_sizes] != (size_t)-1) + n_amounts += sizes[n_sizes++]; + CHECK_TEST_CONDITION(tx.rct_signatures.p.bulletproofs_plus.size() == n_sizes); + CHECK_TEST_CONDITION(rct::n_bulletproof_plus_max_amounts(tx.rct_signatures.p.bulletproofs_plus) == n_amounts); + for (size_t n = 0; n < n_sizes; ++n) + CHECK_TEST_CONDITION(rct::n_bulletproof_plus_max_amounts(tx.rct_signatures.p.bulletproofs_plus[n]) == sizes[n]); + return true; +} + +bool gen_bpp_tx_invalid_before_fork::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const size_t bp_sizes[] = {2, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS - 1, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_invalid_before_fork"); }); +} + +bool gen_bpp_tx_valid_at_fork::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const size_t bp_sizes[] = {2, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_at_fork"); }); +} + +bool gen_bpp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL); +} + +bool gen_bpp_tx_valid_2::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const size_t bp_sizes[] = {2, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_2"); }); +} + +bool gen_bpp_tx_valid_3::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1}; + const size_t bp_sizes[] = {4, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 4 } }; + return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_3"); }); +} + +bool gen_bpp_tx_valid_16::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1}; + const size_t bp_sizes[] = {16, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 4 } }; + return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_tx_valid_16"); }); +} + +bool gen_bpp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL); +} + +bool gen_bpp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL); +} + +bool gen_bpp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1}; + const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 4 }, {rct::RangeProofPaddedBulletproof, 4 } }; + return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_txs_valid_2_and_2"); }); +} + +bool gen_bpp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 4}, {rct::RangeProofMultiOutputBulletproof, 4}, {rct::RangeProofMultiOutputBulletproof, 4}}; + return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, NULL); +} + +bool gen_bpp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry>& events) const +{ + const size_t mixin = 10; + const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}, {rct::RangeProofPaddedBulletproof, 4}}; + const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1}; + return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bpp(tx, tx_idx, bp_sizes, "gen_bpp_txs_valid_2_and_3_and_2_and_4"); }); +} + +bool gen_bpp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_not_enough_proofs"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){ + CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty()); + tx.rct_signatures.p.bulletproofs_plus.pop_back(); + CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty()); + return true; + }); +} + +bool gen_bpp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_empty_proofs"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){ + CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + tx.rct_signatures.p.bulletproofs_plus.clear(); + return true; + }); +} + +bool gen_bpp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_too_many_proofs"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){ + CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty()); + tx.rct_signatures.p.bulletproofs_plus.push_back(tx.rct_signatures.p.bulletproofs_plus.back()); + return true; + }); +} + +bool gen_bpp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_wrong_amount"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 4 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS, NULL, [&](cryptonote::transaction &tx, size_t idx){ + CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); + CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs_plus.empty()); + tx.rct_signatures.p.bulletproofs_plus.back() = rct::bulletproof_plus_PROVE(1000, rct::skGen()); + return true; + }); +} + +bool gen_bpp_tx_invalid_clsag_type::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bpp_tx_invalid_clsag_type"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){ + return true; + }); +} diff --git a/tests/core_tests/bulletproof_plus.h b/tests/core_tests/bulletproof_plus.h new file mode 100644 index 000000000..481044fd4 --- /dev/null +++ b/tests/core_tests/bulletproof_plus.h @@ -0,0 +1,206 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "chaingen.h" + +struct gen_bpp_tx_validation_base : public test_chain_unit_base +{ + gen_bpp_tx_validation_base() + : m_invalid_tx_index(0) + , m_invalid_block_index(0) + { + REGISTER_CALLBACK_METHOD(gen_bpp_tx_validation_base, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(gen_bpp_tx_validation_base, mark_invalid_block); + } + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + { + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; + } + + bool check_tx_verification_context_array(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/) + { + size_t failed = 0; + for (const cryptonote::tx_verification_context &tvc: tvcs) + if (tvc.m_verifivation_failed) + ++failed; + if (m_invalid_tx_index == event_idx) + return failed > 0; + else + return failed == 0 && tx_added == tvcs.size(); + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + { + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + + bool generate_with(std::vector<test_event_entry>& events, size_t mixin, + size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version, + const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx, + const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const; + + bool check_bpp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const; + +private: + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + +template<> +struct get_test_options<gen_bpp_tx_validation_base> { + const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(HF_VERSION_BULLETPROOF_PLUS, 73), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +template<uint8_t test_version = 1> +struct get_bpp_versioned_test_options: public get_test_options<gen_bpp_tx_validation_base> { + const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks, 0 + }; +}; + +struct gen_bpp_tx_invalid_before_fork : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_before_fork>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS - 1> {}; + +struct gen_bpp_tx_valid_at_fork : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_valid_at_fork>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_1_1 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_1_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_valid_2 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_valid_2>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_valid_3 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_valid_3>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_valid_16 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_valid_16>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_4_2_1 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_4_2_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_16_16 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_16_16>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_txs_valid_2_and_2 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_txs_valid_2_and_2>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_txs_invalid_2_and_8_2_and_16_16_1>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_txs_valid_2_and_3_and_2_and_4 : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_txs_valid_2_and_3_and_2_and_4>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_not_enough_proofs : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_not_enough_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_empty_proofs : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_empty_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_too_many_proofs : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_too_many_proofs>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_wrong_amount : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_wrong_amount>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS> {}; + +struct gen_bpp_tx_invalid_clsag_type : public gen_bpp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bpp_tx_invalid_clsag_type>: public get_bpp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS + 1> {}; diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp index c46b5e657..e7b42405b 100644 --- a/tests/core_tests/bulletproofs.cpp +++ b/tests/core_tests/bulletproofs.cpp @@ -158,7 +158,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve crypto::derivation_to_scalar(derivation, o, amount_key); rct::key rct_tx_mask; const uint8_t type = rct_txes.back().rct_signatures.type; - if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG) + if (rct::is_rct_simple(type)) rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); else rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); @@ -232,7 +232,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) cons { const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL); } @@ -241,7 +241,7 @@ bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); }); } @@ -250,7 +250,7 @@ bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1}; const size_t bp_sizes[] = {4, (size_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); }); } @@ -259,7 +259,7 @@ bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const const size_t mixin = 10; const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1}; const size_t bp_sizes[] = {16, (size_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); }); } @@ -267,7 +267,7 @@ bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) co { const size_t mixin = 10; const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL); } @@ -275,7 +275,7 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) co { const size_t mixin = 10; const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL); } @@ -284,7 +284,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) c const size_t mixin = 10; const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 }, {rct::RangeProofPaddedBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 }, {rct::RangeProofPaddedBulletproof, 3 } }; return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); }); } @@ -292,7 +292,7 @@ bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_e { const size_t mixin = 10; const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}}; + const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 3}, {rct::RangeProofMultiOutputBulletproof, 3}, {rct::RangeProofMultiOutputBulletproof, 3}}; return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL); } @@ -300,7 +300,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry { const size_t mixin = 10; const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}}; + const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}, {rct::RangeProofPaddedBulletproof, 3}}; const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1}; return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); }); } @@ -310,7 +310,7 @@ bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry> DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_not_enough_proofs"); const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); @@ -325,7 +325,7 @@ bool gen_bp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& eve DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_empty_proofs"); const size_t mixin = 10; const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); tx.rct_signatures.p.bulletproofs.clear(); @@ -338,7 +338,7 @@ bool gen_bp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>& DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_too_many_proofs"); const size_t mixin = 10; const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); @@ -352,7 +352,7 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& eve DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_wrong_amount"); const size_t mixin = 10; const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){ CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); @@ -366,7 +366,7 @@ bool gen_bp_tx_invalid_borromean_type::generate(std::vector<test_event_entry>& e DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_borromean_type"); const size_t mixin = 10; const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; - const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 0 } }; + const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 3 } }; return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 11, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){ return true; }); @@ -382,3 +382,14 @@ bool gen_bp_tx_invalid_bulletproof2_type::generate(std::vector<test_event_entry> return true; }); } + +bool gen_bp_tx_invalid_clsag_type::generate(std::vector<test_event_entry>& events) const +{ + DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_clsag_type"); + const size_t mixin = 10; + const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; + const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } }; + return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_BULLETPROOF_PLUS + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){ + return true; + }); +} diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h index b30d82e68..115ad49b5 100644 --- a/tests/core_tests/bulletproofs.h +++ b/tests/core_tests/bulletproofs.h @@ -211,3 +211,9 @@ struct gen_bp_tx_invalid_bulletproof2_type : public gen_bp_tx_validation_base bool generate(std::vector<test_event_entry>& events) const; }; template<> struct get_test_options<gen_bp_tx_invalid_bulletproof2_type>: public get_bp_versioned_test_options<HF_VERSION_CLSAG + 1> {}; + +struct gen_bp_tx_invalid_clsag_type : public gen_bp_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_bp_tx_invalid_clsag_type>: public get_bp_versioned_test_options<HF_VERSION_BULLETPROOF_PLUS + 1> {}; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 3f9f01a11..a1101a3b1 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -836,7 +836,7 @@ inline bool do_replay_file(const std::string& filename) { \ for (size_t msidx = 0; msidx < total; ++msidx) \ account[msidx].generate(); \ - make_multisig_accounts(account, threshold); \ + CHECK_AND_ASSERT_MES(make_multisig_accounts(account, threshold), false, "Failed to make multisig accounts."); \ } while(0) #define MAKE_ACCOUNT(VEC_EVENTS, account) \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index c55154917..c573eb995 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -265,6 +265,24 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount); GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type); GENERATE_AND_PLAY(gen_bp_tx_invalid_bulletproof2_type); + GENERATE_AND_PLAY(gen_bp_tx_invalid_clsag_type); + + GENERATE_AND_PLAY(gen_bpp_tx_invalid_before_fork); + GENERATE_AND_PLAY(gen_bpp_tx_valid_at_fork); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_1_1); + GENERATE_AND_PLAY(gen_bpp_tx_valid_2); + GENERATE_AND_PLAY(gen_bpp_tx_valid_3); + GENERATE_AND_PLAY(gen_bpp_tx_valid_16); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_4_2_1); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_16_16); + GENERATE_AND_PLAY(gen_bpp_txs_valid_2_and_2); + GENERATE_AND_PLAY(gen_bpp_txs_invalid_2_and_8_2_and_16_16_1); + GENERATE_AND_PLAY(gen_bpp_txs_valid_2_and_3_and_2_and_4); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_not_enough_proofs); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_empty_proofs); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_too_many_proofs); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_wrong_amount); + GENERATE_AND_PLAY(gen_bpp_tx_invalid_clsag_type); GENERATE_AND_PLAY(gen_rct2_tx_clsag_malleability); diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index db78c3e41..1a5489b9e 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -43,6 +43,7 @@ #include "rct.h" #include "multisig.h" #include "bulletproofs.h" +#include "bulletproof_plus.h" #include "rct2.h" /************************************************************************/ /* */ diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index f098e1bce..49ae86eb0 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -28,98 +28,88 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include "ringct/rctSigs.h" -#include "cryptonote_basic/cryptonote_basic.h" -#include "multisig/multisig.h" -#include "common/apply_permutation.h" #include "chaingen.h" #include "multisig.h" + +#include "common/apply_permutation.h" +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "device/device.hpp" +#include "multisig/multisig.h" +#include "multisig/multisig_account.h" +#include "multisig/multisig_kex_msg.h" +#include "ringct/rctOps.h" +#include "ringct/rctSigs.h" + using namespace epee; using namespace crypto; using namespace cryptonote; +using namespace multisig; //#define NO_MULTISIG -void make_multisig_accounts(std::vector<cryptonote::account_base>& account, uint32_t threshold) +static bool make_multisig_accounts(std::vector<cryptonote::account_base> &accounts, const uint32_t threshold) { - std::vector<crypto::secret_key> all_view_keys; - std::vector<std::vector<crypto::public_key>> derivations(account.size()); - //storage for all set of multisig derivations and spend public key (in first round) - std::unordered_set<crypto::public_key> exchanging_keys; + CHECK_AND_ASSERT_MES(accounts.size() > 0, false, "Invalid multisig scheme"); - for (size_t msidx = 0; msidx < account.size(); ++msidx) + std::vector<multisig_account> multisig_accounts; + std::vector<crypto::public_key> signers; + std::vector<multisig_kex_msg> round_msgs; + multisig_accounts.reserve(accounts.size()); + signers.reserve(accounts.size()); + round_msgs.reserve(accounts.size()); + + // create multisig accounts + for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index) { - crypto::secret_key vkh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_view_secret_key); - all_view_keys.push_back(vkh); + // create account and collect signer + multisig_accounts.emplace_back( + multisig_account{ + get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_spend_secret_key), + get_multisig_blinded_secret_key(accounts[account_index].get_keys().m_view_secret_key) + } + ); - crypto::secret_key skh = cryptonote::get_multisig_blinded_secret_key(account[msidx].get_keys().m_spend_secret_key); - crypto::public_key pskh; - crypto::secret_key_to_public_key(skh, pskh); + signers.emplace_back(multisig_accounts.back().get_base_pubkey()); - derivations[msidx].push_back(pskh); - exchanging_keys.insert(pskh); + // collect account's first kex msg + round_msgs.emplace_back(multisig_accounts.back().get_next_kex_round_msg()); } - uint32_t roundsTotal = 1; - if (threshold < account.size()) - roundsTotal = account.size() - threshold; - - //secret multisig keys of every account - std::vector<std::vector<crypto::secret_key>> multisig_keys(account.size()); - std::vector<crypto::secret_key> spend_skey(account.size()); - std::vector<crypto::public_key> spend_pkey(account.size()); - for (uint32_t round = 0; round < roundsTotal; ++round) + // initialize accounts and collect kex messages for the next round + std::vector<multisig_kex_msg> temp_round_msgs(multisig_accounts.size()); + for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index) { - std::unordered_set<crypto::public_key> roundKeys; - for (size_t msidx = 0; msidx < account.size(); ++msidx) - { - // subtracting one's keys from set of all unique keys is the same as key exchange - auto myKeys = exchanging_keys; - for (const auto& d: derivations[msidx]) - myKeys.erase(d); - - if (threshold == account.size()) - { - cryptonote::generate_multisig_N_N(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end()), multisig_keys[msidx], (rct::key&)spend_skey[msidx], (rct::key&)spend_pkey[msidx]); - } - else - { - derivations[msidx] = cryptonote::generate_multisig_derivations(account[msidx].get_keys(), std::vector<crypto::public_key>(myKeys.begin(), myKeys.end())); - roundKeys.insert(derivations[msidx].begin(), derivations[msidx].end()); - } - } + multisig_accounts[account_index].initialize_kex(threshold, signers, round_msgs); - exchanging_keys = roundKeys; - roundKeys.clear(); + if (!multisig_accounts[account_index].multisig_is_ready()) + temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg(); } - std::unordered_set<crypto::public_key> all_multisig_keys; - for (size_t msidx = 0; msidx < account.size(); ++msidx) + // perform key exchange rounds + while (!multisig_accounts[0].multisig_is_ready()) { - std::unordered_set<crypto::secret_key> view_keys(all_view_keys.begin(), all_view_keys.end()); - view_keys.erase(all_view_keys[msidx]); + round_msgs = temp_round_msgs; - crypto::secret_key view_skey = cryptonote::generate_multisig_view_secret_key(account[msidx].get_keys().m_view_secret_key, std::vector<secret_key>(view_keys.begin(), view_keys.end())); - if (threshold < account.size()) + for (std::size_t account_index{0}; account_index < multisig_accounts.size(); ++account_index) { - multisig_keys[msidx] = cryptonote::calculate_multisig_keys(derivations[msidx]); - spend_skey[msidx] = cryptonote::calculate_multisig_signer_key(multisig_keys[msidx]); - } - account[msidx].make_multisig(view_skey, spend_skey[msidx], spend_pkey[msidx], multisig_keys[msidx]); - for (const auto &k: multisig_keys[msidx]) { - all_multisig_keys.insert(rct::rct2pk(rct::scalarmultBase(rct::sk2rct(k)))); + multisig_accounts[account_index].kex_update(round_msgs); + + if (!multisig_accounts[account_index].multisig_is_ready()) + temp_round_msgs[account_index] = multisig_accounts[account_index].get_next_kex_round_msg(); } } - if (threshold < account.size()) + // update accounts post key exchange + for (std::size_t account_index{0}; account_index < accounts.size(); ++account_index) { - std::vector<crypto::public_key> public_keys(std::vector<crypto::public_key>(all_multisig_keys.begin(), all_multisig_keys.end())); - crypto::public_key spend_pkey = cryptonote::generate_multisig_M_N_spend_public_key(public_keys); - - for (size_t msidx = 0; msidx < account.size(); ++msidx) - account[msidx].finalize_multisig(spend_pkey); + accounts[account_index].make_multisig(multisig_accounts[account_index].get_common_privkey(), + multisig_accounts[account_index].get_base_privkey(), + multisig_accounts[account_index].get_multisig_pubkey(), + multisig_accounts[account_index].get_multisig_privkeys()); } + + return true; } //---------------------------------------------------------------------------------------------------------------------- @@ -175,7 +165,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n]; CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, - 10, 10, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + HF_VERSION_BULLETPROOF_PLUS, HF_VERSION_BULLETPROOF_PLUS, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), false, "Failed to generate block"); events.push_back(blocks[n]); @@ -191,7 +181,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry cryptonote::block blk; CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0], test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, - 10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + HF_VERSION_BULLETPROOF_PLUS, HF_VERSION_BULLETPROOF_PLUS, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4), false, "Failed to generate block"); events.push_back(blk); @@ -238,13 +228,13 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t n = 0; n < nlr; ++n) { account_k[msidx][tdidx].push_back(rct::rct2sk(rct::skGen())); - cryptonote::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]); + multisig::generate_multisig_LR(output_pub_key[tdidx], account_k[msidx][tdidx][n], account_L[msidx][tdidx][n], account_R[msidx][tdidx][n]); } size_t numki = miner_account[msidx].get_multisig_keys().size(); account_ki[msidx][tdidx].resize(numki); for (size_t kiidx = 0; kiidx < numki; ++kiidx) { - r = cryptonote::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]); + r = multisig::generate_multisig_key_image(miner_account[msidx].get_keys(), kiidx, output_pub_key[tdidx], account_ki[msidx][tdidx][kiidx]); CHECK_AND_ASSERT_MES(r, false, "Failed to generate multisig export key image"); } MDEBUG("Party " << msidx << ":"); @@ -303,7 +293,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t msidx = 0; msidx < total; ++msidx) for (size_t n = 0; n < account_ki[msidx][tdidx].size(); ++n) pkis.push_back(account_ki[msidx][tdidx][n]); - r = cryptonote::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki); + r = multisig::generate_multisig_composite_key_image(miner_account[0].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)kLRki.ki); CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); MDEBUG("composite ki: " << kLRki.ki); MDEBUG("L: " << kLRki.L); @@ -311,7 +301,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry for (size_t n = 1; n < total; ++n) { rct::key ki; - r = cryptonote::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki); + r = multisig::generate_multisig_composite_key_image(miner_account[n].get_keys(), subaddresses, output_pub_key[tdidx], tx_pub_key[tdidx], additional_tx_keys, 0, pkis, (crypto::key_image&)ki); CHECK_AND_ASSERT_MES(r, false, "Failed to generate composite key image"); CHECK_AND_ASSERT_MES(kLRki.ki == ki, false, "Composite key images do not match"); } @@ -349,6 +339,11 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry td.amount = amount_paid; std::vector<tx_destination_entry> destinations; destinations.push_back(td); + cryptonote::account_base dummy; + dummy.generate(); + td.addr = dummy.get_keys().m_account_address; + td.amount = 0; + destinations.push_back(td); if (pre_tx) pre_tx(sources, destinations); @@ -363,7 +358,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry #endif std::vector<crypto::secret_key> additional_tx_secret_keys; auto sources_copy = sources; - r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 2 }, msoutp); + r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 0 }, msoutp); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); #ifndef NO_MULTISIG @@ -453,8 +448,10 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry crypto::secret_key scalar1; crypto::derivation_to_scalar(derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG); + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG || tx.rct_signatures.type == rct::RCTTypeBulletproofPlus); rct::key C = tx.rct_signatures.outPk[n].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + C = rct::scalarmult8(C); rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount"); amount += rct::h2d(ecdh_info.amount); diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index 333c4fe38..c7376bf63 100644 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -82,7 +82,7 @@ private: template<> struct get_test_options<gen_multisig_tx_validation_base> { - const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(10, 1), std::make_pair(0, 0)}; + const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(HF_VERSION_BULLETPROOF_PLUS, 1), std::make_pair(0, 0)}; const cryptonote::test_options test_options = { hard_forks, 0 }; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 6ce99e76e..035128177 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -133,7 +133,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry crypto::secret_key amount_key; crypto::derivation_to_scalar(derivation, o, amount_key); const uint8_t type = rct_txes[n].rct_signatures.type; - if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG) + if (rct::is_rct_simple(type)) rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default")); else rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default")); diff --git a/tests/core_tests/rct2.cpp b/tests/core_tests/rct2.cpp index 8d7c4b3eb..4772be38c 100644 --- a/tests/core_tests/rct2.cpp +++ b/tests/core_tests/rct2.cpp @@ -158,7 +158,7 @@ bool gen_rct2_tx_validation_base::generate_with(std::vector<test_event_entry>& e crypto::derivation_to_scalar(derivation, o, amount_key); rct::key rct_tx_mask; const uint8_t type = rct_txes.back().rct_signatures.type; - if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG) + if (rct::is_rct_simple(type)) rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); else rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default")); diff --git a/tests/crypto/crypto-tests.h b/tests/crypto/crypto-tests.h index e833c0444..29a50e0fb 100644 --- a/tests/crypto/crypto-tests.h +++ b/tests/crypto/crypto-tests.h @@ -46,4 +46,6 @@ void random_scalar(crypto::ec_scalar &res); void hash_to_scalar(const void *data, std::size_t length, crypto::ec_scalar &res); void hash_to_point(const crypto::hash &h, crypto::ec_point &res); void hash_to_ec(const crypto::public_key &key, crypto::ec_point &res); +bool check_ge_p3_identity_failure(const crypto::public_key &point); +bool check_ge_p3_identity_success(const crypto::public_key &point); #endif diff --git a/tests/crypto/crypto.cpp b/tests/crypto/crypto.cpp index 145ec1d86..e1be38054 100644 --- a/tests/crypto/crypto.cpp +++ b/tests/crypto/crypto.cpp @@ -32,6 +32,36 @@ #include "crypto-tests.h" +static void get_ge_p3_for_identity_test(const crypto::public_key &point, crypto::ge_p3 &result_out_p3) +{ + // compute (K + K) - K - K to get a specific ge_p3 point representation of identity + crypto::ge_cached temp_cache; + crypto::ge_p1p1 temp_p1p1; + + crypto::ge_frombytes_vartime(&result_out_p3, &point); // K + crypto::ge_p3_to_cached(&temp_cache, &result_out_p3); + crypto::ge_add(&temp_p1p1, &result_out_p3, &temp_cache); // K + K + crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1); + crypto::ge_sub(&temp_p1p1, &result_out_p3, &temp_cache); // (K + K) - K + crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1); + crypto::ge_sub(&temp_p1p1, &result_out_p3, &temp_cache); // ((K + K) - K) - K + crypto::ge_p1p1_to_p3(&result_out_p3, &temp_p1p1); +} + +static int ge_p3_is_point_at_infinity_vartime_bad(const crypto::ge_p3 *p) { + // X = 0 and Y == Z + // bad: components of 'p' are not reduced mod q + int n; + for (n = 0; n < 10; ++n) + { + if (p->X[n] | p->T[n]) + return 0; + if (p->Y[n] != p->Z[n]) + return 0; + } + return 1; +} + bool check_scalar(const crypto::ec_scalar &scalar) { return crypto::sc_check(crypto::operator &(scalar)) == 0; } @@ -55,3 +85,19 @@ void hash_to_ec(const crypto::public_key &key, crypto::ec_point &res) { crypto::hash_to_ec(key, tmp); crypto::ge_p3_tobytes(crypto::operator &(res), &tmp); } + +bool check_ge_p3_identity_failure(const crypto::public_key &point) +{ + crypto::ge_p3 ident_p3; + get_ge_p3_for_identity_test(point, ident_p3); + + return ge_p3_is_point_at_infinity_vartime_bad(&ident_p3) == 1; +} + +bool check_ge_p3_identity_success(const crypto::public_key &point) +{ + crypto::ge_p3 ident_p3; + get_ge_p3_for_identity_test(point, ident_p3); + + return crypto::ge_p3_is_point_at_infinity_vartime(&ident_p3) == 1; +} diff --git a/tests/crypto/main.cpp b/tests/crypto/main.cpp index f804c45dc..5486937c2 100644 --- a/tests/crypto/main.cpp +++ b/tests/crypto/main.cpp @@ -259,6 +259,16 @@ int main(int argc, char *argv[]) { if (expected != actual) { goto error; } + } else if (cmd == "check_ge_p3_identity") { + cerr << "Testing: " << cmd << endl; + public_key point; + bool expected_bad, expected_good, result_badfunc, result_goodfunc; + get(input, point, expected_bad, expected_good); + result_badfunc = check_ge_p3_identity_failure(point); + result_goodfunc = check_ge_p3_identity_success(point); + if (expected_bad != result_badfunc || expected_good != result_goodfunc) { + goto error; + } } else { throw ios_base::failure("Unknown function: " + cmd); } diff --git a/tests/crypto/tests.txt b/tests/crypto/tests.txt index 8e6534a87..3a7fe5453 100644 --- a/tests/crypto/tests.txt +++ b/tests/crypto/tests.txt @@ -5467,3 +5467,9 @@ check_ring_signature 8427e7179050bc38d5c25e68f70c2c98990042388e326d6bebd5674ca8d check_ring_signature f3d2b5b25d663325acca133163bbf3219f1b22fea6bd6d6e3194db8bc30dc6fa 448071ee63780f0fcfe35245353e4fd28f5c5362d9a5f3d74e5bd6685986729f 6 fb706555f8358ac3db60d9a52eb4981f91d28cc4d518c1a5c988ce94c7051379 e07dbe16cee565a221af2353c4761cdb7c7fab5880372b0e46d49ab4842d3b4c 05db3f4b53f17fe0525e2b5002664d9b0d5680c10146640cbbf23a118d6d88fb a446a6e907f653e0db5888927971d0dfd5c854d2b04f02367e18b02378271b11 72ce0ff9a80faaf2ea826cfe8244cc2d345a7c887143e481ffe826b8630b6f93 d17c0b9ba4aeb04ddb8288222a0d5d22abe327981786f790cf8b9ee8831090a7 8a11ee60dd8470e1bd447f472b60723d8a30ac8a84fb4bd98580b2f3ddfc32027193f368c87e02deb9219eb50e0a9b2d9d43bab27c14ac4be23640a1fca01a0dd12337332fdfe9f3aef0235281614f3e94cf7902486b8a5b76444e9a56e21a09beab12ff902fdc05b16a2ad417d92711107bcf7a554cf82fa069e9432874680dce00e8a862e417b2efaa66cb9e4693e00b7d6cd1c452fc71630473799fdc7603080d006b562aafe0e75e456e6e09f1a655dcc40e296f0f3083c5f58c75e9dd03c61115f05d75c65e04f2d02c6232c72c99a32e9f8c851b23219e2dbcdbd6190de6c9ae374530c0f268758611fffac7dbb8f16d8c71a0da950ae12603d942b808146f9131049a75364f45cc606ab4d6882a137999c163312c87fb3d8e78ef3406b97dbd90bbdd20a8025d4ba438491ff0923da5055b7c3ee446b7ddac341f350056efeaa3f706fa2ac8a6d02d5f2a4cd5644af7f9a48a699801469d33601667081fa0971b1b6dc3c86d539197a531a76ebe611fc967d26067f2c2f0d879ef0309 false check_ring_signature 07a23b78f73ca487ec5ab0f4d7725d7ffb547543ae4f96e30df871c2241489ca 2073d5a5ccb03402ded68c31d3de658f7c5be2265bec656a12212c83e2499a75 9 ce3fe390c5309c0aa6c0a1e4dc29ef63fdf55ad2fef737b775bae9857c666966 08088bfe1f076131d82458ef08e0a6d8003d26d824360033895e62409fceadba 4faffacfacc069e09f80a0249daf97a40b53a64ab870c62dfd08998d382dba52 386e435138fba8c063966d8308927f8c8788782a3a263500133325c9c82d38a3 efd955c96135d72fee34c765998cf714f37365af8f77cae145ba5d126f1fe914 60dbea373e81c0276c4fbb83ca1fcbb647e2fa11a8bbc62c8e56d4d147b39bd5 1495004110d8e2fe1774ba6eb9492b1bbc54f674ed2082401cd6eab71ad9dc40 7a2af9f6dad76479c5f3345ffd250f55b234682b4fd51d3ee9ae8da2e5a35cb2 02548abda0688fc63cc485b956fd4303bf0dfc43a581e01c59c62243461ea348 e784e4e19099667803c6f08fd0877e85937bec50ab02f75b6f2e3dd61876b40510bf63faa2346e3b35b0ade96bfef145dd17b92ebdb1cf96899e10e3442aaf0d782eafc8be306390ffcda93025f6832a2d4c4e4d1c2b56add53550b3b512b71af959bc1b5902a7b628eae1d16d242c099fb4ce33a4bee12af49a41aab958940910b0d0ef596b753688f228a7184e38e7df8644cbecfa658d721736ed2882e20f9a2fb61818c0060b3fd4b2ced7984bb17b10381efde2330bbb1de208655e080cd33d9e5d5d853a8875623195ed30e2c2e475ebe4ac97e3c5216520b0a201a608b6c6013266ab0e63a461dae171af4c3a14a5fa8c4c33ca25b84540e32efcf302b18e610545ff35d45dae4116ba6d51ac9125cdb7f681739744237827e768bb0487b6b0a1d9e8d5ce809b9e17a6c32df9ef77a26af987b2348c748cba73e917034e21609b0d92a5b7106d39c8de2f057013bd347be67e553e608cbb70683f2e04ca7ed84ed7f4c671efac5deb3db17498f23170cc8deb4596d1ea958caa7e4e06204833104aed36ee0b7808dba1194cc374c193e4e926832ac171d03f3abe6607803d6b33a1350ec428afc88977eb411505bcb54113da91f63fdd2bb85a08140cbc040fadb23ba1c92fd3bad8c4930f36d37403f8e9d4b87bf3636ccffc53c30cc4eeb9945214f9d8e288edbfa1d7546baae6860f9f56af7d79f9349c104579064d404a3d7a893d64fbcca32c5d4e6158d92cd87c8a78bb3a61eb55ec92a7ee0aaabe59d33af5534ae258f48bca9d5459e0708ebf9289d9bb22acb540b0f39003 false check_ring_signature 0e6194f7b3ec1594e6b727b990c6bf65a2b1eb1a9b73ea00d18a709ffdee1276 f931b871addf92f407f087ae176804734ba35fd65086f6c3e07d9d7b001be265 51 8daf0c2e434171ef0e31f1fd17307a30690639fc7fef1a85a9a7858868924c1d 93edf23dcd46477698f2e4795ab9e4e75ef04c8ca561670c22d0a379f7cc9d12 5fb23ddfd7bc6db6798d0dfadc6accc8c7fd75adb090ecb6bad2021bc3bac5da d093463f76e271597f6cddc74b5685b0c4d4ab6b6f6bae4b0217c7097ced9bb6 1504b6cab3b2454a066fe9462be135eda844480da85ad3baf67fbf39679cdbf0 e812fa7c7f0b6e11dd0d87fd88767a768f9e89f2c54396a9a53d443d0edafdcf 6570828a5e056a4e035b1042b80007a872c71545ea225feb982d24eea14373f0 a3adddfa513dca877eca62e12255c981b148a605c998458c6786aba6aaf1207a 43f3fb57f9a0c90200a940217893685296537f34041fd9586586937386f8d33d 9ff696ea156a4468e1d0d32590fcc865f491d821254594535c1694d7eb0102c2 3f0245304a5047ce42fb0b36544220d7df36c919aa2321bf39e83fa71c4de21c 01c527e626c1b5c928058d7ea772cda93833ba111792da212a5eae0041ccae51 61a79b26403f88acf0d4a7bf86efff4bd9f32e05e9a900e7d890ce36ab8abbef be302d5d4b83cd447536c08dfe66a32fab021889d9eb7a8621e59a6e3756b14a 693a9d20a1d12828a94d01a2bdc856f3499745e4c5830cda407962d2b6953784 62438ad0ca1dc718def66c97d8a4b2a1c4e61322c7cb8c3b904177cc8d8fbcaf 88dbaf2eab9896bac05f93c953c548de45c032d40b7c7e452c17ac73606b942b 894905861e3c952ab0e350b12900b454b3870ce3e9b590f94203ce2bbc41f944 cbf933f67076f66793bf06f2a5008bd01b36b6aa094483269da52bbba7465580 557046df81dbcfe18c65e1364448c8d94c710cd1fbf8a3c10550254d97afcdf7 7b24c0ee3a7836c1bef02956b81358d8f698da40f0ccd706daf3038cb0b28793 28ddf7553a328fe5ef8c8a6abd52ba6d725240f1511665884cb68c96d46d92d5 ffd267319c8ed9e424371ef74e5301102997c26265a2044bbbe05ee291f5bcc9 d467502be7c3753d9e4d2a147d3a40d1d817a865184030870bf97ef6f1610da7 b1ec5f6565cc19a6d015696594ada55b5fbe82108fcec0d54fb11b05bfd37408 19e7e65cdd957ab0d25dfc9e4e1449b1d4283d2561754fc147adb51e057dc7ba c5d4274eef9f5d92e7a8dda59de38f5728d60350d8e98871968765925d18ed09 a44814c8f2912d13c09242b0b75dad90a6357b844e021dc96668c45e311b64b3 cd88baa50640c7409a98921e0ad1e7f2496b39a3b07544eeef2455fc5018b17e 9ab6f34f081bccc8d8b29513851a402759c96b8d95d836db977e24e595deb2fa 56123960591257a70a334628d9868505176533debe06cdfc24f906ec8a997efd a37b03d5446abb9a3fb7aee94d6740d85f984fb363a9e78a9d363d5882cd2823 bd9607c5e6c167f5eb6a289be620592c7e850590393787d01acd958fd717b0c2 d0d69b420c93d9b1b8eee6796db2c7cc2110dc78b624396e144c135fe69c812d b6a2ee98807a00bcff0fa5403102c9a628948d3478234cdde85e5effbefcf204 e183a5d8daabdea01f7cd7f0960b75566aec78874cc933f5acc3dce0512ab335 ea7f86e49af2a4883e11b3362127dfdb14f0f517170ddb4338a5a38d2c92566a 4ea9d24ebb852962e745d4ec30679bed40f581d1ccf6825e80ddb38759025ac7 d0c887ff472e65d7fcb502988dfb0c919c8d23c2897d49b8c8de90d725604ea8 0177735a5271e254705979ef38a0737897f608aa144f4c4ed97e220dcb32f1af fc49aac2eaf95de87156c0348381d78d9b468a1da3eae6cc25bb398563b0bdad 84a98d5f20b608d31af41925052dfd378f0252e1bf7c13cf9b773e0cd4b95a52 8fd6df156a95abf7ca37fa1d65e820b56f2498c9ada0a4027f0dbc285b9a9b86 f7bcadf3751cdc85db850619a857288d1291c2f403732d79daec6eb774fd26cb c87e4373dadc7a65f53df413d22b3370f7a8c47a929ded626f3f71f3b249eb4d 02398630b92a7f782dd0153d39a56123ec7ea8d5a0fde77aebd302e641de2a54 f655dea86af87ed4d93ea10c2ffd5e52aff83a6fb5fab65a1ab67a02cfae6523 990747d3687fcc37ba0fe4845f9b247ebddd0d0a5a61f2637bb498f0b05e9fb3 38050abbc28c5847708f0142d8e89d44dad6d0ee8bd60b5771bd03d590a7ff85 867b6cdc08dbecaf1b6f9ee2add2d2c9dfe18474a805a028533b619f52abf6b3 0bda8ca264a6747f18195bbf56fafe96c9a0c8e6557f1b17c50eec71ae52fb50 cc925498a1aaccee819efd183df25795472fda1c7abbad6eaf42b00192c1e7005cacf5a965e831702169f97d374b7b30a7cb3343170c89c325090815e663a805f1d64336bfcdc25794467e68c093403faa50071b992863da69b6399f8bd33308abac8a69fe14a4a3de62ccb4bb5b49f760d1567fb1c966881476f7c5cdd20d0738b0452fa686ec759a6f19f50f1cee1ef06d69162d6d9c6d97055ce7b746a7038ad3913cf9c92a4a5faebecc5417ee900e8cb9e58c35d06e7b554a38815efa05a2f578665a2f96fff87bc9c025b0a23775a8e079f98805a350cac06456ed230f3bf10e3a133abd448f811ed82663f4bfd3fd6492d3ff6e49625b1efc8b9a090bbd70ee46aba76648c7e899cc19433a230f59314dc72d10d7c5dff9c9e2b79907161b00e307b10a9f53332a325c305744d2b96229831fefd891fa1f0776278005fc792d7d1882368def90bb0c75c927464eb4a95ad133e9cca7c57708c620520cb4a4a25880d80cbd1101d0a78e5cc7cf9aae6fe52449911be59a405f35356709977a9532074a05627c13787192cdad3b40f9434d469f189e311c0d917d788d011693c8319cc0b0c4c0ef4fdf08f945be0c6e65fd1c548bba7608a128c3a327020818f93aad968cd094149dbe064e3873b4d704bd0a5ee97fe040944de667a3063ecf7be8aedfd54bd73d13a56b944c546c4c6fb7cc5a9c74a6508c8af26410095f34c6787f38abd729bbb728a4213e9e079b98b81d5f6562f29ebf21ac85010fd4d83ffb1851ed601ae7bc93ae5b36da273101f7485a2d38ed29f68636dd5700b2fcdc9709461a027e02850560fc3e3f1c6fd8dbf4cf1084e059bdddcb8d450f8f3bc1834060ec6375cb94cab89cfce4aabcfc3ef1bf13018535607234e63800d9f6d20030fab8384f287271183563e0730708203261ebbcc0531f4637e0540c7354ffb862e9c435afcf4a11313a2c60f8fccc6483cb70b3dd7b1e7abdbc8e0779dae0268351518fe07b03fe764675ba91f7e612c6d07231e6deab9a909bdd0f419adc092fe62fb5378be479f7cde0e80eb642aa89441feb6b0119595bd7770446d253edeaf3c0dee883f046f0f7e1d2a26701eff4d7b3e9c5f76e27bed51002ee4a57c4e51ee3d2bece02c959ddf90e422f739f1f3b628b5be04f8845f06b00fc891ff96e22af1869f22c1b79afa8ce7eaa4afa70158cde42310f312f92d8013e3aeca05cceb2ee5bcf0a50a5c267f4e548bf32e7cb935ee28c644511beaa071923a1d8b15b2e72b864cf5a4e3f4c290467c9a70dcf924345cd82c2d1f68c0e031f05602b53686d50a7a7bc429e1b54287fbedd16c6cc4aedfc9d66bdf31f0959689980fba888d99ffc6d40aa6f7914bbf593fe736e9edad1a4fc7811853e03f927c357cf3d5dbf58327d4b43ce3f8797063492961a04c30995bbddfe281b0b153043fe51178ff43933a0699ab3a2d995457e7cfba7149ca8d3da1e99d2280bf505e8bed2cdb4f037c2d5c1efd00cda5db5cede0d37a3ebdd94b0f12c457b05f546748f69c5212c58f028bc6a29e11ddb4647e58414ee25060e47506fdb7a0232a7ac6df4481bde4ca5f3ee88a97dd96396ad4727b076743e64e8ee1eaeff0b2532804e4cc7a28306c80b9498eeaaf5be9087caa51807bca9377f76ef6a500cadc72f6f95aea184d559be04f14bb0b05f0ce303fb68cb7091ac96007264ec05c442a68d867351823252d8951d7f6b9671b857244d477e75466c3017f8621808fd511a14c5dab254e5d685d08a922317dadc6fa90b6b9df5b2d23dd392ac910bc8fec0ac56c9ee5d491d9685709fb0d3683905fa390472ecaa01090bd4969c0df17f699e6c78942625672228e7474c37db2336b1136b4dea290940f4a35285013264411791a89ae770a63257478339572c85e7b838078a56db7c97a029a04e0ff040a4d5f08fa4e4a0933b7566d8dce9970eb471b270b70970b5ea19124e0d09e643c89b98f199fae32c088512e6839c33ebd646920e360294d869ad6bffa30de976920a838bc8b88025514d0312f6f28066aec9b4d2d9253ce94e85358b54078ce1b56aed1de3f9071419e2d3590df4d3072ea1d93f11d1c9bb011097f2380ab7341e0b1c7a2d4de64e82f4203a87c9805e1298d69f14b3911622f1119a900d49de89000147275fc18aa830228bafb687e8cc9eb8ca4c2ec4c90bb33fe25201417fd7408111e0efdb16d59412852deac263800de4fcb96b19a2ebbbe4d719051ae02b8b3c4292f097380c9587e96bdfd82037c54649154034d95057e883e00cfe4250f079e2a5460a06aeb4103e6c4b7b64a38a851e1650d758e9a5bc12d3093fc4e9f701612625c969d7dbfc1369349c255f883dc436abaef5c16e453d7a0571cf9797a89446077045d3eb3169261025698dbbff5f1354426e7375bf04ed07b930b24bf02049e577c98bd7abe8c5903f48af2eb03686166364ce54aaf9c20a9774b7b04b922ad510182f41a8b84e0b5e8e8cb404e775f9cf146dcae0fa5f0460da3dfb2ecabe850b5dda46d54c8375003ffaa9f7efd508ffc799523c6c1b0ec1406363554dce15ecd47d91936292b448022b5f162ec09937f50f86d1e50908762bc5330f51c639e035dd07b227e6f499884b51829d920ebb517c3732044500150323d68280d23c6711473abe8d5d835c5f071779af65647334d656caded90a6e66585fa0cc6806375853481eb0b409211473afdd46d6686c8ab95c27aee304a048fadac986152ba89037f084dee9368e5768434610f7904a3552d0d1bcdd06ee33c31485d28838266987b1584ee02a1ff0383a134d11114bbd11b5f8e0b10cd19a175be6520ec55c0380478f723bfc1f0ad1f14ee839547657979ee73e9907939245c535483d3fd79ccc65d776903ebd014303ee2c508d87f053666b42200e8731cb3e79f0a543673cbe1acd23740c442a00db5c6c23c06d008029184dea0970641273d935bc76bdef35f483052e57cfd00b1188c54948f7856857dcc58f05c0735df9a9896da2fc8b82b5c3ceaf98db68508a1004230e0f8f78595da2b702508216f0ba4d65d5dfa14f0cf2d54c67ab4bb54eb4e820c5af761978d0531b0a25a136f6bca068eeafb0828bb76bf4b8bae4cbfce93e8cf9a4c5ce2a1d00c20c2a713c981bd9e46e48dc3e90d2a09e40722212dc3d46c7ec98c830af1545dd014cddd1f0492fc19c597f24acdb0cb9836bf16507be54d9eb229d6b778c20370149e7ee1d371105f331f829e9b611340e1120ee6ce4bffe9c3a7f96f1adf2b802a62f4331c3052309a74465d7f3ac8059bb7b1e0093977c1ce177c04a4be8060447c3a9a49e13f4a2f8579668d929b6782c76b41ef127b21cf91c766de44c930005089f2fad932b12ec662c1bfd7d17c8384b98db2a64ff3b952034131cfa370b5ed0b3bc48d3fd66cf1200984c8929298bf7102932b658432ba2e93d0c01400a75775b1dbea79c9acb1b3348ba24a454f104b72c39ffff01a56d60fe2084af02078204e1c51773f8a91aac91564e8b5e069ce2e88319e47e65f01965743ace06f6540d22fc0e0e2038173960189818af53095c82b0726b6ddeeef14edb01950e7c381d852224666623294305816e713561e1146a37a1212b41df75079a26e601ae81025abd07cd3bfe475f3c44db05a2c715ed902bb43cdcf4e505925d3e18006518b18a46dd21adb117bfb342c68b96f974f1be18c47e7bd5f00a90c225500aa0d5c11207714e4bc729f3662664534064a6fdae4c03456e9e7a75821eefbc0b9cfa05ec403734d1ac35e3a03ca5bf085d0473195dd9490375d241c73ef5d50bf79ed1fe14e93d0710ebf915f8f52d0062b7986129ae8903f78a4b449b7ce7013f5b2aebbd5fcc76b71d0c36dbbb24cb6ed6cb50de9b06a01ba11de6025f2d0c4af71c28d9eb53d9ab885981e27554255cd0aed13438469d4713c0e37fb8ad0c751d50fd4db099dac66358fbada177b5f69f3a6518216f0eb7c92878f94da6d6c18a4c5606f55afb9e66610ef4a41953cff6f1ac7181b6f76ee54aa728466f0c73129d70c6affca90b8931deef27bb1185e7097a93b12b3d389388e1ad1adc07dfd96e6dc926eefdcf373c64566f690b970717afd1df3e4326d73c6d1cc5330c8fdee071684fe4e042b8aec497e2e4fc1098149d0707ce95b550b63fa74cd808ae6a0059002b016d814e0a6e716dde400e5b3716ae33fc8c92915488ce7fe50d94c19f927482e0d6b3cc2dcd9e5fb9d741aeb81c07a9dbad27b926257895af087791a6b9c4048f17832148be5c58f89e4877e22c5087ddbf0a7bf273ae45a50b71d9305c8813c886ea454d6c24174f143856abadc9319d6eabd8d48ec3c66a031394f78c9c9cdb2d0de8f44a932d30a6b913378999ebf0560c1c5f7c4098ce06f117b53db72a4436c885eb9c748deb2587ca0904743138de7e98bc3b0494e40a5a56d71f4204e8b8cad941bfd3abd384b976baa2f6b88c987e61f049f80c890df6470aad55283b8b891b0707aa22089351441fac575587b2ea4ac2f9f9c38b071fdbcc7f5efab14d86b22181e741ed824447a2facb46a63e91538af78c5fdf02 false +check_ge_p3_identity 078c63ceebcfc9d8af51e232b497fb3cd5f491bb9bed5aa3c556a47e62cb186b false true +check_ge_p3_identity 62218e5f0710af551bee941adb3981650862d2a3c4c18c794f450b5bb538975b false true +check_ge_p3_identity 046e1450f147f3ade34d149973913cc75d4e7b9669eb1ed61da0f1d4a0bd7f13 false true +check_ge_p3_identity ca8a2f621cfc7aa3efcd7ddf55dce5352e757b38aca0869b050c0a27824e5c5e true true +check_ge_p3_identity 64a247eef6087d86e1e9fa048a3c181fdb1728431f29ba738634bdc38f02a859 true true +check_ge_p3_identity cff0c7170a41395b0658ee42b76545c45360736b973ab2f31f6f227b9415df67 true true diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 0a58f469a..bb7ccbe56 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -39,40 +39,40 @@ from framework.wallet import Wallet class MultisigTest(): def run_test(self): self.reset() - self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5) - self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5) - self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5) - self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5) - self.mine('4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW', 5) + self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5) + self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5) + self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5) + self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5) + self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5) self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60) self.test_states() - self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk') + self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG') self.import_multisig_info([1, 0], 5) txid = self.transfer([1, 0]) self.import_multisig_info([0, 1], 6) self.check_transaction(txid) - self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y') + self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i') self.import_multisig_info([0, 2], 5) txid = self.transfer([0, 2]) self.import_multisig_info([0, 1, 2], 6) self.check_transaction(txid) - self.create_multisig_wallets(3, 3, '4ADHswEU3XBUee8pudBkZQd9beJainqNo1BQKkHJujAEPJyQrLj9U4dNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRUDxgjW') + self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP') self.import_multisig_info([2, 0, 1], 5) txid = self.transfer([2, 1, 0]) self.import_multisig_info([0, 2, 1], 6) self.check_transaction(txid) - self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53') + self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff') self.import_multisig_info([0, 2, 3], 5) txid = self.transfer([0, 2, 3]) self.import_multisig_info([0, 1, 2, 3], 6) self.check_transaction(txid) - self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR') + self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U') self.import_multisig_info([1, 2], 5) txid = self.transfer([1, 2]) self.import_multisig_info([0, 1, 2, 3], 6) @@ -177,10 +177,6 @@ class MultisigTest(): for i in range(3): ok = False - try: res = wallet[i].finalize_multisig(info) - except: ok = True - assert ok - ok = False try: res = wallet[i].exchange_multisig_keys(info) except: ok = True assert ok @@ -193,11 +189,6 @@ class MultisigTest(): assert res.ready ok = False - try: res = wallet[0].finalize_multisig(info) - except: ok = True - assert ok - - ok = False try: res = wallet[0].prepare_multisig() except: ok = True assert ok diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index fca9ce91c..07a24546e 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -171,7 +171,7 @@ class TransferTest(): assert e.double_spend_seen == False assert not 'confirmations' in e or e.confirmations == 0 - running_balances[0] -= 1000000000000 + fee + running_balances[0] -= fee res = self.wallet[0].get_balance() assert res.balance == running_balances[0] @@ -183,8 +183,6 @@ class TransferTest(): running_balances[0] += res.block_header.reward self.wallet[0].refresh() - running_balances[0] += 1000000000000 - res = self.wallet[0].get_transfers() assert len(res['in']) == height # coinbases assert len(res.out) == 1 # not mined yet @@ -337,7 +335,7 @@ class TransferTest(): assert len(res.unsigned_txset) == 0 unsigned_txset = res.unsigned_txset - running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + fee + running_balances[0] -= 1100000000000 + 1200000000000 + fee res = self.wallet[0].get_balance() assert res.balance == running_balances[0] @@ -347,7 +345,6 @@ class TransferTest(): daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) res = daemon.getlastblockheader() running_balances[0] += res.block_header.reward - running_balances[0] += 1000000000000 running_balances[1] += 1100000000000 running_balances[2] += 1200000000000 self.wallet[0].refresh() diff --git a/tests/functional_tests/uri.py b/tests/functional_tests/uri.py index c193ac926..4d79c6b82 100755 --- a/tests/functional_tests/uri.py +++ b/tests/functional_tests/uri.py @@ -142,15 +142,11 @@ class URITest(): assert res.uri.recipient_name == utf8string[0] assert not 'unknown_parameters' in res or len(res.unknown_parameters) == 0 - res = wallet.make_uri(address = address, recipient_name = utf8string[0], tx_description = utf8string[1], amount = 1000000000000, payment_id = '1' * 64) - assert res.uri == 'monero:' + address + '?tx_payment_id=' + '1' * 64 + '&tx_amount=1.000000000000&recipient_name=' + quoted_utf8string[0] + '&tx_description=' + quoted_utf8string[1] - res = wallet.parse_uri(res.uri) - assert res.uri.address == address - assert res.uri.payment_id == '1' * 64 - assert res.uri.amount == 1000000000000 - assert res.uri.tx_description == utf8string[1] - assert res.uri.recipient_name == utf8string[0] - assert not 'unknown_parameters' in res or len(res.unknown_parameters) == 0 + # external payment ids are not supported anymore + ok = False + try: res = wallet.make_uri(address = address, recipient_name = utf8string[0], tx_description = utf8string[1], amount = 1000000000000, payment_id = '1' * 64) + except: ok = True + assert ok # spaces must be encoded as %20 res = wallet.make_uri(address = address, tx_description = ' ' + utf8string[1] + ' ' + utf8string[0] + ' ', amount = 1000000000000) diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt index 542d204e0..e8810ff85 100644 --- a/tests/performance_tests/CMakeLists.txt +++ b/tests/performance_tests/CMakeLists.txt @@ -46,6 +46,7 @@ set(performance_tests_headers subaddress_expand.h range_proof.h bulletproof.h + bulletproof_plus.h crypto_ops.h sc_reduce32.h sc_check.h diff --git a/tests/performance_tests/bulletproof_plus.h b/tests/performance_tests/bulletproof_plus.h new file mode 100644 index 000000000..9aad61065 --- /dev/null +++ b/tests/performance_tests/bulletproof_plus.h @@ -0,0 +1,99 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "ringct/rctSigs.h" +#include "ringct/bulletproofs_plus.h" + +template<bool a_verify, size_t n_amounts> +class test_bulletproof_plus +{ +public: + static const size_t approx_loop_count = 100 / n_amounts; + static const size_t loop_count = (approx_loop_count >= 10 ? approx_loop_count : 10) / (a_verify ? 1 : 5); + static const bool verify = a_verify; + + bool init() + { + proof = rct::bulletproof_plus_PROVE(std::vector<uint64_t>(n_amounts, 749327532984), rct::skvGen(n_amounts)); + return true; + } + + bool test() + { + bool ret = true; + if (verify) + ret = rct::bulletproof_plus_VERIFY(proof); + else + rct::bulletproof_plus_PROVE(std::vector<uint64_t>(n_amounts, 749327532984), rct::skvGen(n_amounts)); + return ret; + } + +private: + rct::BulletproofPlus proof; +}; + +template<bool batch, size_t start, size_t repeat, size_t mul, size_t add, size_t N> +class test_aggregated_bulletproof_plus +{ +public: + static const size_t loop_count = 500 / (N * repeat); + + bool init() + { + size_t o = start; + for (size_t n = 0; n < N; ++n) + { + for (size_t i = 0; i < repeat; ++i) + proofs.push_back(rct::bulletproof_plus_PROVE(std::vector<uint64_t>(o, 749327532984), rct::skvGen(o))); + o = o * mul + add; + } + return true; + } + + bool test() + { + if (batch) + { + return rct::bulletproof_plus_VERIFY(proofs); + } + else + { + for (const rct::BulletproofPlus &proof: proofs) + if (!rct::bulletproof_plus_VERIFY(proof)) + return false; + return true; + } + } + +private: + std::vector<rct::BulletproofPlus> proofs; +}; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index e59bb52fd..a61d84bed 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -58,6 +58,7 @@ #include "equality.h" #include "range_proof.h" #include "bulletproof.h" +#include "bulletproof_plus.h" #include "crypto_ops.h" #include "multiexp.h" #include "sig_mlsag.h" @@ -241,6 +242,26 @@ int main(int argc, char** argv) TEST_PERFORMANCE1(filter, p, test_range_proof, true); TEST_PERFORMANCE1(filter, p, test_range_proof, false); + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 1); // 1 bulletproof_plus with 1 amount + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 1); + + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 2); // 1 bulletproof_plus with 2 amounts + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 2); + + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, true, 15); // 1 bulletproof_plus with 15 amounts + TEST_PERFORMANCE2(filter, p, test_bulletproof_plus, false, 15); + + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 2, 1, 1, 0, 4); + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 2, 1, 1, 0, 4); // 4 proofs, each with 2 amounts + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 8, 1, 1, 0, 4); + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 8, 1, 1, 0, 4); // 4 proofs, each with 8 amounts + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 1, 1, 2, 0, 4); + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 1, 1, 2, 0, 4); // 4 proofs with 1, 2, 4, 8 amounts + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 1, 8, 1, 1, 4); + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 1, 8, 1, 1, 4); // 32 proofs, with 1, 2, 3, 4 amounts, 8 of each + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, false, 2, 1, 1, 0, 64); + TEST_PERFORMANCE6(filter, p, test_aggregated_bulletproof_plus, true, 2, 1, 1, 0, 64); // 64 proof, each with 2 amounts + TEST_PERFORMANCE2(filter, p, test_bulletproof, true, 1); // 1 bulletproof with 1 amount TEST_PERFORMANCE2(filter, p, test_bulletproof, false, 1); diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 556e0ec40..5f6b1e749 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(unit_tests_sources block_reward.cpp bootstrap_node_selector.cpp bulletproofs.cpp + bulletproofs_plus.cpp canonical_amounts.cpp chacha.cpp checkpoints.cpp diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp index ee617938c..43a359a59 100644 --- a/tests/unit_tests/bulletproofs.cpp +++ b/tests/unit_tests/bulletproofs.cpp @@ -131,7 +131,7 @@ TEST(bulletproofs, multi_splitting) } rct::ctkeyV outSk; - rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 0 }; + rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, 4 }; rct::rctSig s = rct::genRctSimple(rct::zero(), sc, destinations, inamounts, outamounts, available, mixRing, amount_keys, NULL, NULL, index, outSk, rct_config, hw::get_device("default")); ASSERT_TRUE(rct::verRctSimple(s)); for (size_t i = 0; i < n_outputs; ++i) diff --git a/tests/unit_tests/bulletproofs_plus.cpp b/tests/unit_tests/bulletproofs_plus.cpp new file mode 100644 index 000000000..a64320233 --- /dev/null +++ b/tests/unit_tests/bulletproofs_plus.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2017-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "gtest/gtest.h" + +#include "string_tools.h" +#include "ringct/rctOps.h" +#include "ringct/rctSigs.h" +#include "ringct/bulletproofs_plus.h" +#include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "device/device.hpp" +#include "misc_log_ex.h" + +TEST(bulletproofs_plus, valid_zero) +{ + rct::BulletproofPlus proof = bulletproof_plus_PROVE(0, rct::skGen()); + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof)); +} + +TEST(bulletproofs_plus, valid_max) +{ + rct::BulletproofPlus proof = bulletproof_plus_PROVE(0xffffffffffffffff, rct::skGen()); + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof)); +} + +TEST(bulletproofs_plus, valid_random) +{ + for (int n = 0; n < 8; ++n) + { + rct::BulletproofPlus proof = bulletproof_plus_PROVE(crypto::rand<uint64_t>(), rct::skGen()); + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof)); + } +} + +TEST(bulletproofs_plus, valid_multi_random) +{ + for (int n = 0; n < 8; ++n) + { + size_t outputs = 2 + n; + std::vector<uint64_t> amounts; + rct::keyV gamma; + for (size_t i = 0; i < outputs; ++i) + { + amounts.push_back(crypto::rand<uint64_t>()); + gamma.push_back(rct::skGen()); + } + rct::BulletproofPlus proof = bulletproof_plus_PROVE(amounts, gamma); + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof)); + } +} + +TEST(bulletproofs_plus, valid_aggregated) +{ + static const size_t N_PROOFS = 8; + std::vector<rct::BulletproofPlus> proofs(N_PROOFS); + for (size_t n = 0; n < N_PROOFS; ++n) + { + size_t outputs = 2 + n; + std::vector<uint64_t> amounts; + rct::keyV gamma; + for (size_t i = 0; i < outputs; ++i) + { + amounts.push_back(crypto::rand<uint64_t>()); + gamma.push_back(rct::skGen()); + } + proofs[n] = bulletproof_plus_PROVE(amounts, gamma); + } + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proofs)); +} + +TEST(bulletproofs_plus, invalid_8) +{ + rct::key invalid_amount = rct::zero(); + invalid_amount[8] = 1; + rct::BulletproofPlus proof = bulletproof_plus_PROVE(invalid_amount, rct::skGen()); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); +} + +TEST(bulletproofs_plus, invalid_31) +{ + rct::key invalid_amount = rct::zero(); + invalid_amount[31] = 1; + rct::BulletproofPlus proof = bulletproof_plus_PROVE(invalid_amount, rct::skGen()); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); +} + +static const char * const torsion_elements[] = +{ + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", + "0000000000000000000000000000000000000000000000000000000000000000", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", + "0000000000000000000000000000000000000000000000000000000000000080", + "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", +}; + +TEST(bulletproofs_plus, invalid_torsion) +{ + rct::BulletproofPlus proof = bulletproof_plus_PROVE(7329838943733, rct::skGen()); + ASSERT_TRUE(rct::bulletproof_plus_VERIFY(proof)); + for (const auto &xs: torsion_elements) + { + rct::key x; + ASSERT_TRUE(epee::string_tools::hex_to_pod(xs, x)); + ASSERT_FALSE(rct::isInMainSubgroup(x)); + for (auto &k: proof.V) + { + const rct::key org_k = k; + rct::addKeys(k, org_k, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + k = org_k; + } + for (auto &k: proof.L) + { + const rct::key org_k = k; + rct::addKeys(k, org_k, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + k = org_k; + } + for (auto &k: proof.R) + { + const rct::key org_k = k; + rct::addKeys(k, org_k, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + k = org_k; + } + const rct::key org_A = proof.A; + rct::addKeys(proof.A, org_A, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + proof.A = org_A; + const rct::key org_A1 = proof.A1; + rct::addKeys(proof.A1, org_A1, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + proof.A1 = org_A1; + const rct::key org_B = proof.B; + rct::addKeys(proof.B, org_B, x); + ASSERT_FALSE(rct::bulletproof_plus_VERIFY(proof)); + proof.B = org_B; + } +} diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index 30d6f8133..069bc19b2 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -265,11 +265,17 @@ namespace virtual void callback(cryptonote::levin::detail::p2p_context& context) override final {} - virtual void on_connection_new(cryptonote::levin::detail::p2p_context&) override final - {} + virtual void on_connection_new(cryptonote::levin::detail::p2p_context& context) override final + { + if (notifier) + notifier->on_handshake_complete(context.m_connection_id, context.m_is_income); + } - virtual void on_connection_close(cryptonote::levin::detail::p2p_context&) override final - {} + virtual void on_connection_close(cryptonote::levin::detail::p2p_context& context) override final + { + if (notifier) + notifier->on_connection_close(context.m_connection_id); + } public: test_receiver() @@ -306,6 +312,8 @@ namespace { return get_raw_message(notified_); } + + std::shared_ptr<cryptonote::levin::notify> notifier{}; }; class levin_notify : public ::testing::Test @@ -343,13 +351,16 @@ namespace EXPECT_EQ(connection_ids_.size(), connections_->get_connections_count()); } - cryptonote::levin::notify make_notifier(const std::size_t noise_size, bool is_public, bool pad_txs) + std::shared_ptr<cryptonote::levin::notify> make_notifier(const std::size_t noise_size, bool is_public, bool pad_txs) { epee::byte_slice noise = nullptr; if (noise_size) noise = epee::levin::make_noise_notify(noise_size); epee::net_utils::zone zone = is_public ? epee::net_utils::zone::public_ : epee::net_utils::zone::i2p; - return cryptonote::levin::notify{io_service_, connections_, std::move(noise), zone, pad_txs, events_}; + receiver_.notifier.reset( + new cryptonote::levin::notify{io_service_, connections_, std::move(noise), zone, pad_txs, events_} + ); + return receiver_.notifier; } boost::uuids::random_generator random_generator_; @@ -590,7 +601,8 @@ TEST_F(levin_notify, defaulted) TEST_F(levin_notify, fluff_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -636,7 +648,8 @@ TEST_F(levin_notify, fluff_without_padding) TEST_F(levin_notify, stem_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -708,7 +721,8 @@ TEST_F(levin_notify, stem_without_padding) TEST_F(levin_notify, stem_no_outs_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(true); @@ -764,7 +778,8 @@ TEST_F(levin_notify, stem_no_outs_without_padding) TEST_F(levin_notify, local_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -836,7 +851,8 @@ TEST_F(levin_notify, local_without_padding) TEST_F(levin_notify, forward_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -908,7 +924,8 @@ TEST_F(levin_notify, forward_without_padding) TEST_F(levin_notify, block_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -937,7 +954,8 @@ TEST_F(levin_notify, block_without_padding) TEST_F(levin_notify, none_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -966,7 +984,8 @@ TEST_F(levin_notify, none_without_padding) TEST_F(levin_notify, fluff_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1012,7 +1031,8 @@ TEST_F(levin_notify, fluff_with_padding) TEST_F(levin_notify, stem_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1079,7 +1099,8 @@ TEST_F(levin_notify, stem_with_padding) TEST_F(levin_notify, stem_no_outs_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(true); @@ -1135,7 +1156,8 @@ TEST_F(levin_notify, stem_no_outs_with_padding) TEST_F(levin_notify, local_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1202,7 +1224,8 @@ TEST_F(levin_notify, local_with_padding) TEST_F(levin_notify, forward_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1269,7 +1292,8 @@ TEST_F(levin_notify, forward_with_padding) TEST_F(levin_notify, block_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1298,7 +1322,8 @@ TEST_F(levin_notify, block_with_padding) TEST_F(levin_notify, none_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1327,7 +1352,8 @@ TEST_F(levin_notify, none_with_padding) TEST_F(levin_notify, private_fluff_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1378,7 +1404,8 @@ TEST_F(levin_notify, private_fluff_without_padding) TEST_F(levin_notify, private_stem_without_padding) { // private mode always uses fluff but marked as stem - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1429,7 +1456,8 @@ TEST_F(levin_notify, private_stem_without_padding) TEST_F(levin_notify, private_local_without_padding) { // private mode always uses fluff but marked as stem - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1480,7 +1508,8 @@ TEST_F(levin_notify, private_local_without_padding) TEST_F(levin_notify, private_forward_without_padding) { // private mode always uses fluff but marked as stem - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1531,7 +1560,8 @@ TEST_F(levin_notify, private_forward_without_padding) TEST_F(levin_notify, private_block_without_padding) { // private mode always uses fluff but marked as stem - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1561,7 +1591,8 @@ TEST_F(levin_notify, private_block_without_padding) TEST_F(levin_notify, private_none_without_padding) { // private mode always uses fluff but marked as stem - cryptonote::levin::notify notifier = make_notifier(0, false, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1590,7 +1621,8 @@ TEST_F(levin_notify, private_none_without_padding) TEST_F(levin_notify, private_fluff_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1640,7 +1672,8 @@ TEST_F(levin_notify, private_fluff_with_padding) TEST_F(levin_notify, private_stem_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1690,7 +1723,8 @@ TEST_F(levin_notify, private_stem_with_padding) TEST_F(levin_notify, private_local_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1740,7 +1774,8 @@ TEST_F(levin_notify, private_local_with_padding) TEST_F(levin_notify, private_forward_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1790,7 +1825,8 @@ TEST_F(levin_notify, private_forward_with_padding) TEST_F(levin_notify, private_block_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1819,7 +1855,8 @@ TEST_F(levin_notify, private_block_with_padding) TEST_F(levin_notify, private_none_with_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, false, true); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -1850,7 +1887,8 @@ TEST_F(levin_notify, stem_mappings) { static constexpr const unsigned test_connections_count = (CRYPTONOTE_DANDELIONPP_STEMS + 1) * 2; - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < test_connections_count; ++count) add_connection(count % 2 == 0); @@ -1973,7 +2011,8 @@ TEST_F(levin_notify, fluff_multiple) { static constexpr const unsigned test_connections_count = (CRYPTONOTE_DANDELIONPP_STEMS + 1) * 2; - cryptonote::levin::notify notifier = make_notifier(0, true, false); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(0, true, false); + auto ¬ifier = *notifier_ptr; for (unsigned count = 0; count < test_connections_count; ++count) add_connection(count % 2 == 0); @@ -2091,7 +2130,8 @@ TEST_F(levin_notify, noise) txs[0].resize(1900, 'h'); const boost::uuids::uuid incoming_id = random_generator_(); - cryptonote::levin::notify notifier = make_notifier(2048, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(2048, false, true); + auto ¬ifier = *notifier_ptr; { const auto status = notifier.get_status(); @@ -2182,7 +2222,8 @@ TEST_F(levin_notify, noise_stem) txs[0].resize(1900, 'h'); const boost::uuids::uuid incoming_id = random_generator_(); - cryptonote::levin::notify notifier = make_notifier(2048, false, true); + std::shared_ptr<cryptonote::levin::notify> notifier_ptr = make_notifier(2048, false, true); + auto ¬ifier = *notifier_ptr; { const auto status = notifier.get_status(); diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index f075034bd..c0b057c9a 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -106,10 +106,16 @@ static uint32_t lcg() } +struct BlockchainAndPool +{ + cryptonote::tx_memory_pool txpool; + cryptonote::Blockchain bc; + BlockchainAndPool(): txpool(bc), bc(txpool) {} +}; + #define PREFIX_WINDOW(hf_version,window) \ - std::unique_ptr<cryptonote::Blockchain> bc; \ - cryptonote::tx_memory_pool txpool(*bc); \ - bc.reset(new cryptonote::Blockchain(txpool)); \ + BlockchainAndPool bap; \ + cryptonote::Blockchain *bc = &bap.bc; \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -118,8 +124,7 @@ static uint32_t lcg() }; \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ } opts; \ - cryptonote::Blockchain *blockchain = bc.get(); \ - bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ + bool r = bc->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ ASSERT_TRUE(r) #define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW) diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 79775960d..362a658de 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -26,12 +26,16 @@ // 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 "crypto/crypto.h" +#include "multisig/multisig_account.h" +#include "multisig/multisig_kex_msg.h" +#include "ringct/rctOps.h" +#include "wallet/wallet2.h" + #include "gtest/gtest.h" #include <cstdint> -#include "wallet/wallet2.h" - static const struct { const char *address; @@ -86,59 +90,145 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet) } } -static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& mis) +static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wallets, const std::vector<std::string>& infos) { std::vector<std::string> new_infos; - for (size_t i = 0; i < wallets.size(); ++i) { - new_infos.push_back(wallets[i].exchange_multisig_keys("", mis)); + new_infos.reserve(infos.size()); + + for (size_t i = 0; i < wallets.size(); ++i) + { + new_infos.push_back(wallets[i].exchange_multisig_keys("", infos)); } return new_infos; } +static void check_results(const std::vector<std::string> &intermediate_infos, + std::vector<tools::wallet2>& wallets, + std::uint32_t M) +{ + // check results + std::unordered_set<crypto::secret_key> unique_privkeys; + rct::key composite_pubkey = rct::identity(); + + wallets[0].decrypt_keys(""); + crypto::public_key spend_pubkey = wallets[0].get_account().get_keys().m_account_address.m_spend_public_key; + crypto::secret_key view_privkey = wallets[0].get_account().get_keys().m_view_secret_key; + crypto::public_key view_pubkey; + EXPECT_TRUE(crypto::secret_key_to_public_key(view_privkey, view_pubkey)); + wallets[0].encrypt_keys(""); + + for (size_t i = 0; i < wallets.size(); ++i) + { + EXPECT_TRUE(intermediate_infos[i].empty()); + bool ready; + uint32_t threshold, total; + EXPECT_TRUE(wallets[i].multisig(&ready, &threshold, &total)); + EXPECT_TRUE(ready); + EXPECT_TRUE(threshold == M); + EXPECT_TRUE(total == wallets.size()); + + wallets[i].decrypt_keys(""); + + if (i != 0) + { + // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses. + // no need to compare 0's address with itself. + EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == + wallets[i].get_account().get_public_address_str(cryptonote::TESTNET)); + + EXPECT_EQ(spend_pubkey, wallets[i].get_account().get_keys().m_account_address.m_spend_public_key); + EXPECT_EQ(view_privkey, wallets[i].get_account().get_keys().m_view_secret_key); + EXPECT_EQ(view_pubkey, wallets[i].get_account().get_keys().m_account_address.m_view_public_key); + } + + // sum together unique multisig keys + for (const auto &privkey : wallets[i].get_account().get_keys().m_multisig_keys) + { + EXPECT_NE(privkey, crypto::null_skey); + + if (unique_privkeys.find(privkey) == unique_privkeys.end()) + { + unique_privkeys.insert(privkey); + crypto::public_key pubkey; + crypto::secret_key_to_public_key(privkey, pubkey); + EXPECT_NE(privkey, crypto::null_skey); + EXPECT_NE(pubkey, crypto::null_pkey); + EXPECT_NE(pubkey, rct::rct2pk(rct::identity())); + rct::addKeys(composite_pubkey, composite_pubkey, rct::pk2rct(pubkey)); + } + } + wallets[i].encrypt_keys(""); + } + + // final key via sums should equal the wallets' public spend key + wallets[0].decrypt_keys(""); + EXPECT_EQ(wallets[0].get_account().get_keys().m_account_address.m_spend_public_key, rct::rct2pk(composite_pubkey)); + wallets[0].encrypt_keys(""); +} + static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M) { ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT); ASSERT_TRUE(M <= wallets.size()); + std::uint32_t rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M); + std::uint32_t rounds_complete{0}; - std::vector<std::string> mis(wallets.size()); + // initialize wallets, get first round multisig kex msgs + std::vector<std::string> initial_infos(wallets.size()); - for (size_t i = 0; i < wallets.size(); ++i) { + for (size_t i = 0; i < wallets.size(); ++i) + { make_wallet(i, wallets[i]); wallets[i].decrypt_keys(""); - mis[i] = wallets[i].get_multisig_info(); + initial_infos[i] = wallets[i].get_multisig_first_kex_msg(); wallets[i].encrypt_keys(""); } - for (auto& wallet: wallets) { + // wallets should not be multisig yet + for (const auto &wallet: wallets) + { ASSERT_FALSE(wallet.multisig()); } - std::vector<std::string> mxis; - for (size_t i = 0; i < wallets.size(); ++i) { - // it's ok to put all of multisig keys in this function. it throws in case of error - mxis.push_back(wallets[i].make_multisig("", mis, M)); - } + // make wallets multisig, get second round kex messages (if appropriate) + std::vector<std::string> intermediate_infos(wallets.size()); - while (!mxis[0].empty()) { - mxis = exchange_round(wallets, mxis); + for (size_t i = 0; i < wallets.size(); ++i) + { + intermediate_infos[i] = wallets[i].make_multisig("", initial_infos, M); } - for (size_t i = 0; i < wallets.size(); ++i) { - ASSERT_TRUE(mxis[i].empty()); - bool ready; - uint32_t threshold, total; - ASSERT_TRUE(wallets[i].multisig(&ready, &threshold, &total)); - ASSERT_TRUE(ready); - ASSERT_TRUE(threshold == M); - ASSERT_TRUE(total == wallets.size()); - - if (i != 0) { - // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses. no need to compare 0's address with itself. - ASSERT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == wallets[i].get_account().get_public_address_str(cryptonote::TESTNET)); - } + ++rounds_complete; + + // perform kex rounds until kex is complete + while (!intermediate_infos[0].empty()) + { + bool ready{false}; + wallets[0].multisig(&ready); + EXPECT_FALSE(ready); + + intermediate_infos = exchange_round(wallets, intermediate_infos); + + ++rounds_complete; } + + EXPECT_EQ(rounds_required, rounds_complete); + + check_results(intermediate_infos, wallets, M); +} + +TEST(multisig, make_1_2) +{ + std::vector<tools::wallet2> wallets(2); + make_wallets(wallets, 1); +} + +TEST(multisig, make_1_3) +{ + std::vector<tools::wallet2> wallets(3); + make_wallets(wallets, 1); } TEST(multisig, make_2_2) @@ -165,8 +255,88 @@ TEST(multisig, make_2_4) make_wallets(wallets, 2); } -TEST(multisig, make_2_5) +TEST(multisig, multisig_kex_msg) { - std::vector<tools::wallet2> wallets(5); - make_wallets(wallets, 2); + using namespace multisig; + + crypto::public_key pubkey1; + crypto::public_key pubkey2; + crypto::public_key pubkey3; + crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey1); + crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey2); + crypto::secret_key_to_public_key(rct::rct2sk(rct::skGen()), pubkey3); + + crypto::secret_key signing_skey = rct::rct2sk(rct::skGen()); + crypto::public_key signing_pubkey; + while(!crypto::secret_key_to_public_key(signing_skey, signing_pubkey)) + { + signing_skey = rct::rct2sk(rct::skGen()); + } + + crypto::secret_key ancillary_skey = rct::rct2sk(rct::skGen()); + while (ancillary_skey == crypto::null_skey) + ancillary_skey = rct::rct2sk(rct::skGen()); + + // misc. edge cases + EXPECT_NO_THROW((multisig_kex_msg{})); + EXPECT_ANY_THROW((multisig_kex_msg{multisig_kex_msg{}.get_msg()})); + EXPECT_ANY_THROW((multisig_kex_msg{"abc"})); + EXPECT_ANY_THROW((multisig_kex_msg{0, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey})); + EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, crypto::null_skey})); + EXPECT_ANY_THROW((multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, crypto::null_skey})); + EXPECT_ANY_THROW((multisig_kex_msg{1, crypto::null_skey, std::vector<crypto::public_key>{}, ancillary_skey})); + + // test that messages are both constructible and reversible + + // round 1 + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{}, ancillary_skey}.get_msg() + })); + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg() + })); + + // round 2 + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}.get_msg() + })); + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1}, crypto::null_skey}.get_msg() + })); + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}.get_msg() + })); + EXPECT_NO_THROW((multisig_kex_msg{ + multisig_kex_msg{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2, pubkey3}, crypto::null_skey}.get_msg() + })); + + // test that keys can be recovered if stored in a message and the message's reverse + + // round 1 + multisig_kex_msg msg_rnd1{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}; + multisig_kex_msg msg_rnd1_reverse{msg_rnd1.get_msg()}; + EXPECT_EQ(msg_rnd1.get_round(), 1); + EXPECT_EQ(msg_rnd1.get_round(), msg_rnd1_reverse.get_round()); + EXPECT_EQ(msg_rnd1.get_signing_pubkey(), signing_pubkey); + EXPECT_EQ(msg_rnd1.get_signing_pubkey(), msg_rnd1_reverse.get_signing_pubkey()); + EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), 0); + EXPECT_EQ(msg_rnd1.get_msg_pubkeys().size(), msg_rnd1_reverse.get_msg_pubkeys().size()); + EXPECT_EQ(msg_rnd1.get_msg_privkey(), ancillary_skey); + EXPECT_EQ(msg_rnd1.get_msg_privkey(), msg_rnd1_reverse.get_msg_privkey()); + + // round 2 + multisig_kex_msg msg_rnd2{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}; + multisig_kex_msg msg_rnd2_reverse{msg_rnd2.get_msg()}; + EXPECT_EQ(msg_rnd2.get_round(), 2); + EXPECT_EQ(msg_rnd2.get_round(), msg_rnd2_reverse.get_round()); + EXPECT_EQ(msg_rnd2.get_signing_pubkey(), signing_pubkey); + EXPECT_EQ(msg_rnd2.get_signing_pubkey(), msg_rnd2_reverse.get_signing_pubkey()); + ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), 2); + ASSERT_EQ(msg_rnd2.get_msg_pubkeys().size(), msg_rnd2_reverse.get_msg_pubkeys().size()); + EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], pubkey1); + EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], pubkey2); + EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[0], msg_rnd2_reverse.get_msg_pubkeys()[0]); + EXPECT_EQ(msg_rnd2.get_msg_pubkeys()[1], msg_rnd2_reverse.get_msg_pubkeys()[1]); + EXPECT_EQ(msg_rnd2.get_msg_privkey(), crypto::null_skey); + EXPECT_EQ(msg_rnd2.get_msg_privkey(), msg_rnd2_reverse.get_msg_privkey()); } diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index cab600b3d..7907e9a9a 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -35,6 +35,7 @@ #include "cryptonote_core/i_core_events.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "cryptonote_protocol/cryptonote_protocol_handler.inl" +#include <condition_variable> #define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0} #define MAKE_IPV4_ADDRESS_PORT(a,b,c,d,e) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),e} @@ -303,6 +304,9 @@ TEST(node_server, bind_same_p2p_port) Relevant part about REUSEADDR from man: https://www.man7.org/linux/man-pages/man7/ip.7.html + + For Mac OSX, set the following alias, before running the test, or else it will fail: + sudo ifconfig lo0 alias 127.0.0.2 */ vm.find(nodetool::arg_p2p_bind_ip.name)->second = boost::program_options::variable_value(std::string("127.0.0.2"), false); vm.find(nodetool::arg_p2p_bind_port.name)->second = boost::program_options::variable_value(std::string(port), false); @@ -906,5 +910,264 @@ TEST(cryptonote_protocol_handler, race_condition) remove_tree(dir); } +TEST(node_server, race_condition) +{ + struct contexts { + using cryptonote = cryptonote::cryptonote_connection_context; + using p2p = nodetool::p2p_connection_context_t<cryptonote>; + }; + using context_t = contexts::cryptonote; + using options_t = boost::program_options::variables_map; + using options_description_t = boost::program_options::options_description; + using worker_t = std::thread; + struct protocol_t { + private: + using p2p_endpoint_t = nodetool::i_p2p_endpoint<context_t>; + using lock_t = std::mutex; + using condition_t = std::condition_variable_any; + using unique_lock_t = std::unique_lock<lock_t>; + p2p_endpoint_t *p2p_endpoint; + lock_t lock; + condition_t condition; + bool started{}; + size_t counter{}; + public: + using payload_t = cryptonote::CORE_SYNC_DATA; + using blob_t = cryptonote::blobdata; + using connection_context = context_t; + using payload_type = payload_t; + using relay_t = cryptonote::relay_method; + using string_t = std::string; + using span_t = epee::span<const uint8_t>; + using blobs_t = epee::span<const cryptonote::blobdata>; + using connections_t = std::list<cryptonote::connection_info>; + using block_queue_t = cryptonote::block_queue; + using stripes_t = std::pair<uint32_t, uint32_t>; + using byte_stream_t = epee::byte_stream; + struct core_events_t: cryptonote::i_core_events { + uint64_t get_current_blockchain_height() const override { return {}; } + bool is_synchronized() const override { return {}; } + void on_transactions_relayed(blobs_t blobs, relay_t relay) override {} + }; + int handle_invoke_map(bool is_notify, int command, const span_t in, byte_stream_t &out, context_t &context, bool &handled) { + return {}; + } + bool on_idle() { + if (not p2p_endpoint) + return {}; + { + unique_lock_t guard(lock); + if (not started) + started = true; + else + return {}; + } + std::vector<blob_t> txs(128 / 64 * 1024 * 1024, blob_t(1, 'x')); + worker_t worker([this]{ + p2p_endpoint->for_each_connection( + [this](context_t &, uint64_t, uint32_t){ + { + unique_lock_t guard(lock); + ++counter; + condition.notify_all(); + condition.wait(guard, [this]{ return counter >= 3; }); + } + std::this_thread::sleep_for(std::chrono::milliseconds(8)); + return false; + } + ); + }); + { + unique_lock_t guard(lock); + ++counter; + condition.notify_all(); + condition.wait(guard, [this]{ return counter >= 3; }); + ++counter; + condition.notify_all(); + condition.wait(guard, [this]{ return counter >= 5; }); + } + p2p_endpoint->send_txs( + std::move(txs), + epee::net_utils::zone::public_, + {}, + relay_t::fluff + ); + worker.join(); + return {}; + } + bool init(const options_t &options) { return {}; } + bool deinit() { return {}; } + void set_p2p_endpoint(p2p_endpoint_t *p2p_endpoint) { + this->p2p_endpoint = p2p_endpoint; + } + bool process_payload_sync_data(const payload_t &payload, contexts::p2p &context, bool is_inital) { + context.m_state = context_t::state_normal; + context.m_needed_objects.resize(512 * 1024); + { + unique_lock_t guard(lock); + ++counter; + condition.notify_all(); + condition.wait(guard, [this]{ return counter >= 3; }); + ++counter; + condition.notify_all(); + condition.wait(guard, [this]{ return counter >= 5; }); + } + return true; + } + bool get_payload_sync_data(blob_t &blob) { return {}; } + bool get_payload_sync_data(payload_t &payload) { return {}; } + bool on_callback(context_t &context) { return {}; } + core_events_t &get_core(){ static core_events_t core_events; return core_events;} + void log_connections() {} + connections_t get_connections() { return {}; } + const block_queue_t &get_block_queue() const { + static block_queue_t block_queue; + return block_queue; + } + void stop() {} + void on_connection_close(context_t &context) {} + void set_max_out_peers(unsigned int max) {} + bool no_sync() const { return {}; } + void set_no_sync(bool value) {} + string_t get_peers_overview() const { return {}; } + stripes_t get_next_needed_pruning_stripe() const { return {}; } + bool needs_new_sync_connections() const { return {}; } + bool is_busy_syncing() { return {}; } + }; + using node_server_t = nodetool::node_server<protocol_t>; + auto conduct_test = [](protocol_t &protocol){ + struct messages { + struct core { + using sync = cryptonote::CORE_SYNC_DATA; + }; + using handshake = nodetool::COMMAND_HANDSHAKE_T<core::sync>; + }; + using handler_t = epee::levin::async_protocol_handler<context_t>; + using connection_t = epee::net_utils::connection<handler_t>; + using connection_ptr = boost::shared_ptr<connection_t>; + using shared_state_t = typename connection_t::shared_state; + using shared_state_ptr = std::shared_ptr<shared_state_t>; + using io_context_t = boost::asio::io_service; + using work_t = boost::asio::io_service::work; + using work_ptr = std::shared_ptr<work_t>; + using workers_t = std::vector<std::thread>; + using endpoint_t = boost::asio::ip::tcp::endpoint; + using event_t = epee::simple_event; + struct command_handler_t: epee::levin::levin_commands_handler<context_t> { + using span_t = epee::span<const uint8_t>; + using byte_stream_t = epee::byte_stream; + int invoke(int, const span_t, byte_stream_t &, context_t &) override { return {}; } + int notify(int, const span_t, context_t &) override { return {}; } + void callback(context_t &) override {} + void on_connection_new(context_t &) override {} + void on_connection_close(context_t &) override {} + ~command_handler_t() override {} + static void destroy(epee::levin::levin_commands_handler<context_t>* ptr) { delete ptr; } + }; + io_context_t io_context; + work_ptr work = std::make_shared<work_t>(io_context); + workers_t workers; + while (workers.size() < 4) { + workers.emplace_back([&io_context]{ + io_context.run(); + }); + } + io_context.post([&]{ + protocol.on_idle(); + }); + io_context.post([&]{ + protocol.on_idle(); + }); + shared_state_ptr shared_state = std::make_shared<shared_state_t>(); + shared_state->set_handler(new command_handler_t, &command_handler_t::destroy); + connection_ptr conn{new connection_t(io_context, shared_state, {}, {})}; + endpoint_t endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 48080); + conn->socket().connect(endpoint); + conn->socket().set_option(boost::asio::ip::tcp::socket::reuse_address(true)); + conn->start({}, {}); + context_t context; + conn->get_context(context); + event_t handshaked; + typename messages::handshake::request_t msg{{ + ::config::NETWORK_ID, + 58080, + }}; + epee::net_utils::async_invoke_remote_command2<typename messages::handshake::response>( + context, + messages::handshake::ID, + msg, + *shared_state, + [conn, &handshaked](int code, const typename messages::handshake::response &msg, context_t &context){ + EXPECT_TRUE(code >= 0); + handshaked.raise(); + }, + P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT + ); + handshaked.wait(); + conn->strand_.post([conn]{ + conn->cancel(); + }); + conn.reset(); + work.reset(); + for (auto& w: workers) { + w.join(); + } + }; + using path_t = boost::filesystem::path; + using ec_t = boost::system::error_code; + auto create_dir = []{ + ec_t ec; + path_t path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("daemon-%%%%%%%%%%%%%%%%", ec); + if (ec) + return path_t{}; + auto success = boost::filesystem::create_directory(path, ec); + if (not ec && success) + return path; + return path_t{}; + }; + auto remove_tree = [](const path_t &path){ + ec_t ec; + boost::filesystem::remove_all(path, ec); + }; + const auto dir = create_dir(); + ASSERT_TRUE(not dir.empty()); + protocol_t protocol{}; + node_server_t node_server(protocol); + protocol.set_p2p_endpoint(&node_server); + node_server.init( + [&dir]{ + options_t options; + boost::program_options::store( + boost::program_options::command_line_parser({ + "--p2p-bind-ip=127.0.0.1", + "--p2p-bind-port=48080", + "--out-peers=0", + "--data-dir", + dir.string(), + "--no-igd", + "--add-exclusive-node=127.0.0.1:48080", + "--check-updates=disabled", + "--disable-dns-checkpoints", + }).options([]{ + options_description_t options_description{}; + cryptonote::core::init_options(options_description); + node_server_t::init_options(options_description); + return options_description; + }()).run(), + options + ); + return options; + }() + ); + worker_t worker([&]{ + node_server.run(); + }); + conduct_test(protocol); + node_server.send_stop_signal(); + worker.join(); + node_server.deinit(); + remove_tree(dir); +} + namespace nodetool { template class node_server<cryptonote::t_cryptonote_protocol_handler<test_core>>; } namespace cryptonote { template class t_cryptonote_protocol_handler<test_core>; } diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 535752665..f4c73d3d5 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -132,7 +132,8 @@ TEST(Serialization, BinaryArchiveInts) { ASSERT_EQ(8, oss.str().size()); ASSERT_EQ(string("\0\0\0\0\xff\0\0\0", 8), oss.str()); - binary_archive<false> iar{epee::strspan<std::uint8_t>(oss.str())}; + const std::string s = oss.str(); + binary_archive<false> iar{epee::strspan<std::uint8_t>(s)}; iar.serialize_int(x1); ASSERT_EQ(8, iar.getpos()); ASSERT_TRUE(iar.good()); @@ -150,7 +151,8 @@ TEST(Serialization, BinaryArchiveVarInts) { ASSERT_EQ(6, oss.str().size()); ASSERT_EQ(string("\x80\x80\x80\x80\xF0\x1F", 6), oss.str()); - binary_archive<false> iar{epee::strspan<std::uint8_t>(oss.str())}; + const std::string s = oss.str(); + binary_archive<false> iar{epee::strspan<std::uint8_t>(s)}; iar.serialize_varint(x1); ASSERT_TRUE(iar.good()); ASSERT_EQ(x, x1); diff --git a/utils/health/README.md b/utils/health/README.md index 05eb057a1..d4ca4a538 100644 --- a/utils/health/README.md +++ b/utils/health/README.md @@ -16,7 +16,8 @@ On the first run, the script will complain about the missing ClangBuildAnalyzer ## clang-tidy -`utils/health/clang-tidy-run.sh` +`utils/health/clang-tidy-run-cc.sh` +`utils/health/clang-tidy-run-cpp.sh` Performs Lint checks on the source code and stores the result in the build directory. More information on the [home page](https://clang.llvm.org/extra/clang-tidy/). ## include-what-you-use diff --git a/utils/health/clang-tidy-run-cc.sh b/utils/health/clang-tidy-run-cc.sh new file mode 100755 index 000000000..0cf737131 --- /dev/null +++ b/utils/health/clang-tidy-run-cc.sh @@ -0,0 +1,37 @@ +#!/bin/bash -e + +# Copyright (c) 2014-2021, 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. + +# clang-tidy runs lint checks on C & C++ sources and headers. +# Run this script from the source directory. + +DIR_THIS="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source "$DIR_THIS/clang-tidy-run-common.sh" + +tidy_for_language "C" diff --git a/utils/health/clang-tidy-run.sh b/utils/health/clang-tidy-run-common.sh index 6b34f6a3b..a086821a6 100755 --- a/utils/health/clang-tidy-run.sh +++ b/utils/health/clang-tidy-run-common.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -# Copyright (c) 2014-2020, The Monero Project +# Copyright (c) 2014-2021, The Monero Project # # All rights reserved. # @@ -29,7 +29,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # clang-tidy runs lint checks on C & C++ sources and headers. -# Run this script from the source directory. +# Don't use this script directly but call clang-tidy-run-cc.sh or clang-tidy-run-cpp.sh instead DIR_BUILD_BASE="build/clang-tidy" RESULT_BASE="clang-tidy-result" @@ -40,6 +40,7 @@ function tidy_for_language() { RESULT="${RESULT_BASE}-${LANG}.txt" mkdir -p "$DIR_BUILD" && pushd "$DIR_BUILD" + rm `find . -name "CMakeCache.txt"` || true cmake ../.. \ -DCMAKE_C_COMPILER=clang \ @@ -52,14 +53,13 @@ function tidy_for_language() { make clean # Clean up, so that the result can be regenerated from scratch time make -k 2>&1 | tee "$RESULT" # Build and store the result. -k means: ignore errors #time make -k easylogging 2>&1 | tee "$RESULT" # Quick testing: build a single target - gzip -f "$RESULT" # Zip the result, because it's huge. -f overwrites the previously generated result - + KPI=$(cat "$RESULT" | wc -l) + tar -cJvf "$RESULT.txz" "$RESULT" # Zip the result, because it's huge. + rm -v "$RESULT" echo "" - echo "Readable result stored in: $DIR_BUILD/$RESULT.gz" + echo "Readable result stored in: $DIR_BUILD/$RESULT.txz" + + echo "$KPI" > "kpis.txt" popd } - -tidy_for_language "C" -tidy_for_language "CXX" - diff --git a/utils/health/clang-tidy-run-cpp.sh b/utils/health/clang-tidy-run-cpp.sh new file mode 100755 index 000000000..7cf08fe78 --- /dev/null +++ b/utils/health/clang-tidy-run-cpp.sh @@ -0,0 +1,37 @@ +#!/bin/bash -e + +# Copyright (c) 2014-2021, 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. + +# clang-tidy runs lint checks on C & C++ sources and headers. +# Run this script from the source directory. + +DIR_THIS="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source "$DIR_THIS/clang-tidy-run-common.sh" + +tidy_for_language "CXX" diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 207565e6f..397000b68 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -53,6 +53,28 @@ class Daemon(object): return self.rpc.send_json_rpc_request(getblocktemplate) get_block_template = getblocktemplate + def get_miner_data(self): + get_miner_data = { + 'method': 'get_miner_data', + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_miner_data) + + def calc_pow(self, major_version, height, block_blob, seed_hash = ''): + calc_pow = { + 'method': 'calc_pow', + 'params': { + 'major_version': major_version, + 'height': height, + 'block_blob' : block_blob, + 'seed_hash' : seed_hash, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(calc_pow) + def add_aux_pow(self, blocktemplate_blob, aux_pow, client = ""): add_aux_pow = { 'method': 'add_aux_pow', diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 02084620c..e531bf13d 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -512,14 +512,12 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(make_multisig) - def finalize_multisig(self, multisig_info, password = ''): + def finalize_multisig(self): finalize_multisig = { 'method': 'finalize_multisig', 'params' : { - 'multisig_info': multisig_info, - 'password': password, }, - 'jsonrpc': '2.0', + 'jsonrpc': '2.0', 'id': '0' } return self.rpc.send_json_rpc_request(finalize_multisig) diff --git a/utils/systemd/monerod.service b/utils/systemd/monerod.service index c89e31f8c..63daefa82 100644 --- a/utils/systemd/monerod.service +++ b/utils/systemd/monerod.service @@ -9,28 +9,11 @@ WorkingDirectory=~ StateDirectory=monero LogsDirectory=monero -# Clearnet config -# Type=simple ExecStart=/usr/bin/monerod --config-file /etc/monerod.conf --non-interactive StandardOutput=null StandardError=null -# Tor config -# -## We have to use simple, not forking, because we cannot pass --detach -## because stderr/stdout is not available when detached, but torsocks -## attempts to write to it, and fails with 'invalid argument', causing -## monerod to fail. -#Type=simple -#Environment=DNS_PUBLIC=tcp -## The following is needed only when accessing wallet from a different -## host in the LAN, VPN, etc, the RPC must bind to 0.0.0.0, but -## by default torsocks only allows binding to localhost. -#Environment=TORSOCKS_ALLOW_INBOUND=1 -#ExecStart=/usr/bin/torsocks /usr/bin/monerod --config-file /etc/monerod.conf \ -# --non-interactive - Restart=always [Install] |