diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000000..a22f7cf0ae --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,386 @@ +#!/bin/bash +set -euo pipefail + +# Only run in remote (Cloud) environments +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/user/firo}" +BUILD_DIR="${CLAUDE_BUILD_DIR:-${PROJECT_DIR}/build}" +BLS_PREFIX="${CLAUDE_PREFIX_DIR:-/usr/local}" +HOOK_STATE_DIR="${CLAUDE_HOOK_STATE_DIR:-${BLS_PREFIX}/share/firo-cloud-session-start}" +BUILD_STAMP_FILE="${BUILD_DIR}/.build_stamp" +BLS_STAMP_FILE="${HOOK_STATE_DIR}/bls-dash.stamp" +LIBBACKTRACE_STAMP_FILE="${HOOK_STATE_DIR}/libbacktrace.stamp" +LIBTOR_STAMP_FILE="${HOOK_STATE_DIR}/libtor.stamp" +PATH_EXPORT_LINE="export PATH=\"${BUILD_DIR}/bin:\$PATH\"" + +REQUIRED_BUILD_BINS=( + firod + firo-cli + firo-tx + test_firo +) + +BLS_VERSION="1.1.0" +BLS_SHA256="276c8573104e5f18bb5b9fd3ffd49585dda5ba5f6de2de74759dda8ca5a9deac" +RELIC_COMMIT="3a23142be0a5510a3aa93cd6c76fc59d3fc732a5" +RELIC_SHA256="ddad83b1406985a1e4703bd03bdbab89453aa700c0c99567cf8de51c205e5dde" + +# Pin to the same commit and hash used by depends/packages/backtrace.mk +LIBBACKTRACE_COMMIT="b9e40069c0b47a722286b94eb5231f7f05c08713" +LIBBACKTRACE_SHA256="81b37e762965c676b3316e90564c89f6480606add446651c785862571a1fdbca" +LIBTOR_STUB_VERSION="1" + +ensure_path_export() +{ + if [ -n "${CLAUDE_ENV_FILE:-}" ]; then + if ! grep -qF "${PATH_EXPORT_LINE}" "$CLAUDE_ENV_FILE" 2>/dev/null; then + echo "${PATH_EXPORT_LINE}" >> "$CLAUDE_ENV_FILE" + fi + fi +} + +stamp_matches() +{ + local stamp_path="$1" + local expected="$2" + local current + + [ -f "${stamp_path}" ] || return 1 + current=$(<"${stamp_path}") + [ "${current}" = "${expected}" ] +} + +write_stamp() +{ + local stamp_path="$1" + local content="$2" + local stamp_dir tmp_file + + stamp_dir="$(dirname "${stamp_path}")" + tmp_file="$(mktemp)" + printf '%s\n' "${content}" > "${tmp_file}" + + if mkdir -p "${stamp_dir}" 2>/dev/null && install -m 644 "${tmp_file}" "${stamp_path}" 2>/dev/null; then + rm -f "${tmp_file}" + return 0 + fi + + sudo install -d "${stamp_dir}" + sudo install -m 644 "${tmp_file}" "${stamp_path}" + rm -f "${tmp_file}" +} + +build_outputs_present() +{ + local bin_name + + for bin_name in "${REQUIRED_BUILD_BINS[@]}"; do + [ -f "${BUILD_DIR}/bin/${bin_name}" ] || return 1 + done +} + +build_stamp_content() +{ + local git_head git_status_hash + + git_head="$(git -C "${PROJECT_DIR}" rev-parse HEAD)" + git_status_hash="$(git -C "${PROJECT_DIR}" status --porcelain --untracked-files=no | sha256sum | cut -d' ' -f1)" + + cat </dev/null + echo "Acquire::https::Proxy \"${HTTP_PROXY}\";" | sudo tee -a /etc/apt/apt.conf.d/99proxy >/dev/null +fi + +######################################## +# 2. Install system packages +######################################## +PACKAGES=( + # Build tools + ccache + ninja-build + pkg-config + autoconf + automake + libtool + bsdmainutils + + # Core libraries + libboost-system-dev + libboost-filesystem-dev + libboost-program-options-dev + libboost-thread-dev + libboost-chrono-dev + libboost-atomic-dev + libboost-test-dev + libssl-dev + libevent-dev + libgmp-dev + libzmq3-dev + libsqlite3-dev + libdb5.3++-dev + libminiupnpc-dev + zlib1g-dev + + # Linting + clang-format + + # Python test dependencies + python3-zmq +) + +MISSING=() +for pkg in "${PACKAGES[@]}"; do + if ! dpkg -s "$pkg" &>/dev/null; then + MISSING+=("$pkg") + fi +done + +if [ ${#MISSING[@]} -gt 0 ]; then + sudo apt-get update -qq + sudo apt-get install -y --no-install-recommends "${MISSING[@]}" +fi + +######################################## +# 3. Build bls-dash from source +# (github.com is accessible via proxy) +######################################## +if [ ! -f "${BLS_PREFIX}/lib/libbls-dash.a" ] \ + || ! stamp_matches "${BLS_STAMP_FILE}" "${BLS_STAMP_CONTENT}"; then + WORK_DIR=$(mktemp -d) + + curl -sL "https://github.com/dashpay/bls-signatures/archive/${BLS_VERSION}.tar.gz" \ + -o "${WORK_DIR}/bls.tar.gz" + curl -sL "https://github.com/relic-toolkit/relic/archive/${RELIC_COMMIT}.tar.gz" \ + -o "${WORK_DIR}/relic.tar.gz" + + # Verify downloaded tarballs against known hashes + echo "${BLS_SHA256} ${WORK_DIR}/bls.tar.gz" | sha256sum -c - || \ + { echo "ERROR: bls-signatures tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; } + echo "${RELIC_SHA256} ${WORK_DIR}/relic.tar.gz" | sha256sum -c - || \ + { echo "ERROR: relic tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; } + + mkdir -p "${WORK_DIR}/bls-signatures" + tar -xzf "${WORK_DIR}/bls.tar.gz" -C "${WORK_DIR}/bls-signatures" --strip-components=1 + + # Apply Firo's patches first (before URL substitution) + cd "${WORK_DIR}/bls-signatures" + if [ -f "${PROJECT_DIR}/depends/patches/bls-dash/bls-signatures.patch" ]; then + patch -p1 < "${PROJECT_DIR}/depends/patches/bls-dash/bls-signatures.patch" + fi + + # Point CMake at local relic tarball instead of git + sed -i "s|GIT_REPOSITORY https://github.com/relic-toolkit/relic.git|URL \"${WORK_DIR}/relic.tar.gz\"|" \ + src/CMakeLists.txt + sed -i "s|GIT_TAG.*RELIC_GIT_TAG.*|URL_HASH SHA256=${RELIC_SHA256}|" \ + src/CMakeLists.txt + + # Build with Unix Makefiles (Ninja has globbing issues with the combined archive step) + cmake -G "Unix Makefiles" \ + -DCMAKE_INSTALL_PREFIX="${BLS_PREFIX}" \ + -DCMAKE_PREFIX_PATH="${BLS_PREFIX}" \ + -DSTLIB=ON -DSHLIB=OFF -DSTBIN=ON \ + -DBUILD_BLS_PYTHON_BINDINGS=0 \ + -DBUILD_BLS_TESTS=0 \ + -DBUILD_BLS_BENCHMARKS=0 \ + -DOPSYS=LINUX -DCMAKE_SYSTEM_NAME=Linux \ + -DWSIZE=64 \ + "-DCMAKE_C_FLAGS=-UBLSALLOC_SODIUM" \ + "-DCMAKE_CXX_FLAGS=-UBLSALLOC_SODIUM" \ + -S"${WORK_DIR}/bls-signatures" -B"${WORK_DIR}/bls-build" + + make -C "${WORK_DIR}/bls-build" -j"$(nproc)" + sudo cmake --install "${WORK_DIR}/bls-build" + sudo ldconfig + write_stamp "${BLS_STAMP_FILE}" "${BLS_STAMP_CONTENT}" + + rm -rf "${WORK_DIR}" + cd "${PROJECT_DIR}" +fi + +######################################## +# 4. Build libbacktrace from source +# (needed for ENABLE_CRASH_HOOKS) +######################################## +if [ ! -f "${BLS_PREFIX}/lib/libbacktrace.a" ] \ + || ! stamp_matches "${LIBBACKTRACE_STAMP_FILE}" "${LIBBACKTRACE_STAMP_CONTENT}"; then + WORK_DIR=$(mktemp -d) + curl -sL "https://github.com/ianlancetaylor/libbacktrace/archive/${LIBBACKTRACE_COMMIT}.tar.gz" \ + -o "${WORK_DIR}/libbacktrace.tar.gz" + echo "${LIBBACKTRACE_SHA256} ${WORK_DIR}/libbacktrace.tar.gz" | sha256sum -c - || \ + { echo "ERROR: libbacktrace tarball SHA256 mismatch"; rm -rf "${WORK_DIR}"; exit 1; } + mkdir -p "${WORK_DIR}/libbacktrace" + tar -xzf "${WORK_DIR}/libbacktrace.tar.gz" -C "${WORK_DIR}/libbacktrace" --strip-components=1 + + cd "${WORK_DIR}/libbacktrace" + ./configure --prefix="${BLS_PREFIX}" --enable-static --disable-shared CFLAGS="-fPIC" + make -j"$(nproc)" + sudo make install + write_stamp "${LIBBACKTRACE_STAMP_FILE}" "${LIBBACKTRACE_STAMP_CONTENT}" + + rm -rf "${WORK_DIR}" + cd "${PROJECT_DIR}" +fi + +######################################## +# 5. Create stub libtor.a +# archive.torproject.org is blocked +# by the cloud egress proxy, so we +# create a stub with the symbols +# referenced by src/init.cpp. +# The embedded Tor is not needed for +# development/testing workflows. +######################################## +if [ ! -f "${BLS_PREFIX}/lib/libtor.a" ] \ + || ! stamp_matches "${LIBTOR_STAMP_FILE}" "${LIBTOR_STAMP_CONTENT}"; then + WORK_DIR=$(mktemp -d) + + cat > "${WORK_DIR}/tor_stub.c" << 'STUBEOF' +#include +int tor_main(int argc, char *argv[]) { + (void)argc; (void)argv; + fprintf(stderr, "tor: stub library - embedded Tor not available in this build\n"); + return 1; +} +void tor_cleanup(void) {} +STUBEOF + + gcc -c -fPIC "${WORK_DIR}/tor_stub.c" -o "${WORK_DIR}/tor_stub.o" + ar rcs "${WORK_DIR}/libtor.a" "${WORK_DIR}/tor_stub.o" + sudo install -m 644 "${WORK_DIR}/libtor.a" "${BLS_PREFIX}/lib/libtor.a" + write_stamp "${LIBTOR_STAMP_FILE}" "${LIBTOR_STAMP_CONTENT}" + + rm -rf "${WORK_DIR}" +fi + +######################################## +# 6. Configure CMake +# The upstream AddBoostIfNeeded.cmake +# hardcodes Boost_USE_STATIC_RUNTIME=ON. +# Cloud uses system shared Boost, so we +# patch the local copy and hide the +# change from git with assume-unchanged. +######################################## +if [ ! -f "${BUILD_DIR}/build.ninja" ] \ + || ! stamp_matches "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}"; then + ensure_boost_runtime_override + cmake -G Ninja \ + -DBUILD_DAEMON=ON \ + -DBUILD_CLI=ON \ + -DBUILD_TX=ON \ + -DBUILD_GUI=OFF \ + -DBUILD_TESTS=ON \ + -DENABLE_WALLET=ON \ + -DWITH_BDB=ON \ + -DWITH_ZMQ=ON \ + -DENABLE_CRASH_HOOKS=ON \ + -DWARN_INCOMPATIBLE_BDB=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -S"${PROJECT_DIR}" -B"${BUILD_DIR}" +fi + +######################################## +# 7. Build +######################################## +if ! build_outputs_present \ + || ! stamp_matches "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}"; then + cmake --build "${BUILD_DIR}" -j"$(nproc)" +fi + +if build_outputs_present; then + write_stamp "${BUILD_STAMP_FILE}" "${BUILD_STAMP_CONTENT}" +fi + +######################################## +# 8. Set environment variables +######################################## +ensure_path_export diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..3d10a7757b --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh", + "timeout": 1800 + } + ] + } + ] + } +} diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 832a46236d..bed3ce726e 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -19,7 +19,7 @@ env: jobs: get-commit-hash: name: Get Commit Hash String - runs-on: ubuntu-latest + runs-on: ubuntu-slim outputs: short_sha: ${{ steps.short-sha.outputs.sha }} steps: @@ -78,7 +78,7 @@ jobs: steps: - name: Set up Python 3.8 if: matrix.is_linux - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: '3.8' - name: Checkout @@ -101,19 +101,19 @@ jobs: run: | set -exo pipefail sudo apt-get update - sudo apt-get install -y python3-zmq cmake build-essential ninja-build ccache + sudo apt-get install -y python3-zmq cmake build-essential ninja-build meson ccache - name: Install Required Packages (Windows) if: matrix.is_windows run: | set -exo pipefail sudo apt-get update - sudo apt-get install -y g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 cmake mingw-w64-common mingw-w64-i686-dev mingw-w64-x86-64-dev wget unzip ninja-build ccache + sudo apt-get install -y g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 cmake mingw-w64-common mingw-w64-i686-dev mingw-w64-x86-64-dev wget unzip ninja-build meson ccache - name: Install Required Packages (Mac) if: matrix.is_mac run: | set -exo pipefail brew uninstall --force cmake - brew install --force automake coreutils python-setuptools cmake make pkg-config ninja ccache + brew install --force automake coreutils python-setuptools cmake make pkg-config ninja meson ccache - name: Install setuptools if: matrix.is_mac run: | @@ -175,9 +175,11 @@ jobs: depends-${{ matrix.platform }}-${{ steps.depends-host-triplet.outputs.value }}- depends-${{ matrix.platform }}- - name: Download Dependencies - if: steps.cache-depends.outputs.cache-hit != 'true' run: | set -exo pipefail + # Clear download stamps to ensure new/changed extra sources are fetched + # even when restoring from a stale cache (partial restore-key match). + rm -rf depends/sources/download-stamps attempt=1 max=5 until make -C depends download; do @@ -301,7 +303,7 @@ jobs: mkdir -p $ARTIFACT_DIR mv build/bin/firo-cli${{ matrix.bin_ext }} build/bin/firo-tx${{ matrix.bin_ext }} build/bin/firod${{ matrix.bin_ext }} build/bin/firo-qt${{ matrix.bin_ext }} $ARTIFACT_DIR - name: Upload Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.platform }}-cmake-${{ matrix.build_type }}-binaries-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }} @@ -323,7 +325,7 @@ jobs: fi - name: Upload Test Logs Artifact if: failure() && matrix.is_linux - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: test-${{ matrix.platform }}-cmake-${{ matrix.build_type }}-logs-${{ env.COMMIT_HASH }} path: ${{ env.TEST_LOG_ARTIFACT_DIR }} @@ -367,10 +369,6 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 # Optional: Ensures git describe finds tags correctly - - name: Set up Python 3.8 - uses: actions/setup-python@v4 - with: - python-version: '3.8' - name: Free Disk Space run: | set -exo pipefail @@ -430,26 +428,90 @@ jobs: sudo aa-complain unprivileged_userns || true # Remove apparmor_parser so Guix installer skips AppArmor setup sudo apt-get remove -y apparmor || true - # Pre-import Guix signing keys into root's keyring (installer runs as sudo), sometimes guix script fail to fetch keys from ftpmirror.gnu.org + export VER=1.5.0 export ARCH=x86_64-linux export BASE=https://ftp.gnu.org/gnu/guix export TAR="guix-binary-${VER}.${ARCH}.tar.xz" - # --- verify the signature, but never abort the job --- - if gpg --verify "${TAR}.sig" "${TAR}" &> /dev/null - then - echo "GPG verification succeeded." - else - echo "GPG verification failed – continuing anyway." - fi + export GUIX_INSTALLER_SHA256=7f544e48c9082baf489207ac506acd78bf251355bbafffd744498e658880879d + export GUIX_TAR_SHA256=aa41025489c5061543e9c48873eaa829b900b2da75d40f9648913622f5f47817 + + # Keep these trust anchors explicit and reviewed. This set matches + # the release-signing keys trusted by the Guix 1.5.0 installer, so + # the release can be signed by a different Guix maintainer without + # letting a downloaded script decide our trust policy. + GUIX_KEY_FPRS=( + 3CE464558A84FDC69DB40CFB090B11993D9AEBB5 + 27D586A4F8900854329FF09F1260E46482E63562 + A28BF40C3E551372662D14F741AAE7DCCA3D8351 + ) + GUIX_FPR_REGEX="$(IFS='|'; echo "${GUIX_KEY_FPRS[*]}")" + cd /tmp - wget -q "${BASE}/${TAR}" -O "${TAR}" - wget -q "${BASE}/${TAR}.sig" -O "${TAR}.sig" - wget -q https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh -O guix-install.sh + + # 1. Download and pin-check all unsigned inputs before use. + echo "::group::Download Guix installer and binary tarball" + wget --tries=5 --timeout=30 --waitretry=10 \ + "https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh?h=v${VER}" \ + -O guix-install.sh + echo "${GUIX_INSTALLER_SHA256} guix-install.sh" | sha256sum -c - chmod +x guix-install.sh + + wget --tries=5 --timeout=30 --waitretry=10 "${BASE}/${TAR}" -O "${TAR}" + echo "${GUIX_TAR_SHA256} ${TAR}" | sha256sum -c - + wget --tries=5 --timeout=30 --waitretry=10 "${BASE}/${TAR}.sig" -O "${TAR}.sig" + echo "::endgroup::" + + # 2. Best-effort import each trusted key into an isolated keyring. + # Some maintainers may not have published their key to every + # keyserver (or to any keyserver at all), so we don't fail on + # individual misses. We only fail later if NONE of the imported + # keys can verify the actual tarball signature. The trust set + # is still bounded by the pinned list: VALIDSIG must + # name a fingerprint from GUIX_FPR_REGEX, regardless of which + # keys were imported. + echo "::group::Import trusted Guix maintainer keys" + export GNUPGHOME=$(mktemp -d) + chmod 700 "${GNUPGHOME}" + imported_count=0 + for fpr in "${GUIX_KEY_FPRS[@]}"; do + for ks in hkps://keys.openpgp.org hkps://keyserver.ubuntu.com hkps://pgp.mit.edu; do + if gpg --batch --keyserver "${ks}" --recv-keys "${fpr}"; then + # Confirm the imported key actually has the expected fingerprint. + if gpg --list-keys --with-colons "${fpr}" \ + | awk -F: '$1=="fpr"{print $10}' | grep -qx "${fpr}"; then + imported_count=$((imported_count + 1)) + break + fi + fi + echo "Keyserver ${ks} failed for ${fpr}, trying next..." + done + done + if [ "${imported_count}" -eq 0 ]; then + echo "Could not import any trusted Guix signing key from any keyserver" + exit 1 + fi + echo "Imported ${imported_count}/${#GUIX_KEY_FPRS[@]} trusted keys" + echo "::endgroup::" + + # 3. Verify the tarball signature against the trusted keys (blocking). + # The keyring contains ONLY the pinned trust anchors, so a valid + # signature must come from one of them; the explicit VALIDSIG + # fingerprint match also documents which key signed this release. + echo "::group::Verify tarball signature" + gpg --status-fd 1 --verify "${TAR}.sig" "${TAR}" > gpg-status.txt + cat gpg-status.txt + awk -v allowed="${GUIX_FPR_REGEX}" ' + $1 == "[GNUPG:]" && $2 == "VALIDSIG" && + ($3 ~ "^(" allowed ")$" || $NF ~ "^(" allowed ")$") { found=1 } + END { exit found ? 0 : 1 } + ' gpg-status.txt + echo "::endgroup::" + + # 4. Run the pinned installer with the cryptographically verified tarball. # Disable pipefail strictly for this command so 'yes' crashing doesn't fail the build set +o pipefail - yes '' 2>/dev/null | sudo env GUIX_BINARY_FILE_NAME="/tmp/${TAR}" ./guix-install.sh # Tell installer to use your already-verified tarball + yes '' 2>/dev/null | sudo env GUIX_BINARY_FILE_NAME="/tmp/${TAR}" ./guix-install.sh set -o pipefail echo "Guix install script completed" source /etc/profile.d/zzz-guix.sh @@ -543,14 +605,14 @@ jobs: mv share/man . || true - name: Upload Release Binaries (Linux) if: ${{ !matrix.is_darwin && !matrix.is_windows }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-release-binaries-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-release-binaries.tar.gz compression-level: 0 - name: Upload Debug Binaries (Linux) if: ${{ !matrix.is_darwin && !matrix.is_windows }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-debug-binaries-${{ env.COMMIT_HASH }} path: | @@ -562,56 +624,56 @@ jobs: compression-level: 6 - name: Upload Debug Symbols (Linux) if: ${{ !matrix.is_darwin && !matrix.is_windows }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-debug-symbols-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-debug-symbols.tar.gz compression-level: 0 - name: Upload Check Sums (Linux) if: ${{ !matrix.is_darwin && !matrix.is_windows }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-checksums-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-checksums-sha256 compression-level: 0 - name: Upload Windows Release Binaries if: matrix.is_windows - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: x86_64-win64-guix-release-binaries-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-release-binaries.zip compression-level: 0 - name: Upload Windows Debug Symbols if: matrix.is_windows - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: x86_64-win64-guix-debug-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-debug-symbols.zip compression-level: 0 - name: Upload Windows Installer (Unsigned) if: matrix.is_windows - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: x86_64-win64-guix-installer-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-installer-unsigned.exe compression-level: 0 - name: Upload Windows Setup Sign Bundle if: matrix.is_windows - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: x86_64-win64-guix-setup-sign-bundle-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-setup-sign-bundle.tar.gz compression-level: 0 - name: Upload Windows Checksums if: matrix.is_windows - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: x86_64-win64-guix-checksums-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/x86_64-win64-guix-checksums-sha256 compression-level: 0 - name: Upload MacOS Release Binaries if: matrix.is_darwin - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-release-binaries-${{ env.COMMIT_HASH }} path: | @@ -624,22 +686,22 @@ jobs: compression-level: 6 - name: Upload MacOS Sign Bundle if: matrix.is_darwin - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-sign-bundle-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-sign-bundle.tar.gz compression-level: 0 - name: Upload MacOS App Image if: matrix.is_darwin - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-app-image-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-app-image.tar.gz compression-level: 0 - name: Upload Check Sums (Darwin) if: matrix.is_darwin - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ${{ matrix.artifact_prefix }}-checksums-${{ env.COMMIT_HASH }} path: ${{ env.ARTIFACT_DIR }}/${{ matrix.artifact_prefix }}-checksums-sha256 - compression-level: 0 \ No newline at end of file + compression-level: 0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..3e35a0bcdf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,297 @@ +# AGENTS.md - Firo repository guide + +This file is a concise guide for coding agents working in the Firo repository. It is intentionally practical: use it to orient yourself quickly, choose the right build and test commands, and avoid risky changes in consensus- or wallet-sensitive code. + +## Project snapshot + +- Privacy-focused cryptocurrency forked from Bitcoin Core +- Current version in `CMakeLists.txt`: `0.14.15.3` +- Languages: C++20 and C11 +- Build system: CMake 3.22+ with out-of-tree builds only +- Main executables: + - `firod` + - `firo-cli` + - `firo-tx` + - `firo-qt` + +Core features include Spark and Lelantus privacy protocols, deterministic masternodes, LLMQ-based ChainLocks and InstantSend, BIP47 payment codes, and FiroPOW mining. + +## Repository map + +Top-level directories you will touch most often: + +- `src/` - production C++ code +- `src/test/` - Boost unit tests +- `src/wallet/test/`, `src/libspark/test/`, `src/liblelantus/test/`, `src/hdmint/test/` - domain-specific unit tests +- `qa/rpc-tests/` - Python functional tests +- `qa/pull-tester/` - functional test runner and framework +- `depends/` - deterministic dependency build system and cross-compilation support +- `cmake/` - CMake modules and helper logic +- `doc/` - developer notes and build documentation +- `.github/workflows/ci-master.yml` - main CI pipeline + +Important source areas: + +- `src/validation.cpp` - block and transaction validation, consensus-critical +- `src/net_processing.cpp` / `src/net.cpp` - peer-to-peer networking +- `src/init.cpp` - startup and shutdown wiring +- `src/miner.cpp` / `src/pow.cpp` - mining and proof-of-work logic +- `src/chainparams.cpp` - network configuration +- `src/txmempool.cpp` - mempool behavior +- `src/wallet/` - wallet storage, RPC, coin selection, privacy spends +- `src/libspark/`, `src/spark/` - current privacy protocol +- `src/liblelantus/` - legacy privacy protocol +- `src/evo/`, `src/llmq/` - masternodes, quorums, ChainLocks, InstantSend + +## Build quickstart + +Firo uses a two-stage build: + +1. Build dependencies with `depends/` +2. Configure and build with CMake + +Preferred Linux build matching CI: + +```bash +make -C depends -j$(nproc) + +export HOST_TRIPLET=$(depends/config.guess) +env PKG_CONFIG_PATH="$(realpath depends/$HOST_TRIPLET/lib/pkgconfig):$PKG_CONFIG_PATH" \ +cmake -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$(realpath depends/$HOST_TRIPLET/toolchain.cmake)" \ + -DBUILD_CLI=ON \ + -DBUILD_DAEMON=ON \ + -DBUILD_GUI=ON \ + -DBUILD_TESTS=ON \ + -DENABLE_CRASH_HOOKS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -S "$(pwd)" \ + -B "$(pwd)/build" + +cmake --build build +``` + +Headless build: + +```bash +cmake -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE="$(realpath depends/$HOST_TRIPLET/toolchain.cmake)" \ + -DBUILD_GUI=OFF \ + -DBUILD_CLI=ON \ + -DBUILD_DAEMON=ON \ + -DBUILD_TESTS=ON \ + -S "$(pwd)" \ + -B "$(pwd)/build" +cmake --build build +``` + +Useful CMake options: + +| Option | Default | Notes | +|--------|---------|-------| +| `BUILD_DAEMON` | `ON` | Builds `firod` | +| `BUILD_GUI` | `ON` | Builds `firo-qt`; requires Qt `6.7.3` | +| `BUILD_CLI` | `ON` | Builds `firo-cli` | +| `BUILD_TX` | `${BUILD_CLI}` | Builds `firo-tx` | +| `BUILD_TESTS` | `OFF` | Builds `test_firo` and test targets | +| `BUILD_BENCH` | `OFF` | Builds `bench_firo` | +| `ENABLE_WALLET` | `ON` | Wallet support | +| `WITH_BDB` | `OFF` | Legacy Berkeley DB wallet support | +| `WITH_ZMQ` | `ON` | ZMQ notification support | +| `ENABLE_CRASH_HOOKS` | config-dependent | Enabled by default for release-like single-config builds | +| `CLIENT_VERSION_IS_RELEASE` | `false` | Set in CI per build type | + +Build outputs are placed under `build/bin/` and libraries under `build/lib/`. + +### Cross-compilation + +`depends/` supports multiple host triplets. Typical examples: + +- Native Linux: `$(depends/config.guess)` +- Windows: `x86_64-w64-mingw32` +- Linux ARM64: `aarch64-linux-gnu` +- macOS cross-builds require an SDK in `depends/SDKs/` + +Example Windows dependency build: + +```bash +make -C depends HOST=x86_64-w64-mingw32 -j$(nproc) +``` + +## Testing + +Always run the most targeted tests that cover your change. For high-risk areas such as consensus, wallet accounting, Spark/Lelantus logic, or networking, broaden coverage before finishing. + +### Unit tests + +Build with `-DBUILD_TESTS=ON`, then run: + +```bash +cd build +ctest --output-on-failure +``` + +Run a specific Boost test suite: + +```bash +./bin/test_firo --run_test= --catch_system_error=no --log_level=test_suite -- DEBUG_LOG_OUT +``` + +Examples: + +- `./bin/test_firo --run_test=lelantus_tests` +- `./bin/test_firo --run_test=spark_wallet_tests` + +Test registration is driven from `src/test/CMakeLists.txt`. + +### Functional / RPC tests + +Functional tests live under `qa/rpc-tests/` and launch real `firod` nodes in regtest mode. + +Before running the harness, copy binaries where the test framework expects them: + +```bash +cp -rf build/bin/* build/src/ +qa/pull-tester/rpc-tests.py -extended +``` + +Run a single script directly: + +```bash +qa/rpc-tests/.py +``` + +Useful environment variables: + +- `FIROD` - defaults to `build/src/firod` +- `FIROCLI` - defaults to `build/src/firo-cli` + +### Benchmarks and fuzzing + +Benchmarks: + +```bash +cmake -B build -DBUILD_BENCH=ON ... +cmake --build build +./build/bin/bench_firo +``` + +Fuzz targets live in `src/fuzz/`. See `doc/fuzzing.md` for setup details. + +## CI reference + +The main workflow is `.github/workflows/ci-master.yml`. + +Current CI behavior: + +- Runs on pushes to all branches and PRs targeting `master` +- Ignores `doc/**` and `**/README.md` +- Linux matrix: Release and Debug, with unit tests and RPC tests +- Windows matrix: Release and Debug cross-builds on Ubuntu +- macOS matrix: Release and Debug builds +- Uses `ccache` +- Caches `depends/` artifacts + +If you want to reproduce CI most closely, use Ninja, the `depends/` toolchain, and the same build flags shown above. + +## Coding conventions + +Source of truth: + +- `src/.clang-format` +- `doc/developer-notes.md` + +Important rules: + +- 4-space indentation, no tabs +- Linux brace style: new line for namespaces, classes, and function definitions +- No column limit in formatting rules +- Prefer `++i` over `i++` +- Avoid `using namespace` +- Use RAII and smart pointers instead of manual ownership +- Use `std::string` instead of C string APIs where practical +- Use `.find()` on maps for reads; avoid `operator[]` when it would insert +- Use `ParseInt32`, `ParseInt64`, `ParseUInt32`, `ParseUInt64`, and `ParseDouble` from `utilstrencodings.h` +- Avoid variable shadowing; `-Wshadow` is enabled +- Use `uint8_t` or `int8_t` instead of plain `char` when signedness matters +- Assertions must not have side effects + +Threading and locking: + +- Use `LOCK` / `TRY_LOCK` macros +- Scope lock regions carefully with braces +- Debug builds can help surface lock-order issues + +Documentation style: + +- Prefer Doxygen-compatible comments for non-obvious public interfaces +- Use succinct comments for tricky logic, not commentary on obvious assignments + +## Risk areas and change guidance + +Be especially careful in these parts of the tree: + +- Consensus and validation: `src/validation.cpp`, `src/consensus/`, `src/pow.cpp` +- Chain parameters and network selection: `src/chainparams.cpp` +- Mempool and relay behavior: `src/txmempool.cpp`, `src/net_processing.cpp` +- Wallet correctness and privacy spends: `src/wallet/`, `src/spark/`, `src/liblelantus/`, `src/libspark/` +- LLMQ / masternodes: `src/llmq/`, `src/evo/` + +Guidelines for agents: + +- Keep changes minimal and localized +- Do not mix pure formatting changes with behavior changes +- Add or update tests when behavior changes +- Treat privacy, consensus, wallet balance accounting, serialization, and networking as high-regression areas +- Prefer exposing new behavior through RPC before adding GUI surfaces +- When touching a subtree directory such as `src/secp256k1`, `src/leveldb`, `src/univalue`, or `src/crypto/ctaes`, verify whether the change belongs upstream + +## Network ports + +| Network | P2P | RPC | +|---------|-----|-----| +| Mainnet | 8168 | 8888 | +| Testnet | 18168 | 18888 | +| Devnet | 38168 | 38888 | +| Regtest | 18444 | 28888 | + +Use `-regtest` for local multi-node functional testing and `-testnet` for public test network behavior. + +## PR and commit conventions + +Preferred PR title prefixes: + +- `Consensus:` +- `Net:` or `P2P:` +- `RPC/REST/ZMQ:` +- `Wallet:` +- `Qt:` +- `Mining:` +- `Tests:` +- `Docs:` +- `Utils and libraries:` +- `Scripts and tools:` +- `Trivial:` + +PR template: `.github/pull_request_template.md` + +It expects: + +1. `## PR intention` +2. `## Code changes brief` + +Commit guidance: + +- Keep commits atomic +- Do not mix formatting-only changes into logic commits +- Use a short subject line, ideally 50 characters or less +- Add a blank line before the body when a body is needed +- Reference issues with `refs #1234`, `fixes #1234`, or similar when applicable + +## Related docs worth reading + +- `CLAUDE.md` +- `doc/developer-notes.md` +- `.github/workflows/ci-master.yml` +- `src/.clang-format` +- `doc/fuzzing.md` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..0a4d851b76 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,239 @@ +# CLAUDE.md - Firo Development Guide + +## Project Overview + +Firo is a privacy-focused cryptocurrency forked from Bitcoin Core, featuring zero-knowledge proof protocols (Spark, Lelantus), masternode infrastructure (LLMQ), and FiroPOW mining. Current version: **0.14.15.3**. Licensed under MIT. + +## Build System + +**CMake 3.22+** with **C++20** standard. No autotools. + +### Quick Build (Linux) + +```bash +# 1. Build dependencies +make -C depends -j$(nproc) + +# 2. Configure and build +export HOST_TRIPLET=$(depends/config.guess) +cmake -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=$(pwd)/depends/$HOST_TRIPLET/toolchain.cmake \ + -DBUILD_TESTS=ON -DBUILD_GUI=ON -DENABLE_CRASH_HOOKS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -S. -Bbuild +cd build && ninja +``` + +### Key CMake Options + +| Option | Default | Description | +|--------|---------|-------------| +| `BUILD_DAEMON` | ON | Build `firod` | +| `BUILD_GUI` | ON | Build `firo-qt` (requires Qt 6.7.3+) | +| `BUILD_CLI` | ON | Build `firo-cli` | +| `BUILD_TX` | `${BUILD_CLI}` | Build `firo-tx` (defaults to same as `BUILD_CLI`) | +| `BUILD_TESTS` | OFF | Build unit test suite | +| `ENABLE_WALLET` | ON | Wallet functionality | +| `WITH_ZMQ` | ON | ZeroMQ notifications | +| `ENABLE_CRASH_HOOKS` | OFF | Stack trace generation (auto-enabled for Release/RelWithDebInfo/MinSizeRel) | +| `CLIENT_VERSION_IS_RELEASE` | false | Release build flag | + +### Build Outputs + +Binaries go to `build/bin/`: `firod`, `firo-cli`, `firo-qt`, `firo-tx` + +### Cross-Compilation + +Use the `depends/` system with host triplets: +- Linux: `x86_64-pc-linux-gnu` (default), `aarch64-linux-gnu` +- Windows: `make -C depends HOST=x86_64-w64-mingw32` +- macOS: requires SDK in `depends/SDKs/` + +### Docker Build + +```bash +docker build . -t firo-local +docker run -d --name firod -v "${HOME}/.firo:/home/firod/.firo" firo-local +``` + +## Testing + +### Unit Tests (Boost.Test) + +```bash +cmake -Bbuild -DBUILD_TESTS=ON ... +cd build && ctest --output-on-failure +``` + +Test source: `src/test/` (~80 test files). Framework setup in `src/test/test_bitcoin.h`. + +### Integration Tests (Python) + +```bash +# Copy binaries where the test harness expects them +cp -rf build/bin/* build/src/ +qa/pull-tester/rpc-tests.py -extended +``` + +Test scripts: `qa/rpc-tests/` (100+ Python scripts covering wallet, privacy protocols, masternodes, consensus). + +### Test Networks + +- `-testnet` for multi-node testing over the network +- `-regtest` for local single-node testing with on-demand block creation + +## Repository Structure + +``` +src/ # Main source code +├── libspark/ # Spark protocol (current privacy protocol) - ZK proofs, crypto primitives +├── spark/ # Spark wallet integration and state management +├── liblelantus/ # Lelantus protocol (legacy privacy protocol) +├── wallet/ # Full wallet implementation +├── rpc/ # JSON-RPC API endpoints +├── qt/ # Qt GUI wallet +├── evo/ # Deterministic masternode lists, special transactions +├── llmq/ # Long Living Masternode Quorums (chainlocks, instant send) +├── bls/ # BLS signatures for quorum signing +├── bip47/ # BIP47 payment codes +├── hdmint/ # Hierarchical deterministic minting +├── crypto/ # Cryptographic functions (SHA, HMAC, ChaCha20, Lyra2Z, ProgPoW) +├── consensus/ # Consensus rules and validation parameters +├── primitives/ # Block and transaction primitives +├── script/ # Bitcoin script interpreter +├── policy/ # Transaction policy (fees) +├── secp256k1/ # ECDSA library (subtree) +├── leveldb/ # Key-value storage (subtree) +├── univalue/ # JSON parsing (subtree) +├── test/ # Unit tests +├── fuzz/ # Fuzz testing +├── bench/ # Benchmarks +├── config/ # Build configuration headers +├── validation.cpp/h # Core block/transaction validation (~6100 lines) +├── net.cpp/h # Network layer +├── net_processing.cpp/h # Peer protocol handling +├── init.cpp/h # Application initialization +├── miner.cpp/h # Block mining (FiroPOW) +├── pow.cpp/h # Proof-of-Work consensus +├── chainparams.cpp/h # Network parameters (mainnet/testnet/regtest) +└── firo_params.h # Protocol parameters +depends/ # Deterministic dependency build system +cmake/ # CMake modules +contrib/ # Auxiliary tools (gitian, guix, packaging, devtools) +qa/ # Integration test suite +doc/ # Documentation (build guides, developer notes, API docs) +share/ # UI resources, scripts +``` + +## Coding Conventions + +### Style Rules (from `src/.clang-format` and `doc/developer-notes.md`) + +- **Indentation**: 4 spaces, no tabs +- **Braces**: Linux style - new line for namespaces, classes, functions; same line for control flow +- **No column limit** (ColumnLimit: 0) +- **Pointer alignment**: Left (`int* p`) +- **Prefer `++i`** over `i++` +- **Single-statement `if`** may omit braces on same line; otherwise braces required + +```cpp +namespace foo +{ +class Class +{ + bool Function(const std::string& s, int n) + { + for (int i = 0; i < n; ++i) { + if (!Something()) return false; + if (SomethingElse()) { + DoMore(); + } else { + DoLess(); + } + } + return true; + } +} +} +``` + +### Naming Conventions + +- **Classes**: `C` prefix (e.g., `CBlock`, `CTransaction`, `CKey`, `CWallet`) +- **Member variables**: `m_` prefix or no prefix with context +- **Boolean flags**: `f` prefix (e.g., `fCompressed`, `fValid`) +- **Constants**: `UPPER_CASE` +- **Functions**: `CamelCase` or `camelCase` +- **Header guards**: `#ifndef BITCOIN___H` (legacy Bitcoin naming preserved) + +### Documentation + +Use Doxygen-compatible comments: +```cpp +/** + * Description of function. + * @param[in] arg1 Description + * @param[in] arg2 Description + * @return Description + * @pre Precondition + */ +bool Function(int arg1, const char* arg2) + +int var; //!< Inline member description +``` + +### C++ Guidelines + +- Use `std::string` over C string functions +- Use `.find()` on maps for reading, never `[]` (it inserts defaults) +- Use RAII (`unique_ptr`) for resource management +- Use `ParseInt32`, `ParseInt64`, `ParseDouble` from `utilstrencodings.h` +- Use explicitly signed/unsigned `char`, prefer `uint8_t`/`int8_t` +- Assertions must not have side effects +- Watch for out-of-bounds vector access; use `.data()` instead of `&v[0]` +- `-Wshadow` is enabled; avoid variable name shadowing +- New features should be exposed via RPC first, then GUI + +### Threading + +The codebase is multi-threaded using `LOCK`/`TRY_LOCK` macros. Compile with `-DDEBUG_LOCKORDER` to detect lock ordering issues. Key threads: `ThreadScriptCheck`, `ThreadImport`, `ThreadSocketHandler`, `ThreadMessageHandler`, `ThreadRPCServer`. + +## CI/CD + +GitHub Actions (`.github/workflows/ci-master.yml`): +- Triggers on pushes to all branches and PRs to master (ignores `doc/**` and `README.md`) +- **Linux** (ubuntu-22.04): Release + Debug builds, unit tests, RPC integration tests +- **Windows** (cross-compile via MinGW): Release + Debug builds +- **macOS** (macos-latest): Release + Debug builds +- **Guix** reproducible builds for all platforms +- Uses ccache for build acceleration +- Dependencies cached via `actions/cache` + +## Network Ports + +| Network | P2P | RPC | +|---------|-----|-----| +| Mainnet | 8168 | 8888 | +| Testnet | 18168 | 18888 | +| Devnet | 38168 | 38888 | +| Regtest | 18444 | 28888 | + +## Key Privacy Protocols + +- **Spark** (`libspark/`, `spark/`): Current privacy protocol using Bulletproofs++, Grootle proofs, Chaum proofs, AEAD encryption, Bech32 addresses +- **Lelantus** (`liblelantus/`): Legacy privacy protocol with joinsplit, range proofs, Schnorr proofs +- **BIP47** (`bip47/`): Reusable payment codes to prevent address reuse +- **Dandelion++**: Transaction propagation privacy (IP obscuring) + +## Consensus + +- **FiroPOW**: ProgPoW variant - GPU-friendly, ASIC-resistant proof of work +- **LLMQ Chainlocks**: Deterministic masternode quorums for chain finality +- **Instant Send**: Quorum-based instant transaction confirmation + +## PR and Commit Conventions + +- PR titles prefixed by area: `Consensus:`, `Net:` or `P2P:`, `Qt:`, `Wallet:`, `RPC/REST/ZMQ:`, `Mining:`, `Scripts and tools:`, `Tests:`, `Trivial:`, `Utils and libraries:`, `Docs:` +- Commits should be atomic; don't mix formatting with logic changes +- Commit messages: short subject (50 chars max), blank line, detailed body +- Reference issues with `refs #1234`, `fixes #4321`, or `closes #1234` diff --git a/CMakeLists.txt b/CMakeLists.txt index e485c9baeb..d5ef11ea1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,8 @@ endif() set(CLIENT_NAME "Firo Core") set(CLIENT_VERSION_MAJOR 0) set(CLIENT_VERSION_MINOR 14) -set(CLIENT_VERSION_REVISION 15) -set(CLIENT_VERSION_BUILD 3) +set(CLIENT_VERSION_REVISION 16) +set(CLIENT_VERSION_BUILD 1) set(CLIENT_VERSION_RC 0) set(CLIENT_VERSION_IS_RELEASE "false" CACHE STRING "Is this a release build?") set(COPYRIGHT_YEAR "2026") @@ -234,9 +234,30 @@ endif() cmake_dependent_option(WITH_DBUS "Enable DBus support." ON "CMAKE_SYSTEM_NAME STREQUAL \"Linux\" AND BUILD_GUI" OFF) +function(clear_stale_depends_shared_cache_entries) + get_cmake_property(cache_vars CACHE_VARIABLES) + foreach(cache_var IN LISTS cache_vars) + get_property(cache_value CACHE "${cache_var}" PROPERTY VALUE) + string(FIND "${cache_value}" "${CMAKE_SOURCE_DIR}/depends/" depends_prefix_pos) + if(NOT depends_prefix_pos EQUAL 0) + continue() + endif() + if(NOT cache_value MATCHES "/lib/.*\\.so(\\.[0-9]+)*$") + continue() + endif() + if(EXISTS "${cache_value}") + continue() + endif() + + message(STATUS "Clearing stale cached library path ${cache_var}: ${cache_value}") + unset(${cache_var} CACHE) + endforeach() +endfunction() + cmake_dependent_option(BUILD_GUI_TESTS "Build test_firo-qt executable." ON "BUILD_GUI;BUILD_TESTS" OFF) if(BUILD_GUI) + clear_stale_depends_shared_cache_entries() set(qt_components Core Gui Widgets LinguistTools) if(ENABLE_WALLET) list(APPEND qt_components Network) diff --git a/Dockerfile b/Dockerfile index b789491b3d..5b4559907d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,9 @@ RUN apt-get update && apt-get install -y \ make \ pkg-config \ rsync \ - patch + patch \ + ninja-build \ + meson # Build Firo COPY . /tmp/firo/ diff --git a/README.md b/README.md index 2f4d6b185b..b70803a0cb 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,9 @@ [![GitHub commits-per-month](https://img.shields.io/github/commit-activity/m/firoorg/firo)](https://github.com/firoorg/firo/graphs/code-frequency) [![GitHub last-commit](https://img.shields.io/github/last-commit/firoorg/firo)](https://github.com/firoorg/firo/commits/master) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/firoorg/firo?utm_source=oss&utm_medium=github&utm_campaign=firoorg%2Ffiro&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/firoorg/firo) -[Firo](https://firo.org) formerly known as Zcoin, is a privacy focused cryptocurrency that utilizes the [Lelantus Spark protocol](https://eprint.iacr.org/2021/1173) which supports high anonymity sets without requiring trusted setup and relying on standard cryptographic assumptions. - -The Lelantus Spark cryptographic library and implementation was audited by [HashCloak](https://firo.org/about/research/papers/lelantus_spark_code_audit_report.pdf). The Lelantus Spark cryptography paper has undergone two separate audits by [HashCloak](https://firo.org/about/research/papers/Lelantus_Spark_Audit_Report.pdf) and [Daniel (Linfeng) Zhao](https://firo.org/about/research/papers/LinfengSparkAudit.pdf). +[Firo](https://firo.org) formerly known as Zcoin, is a privacy-focused cryptocurrency that utilizes the [Spark protocol](https://eprint.iacr.org/2021/1173) which supports high anonymity sets without requiring trusted setup and relying on standard cryptographic assumptions. Firo also utilises [Dandelion++](https://arxiv.org/abs/1805.11060) to obscure the originating IP of transactions without relying on any external services such as Tor/i2P. @@ -108,9 +107,9 @@ Bootstrappable builds can [be achieved with Guix.](contrib/guix/README.md) ```sh sudo apt-get update -sudo apt-get install python git curl build-essential cmake pkg-config +sudo apt-get install python git curl build-essential cmake pkg-config ccache # Also needed for GUI wallet only: -sudo apt-get install qttools5-dev qttools5-dev-tools libxcb-xkb-dev bison +sudo apt-get install qttools5-dev qttools5-dev-tools libxcb-xkb-dev bison ninja-build meson libx11-dev ``` If you use a later version of Ubuntu, you may need to replace `python` with `python3`. diff --git a/contrib/debian/firo-qt.desktop b/contrib/debian/firo-qt.desktop index 0d610ac4c3..1033a2b756 100644 --- a/contrib/debian/firo-qt.desktop +++ b/contrib/debian/firo-qt.desktop @@ -5,5 +5,5 @@ Comment=Connect to Firo Exec=firo-qt %u Terminal=false Type=Application -Icon=/app/share/icons/hicolor/scalable/apps/org.firo.firo-qt.svg +Icon=firo-qt Categories=Office;Finance; diff --git a/contrib/debian/qt-screenshot.jpg b/contrib/debian/qt-screenshot.jpg deleted file mode 100644 index 135ff4acba..0000000000 Binary files a/contrib/debian/qt-screenshot.jpg and /dev/null differ diff --git a/contrib/debian/qt-screenshot.png b/contrib/debian/qt-screenshot.png new file mode 100644 index 0000000000..bdf3f05ef2 Binary files /dev/null and b/contrib/debian/qt-screenshot.png differ diff --git a/contrib/docker/builder/Dockerfile b/contrib/docker/builder/Dockerfile index 223f84fd79..115840c47d 100644 --- a/contrib/docker/builder/Dockerfile +++ b/contrib/docker/builder/Dockerfile @@ -1,17 +1,18 @@ -FROM ubuntu:20.04 +FROM ubuntu:24.04 # install required packages RUN apt-get update && apt-get install -y software-properties-common RUN apt-get update && apt-get install -y \ autoconf automake bsdmainutils ccache cmake curl g++ g++-mingw-w64-x86-64 gcc gcc-mingw-w64-x86-64 git \ - libbz2-dev libtool make pkg-config python3-pip python3-zmq build-essential minizip lcov default-jre bison + libbz2-dev libtool make pkg-config python3-pip python3-zmq build-essential minizip lcov default-jre bison flex python3-pyasyncore \ + ninja-build meson libx11-dev # update mingw alternatives RUN update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix RUN update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix -RUN pip3 install ez_setup +RUN pip3 install ez_setup --break-system-packages # create user to use RUN useradd -m -U firo-builder diff --git a/contrib/flathub/org.firo.firo-qt.json b/contrib/flathub/org.firo.firo-qt.json new file mode 100644 index 0000000000..0d95a747cf --- /dev/null +++ b/contrib/flathub/org.firo.firo-qt.json @@ -0,0 +1,61 @@ +{ + "id": "org.firo.firo-qt", + "runtime": "org.freedesktop.Platform", + "sdk": "org.freedesktop.Sdk", + "runtime-version": "25.08", + "command": "firo-qt", + "finish-args": [ + "--socket=x11", + "--socket=pulseaudio", + "--device=dri", + "--share=ipc", + "--share=network", + "--filesystem=/run/tor/control.authcookie:ro" + ], + "modules": [ + { + "name": "firo", + "buildsystem": "simple", + "build-commands": [ + "install -m 0755 -D -t /app/bin/internal bin/firod", + "install -m 0755 -D -t /app/bin/internal bin/firo-qt", + "install -m 0755 -D -t /app/bin/internal bin/firo-cli", + "desktop-file-edit --set-key=\"Icon\" --set-value=${FLATPAK_ID} firo-qt.desktop", + "install -Dp -m 644 firo-qt.desktop /app/share/applications/${FLATPAK_ID}.desktop", + "install -Dp -m 644 firo.svg /app/share/icons/hicolor/scalable/apps/org.firo.firo-qt.svg", + "install -Dp -m 644 org.firo.firo-qt.metainfo.xml /app/share/metainfo/org.firo.firo-qt.metainfo.xml", + "for b in firo{d,-qt,-cli}; do echo '#!/usr/bin/bash' > /app/bin/$b && echo \"exec /app/bin/internal/$b\" '-datadir=\"${XDG_DATA_HOME}\" \"$@\"' >> /app/bin/$b && chmod 744 /app/bin/$b; done" + ], + "sources": [ + { + "type": "archive", + "only-arches": [ + "x86_64" + ], + "url": "https://github.com/firoorg/firo/releases/download/v0.14.16.0/firo-0.14.16.0-linux64.tar.gz", + "sha256": "b8f6ff5d7481e5a9dd7c5ad71caa62658b7747a8f3512999786229458397c59d", + "x-checker-data": { + "type": "html", + "url": "https://github.com/firoorg/firo/releases.atom", + "version-pattern": "Firo ([\\d\\.]+)", + "url-template": "https://github.com/firoorg/firo/releases/download/$version/firo-$version-linux64.tar.gz" + } + }, + { + "type": "file", + "url": "https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea54/contrib/debian/firo-qt.desktop", + "sha256": "79070bd3af4334f4239585630d7d7d6b8d417a15ca7c1ba1a4ef6e4bc276bed4" + }, + { + "type": "file", + "path": "org.firo.firo-qt.metainfo.xml" + }, + { + "type": "file", + "url": "https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea547/src/qt/res/icons/firo.svg", + "sha256": "3474d6c9248735896ea2ef27047625f5b114429b9b23e54e852d7ce4f68a2371" + } + ] + } + ] +} \ No newline at end of file diff --git a/contrib/flathub/org.firo.firo-qt.metainfo.xml b/contrib/flathub/org.firo.firo-qt.metainfo.xml index d277784c0a..abe9671f38 100644 --- a/contrib/flathub/org.firo.firo-qt.metainfo.xml +++ b/contrib/flathub/org.firo.firo-qt.metainfo.xml @@ -24,16 +24,20 @@ Bitcoin was originally created as an answer to this by ensuring you can be self sovereign over your money and to serve as uncensorable and unseizable money that isn't controlled by any one entity.

Bitcoin's lack of privacy however has now made it much easier to seize or blacklist funds and due to ossification of the protocol, is unlikely to take serious steps to address this. - Firo has dedicated itself to being a privacy preserving cryptocurrency and have designed and built trustless privacy protocols such as Lelantus and Lelantus Spark that have inspired and shaped the designs of other privacy protocols (for e.g. Triptych, Seraphis, Lelantus-MW).

+ Firo has dedicated itself to being a privacy preserving cryptocurrency and have designed and built trustless privacy protocols such as Spark that have inspired and shaped the designs of other privacy protocols (for e.g. Triptych, Seraphis, Lelantus-MW).

- https://raw.githubusercontent.com/firoorg/firo/0f9d27dfef6f27f5c4d97a060c0954846d3ea547/contrib/debian/qt-screenshot.jpg + https://raw.githubusercontent.com/firoorg/firo/837983954d1bc5b1e70474b14b1376747f42c692/contrib/debian/qt-screenshot.png + + + + diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 124881166f..f9af6c4a9d 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -95,6 +95,11 @@ prepend_to_search_env_var() { export "${1}=${2}${!1:+:}${!1}" } +# Native tools built in depends may need the GCC runtime when executed. +if [ -n "${LIBRARY_PATH}" ]; then + prepend_to_search_env_var LD_LIBRARY_PATH "${LIBRARY_PATH}" +fi + # Set environment variables to point the CROSS toolchain to the right # includes/libs for $HOST case "$HOST" in diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 34e849deea..dce1edd19b 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -15,12 +15,14 @@ (gnu packages llvm) (gnu packages mingw) (gnu packages ninja) + ((gnu packages build-tools) #:select (meson)) (gnu packages pkg-config) ((gnu packages python) #:select (python-minimal)) ((gnu packages python-build) #:select (python-tomli python-poetry-core)) ((gnu packages python-crypto) #:select (python-asn1crypto)) ((gnu packages tls) #:select (openssl)) (gnu packages perl) + (gnu packages xml) ((gnu packages version-control) #:select (git-minimal)) (guix build-system cmake) (guix build-system gnu) @@ -264,10 +266,12 @@ chain for " target " development.")) cmake-minimal gnu-make ninja + meson libtool automake autoconf-2.71 pkg-config + expat ;; Scripting python-minimal ;; (3.10) perl diff --git a/depends/Makefile b/depends/Makefile index 8dc0409a3c..afc7ff53a0 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -159,7 +159,7 @@ libevent_packages_$(NO_LIBEVENT) = $(libevent_packages) qrencode_packages_$(NO_QR) = $(qrencode_packages) qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_) -qt_native_packages_$(NO_QT) = $(qt_native_packages) +qt_native_packages_$(NO_QT) = $(qt_native_packages) $(qt_$(host_os)_native_packages) $(qt_$(host_arch)_$(host_os)_native_packages) bdb_packages_$(NO_BDB) = $(bdb_packages) sqlite_packages_$(NO_SQLITE) = $(sqlite_packages) @@ -240,6 +240,7 @@ $(host_prefix)/toolchain.cmake : toolchain.cmake.in $(host_prefix)/.stamp_$(fina -e 's|@usdt_packages@|$(usdt_packages_)|' \ -e 's|@no_harden@|$(NO_HARDEN)|' \ -e 's|@multiprocess@|$(MULTIPROCESS)|' \ + -e 's|@host_os@|$(host_os)|' \ $< > $@ touch $@ diff --git a/depends/packages/libXdmcp.mk b/depends/packages/libXdmcp.mk new file mode 100644 index 0000000000..f50ac117ef --- /dev/null +++ b/depends/packages/libXdmcp.mk @@ -0,0 +1,27 @@ +package=libXdmcp +$(package)_version=1.1.5 +$(package)_download_path=https://www.x.org/releases/individual/lib/ +$(package)_file_name=$(package)-$($(package)_version).tar.xz +$(package)_sha256_hash=d8a5222828c3adab70adf69a5583f1d32eb5ece04304f7f8392b6a353aa2228c +$(package)_dependencies=xproto + +define $(package)_set_vars + $(package)_config_opts=--disable-shared --disable-docs + $(package)_config_opts_linux=--with-pic +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef + +define $(package)_postprocess_cmds + rm -f lib/*.la +endef diff --git a/depends/packages/libffi.mk b/depends/packages/libffi.mk new file mode 100644 index 0000000000..aa4b223b31 --- /dev/null +++ b/depends/packages/libffi.mk @@ -0,0 +1,28 @@ +package := libffi +$(package)_version := 3.5.2 +$(package)_download_path := https://github.com/libffi/$(package)/releases/download/v$($(package)_version) +$(package)_file_name := $(package)-$($(package)_version).tar.gz +$(package)_sha256_hash := f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc + +define $(package)_set_vars + $(package)_config_opts := --enable-option-checking --disable-dependency-tracking + $(package)_config_opts += --disable-shared --enable-static --disable-docs + $(package)_config_opts += --disable-multi-os-directory + $(package)_cflags += -D_GNU_SOURCE -std=gnu11 +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef + +define $(package)_postprocess_cmds + rm -rf share +endef diff --git a/depends/packages/libglvnd.mk b/depends/packages/libglvnd.mk new file mode 100644 index 0000000000..92b6a0bea4 --- /dev/null +++ b/depends/packages/libglvnd.mk @@ -0,0 +1,77 @@ +package := libglvnd +$(package)_version := 1.4.0 +$(package)_download_path := https://gitlab.freedesktop.org/glvnd/$(package)/-/archive/v$($(package)_version) +$(package)_file_name := $(package)-v$($(package)_version).tar.bz2 +$(package)_sha256_hash := fdf395391d95f270528dbff6ce2ee54c186753d286ad62e0da5f62c6f67ba915 +$(package)_patches := fix-typeof-gcc14.patch + +define $(package)_set_vars +endef + +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/fix-typeof-gcc14.patch +endef + +define $(package)_config_cmds + cross_arg="" ; \ + if [ "$(host)" != "$(build)" ]; then \ + CC="$$($(package)_cc)" ; \ + CXX="$$($(package)_cxx)" ; \ + cc_first=$$$${CC%% *} ; \ + cc_rest=$$$${CC#* } ; \ + cxx_first=$$$${CXX%% *} ; \ + cxx_rest=$$$${CXX#* } ; \ + if [ "$$$$cc_first" = "$$$$CC" ]; then \ + cc_line="c = ['$$$$CC']" ; \ + else \ + cc_line="c = ['$$$$cc_first', '$$$$cc_rest']" ; \ + fi ; \ + if [ "$$$$cxx_first" = "$$$$CXX" ]; then \ + cxx_line="cpp = ['$$$$CXX']" ; \ + else \ + cxx_line="cpp = ['$$$$cxx_first', '$$$$cxx_rest']" ; \ + fi ; \ + printf '%s\n' "[binaries]" "$$$$cc_line" "$$$$cxx_line" \ + "ar = '$$($(package)_ar)'" \ + "pkg-config = 'pkg-config'" \ + "strip = '$(host_STRIP)'" \ + "" \ + "[built-in options]" \ + "pkg_config_path = ['$(host_prefix)/lib/pkgconfig', '$(host_prefix)/share/pkgconfig']" \ + "" \ + "[properties]" \ + "needs_exe_wrapper = true" \ + "" \ + "[host_machine]" \ + "system = 'linux'" \ + "cpu_family = '$(host_arch)'" \ + "cpu = '$(host_arch)'" \ + "endian = 'little'" \ + > cross.ini ; \ + cross_arg="--cross-file cross.ini" ; \ + fi && \ + PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig \ + PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig \ + CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \ + CFLAGS="$$($(package)_cppflags) $$($(package)_cflags) -D_GNU_SOURCE" \ + CXXFLAGS="$$($(package)_cppflags) $$($(package)_cxxflags)" \ + LDFLAGS="$$($(package)_ldflags)" \ + meson setup build --prefix=$(host_prefix) \ + $$$$cross_arg \ + --libdir=lib \ + --default-library=shared \ + -Dx11=disabled \ + -Dgles1=false +endef + +define $(package)_build_cmds + ninja -C build +endef + +define $(package)_stage_cmds + DESTDIR=$($(package)_staging_dir) ninja -C build install +endef + +define $(package)_postprocess_cmds + rm -f lib/*.la +endef diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index 5bbcab8437..e1dea20e46 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -3,12 +3,13 @@ $(package)_version=1.14 $(package)_download_path=https://xcb.freedesktop.org/dist $(package)_file_name=$(package)-$($(package)_version).tar.xz $(package)_sha256_hash=a55ed6db98d43469801262d81dc2572ed124edc3db31059d4e9916eb9f844c34 -$(package)_dependencies=xcb_proto libXau +$(package)_dependencies=xcb_proto libXau libXdmcp $(package)_patches = remove_pthread_stubs.patch define $(package)_set_vars $(package)_config_opts=--disable-devel-docs --without-doxygen --without-launchd $(package)_config_opts += --disable-dependency-tracking --enable-option-checking +$(package)_config_opts += --disable-shared # Disable unneeded extensions. # More info is available from: https://doc.qt.io/qt-5.15/linux-requirements.html $(package)_config_opts += --disable-composite --disable-damage --disable-dpms @@ -37,5 +38,5 @@ define $(package)_stage_cmds endef define $(package)_postprocess_cmds - rm -rf share lib/*.la + rm -rf share lib/*.la && mkdir -p _xau _xdmcp && (cd _xau && ar x $(host_prefix)/lib/libXau.a) && (cd _xdmcp && ar x $(host_prefix)/lib/libXdmcp.a) && ar rc lib/libxcb.a _xau/*.o _xdmcp/*.o && ranlib lib/libxcb.a && rm -rf _xau _xdmcp && python3 -c "c=open('lib/pkgconfig/xcb.pc').read();n=c.replace(' -lxcb\n',' -lxcb -lXau -lXdmcp\n');assert n!=c,'xcb.pc patch failed: -lxcb not found at end of Libs line';open('lib/pkgconfig/xcb.pc','w').write(n)" endef diff --git a/depends/packages/libxcb_util_image.mk b/depends/packages/libxcb_util_image.mk index fe8891ccab..ec058997b7 100644 --- a/depends/packages/libxcb_util_image.mk +++ b/depends/packages/libxcb_util_image.mk @@ -8,6 +8,7 @@ $(package)_dependencies=libxcb libxcb_util define $(package)_set_vars $(package)_config_opts=--disable-devel-docs --without-doxygen $(package)_config_opts+= --disable-dependency-tracking --enable-option-checking +$(package)_config_opts+= --disable-shared endef define $(package)_preprocess_cmds @@ -27,5 +28,5 @@ define $(package)_stage_cmds endef define $(package)_postprocess_cmds - rm -rf share/man share/doc lib/*.la + rm -rf share/man share/doc lib/*.la && rm -rf _util && mkdir _util && (cd _util && ar x $(host_prefix)/lib/libxcb-util.a) && ar rc lib/libxcb-image.a _util/*.o && ranlib lib/libxcb-image.a && rm -rf _util endef diff --git a/depends/packages/libxcb_util_keysyms.mk b/depends/packages/libxcb_util_keysyms.mk index b114fbed7e..0ccdc1c5e0 100644 --- a/depends/packages/libxcb_util_keysyms.mk +++ b/depends/packages/libxcb_util_keysyms.mk @@ -8,6 +8,7 @@ $(package)_dependencies=libxcb xproto define $(package)_set_vars $(package)_config_opts=--disable-devel-docs --without-doxygen $(package)_config_opts += --disable-dependency-tracking --enable-option-checking +$(package)_config_opts += --disable-shared endef define $(package)_preprocess_cmds diff --git a/depends/packages/libxcb_util_render.mk b/depends/packages/libxcb_util_render.mk index 905f932239..dfc557e483 100644 --- a/depends/packages/libxcb_util_render.mk +++ b/depends/packages/libxcb_util_render.mk @@ -8,6 +8,7 @@ $(package)_dependencies=libxcb define $(package)_set_vars $(package)_config_opts=--disable-devel-docs --without-doxygen $(package)_config_opts += --disable-dependency-tracking --enable-option-checking +$(package)_config_opts += --disable-shared endef define $(package)_preprocess_cmds diff --git a/depends/packages/libxcb_util_wm.mk b/depends/packages/libxcb_util_wm.mk index f80f0eb27d..10e1fe34fa 100644 --- a/depends/packages/libxcb_util_wm.mk +++ b/depends/packages/libxcb_util_wm.mk @@ -8,6 +8,7 @@ $(package)_dependencies=libxcb define $(package)_set_vars $(package)_config_opts=--disable-devel-docs --without-doxygen $(package)_config_opts += --disable-dependency-tracking --enable-option-checking +$(package)_config_opts += --disable-shared endef define $(package)_preprocess_cmds diff --git a/depends/packages/libxkbcommon.mk b/depends/packages/libxkbcommon.mk index b8634e9aa5..9881322bbf 100644 --- a/depends/packages/libxkbcommon.mk +++ b/depends/packages/libxkbcommon.mk @@ -12,6 +12,8 @@ $(package)_dependencies=libxcb define $(package)_set_vars $(package)_config_opts = --enable-option-checking --disable-dependency-tracking $(package)_config_opts += --disable-docs +$(package)_config_opts += --disable-shared +$(package)_config_opts += --with-xkb-config-root=/usr/share/X11/xkb $(package)_cflags += -Wno-error=array-bounds endef diff --git a/depends/packages/native_expat.mk b/depends/packages/native_expat.mk new file mode 100644 index 0000000000..695dce32cd --- /dev/null +++ b/depends/packages/native_expat.mk @@ -0,0 +1,21 @@ +package := native_expat +$(package)_version := $(expat_version) +$(package)_download_path := https://github.com/libexpat/libexpat/releases/download/R_$(subst .,_,$($(package)_version))/ +$(package)_file_name := expat-$($(package)_version).tar.bz2 +$(package)_sha256_hash := $(expat_sha256_hash) + +define $(package)_set_vars +$(package)_config_opts := --disable-shared --enable-static +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef \ No newline at end of file diff --git a/depends/packages/native_qt.mk b/depends/packages/native_qt.mk index ffb65bb7bc..e67a5fff04 100644 --- a/depends/packages/native_qt.mk +++ b/depends/packages/native_qt.mk @@ -11,6 +11,7 @@ $(package)_patches += qtbase_avoid_native_float16.patch $(package)_patches += qtbase_skip_tools.patch $(package)_patches += rcc_hardcode_timestamp.patch $(package)_patches += qttools_skip_dependencies.patch +$(package)_patches += qtwayland_build_scanner_without_gui.patch $(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name) $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash) @@ -37,6 +38,14 @@ $(package)_extra_sources += $($(package)_top_cmakelists_file_name)-$($(package)_ $(package)_extra_sources += $($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) $(package)_extra_sources += $($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version) +$(package)_qtwayland_file_name=$(qt_details_qtwayland_file_name) +$(package)_qtwayland_sha256_hash=$(qt_details_qtwayland_sha256_hash) + +ifneq ($(filter linux freebsd,$(host_os)),) +$(package)_dependencies := native_wayland_scanner +$(package)_extra_sources += $($(package)_qtwayland_file_name) +endif + define $(package)_set_vars # Build options. $(package)_config_opts := -release @@ -95,6 +104,9 @@ $(package)_config_env += OBJCXX="$$(build_CXX)" endif $(package)_cmake_opts := -DCMAKE_EXE_LINKER_FLAGS="$$(build_LDFLAGS)" +ifneq ($(filter linux freebsd,$(host_os)),) +$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner +endif ifneq ($(V),) $(package)_cmake_opts += --log-level=STATUS endif @@ -108,6 +120,10 @@ $(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cm $(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_ecmoptionaladdsubdirectory_download_file),$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version),$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_qttoplevelhelpers_download_file),$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version),$($(package)_top_cmake_qttoplevelhelpers_sha256_hash)) endef +ifneq ($(filter linux freebsd,$(host_os)),) +$(package)_fetch_cmds += && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtwayland_file_name),$($(package)_qtwayland_file_name),$($(package)_qtwayland_sha256_hash)) +endif define $(package)_extract_cmds mkdir -p $($(package)_extract_dir) && \ @@ -129,6 +145,12 @@ define $(package)_extract_cmds cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \ cp $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_qttoplevelhelpers_file_name) endef +ifneq ($(filter linux freebsd,$(host_os)),) +$(package)_extract_cmds += && \ + echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" | $(build_SHA256SUM) -c - && \ + mkdir qtwayland && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland +endif define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ @@ -138,6 +160,10 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \ patch -p1 -i $($(package)_patch_dir)/qttools_skip_dependencies.patch endef +ifneq ($(filter linux freebsd,$(host_os)),) +$(package)_preprocess_cmds += && \ + patch -p1 -i $($(package)_patch_dir)/qtwayland_build_scanner_without_gui.patch +endif define $(package)_config_cmds cd qtbase && \ diff --git a/depends/packages/native_wayland_scanner.mk b/depends/packages/native_wayland_scanner.mk new file mode 100644 index 0000000000..1304961d91 --- /dev/null +++ b/depends/packages/native_wayland_scanner.mk @@ -0,0 +1,28 @@ +package := native_wayland_scanner +$(package)_version := 1.20.0 +$(package)_download_path := https://wayland.freedesktop.org/releases +$(package)_file_name := wayland-$($(package)_version).tar.xz +$(package)_sha256_hash := b8a034154c7059772e0fdbd27dbfcda6c732df29cae56a82274f6ec5d7cd8725 +$(package)_dependencies := native_expat + +define $(package)_config_cmds + PKG_CONFIG_LIBDIR=$(build_prefix)/lib/pkgconfig \ + PKG_CONFIG_PATH=$(build_prefix)/share/pkgconfig \ + CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \ + meson setup build --prefix=$(build_prefix) \ + --libdir=lib \ + -Dc_link_args="['-static-libgcc', '-Wl,-rpath,$(build_prefix)/lib']" \ + -Dlibraries=false \ + -Dscanner=true \ + -Ddocumentation=false \ + -Dtests=false \ + -Ddtd_validation=false +endef + +define $(package)_build_cmds + ninja -C build +endef + +define $(package)_stage_cmds + DESTDIR=$($(package)_staging_dir) ninja -C build install +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 3f69b12bf2..c428258856 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -3,8 +3,10 @@ darwin_packages:=zeromq linux_packages:=zeromq native_packages := -qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm +qt_linux_packages:=qt expat libxcb xcb_proto libXau libXdmcp xproto freetype fontconfig libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm libffi wayland libglvnd +qt_linux_native_packages := native_expat native_wayland_scanner qt_freebsd_packages:=$(qt_linux_packages) +qt_freebsd_native_packages := $(qt_linux_native_packages) qt_darwin_packages=qt qt_mingw32_packages=qt ifneq ($(host),$(build)) diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index bb2cc9e3a7..de3003fedf 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -7,7 +7,7 @@ $(package)_sha256_hash=$(qt_details_qtbase_sha256_hash) ifneq ($(host),$(build)) $(package)_dependencies := native_$(package) endif -$(package)_linux_dependencies := freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm +$(package)_linux_dependencies := freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_cursor libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm wayland libglvnd $(package)_freebsd_dependencies := $($(package)_linux_dependencies) $(package)_patches_path := $(qt_details_patches_path) $(package)_patches := dont_hardcode_pwd.patch @@ -17,8 +17,11 @@ $(package)_patches += qtbase_avoid_qmain.patch $(package)_patches += qtbase_platformsupport.patch $(package)_patches += qtbase_plugins_cocoa.patch $(package)_patches += qtbase_skip_tools.patch +$(package)_patches += qtbase_findxcb_image_aux.patch $(package)_patches += rcc_hardcode_timestamp.patch $(package)_patches += qttools_skip_dependencies.patch +$(package)_patches += qtwayland_decoration_no_egl_gate.patch +$(package)_patches += qtbase_fix_qyieldcpu_apple_arm.patch $(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name) $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash) @@ -26,8 +29,12 @@ $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash) $(package)_qttools_file_name=$(qt_details_qttools_file_name) $(package)_qttools_sha256_hash=$(qt_details_qttools_sha256_hash) +$(package)_qtwayland_file_name=$(qt_details_qtwayland_file_name) +$(package)_qtwayland_sha256_hash=$(qt_details_qtwayland_sha256_hash) + $(package)_extra_sources := $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) +$(package)_extra_sources += $($(package)_qtwayland_file_name) $(package)_top_download_path=$(qt_details_top_download_path) $(package)_top_cmakelists_file_name=$(qt_details_top_cmakelists_file_name) @@ -49,13 +56,11 @@ define $(package)_set_vars $(package)_config_env = QT_MAC_SDK_NO_VERSION_CHECK=1 $(package)_config_opts_release = -release $(package)_config_opts_debug = -debug -$(package)_config_opts = -no-egl $(package)_config_opts_debug += -optimized-tools $(package)_config_opts += -bindir $(build_prefix)/bin $(package)_config_opts += -c++std c++20 $(package)_config_opts += -confirm-license $(package)_config_opts += -no-cups -$(package)_config_opts += -no-egl $(package)_config_opts += -no-eglfs $(package)_config_opts += -no-evdev $(package)_config_opts += -no-gif @@ -69,7 +74,6 @@ $(package)_config_opts += -no-libjpeg $(package)_config_opts += -no-libproxy $(package)_config_opts += -no-libudev $(package)_config_opts += -no-mtdev -$(package)_config_opts += -no-opengl $(package)_config_opts += -no-openssl $(package)_config_opts += -no-openvg $(package)_config_opts += -no-reduce-relocations @@ -162,6 +166,7 @@ endif $(package)_config_opts_darwin := -no-dbus $(package)_config_opts_darwin += -no-opengl +$(package)_config_opts_darwin += -no-egl $(package)_config_opts_darwin += -pch $(package)_config_opts_darwin += -no-feature-printsupport $(package)_config_opts_darwin += -no-freetype @@ -175,16 +180,18 @@ $(package)_config_opts_linux += -no-xcb-xlib $(package)_config_opts_linux += -pkg-config $(package)_config_opts_linux += -system-freetype $(package)_config_opts_linux += -fontconfig -$(package)_config_opts_linux += -no-opengl $(package)_config_opts_linux += -no-feature-vulkan $(package)_config_opts_linux += -dbus-runtime $(package)_config_opts_linux += -feature-xcb + ifneq ($(LTO),) $(package)_config_opts_linux += -ltcg endif $(package)_config_opts_freebsd := $$($(package)_config_opts_linux) $(package)_config_opts_mingw32 := -no-dbus +$(package)_config_opts_mingw32 += -no-opengl +$(package)_config_opts_mingw32 += -no-egl $(package)_config_opts_mingw32 += -no-freetype $(package)_config_opts_mingw32 += -no-pkg-config @@ -235,6 +242,12 @@ endif ifeq ($(host_os),linux) $(package)_cmake_opts += -DQT_FEATURE_xcb=ON +$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner +endif + +ifeq ($(host_os),freebsd) +$(package)_cmake_opts += -DQT_FEATURE_xcb=ON +$(package)_cmake_opts += -DWaylandScanner_EXECUTABLE=$(build_prefix)/bin/wayland-scanner endif ifdef GUIX_ENVIRONMENT @@ -277,6 +290,7 @@ define $(package)_fetch_cmds $(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttranslations_file_name),$($(package)_qttranslations_file_name),$($(package)_qttranslations_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttools_file_name),$($(package)_qttools_file_name),$($(package)_qttools_sha256_hash)) && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtwayland_file_name),$($(package)_qtwayland_file_name),$($(package)_qtwayland_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cmakelists_download_file),$($(package)_top_cmakelists_file_name)-$($(package)_version),$($(package)_top_cmakelists_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_ecmoptionaladdsubdirectory_download_file),$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version),$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_cmake_download_path),$($(package)_top_cmake_qttoplevelhelpers_download_file),$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version),$($(package)_top_cmake_qttoplevelhelpers_sha256_hash)) @@ -288,6 +302,7 @@ define $(package)_extract_cmds echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttranslations_sha256_hash) $($(package)_source_dir)/$($(package)_qttranslations_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttools_sha256_hash) $($(package)_source_dir)/$($(package)_qttools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_qttoplevelhelpers_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ @@ -298,6 +313,8 @@ define $(package)_extract_cmds $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttranslations_file_name) -C qttranslations && \ mkdir qttools && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttools_file_name) -C qttools && \ + mkdir qtwayland && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland && \ cp $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version) ./$($(package)_top_cmakelists_file_name) && \ mkdir cmake && \ cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \ @@ -310,12 +327,15 @@ else define $(package)_extract_cmds mkdir -p $($(package)_extract_dir) && \ echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtwayland_sha256_hash) $($(package)_source_dir)/$($(package)_qtwayland_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_qttoplevelhelpers_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ mkdir qtbase && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source) -C qtbase && \ + mkdir qtwayland && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtwayland_file_name) -C qtwayland && \ cp $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version) ./$($(package)_top_cmakelists_file_name) && \ mkdir cmake && \ cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \ @@ -334,7 +354,10 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/qtbase_platformsupport.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase_plugins_cocoa.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase_skip_tools.patch && \ - patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch + patch -p1 -i $($(package)_patch_dir)/qtbase_findxcb_image_aux.patch && \ + patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \ + patch -p1 -i $($(package)_patch_dir)/qtwayland_decoration_no_egl_gate.patch && \ + patch -p1 -i $($(package)_patch_dir)/qtbase_fix_qyieldcpu_apple_arm.patch endef ifeq ($(host),$(build)) $(package)_preprocess_cmds += && patch -p1 -i $($(package)_patch_dir)/qttools_skip_dependencies.patch diff --git a/depends/packages/qt_details.mk b/depends/packages/qt_details.mk index 341448149e..4d7ae09532 100644 --- a/depends/packages/qt_details.mk +++ b/depends/packages/qt_details.mk @@ -11,6 +11,9 @@ qt_details_qttranslations_sha256_hash := dcc762acac043b9bb5e4d369b6d6f53e0ecfcf7 qt_details_qttools_file_name := qttools-$(qt_details_suffix) qt_details_qttools_sha256_hash := f03bb7df619cd9ac9dba110e30b7bcab5dd88eb8bdc9cc752563b4367233203f +qt_details_qtwayland_file_name := qtwayland-$(qt_details_suffix) +qt_details_qtwayland_sha256_hash := e326c7ceb628f503bfc20577d5d2df9690ee10db08eb940cb80c759a6972b2b5 + qt_details_patches_path := $(PATCHES_PATH)/qt qt_details_top_download_path := https://code.qt.io/cgit/qt/qt5.git/plain diff --git a/depends/packages/wayland.mk b/depends/packages/wayland.mk new file mode 100644 index 0000000000..43f7b36ccf --- /dev/null +++ b/depends/packages/wayland.mk @@ -0,0 +1,72 @@ +package := wayland +native_package := native_wayland_scanner +$(package)_version = $($(native_package)_version) +$(package)_download_path = $($(native_package)_download_path) +$(package)_file_name = $($(native_package)_file_name) +$(package)_sha256_hash = $($(native_package)_sha256_hash) +$(package)_dependencies = $(native_package) libffi expat + +define $(package)_config_cmds + cross_arg="" ; \ + if [ "$(host)" != "$(build)" ]; then \ + CC="$$($(package)_cc)" ; \ + CXX="$$($(package)_cxx)" ; \ + cc_first=$$$${CC%% *} ; \ + cc_rest=$$$${CC#* } ; \ + cxx_first=$$$${CXX%% *} ; \ + cxx_rest=$$$${CXX#* } ; \ + if [ "$$$$cc_first" = "$$$$CC" ]; then \ + cc_line="c = ['$$$$CC']" ; \ + else \ + cc_line="c = ['$$$$cc_first', '$$$$cc_rest']" ; \ + fi ; \ + if [ "$$$$cxx_first" = "$$$$CXX" ]; then \ + cxx_line="cpp = ['$$$$CXX']" ; \ + else \ + cxx_line="cpp = ['$$$$cxx_first', '$$$$cxx_rest']" ; \ + fi ; \ + printf '%s\n' "[binaries]" "$$$$cc_line" "$$$$cxx_line" \ + "ar = '$$($(package)_ar)'" \ + "strip = '$$($(package)_ranlib)'" \ + "pkg-config = 'pkg-config'" \ + "" \ + "[built-in options]" \ + "pkg_config_path = ['$(host_prefix)/lib/pkgconfig', '$(build_prefix)/lib/pkgconfig', '$(host_prefix)/share/pkgconfig']" \ + "" \ + "[host_machine]" \ + "system = 'linux'" \ + "cpu_family = '$(host_arch)'" \ + "cpu = '$(host_arch)'" \ + "endian = 'little'" \ + > cross.ini ; \ + cross_arg="--cross-file cross.ini" ; \ + fi && \ + PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig:$(build_prefix)/lib/pkgconfig \ + PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig \ + CC="$$($(package)_cc)" CXX="$$($(package)_cxx)" \ + CFLAGS="-D_GNU_SOURCE $$($(package)_cppflags) $$($(package)_cflags)" \ + CXXFLAGS="$$($(package)_cppflags) $$($(package)_cxxflags)" \ + LDFLAGS="$$($(package)_ldflags)" \ + meson setup build --prefix=$(host_prefix) \ + $$$$cross_arg \ + --libdir=lib \ + --default-library=static \ + -Dlibraries=true \ + -Dscanner=false \ + -Ddocumentation=false \ + -Dtests=false \ + -Ddtd_validation=false +endef + +define $(package)_build_cmds + LD_LIBRARY_PATH="$(host_prefix)/lib:$(build_prefix)/lib$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH}" \ + ninja -C build +endef + +define $(package)_stage_cmds + DESTDIR=$($(package)_staging_dir) ninja -C build install +endef + +define $(package)_postprocess_cmds + rm -f lib/*.la +endef diff --git a/depends/patches/libglvnd/fix-typeof-gcc14.patch b/depends/patches/libglvnd/fix-typeof-gcc14.patch new file mode 100644 index 0000000000..788d5f043e --- /dev/null +++ b/depends/patches/libglvnd/fix-typeof-gcc14.patch @@ -0,0 +1,27 @@ +Description: Replace typeof with __typeof__ for GCC 14+ compatibility + The typeof() GNU extension is not recognized correctly in newer GCC + versions (14+). Use __typeof__ which is the portable GCC/Clang built-in + that works across all C standard modes. + +--- a/include/glvnd_list.h ++++ b/include/glvnd_list.h +@@ -275,7 +275,7 @@ + + #ifdef HAVE_TYPEOF + #define __glvnd_container_of(ptr, sample, member) \ +- glvnd_container_of(ptr, typeof(*sample), member) ++ glvnd_container_of(ptr, __typeof__(*sample), member) + #else + /* This implementation of __glvnd_container_of has undefined behavior according + * to the C standard, but it works in many cases. If your compiler doesn't +--- a/src/util/glvnd_pthread.c ++++ b/src/util/glvnd_pthread.c +@@ -87,7 +87,7 @@ + + #define GET_MT_FUNC(funcs, handle, func) \ + pthreadRealFuncs.func = \ +- (typeof(pthreadRealFuncs.func)) \ ++ (__typeof__(pthreadRealFuncs.func)) \ + dlsym(handle, "pthread_" #func); \ + + static void glvndSetupPthreads(void) diff --git a/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch b/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch new file mode 100644 index 0000000000..d5fa39cc6c --- /dev/null +++ b/depends/patches/native_qt/qtwayland_build_scanner_without_gui.patch @@ -0,0 +1,14 @@ +--- a/qtwayland/CMakeLists.txt ++++ b/qtwayland/CMakeLists.txt +@@ -41,7 +41,10 @@ + #endif() + # special case end + if(NOT TARGET Qt::Gui) +- message(NOTICE "Skipping the build as the condition \"TARGET Qt::Gui\" is not met.") ++ message(NOTICE "Qt::Gui not available, building qtwaylandscanner tool only.") ++ qt_build_repo_begin() ++ add_subdirectory(src/qtwaylandscanner) ++ qt_build_repo_end() + return() + endif() + qt_build_repo() diff --git a/depends/patches/qt/qtbase_findxcb_image_aux.patch b/depends/patches/qt/qtbase_findxcb_image_aux.patch new file mode 100644 index 0000000000..97c5b417d1 --- /dev/null +++ b/depends/patches/qt/qtbase_findxcb_image_aux.patch @@ -0,0 +1,24 @@ +qtbase: model xcb-image's AUX dependency for static XCB builds + +Qt's vendored FindXCB module hardcodes XCB_IMAGE as depending only on XCB +and SHM. That is enough for shared builds, but the static xcb-image archive +also calls xcb_aux_create_gc() from xcb-util. When Qt serializes XCB target +dependencies into libQt6XcbQpa.prl, the missing AUX edge drops libxcb-util.a +from downstream link lines and native tools like qdbusviewer fail to link. + +Teach FindXCB that IMAGE also depends on AUX so static consumers inherit +libxcb-util.a transitively. + +diff --git a/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake b/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake +--- a/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake ++++ b/qtbase/cmake/3rdparty/extra-cmake-modules/find-modules/FindXCB.cmake +@@ -145,7 +145,7 @@ endforeach() + # exceptions + set(XCB_XCB_component_deps) + set(XCB_COMPOSITE_component_deps XCB XFIXES) + set(XCB_DAMAGE_component_deps XCB XFIXES) +-set(XCB_IMAGE_component_deps XCB SHM) ++set(XCB_IMAGE_component_deps XCB SHM AUX) + set(XCB_RENDERUTIL_component_deps XCB RENDER) + set(XCB_XFIXES_component_deps XCB RENDER SHAPE) + set(XCB_XVMC_component_deps XCB XV) diff --git a/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch b/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch new file mode 100644 index 0000000000..b28abe45b7 --- /dev/null +++ b/depends/patches/qt/qtbase_fix_qyieldcpu_apple_arm.patch @@ -0,0 +1,19 @@ +Fix __yield implicit declaration error on Apple ARM (darwin25+) + +On newer Apple Clang (Xcode 16+), __has_builtin(__yield) returns true but +calling __yield() produces an "implicitly declaring library function" error +because the declaration requires which is not included here. +Exclude Apple platforms from this path so the code falls through to the +__builtin_arm_yield() branch, which is properly supported on Apple Silicon. + +--- a/qtbase/src/corelib/thread/qyieldcpu.h ++++ b/qtbase/src/corelib/thread/qyieldcpu.h +@@ -31,7 +31,7 @@ void qYieldCpu(void) + noexcept + #endif + { +-#if __has_builtin(__yield) ++#if __has_builtin(__yield) && !defined(__APPLE__) + __yield(); // Generic + #elif defined(_YIELD_PROCESSOR) && defined(Q_CC_MSVC) + _YIELD_PROCESSOR(); // Generic; MSVC's diff --git a/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch b/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch new file mode 100644 index 0000000000..7dcc4637a8 --- /dev/null +++ b/depends/patches/qt/qtwayland_decoration_no_egl_gate.patch @@ -0,0 +1,38 @@ +qtwayland: do not gate window decorations on a working EGL/client buffer integration + +QWaylandDisplay::supportsWindowDecoration() refuses to enable client-side +decorations unless a client buffer integration (typically wayland-egl) is +present and reports supportsWindowDecoration()==true. On environments +without a working EGL — most commonly WSLg, where Wayland clients render +through SHM only — wayland-egl fails to initialize and the function then +returns false, short-circuiting QWaylandWindow::createDecoration() before +the decoration plugin factory is ever consulted. The result is that +statically linked Qt6 widget apps (such as firo-qt) appear without title +bar or borders even though the bradient plugin is registered and ready. + +Software-rendered Wayland apps do not need EGL to draw their own frames, +so always allow decorations unless the user has explicitly opted out via +QT_WAYLAND_DISABLE_WINDOWDECORATION. + +diff --git a/qtwayland/src/client/qwaylanddisplay.cpp b/qtwayland/src/client/qwaylanddisplay.cpp +--- a/qtwayland/src/client/qwaylanddisplay.cpp ++++ b/qtwayland/src/client/qwaylanddisplay.cpp +@@ -859,13 +859,14 @@ void QWaylandDisplay::forceRoundTrip() + bool QWaylandDisplay::supportsWindowDecoration() const + { + static bool disabled = qgetenv("QT_WAYLAND_DISABLE_WINDOWDECORATION").toInt(); +- // Stop early when disabled via the environment. Do not try to load the integration in +- // order to play nice with SHM-only, buffer integration-less systems. ++ // Stop early when disabled via the environment. + if (disabled) + return false; + +- static bool integrationSupport = clientBufferIntegration() && clientBufferIntegration()->supportsWindowDecoration(); +- return integrationSupport; ++ // Allow client-side decorations even when no client buffer integration is ++ // available. Gating on EGL excluded SHM-only setups (e.g. WSLg) where ++ // software-rendered Wayland windows still need to draw their own frames. ++ return true; + } + + QWaylandWindow *QWaylandDisplay::lastInputWindow() const diff --git a/depends/toolchain.cmake.in b/depends/toolchain.cmake.in index 781dcad665..274c20fe8b 100644 --- a/depends/toolchain.cmake.in +++ b/depends/toolchain.cmake.in @@ -105,6 +105,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# Qt6Core depends on WrapRt::WrapRt which uses find_library(LIBRT rt). +# Since CMAKE_FIND_ROOT_PATH_MODE_LIBRARY is ONLY, librt (a system library) +# cannot be found. Set it directly so the linker resolves it normally. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(LIBRT "-lrt" CACHE STRING "" FORCE) +endif() set(QT_TRANSLATIONS_DIR "${CMAKE_CURRENT_LIST_DIR}/translations") # Make sure we only append once, as this file may be called repeatedly. @@ -143,6 +150,9 @@ if("@qt_packages@" MATCHES "^[ ]*$") else() set(BUILD_GUI ON CACHE BOOL "") set(Qt6_ROOT "${CMAKE_CURRENT_LIST_DIR}" CACHE PATH "") + if("@host_os@" MATCHES "^(linux|freebsd)$") + set(WaylandScanner_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/native/bin/wayland-scanner" CACHE FILEPATH "" FORCE) + endif() endif() if("@qrencode_packages@" MATCHES "^[ ]*$") diff --git a/doc/api-documentation.md b/doc/api-documentation.md new file mode 100644 index 0000000000..575522b68b --- /dev/null +++ b/doc/api-documentation.md @@ -0,0 +1,1935 @@ +# Firo API and Component Documentation + +This document provides comprehensive documentation for all public APIs, functions, and components in the Firo codebase. + +## Table of Contents + +1. [Overview](#overview) +2. [RPC API Reference](#rpc-api-reference) + - [Blockchain RPCs](#blockchain-rpcs) + - [Wallet RPCs](#wallet-rpcs) + - [Utility RPCs](#utility-rpcs) + - [Network RPCs](#network-rpcs) + - [Mining RPCs](#mining-rpcs) + - [Masternode RPCs](#masternode-rpcs) + - [Privacy RPCs](#privacy-rpcs) + - [Mobile RPCs](#mobile-rpcs) + - [Address Index RPCs](#address-index-rpcs) + - [Complete RPC Command Index](#complete-rpc-command-index) +3. [Privacy Protocols](#privacy-protocols) + - [Lelantus Protocol](#lelantus-protocol) + - [Spark Protocol](#spark-protocol) +4. [LLMQ and ChainLocks](#llmq-and-chainlocks) +5. [Masternode System](#masternode-system) +6. [Core Components](#core-components) +7. [Examples](#examples) + +--- + +## Overview + +Firo is a privacy-focused cryptocurrency that utilizes the **Lelantus Spark protocol** for high anonymity without requiring trusted setup. The codebase includes: + +- **Privacy Protocols**: Lelantus and Spark for anonymous transactions +- **LLMQ ChainLocks**: Protection against 51% attacks with quick finality +- **FiroPOW**: ProgPOW-based proof-of-work algorithm +- **Deterministic Masternodes**: For network services and governance + +--- + +## RPC API Reference + +The detailed sections below describe selected high-traffic RPCs. For exhaustive coverage of every command registered in the current branch, see [Complete RPC Command Index](#complete-rpc-command-index). + +### Blockchain RPCs + +#### `getblockchaininfo` +Returns an object containing various state info regarding blockchain processing. + +**Arguments:** None + +**Result:** +```json +{ + "chain": "main", + "blocks": 123456, + "headers": 123456, + "bestblockhash": "000000...", + "difficulty": 12345.678, + "mediantime": 1234567890, + "verificationprogress": 0.99, + "chainwork": "000000...", + "pruned": false, + "softforks": [...], + "bip9_softforks": {...} +} +``` + +**Example:** +```bash +firo-cli getblockchaininfo +``` + +--- + +#### `getblockcount` +Returns the number of blocks in the longest blockchain. + +**Arguments:** None + +**Result:** `n` (numeric) - The current block count + +**Example:** +```bash +firo-cli getblockcount +``` + +--- + +#### `getbestblockhash` +Returns the hash of the best (tip) block in the longest blockchain. + +**Arguments:** None + +**Result:** `"hex"` (string) - The block hash hex encoded + +**Example:** +```bash +firo-cli getbestblockhash +``` + +--- + +#### `getblock "blockhash" [verbose]` +Returns block data for the given block hash. + +**Arguments:** +1. `blockhash` (string, required) - The block hash +2. `verbose` (boolean, optional, default=true) - `true` for a JSON object, `false` for hex-encoded block data + +**Result (verbose=false):** `"data"` (string) - The serialized block encoded as hex + +**Result (verbose=true):** +```json +{ + "hash": "000000...", + "confirmations": 123, + "strippedsize": 1234, + "size": 1234, + "weight": 4936, + "height": 123456, + "version": 4, + "versionHex": "00000004", + "merkleroot": "abc123...", + "tx": ["txid1", "txid2", ...], + "cbTx": { + "version": 2, + "height": 123456, + "merkleRootMNList": "abcd..." + }, + "time": 1234567890, + "mediantime": 1234567800, + "nonce": 12345, + "bits": "1d00ffff", + "difficulty": 12345.678, + "chainwork": "000000...", + "previousblockhash": "000000...", + "nextblockhash": "000000...", + "chainlock": true +} +``` + +**Example:** +```bash +firo-cli getblock "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09" +``` + +--- + +#### `getblockhash height` +Returns hash of block at given height. + +**Arguments:** +1. `height` (numeric, required) - The height index + +**Result:** `"hash"` (string) - The block hash + +**Example:** +```bash +firo-cli getblockhash 1000 +``` + +--- + +#### `getblockheader "hash" [verbose]` +Returns block header information. + +**Arguments:** +1. `hash` (string, required) - The block hash +2. `verbose` (boolean, optional, default=true) - true for JSON, false for hex + +**Result:** +```json +{ + "hash": "...", + "confirmations": 123, + "height": 123456, + "version": 4, + "merkleroot": "...", + "time": 1234567890, + "mediantime": 1234567880, + "nonce": 12345, + "bits": "1d00ffff", + "difficulty": 12345.678, + "chainwork": "...", + "previousblockhash": "...", + "nextblockhash": "...", + "chainlock": true +} +``` + +--- + +#### `getdifficulty` +Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + +**Arguments:** None + +**Result:** `n.nnn` (numeric) - The difficulty value + +--- + +#### `getrawmempool [verbose]` +Returns all transaction ids in memory pool. + +**Arguments:** +1. `verbose` (boolean, optional, default=false) - true for JSON object with details + +**Result (verbose=false):** +```json +["txid1", "txid2", ...] +``` + +**Result (verbose=true):** +```json +{ + "txid": { + "size": 1234, + "fee": 0.0001, + "time": 1234567890, + "height": 123456, + "depends": [...], + "instantlock": true + } +} +``` + +--- + +#### `gettxout "txid" n [include_mempool]` +Returns details about an unspent transaction output. + +**Arguments:** +1. `txid` (string, required) - The transaction id +2. `n` (numeric, required) - vout number +3. `include_mempool` (boolean, optional) - Whether to include mempool + +**Result:** +```json +{ + "bestblock": "...", + "confirmations": 123, + "value": 1.0, + "scriptPubKey": {...}, + "coinbase": false +} +``` + +--- + +#### `gettxoutsetinfo` +Returns statistics about the unspent transaction output set. + +**Arguments:** None + +**Result:** +```json +{ + "height": 123456, + "bestblock": "...", + "transactions": 123456, + "txouts": 234567, + "hash_serialized_2": "...", + "disk_size": 123456789, + "total_amount": 12345678.0 +} +``` + +--- + +#### `getchaintips` +Returns information about all known tips in the block tree. + +**Arguments:** None + +**Result:** +```json +[ + { + "height": 123456, + "hash": "...", + "branchlen": 0, + "status": "active" + } +] +``` + +--- + +#### `getmempoolinfo` +Returns details on the active state of the TX memory pool. + +**Arguments:** None + +**Result:** +```json +{ + "size": 123, + "bytes": 123456, + "usage": 234567, + "maxmempool": 300000000, + "mempoolminfee": 0.00001, + "instantsendlocks": 5 +} +``` + +--- + +#### `verifychain [checklevel] [nblocks]` +Verifies blockchain database. + +**Arguments:** +1. `checklevel` (numeric, optional, 0-4, default=3) - How thorough +2. `nblocks` (numeric, optional, default=6) - Number of blocks to check + +**Result:** `true|false` (boolean) + +--- + +### Spark Name RPCs + +#### `getsparknamedata "sparkname"` +Returns information about a Spark name. + +**Arguments:** +1. `sparkname` (string, required) - The Spark name + +**Result:** +```json +{ + "address": "spark address", + "validUntil": 123456, + "additionalInfo": "optional info" +} +``` + +--- + +#### `getsparknames [onlyown]` +Returns a list of all Spark names. + +**Arguments:** +1. `onlyown` (boolean, optional, default=false) - Only show wallet's names + +**Result:** +```json +[ + { + "name": "myname", + "address": "sm1...", + "validUntil": 123456 + } +] +``` + +--- + +### Wallet RPCs + +#### `getnewaddress ["account"]` +Returns a new Firo address for receiving payments. + +**Arguments:** +1. `account` (string, optional) - DEPRECATED. The account name + +**Result:** `"firoaddress"` (string) - The new Firo address + +**Example:** +```bash +firo-cli getnewaddress +firo-cli getnewaddress "myaccount" +``` + +--- + +#### `getbalance ["account"] [minconf] [include_watchonly]` +Returns the total available balance. + +**Arguments:** +1. `account` (string, optional) - DEPRECATED +2. `minconf` (numeric, optional, default=1) - Minimum confirmations +3. `include_watchonly` (boolean, optional, default=false) - Include watch-only + +**Result:** `n` (numeric) - Balance in FIRO + +**Example:** +```bash +firo-cli getbalance +firo-cli getbalance "*" 6 +``` + +--- + +#### `sendtoaddress "address" amount [comment] [comment_to] [subtractfeefromamount]` +Send an amount to a given address. + +**Arguments:** +1. `address` (string, required) - The Firo address +2. `amount` (numeric, required) - Amount in FIRO +3. `comment` (string, optional) - Comment for reference +4. `comment_to` (string, optional) - Recipient comment +5. `subtractfeefromamount` (boolean, optional, default=false) + +**Result:** `"txid"` (string) - The transaction id + +**Example:** +```bash +firo-cli sendtoaddress "aXy123..." 0.1 +firo-cli sendtoaddress "aXy123..." 0.1 "donation" "seans outance" +``` + +--- + +#### `listunspent [minconf] [maxconf] [addresses]` +Returns array of unspent transaction outputs. + +**Arguments:** +1. `minconf` (numeric, optional, default=1) - Minimum confirmations +2. `maxconf` (numeric, optional, default=9999999) - Maximum confirmations +3. `addresses` (array, optional) - Filter by addresses + +**Result:** +```json +[ + { + "txid": "...", + "vout": 0, + "address": "...", + "amount": 1.0, + "confirmations": 123, + "spendable": true, + "solvable": true + } +] +``` + +--- + +#### `gettransaction "txid" [include_watchonly]` +Get detailed information about an in-wallet transaction. + +**Arguments:** +1. `txid` (string, required) - The transaction id +2. `include_watchonly` (boolean, optional, default=false) + +**Result:** +```json +{ + "amount": 1.0, + "fee": -0.0001, + "confirmations": 123, + "blockhash": "...", + "txid": "...", + "time": 1234567890, + "details": [...], + "hex": "..." +} +``` + +--- + +#### `listreceivedbyaddress [minconf] [include_empty] [include_watchonly]` +List balances by receiving address. + +**Arguments:** +1. `minconf` (numeric, optional, default=1) +2. `include_empty` (boolean, optional, default=false) +3. `include_watchonly` (boolean, optional, default=false) + +**Result:** +```json +[ + { + "address": "...", + "account": "", + "amount": 1.0, + "confirmations": 123 + } +] +``` + +--- + +#### `listtransactions ["account"] [count] [skip] [include_watchonly]` +Returns recent transactions for the wallet. + +**Arguments:** +1. `account` (string, optional) - DEPRECATED +2. `count` (numeric, optional, default=10) +3. `skip` (numeric, optional, default=0) +4. `include_watchonly` (boolean, optional, default=false) + +**Result:** +```json +[ + { + "account": "", + "address": "...", + "category": "send|receive", + "amount": 1.0, + "confirmations": 123, + "txid": "...", + "time": 1234567890 + } +] +``` + +--- + +#### `walletpassphrase "passphrase" timeout` +Stores the wallet decryption key in memory for 'timeout' seconds. + +**Arguments:** +1. `passphrase` (string, required) - The wallet passphrase +2. `timeout` (numeric, required) - Seconds to keep unlocked + +**Example:** +```bash +firo-cli walletpassphrase "my pass phrase" 60 +``` + +--- + +#### `walletlock` +Removes the wallet encryption key from memory, locking the wallet. + +**Arguments:** None + +**Example:** +```bash +firo-cli walletlock +``` + +--- + +#### `encryptwallet "passphrase"` +Encrypts the wallet with 'passphrase'. + +**Arguments:** +1. `passphrase` (string, required) - The passphrase + +**Example:** +```bash +firo-cli encryptwallet "my pass phrase" +``` + +--- + +#### `dumpprivkey "address"` +Reveals the private key corresponding to 'address'. + +**Arguments:** +1. `address` (string, required) - The Firo address + +**Result:** `"key"` (string) - The private key + +--- + +#### `importprivkey "privkey" ["label"] [rescan]` +Adds a private key to your wallet. + +**Arguments:** +1. `privkey` (string, required) - The private key +2. `label` (string, optional, default="") +3. `rescan` (boolean, optional, default=true) + +--- + +#### `backupwallet "destination"` +Safely copies wallet.dat to destination. + +**Arguments:** +1. `destination` (string, required) - Destination path/filename + +--- + +#### `signmessage "firoaddress" "message"` +Sign a message with the private key of a transparent Firo address. + +**Arguments:** +1. `firoaddress` (string, required) - The transparent Firo address whose private key will sign the message +2. `message` (string, required) - The message to sign + +**Result:** `"signature"` (string) - Compact signature encoded in base64 + +**Notes:** +- Requires the wallet to be unlocked +- The address must refer to a wallet key, not a script address + +**Example:** +```bash +firo-cli walletpassphrase "mypassphrase" 30 +firo-cli signmessage "aXy123..." "my message" +``` + +--- + + +### Utility RPCs + +#### `verifymessage "address" "signature" "message"` +Verify a signed message for a transparent Firo address. + +**Arguments:** +1. `address` (string, required) - The transparent Firo address used for signing +2. `signature` (string, required) - The base64 signature returned by `signmessage` +3. `message` (string, required) - The signed message + +**Result:** `true|false` (boolean) - Whether the signature is valid + +**Example:** +```bash +firo-cli verifymessage "aXy123..." "signature" "my message" +``` + +--- + + +#### `signmessagewithprivkey "privkey" "message"` +Sign a message with a WIF private key without using the wallet key store. + +**Arguments:** +1. `privkey` (string, required) - The private key in WIF format +2. `message` (string, required) - The message to sign + +**Result:** `"signature"` (string) - Compact signature encoded in base64 + +**Example:** +```bash +firo-cli signmessagewithprivkey "privkey" "my message" +``` + +--- + +### Network RPCs + +#### `getnetworkinfo` +Returns information about P2P networking. + +**Arguments:** None + +**Result:** +```json +{ + "version": 140400, + "subversion": "/Firo:0.14.4/", + "protocolversion": 70214, + "localservices": "000000000000000d", + "localrelay": true, + "timeoffset": 0, + "networkactive": true, + "connections": 8, + "networks": [...], + "relayfee": 0.00001, + "localaddresses": [...] +} +``` + +--- + +#### `getpeerinfo` +Returns data about each connected network node. + +**Arguments:** None + +**Result:** +```json +[ + { + "id": 1, + "addr": "192.168.1.1:8168", + "services": "000000000000000d", + "lastsend": 1234567890, + "lastrecv": 1234567890, + "bytessent": 123456, + "bytesrecv": 234567, + "conntime": 1234567800, + "version": 70214, + "subver": "/Firo:0.14.4/", + "inbound": false, + "synced_headers": 123456, + "synced_blocks": 123456 + } +] +``` + +--- + +#### `getconnectioncount` +Returns the number of connections to other nodes. + +**Arguments:** None + +**Result:** `n` (numeric) - The connection count + +--- + +#### `addnode "node" "command"` +Attempts to add or remove a node from the addnode list. + +**Arguments:** +1. `node` (string, required) - The node address +2. `command` (string, required) - "add", "remove", or "onetry" + +**Example:** +```bash +firo-cli addnode "192.168.0.6:8168" "onetry" +``` + +--- + +#### `disconnectnode "address"` +Immediately disconnects from the specified node. + +**Arguments:** +1. `address` (string, required) - The IP address/port + +--- + +#### `ping` +Requests a ping to be sent to all other nodes. + +**Arguments:** None + +--- + +#### `setban "subnet" "command" [bantime] [absolute]` +Adds or removes an IP/Subnet from the banned list. + +**Arguments:** +1. `subnet` (string, required) - IP/Subnet +2. `command` (string, required) - "add" or "remove" +3. `bantime` (numeric, optional) - Time in seconds +4. `absolute` (boolean, optional) + +--- + +#### `listbanned` +List all banned IPs/Subnets. + +**Arguments:** None + +--- + +#### `clearbanned` +Clear all banned IPs. + +**Arguments:** None + +--- + +### Mining RPCs + +#### `getmininginfo` +Returns mining-related information. + +**Arguments:** None + +**Result:** +```json +{ + "blocks": 123456, + "currentblocksize": 1234, + "currentblockweight": 4000, + "currentblocktx": 5, + "difficulty": 12345.678, + "networkhashps": 123456789.0, + "pooledtx": 10, + "chain": "main" +} +``` + +--- + +#### `getnetworkhashps [nblocks] [height]` +Returns estimated network hashes per second. + +**Arguments:** +1. `nblocks` (numeric, optional, default=120) - Blocks to average over +2. `height` (numeric, optional, default=-1) - Estimate at height + +**Result:** `n` (numeric) - Hashes per second + +--- + +#### `getblocktemplate [template_request]` +Returns data needed to construct a block for mining. + +**Arguments:** +1. `template_request` (json object, optional) - BIP 22/23 compliant request + +**Result:** +```json +{ + "version": 4, + "previousblockhash": "...", + "transactions": [...], + "coinbaseaux": {...}, + "coinbasevalue": 625000000, + "target": "...", + "mintime": 1234567890, + "mutable": ["time", "transactions", "prevblock"], + "noncerange": "00000000ffffffff", + "sigoplimit": 80000, + "sizelimit": 1000000, + "curtime": 1234567900, + "bits": "1d00ffff", + "height": 123457, + "znode": [...], + "znode_payments_started": true, + "znode_payments_enforced": true +} +``` + +--- + +#### `submitblock "hexdata"` +Attempts to submit a new block to the network. + +**Arguments:** +1. `hexdata` (string, required) - The hex-encoded block data + +--- + +#### `generate nblocks [maxtries]` +Mine up to nblocks blocks immediately (regtest only). + +**Arguments:** +1. `nblocks` (numeric, required) - Number of blocks +2. `maxtries` (numeric, optional, default=1000000) + +**Result:** `[blockhashes]` (array) - Hashes of generated blocks + +--- + +#### `generatetoaddress nblocks address [maxtries]` +Mine blocks immediately to a specified address. + +**Arguments:** +1. `nblocks` (numeric, required) +2. `address` (string, required) +3. `maxtries` (numeric, optional, default=1000000) + +--- + +#### `pprpcsb "header_hash" "mix_hash" "nonce"` +Submit a ProgPOW solution via RPC. + +**Arguments:** +1. `header_hash` (string, required) - ProgPOW header hash +2. `mix_hash` (string, required) - Mix hash from GPU miner +3. `nonce` (string, required) - Block nonce + +--- + +### Masternode RPCs + +#### `evoznode list [mode] [filter]` +Get a list of masternodes in different modes. + +**Arguments:** +1. `mode` (string, optional, default="json") + - `addr` - IP addresses + - `full` - Full info: status, payee, lastpaidtime, lastpaidblock, IP + - `info` - Info: status, payee, IP + - `json` - JSON format + - `lastpaidblock` - Last block paid + - `lastpaidtime` - Last time paid + - `owneraddress` - Owner Firo address + - `payee` - Payout address + - `pubKeyOperator` - Operator public key + - `status` - ENABLED/POSE_BANNED + - `votingaddress` - Voting address +2. `filter` (string, optional) - Filter by outpoint + +**Example:** +```bash +firo-cli evoznode list +firo-cli evoznode list json "aXy123" +``` + +--- + +#### `evoznode count [mode]` +Get information about number of masternodes. + +**Arguments:** +1. `mode` (string, optional) - "total", "enabled", "all" + +**Result (no mode):** +```json +{ + "total": 4000, + "enabled": 3900 +} +``` + +--- + +#### `evoznode status` +Print masternode status information (if running as masternode). + +**Result:** +```json +{ + "outpoint": "txid:n", + "service": "192.168.1.1:8168", + "proTxHash": "...", + "collateralHash": "...", + "collateralIndex": 0, + "dmnState": {...}, + "state": "READY", + "status": "Ready" +} +``` + +--- + +#### `evoznode current` +Print info on current masternode winner. + +**Result:** +```json +{ + "height": 123456, + "IP:port": "192.168.1.1:8168", + "proTxHash": "...", + "outpoint": "txid:n", + "payee": "aXy123..." +} +``` + +--- + +#### `evoznode winner` +Print info on next masternode winner to vote for. + +--- + +#### `evoznode winners [count] [filter]` +Print list of masternode winners. + +**Arguments:** +1. `count` (numeric, optional) - Number of winners +2. `filter` (string, optional) - Filter results + +--- + +#### `evoznsync status` +Returns the masternode sync status. + +**Result:** +```json +{ + "AssetID": 999, + "AssetName": "MASTERNODE_SYNC_FINISHED", + "AssetStartTime": 1234567890, + "Attempt": 0, + "IsBlockchainSynced": true, + "IsSynced": true, + "IsFailed": false +} +``` + +--- + +### Privacy RPCs - Lelantus + +#### `mintlelantus amount` +Mint Lelantus coins from transparent balance. + +**Arguments:** +1. `amount` (numeric, required) - Amount to mint in FIRO + +**Result:** `"txid"` (string) - The transaction id + +--- + +#### `joinsplit [recipients] [subtractfeefrom]` +Create a Lelantus JoinSplit transaction. + +**Arguments:** +1. `recipients` (array, required) - Recipient addresses and amounts +2. `subtractfeefrom` (array, optional) - Indices to subtract fee from + +**Example:** +```bash +firo-cli joinsplit "[{\"address\":\"aXy...\",\"amount\":1.0}]" +``` + +--- + +#### `listlelantusmints [all]` +List Lelantus mints in wallet. + +**Arguments:** +1. `all` (boolean, optional, default=false) - Include used mints + +--- + +#### `resetlelantusmint` +Reset Lelantus mint state for wallet recovery. + +--- + +### Privacy RPCs - Spark + +#### `mintspark [outputs]` +Create a Spark mint transaction. + +**Arguments:** +1. `outputs` (array, required) - Array of {address, amount, memo} + +**Example:** +```bash +firo-cli mintspark "[{\"address\":\"sm1...\",\"amount\":1.0,\"memo\":\"test\"}]" +``` + +--- + +#### `spendspark [recipients] [subtractfeefrom] [coincontrol]` +Create a Spark spend transaction. + +**Arguments:** +1. `recipients` (array, required) - Recipient addresses and amounts +2. `subtractfeefrom` (array, optional) +3. `coincontrol` (object, optional) - Coin selection options + +**Example:** +```bash +firo-cli spendspark "[{\"address\":\"sm1...\",\"amount\":0.5}]" +``` + +--- + +#### `getnewsparkaddress` +Generate a new Spark address. + +**Result:** `"sparkaddress"` (string) - New Spark address + +--- + +#### `getsparkbalance` +Get total Spark balance. + +**Result:** +```json +{ + "availableBalance": 10.5, + "unconfirmedBalance": 0.5, + "totalBalance": 11.0 +} +``` + +--- + +#### `listsparkmints [all]` +List Spark mints in wallet. + +**Arguments:** +1. `all` (boolean, optional, default=false) - Include spent + +--- + +#### `listsparkspends` +List Spark spends in wallet. + +--- + +### Mobile RPCs + +These RPCs are designed for mobile wallet support (requires `-mobile` flag). + +#### `getanonymityset coinGroupId startBlockHash` +Returns Lelantus anonymity set data. + +**Arguments:** +1. `coinGroupId` (int, required) - Coin group ID +2. `startBlockHash` (string, required) - Starting block hash (empty for full set) + +**Result:** +```json +{ + "blockHash": "base64_encoded", + "setHash": "base64_encoded", + "coins": [[coin_data, txhash, tag_or_amount, txhash], ...] +} +``` + +--- + +#### `getsparkanonymityset coinGroupId startBlockHash` +Returns Spark anonymity set data. + +**Arguments:** +1. `coinGroupId` (int, required) - Coin group ID +2. `startBlockHash` (string, required) - Starting block hash + +**Result:** +```json +{ + "blockHash": "base64_encoded", + "setHash": "base64_encoded", + "coins": [[serialized_coin, txhash, serial_context], ...] +} +``` + +--- + +#### `getusedcoinserials startNumber` +Returns used Lelantus coin serials. + +**Arguments:** +1. `startNumber` (int, required) - Starting offset + +**Result:** +```json +{ + "serials": ["base64_serial", ...] +} +``` + +--- + +#### `getusedcoinstags startNumber` +Returns used Spark coin tags. + +**Arguments:** +1. `startNumber` (int, required) - Starting offset + +**Result:** +```json +{ + "tags": ["base64_tag", ...] +} +``` + +--- + +#### `getusedcoinstagstxhashes startNumber` +Returns used Spark coin tags paired with the transaction IDs where they were spent. + +**Arguments:** +1. `startNumber` (int, required) - Number of existing elements on the client side; results before this offset are skipped + +**Result:** +```json +{ + "tagsandtxids": [ + ["base64_tag", "base64_txid"], + ... + ] +} +``` + +**Notes:** +- Requires the `-mobile` flag +- Both the tag and transaction ID are base64 encoded in the result + +--- + +#### `getlatestcoinid` +Returns the latest Lelantus coin group ID. + +**Result:** `n` (numeric) - Latest coin group ID + +--- + +#### `getsparklatestcoinid` +Returns the latest Spark coin group ID. + +**Result:** `n` (numeric) - Latest coin group ID + +--- + +#### `getfeerate` +Returns the current minimum fee rate. + +**Result:** +```json +{ + "rate": 1000 +} +``` + +--- + +#### `getmempoolsparktxids` +Returns Spark transaction IDs in mempool. + +**Result:** `["base64_txid", ...]` + +--- + +#### `getmempoolsparktxs [txids]` +Returns Spark metadata for transactions. + +**Arguments:** +1. `txids` (object, required) - Array of transaction IDs + +**Result:** +```json +{ + "base64_txid": { + "lTags": [...], + "serial_context": [...], + "coins": [...], + "isLocked": true + } +} +``` + +--- + +### Address Index RPCs + +Requires `-addressindex` flag. + +#### `getaddressbalance [addresses]` +Returns balance for addresses. + +**Arguments:** +1. (object, required) - `{"addresses": ["addr1", "addr2"]}` + +**Result:** +```json +{ + "balance": 10000000, + "received": 20000000 +} +``` + +--- + +#### `getaddressutxos [addresses]` +Returns UTXOs for addresses. + +**Arguments:** +1. (object, required) - `{"addresses": ["addr1"]}` + +**Result:** +```json +[ + { + "address": "aXy...", + "txid": "...", + "outputIndex": 0, + "script": "hex...", + "satoshis": 10000000, + "height": 123456 + } +] +``` + +--- + +#### `getaddresstxids [addresses]` +Returns transaction IDs for addresses. + +**Arguments:** +1. (object, required) - `{"addresses": ["addr1"], "start": 0, "end": 100000}` + +**Result:** `["txid1", "txid2", ...]` + +--- + +#### `getaddressdeltas [addresses]` +Returns balance changes for addresses. + +**Arguments:** +1. (object, required) - `{"addresses": ["addr1"], "start": 0, "end": 100000}` + +**Result:** +```json +[ + { + "satoshis": 10000000, + "txid": "...", + "index": 0, + "blockindex": 1, + "height": 123456, + "address": "aXy..." + } +] +``` + +--- + +#### `getaddressmempool [addresses]` +Returns mempool deltas for addresses. + +**Arguments:** +1. (object, required) - `{"addresses": ["addr1"]}` + +--- + + + +### Complete RPC Command Index + +This index is derived from the RPC command registration tables in the current branch. It provides full command coverage even where the detailed sections above only expand a selected subset of commands. Categories such as `hidden` and `test` are internal or debug-oriented interfaces rather than standard end-user RPCs. + +#### `blockchain` + +`clearmempool`, `getbestblockhash`, `getblock`, `getblockchaininfo`, `getblockcount`, `getblockhash`, `getblockhashes`, `getblockheader`, `getchaintips`, `getdifficulty`, `getmempoolancestors`, `getmempooldescendants`, `getmempoolentry`, `getmempoolinfo`, `getrawmempool`, `getsparknamedata`, `getsparknames`, `getsparknametxdetails`, `getspecialtxes`, `gettxout`, `gettxoutproof`, `gettxoutsetinfo`, `preciousblock`, `pruneblockchain`, `verifychain`, `verifytxoutproof` + +#### `wallet` + +`abandontransaction`, `addmultisigaddress`, `addwitnessaddress`, `autoMintlelantus`, `automintspark`, `backupwallet`, `bumpfee`, `dumpprivkey`, `dumpsparkviewkey`, `dumpwallet`, `encryptwallet`, `getaccount`, `getaccountaddress`, `getaddressesbyaccount`, `getallsparkaddresses`, `getbalance`, `getnewaddress`, `getnewsparkaddress`, `getprivatebalance`, `getrawchangeaddress`, `getreceivedbyaccount`, `getreceivedbyaddress`, `getsparkaddressbalance`, `getsparkbalance`, `getsparkcoinaddr`, `getsparkdefaultaddress`, `gettotalbalance`, `gettransaction`, `getunconfirmedbalance`, `getwalletinfo`, `identifysparkcoins`, `importaddress`, `importmulti`, `importprivkey`, `importprunedfunds`, `importpubkey`, `importwallet`, `joinsplit`, `keypoolrefill`, `lelantustospark`, `listaccounts`, `listaddressbalances`, `listaddressgroupings`, `listlelantusjoinsplits`, `listlelantusmints`, `listlockunspent`, `listreceivedbyaccount`, `listreceivedbyaddress`, `listsinceblock`, `listsparkmints`, `listsparkspends`, `listtransactions`, `listunspent`, `listunspentlelantusmints`, `listunspentsparkmints`, `lockunspent`, `mintlelantus`, `mintspark`, `move`, `proveprivatetxown`, `regeneratemintpool`, `registersparkname`, `removeprunedfunds`, `removetxmempool`, `removetxwallet`, `requestsparknametransfer`, `resetlelantusmint`, `resetsparkmints`, `sendfrom`, `sendmany`, `sendspark`, `sendsparkmany`, `sendtoaddress`, `sendtransparent`, `setaccount`, `setlelantusmintstatus`, `setmininput`, `setsparkmintstatus`, `settxfee`, `signmessage`, `spendspark`, `transfersparkname`, `walletlock`, `walletpassphrase`, `walletpassphrasechange` + +#### `util` + +`createmultisig`, `estimatefee`, `estimatepriority`, `estimatesmartfee`, `estimatesmartpriority`, `signmessagewithprivkey`, `validateaddress`, `verifymessage` + +#### `network` + +`addnode`, `clearbanned`, `disconnectnode`, `getaddednodeinfo`, `getconnectioncount`, `getnettotals`, `getnetworkinfo`, `getpeerinfo`, `listbanned`, `ping`, `setban`, `setnetworkactive` + +#### `mining` + +`getblocktemplate`, `getmininginfo`, `getnetworkhashps`, `pprpcsb`, `prioritisetransaction`, `submitblock` + +#### `rawtransactions` + +`createrawtransaction`, `decoderawtransaction`, `decodescript`, `fundrawtransaction`, `getrawtransaction`, `sendrawtransaction`, `signrawtransaction` + +#### `addressindex` + +`getAddressNumWBalance`, `getCVE17144amount`, `getaddressbalance`, `getaddressdeltas`, `getaddressmempool`, `getaddresstxids`, `getaddressutxos`, `gettotalsupply`, `getzerocoinpoolbalance` + +#### `mobile` + +`checkifmncollateral`, `getanonymityset`, `getfeerate`, `getlatestcoinid`, `getmempoolsparktxids`, `getmempoolsparktxs`, `getmintmetadata`, `getsparkanonymityset`, `getsparkanonymitysetmeta`, `getsparkanonymitysetsector`, `getsparklatestcoinid`, `getsparkmintmetadata`, `getusedcoinserials`, `getusedcoinstags`, `getusedcoinstagstxhashes` + +#### `control` + +`getinfo`, `getmemoryinfo`, `help`, `stop` + +#### `generating` + +`generate`, `generatetoaddress`, `setgenerate` + +#### `bip47` + +`createrapaddress`, `listrapaddresses`, `sendtorapaddress`, `setupchannel`, `setusednumber` + +#### `evo` + +`bls`, `protx`, `quorum`, `removeislock`, `spork` + +#### `firo` + +`evoznode`, `evoznodelist`, `evoznsync`, `verifyprivatetxown`, `znode`, `znodelist`, `znsync` + +#### `hidden` + +`echo`, `echojson`, `getinfoex`, `getnewexchangeaddress`, `invalidateblock`, `reconsiderblock`, `resendwallettransactions`, `setmocktime`, `waitforblock`, `waitforblockheight`, `waitfornewblock` + +#### `test` + +`rpcNestedTest` + + + +## Privacy Protocols + +### Lelantus Protocol + +Lelantus is a privacy protocol that provides anonymous transactions without trusted setup. It uses: + +- **One-out-of-many proofs** for anonymity +- **Range proofs** to ensure valid amounts +- **Serial numbers** to prevent double-spending + +#### Key Components + +**PublicCoin** (`src/liblelantus/coin.h`) +```cpp +class PublicCoin { + GroupElement value; // Pedersen commitment + // Getters + const GroupElement& getValue() const; + bool operator==(const PublicCoin& other) const; +}; +``` + +**PrivateCoin** (`src/liblelantus/coin.h`) +```cpp +class PrivateCoin { + const Params* params; + PublicCoin publicCoin; + Scalar serialNumber; // Unique identifier + Scalar randomness; // Blinding factor + uint64_t v; // Value + // Methods + const Scalar& getSerialNumber() const; + const Scalar& getRandomness() const; + uint64_t getV() const; +}; +``` + +**JoinSplit** (`src/liblelantus/joinsplit.h`) +- Combines multiple input coins +- Produces new output coins +- Proves spend authorization without revealing which coins are spent + +```cpp +class JoinSplit { + // Create a JoinSplit transaction + JoinSplit( + const Params* p, + const std::vector>& Cin, + const std::map>& anonymity_sets, + const Scalar& Vout, + const std::vector& Cout, + uint64_t fee, + const uint256& txHash + ); + + // Verify the JoinSplit + bool Verify( + const std::map>& anonymity_sets, + const std::vector& Cout, + uint64_t Vout, + const uint256& txHash + ) const; +}; +``` + +--- + +### Spark Protocol + +Spark is Firo's latest privacy protocol with improved features: + +- **Stealth addresses** for recipient privacy +- **Better scalability** than Lelantus +- **Flexible denominations** + +#### Key Components + +**SpendKey** (`src/libspark/keys.h`) +```cpp +class SpendKey { + SpendKey(const Params* params); + SpendKey(const Params* params, const Scalar& r_); + + const Scalar& get_s1() const; + const Scalar& get_s2() const; + const Scalar& get_r() const; +}; +``` + +**FullViewKey** (`src/libspark/keys.h`) +```cpp +class FullViewKey { + FullViewKey(const SpendKey& spend_key); + + const Scalar& get_s1() const; + const Scalar& get_s2() const; + const GroupElement& get_D() const; + const GroupElement& get_P2() const; +}; +``` + +**IncomingViewKey** (`src/libspark/keys.h`) +```cpp +class IncomingViewKey { + IncomingViewKey(const FullViewKey& full_view_key); + + const Scalar& get_s1() const; + const GroupElement& get_P2() const; + uint64_t get_diversifier(const std::vector& d) const; +}; +``` + +**Address** (`src/libspark/keys.h`) +```cpp +class Address { + Address(const IncomingViewKey& incoming_view_key, uint64_t i); + + std::string encode(unsigned char network) const; + unsigned char decode(const std::string& str); + + const std::vector& get_d() const; + const GroupElement& get_Q1() const; + const GroupElement& get_Q2() const; +}; +``` + +**Coin** (`src/libspark/coin.h`) +```cpp +class Coin { + GroupElement S; // Serial commitment + GroupElement K; // Recovery key + GroupElement C; // Value commitment + // Encrypted data + std::vector r_; + + // Methods for coin recovery and verification +}; +``` + +**SpendTransaction** (`src/libspark/spend_transaction.h`) +```cpp +class SpendTransaction { + SpendTransaction( + const Params* params, + const FullViewKey& full_view_key, + const SpendKey& spend_key, + const std::vector& inputs, + const std::unordered_map& cover_set_data, + const std::unordered_map>& cover_sets, + uint64_t f, + uint64_t vout, + const std::vector& outputs + ); + + uint64_t getFee(); + const std::vector& getUsedLTags() const; + const std::vector& getOutCoins(); + + static bool verify( + const Params* params, + const std::vector& transactions, + const std::unordered_map>& cover_sets + ); +}; +``` + +#### Spark Address Format + +Spark addresses use Bech32m encoding: +- **Mainnet prefix:** `sm` +- **Testnet prefix:** `st` +- **Regtest prefix:** `sr` +- **Devnet prefix:** `sd` + +Example: `sm1qw508d6qejxtdg4y5r3zarvary0c5xw7k...` + +#### Address Validation + +##### Transparent Addresses + +Transparent addresses use Base58Check encoding. On mainnet: +- **P2PKH version byte:** `0x52` (decimal `82`). Standard wallet addresses from this version can begin with `a` or `Z`, depending on the payload. +- **P2SH version byte:** `0x07`. These addresses typically begin with `3`. +- **Exchange address version bytes:** `0x01b9bb`. These encode to the dedicated `EXX...` format. + +On testnet: +- **P2PKH version byte:** `0x41` (decimal `65`), typically beginning with `T` +- **P2SH version byte:** `0xb2` (decimal `178`) + +Useful regexes: +- Mainnet P2PKH addresses beginning with `a`: `^a[1-9A-HJ-NP-Za-km-z]{33}$` +- Mainnet P2PKH addresses beginning with `Z`: `^Z[1-9A-HJ-NP-Za-km-z]{33}$` +- Combined mainnet P2PKH shorthand: `^[aZ][1-9A-HJ-NP-Za-km-z]{33}$` + +##### Spark Addresses + +Spark addresses use Bech32m with a human-readable part composed of `s` plus the network code: +- **Mainnet HRP:** `sm` +- **Testnet HRP:** `st` +- **Regtest HRP:** `sr` +- **Devnet HRP:** `sd` + +Useful regex: +- Mainnet Spark addresses: `^sm1[a-z0-9]{100,}$` + +Validation notes: +- The decoded address must use Bech32m, not legacy Bech32 +- The decoded payload must be exactly `2 * GroupElement::serialize_size + AES_BLOCKSIZE` bytes +- The embedded network byte must match the active chain parameters + +--- + +## LLMQ and ChainLocks + +### ChainLocks + +ChainLocks provide instant finality and 51% attack protection. + +**CChainLockSig** (`src/llmq/quorums_chainlocks.h`) +```cpp +class CChainLockSig { + int32_t nHeight; // Block height + uint256 blockHash; // Block hash + CBLSSignature sig; // BLS signature from quorum +}; +``` + +**CChainLocksHandler** (`src/llmq/quorums_chainlocks.h`) +```cpp +class CChainLocksHandler { + // Check if block has ChainLock + bool HasChainLock(int nHeight, const uint256& blockHash); + + // Check for conflicting ChainLock + bool HasConflictingChainLock(int nHeight, const uint256& blockHash); + + // Check if transaction is safe for mining + bool IsTxSafeForMining(const uint256& txid); + + // Process new ChainLock + void ProcessNewChainLock(NodeId from, const CChainLockSig& clsig, const uint256& hash); +}; +``` + +### InstantSend + +Provides instant transaction confirmation. + +**CInstantSendLock** (`src/llmq/quorums_instantsend.h`) +```cpp +class CInstantSendLock { + std::vector inputs; + uint256 txid; + CBLSSignature sig; +}; +``` + +**CInstantSendManager** (`src/llmq/quorums_instantsend.h`) +```cpp +class CInstantSendManager { + // Check if transaction is locked + bool IsLocked(const uint256& txHash); + + // Get InstantSend lock + CInstantSendLockPtr GetConflictingLock(const CTransaction& tx); +}; +``` + +--- + +## Masternode System + +### Deterministic Masternodes + +Masternodes provide network services and participate in governance. + +**CDeterministicMN** (`src/evo/deterministicmns.h`) +```cpp +class CDeterministicMN { + uint256 proTxHash; // ProRegTx hash + COutPoint collateralOutpoint; // Collateral outpoint + uint16_t nOperatorReward; // Operator reward share + CDeterministicMNState* pdmnState; // Current state +}; +``` + +**CDeterministicMNState** (`src/evo/deterministicmns.h`) +```cpp +class CDeterministicMNState { + int nRegisteredHeight; + int nLastPaidHeight; + int nPoSePenalty; + int nPoSeRevivedHeight; + int nPoSeBanHeight; + uint16_t nRevocationReason; + uint256 confirmedHash; + CService addr; // IP address and port + CKeyID keyIDOwner; + CBLSPublicKey pubKeyOperator; + CKeyID keyIDVoting; + CScript scriptPayout; + CScript scriptOperatorPayout; +}; +``` + +### ProRegTx (Masternode Registration) + +```cpp +class CProRegTx { + uint16_t nVersion; + uint16_t nType; + uint16_t nMode; + COutPoint collateralOutpoint; + CService addr; + CKeyID keyIDOwner; + CBLSPublicKey pubKeyOperator; + CKeyID keyIDVoting; + uint16_t nOperatorReward; + CScript scriptPayout; + uint256 inputsHash; + std::vector vchSig; // Owner signature +}; +``` + +--- + +## Core Components + +### Transaction Types + +Firo supports multiple transaction types: + +| Type | Value | Description | +|------|-------|-------------| +| `TRANSACTION_NORMAL` | 0 | Standard transaction | +| `TRANSACTION_PROVIDER_REGISTER` | 1 | Masternode registration | +| `TRANSACTION_PROVIDER_UPDATE_SERVICE` | 2 | Update masternode service | +| `TRANSACTION_PROVIDER_UPDATE_REGISTRAR` | 3 | Update masternode registrar | +| `TRANSACTION_PROVIDER_UPDATE_REVOKE` | 4 | Revoke masternode | +| `TRANSACTION_COINBASE` | 5 | Coinbase special transaction | +| `TRANSACTION_LELANTUS` | 8 | Lelantus transaction | +| `TRANSACTION_SPARK` | 9 | Spark transaction | + +### CWallet + +The wallet provides key management and transaction handling. + +**Key Methods:** +```cpp +class CWallet { + // Generate new key + bool GetKeyFromPool(CPubKey& result); + + // Get balance + CAmount GetBalance() const; + CAmount GetUnconfirmedBalance() const; + CAmount GetImmatureBalance() const; + + // Transactions + bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, + CReserveKey& reservekey, CAmount& nFeeRet, + std::string& strFailReason, const CCoinControl* coinControl); + bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman); + + // Privacy operations + bool MintAndStoreLelantus(CAmount nValue, std::vector& mints); + bool JoinSplitLelantus(const std::vector& vecSend, CWalletTx& wtxNew); + + // Spark operations + std::pair GetSparkBalance() const; + bool CreateSparkMintTransactions(const std::vector& outputs, + std::vector>& wtxAndFee); +}; +``` + +### Script Types + +```cpp +enum txnouttype { + TX_NONSTANDARD, // Unrecognized script + TX_PUBKEY, // Pay to public key + TX_PUBKEYHASH, // Pay to public key hash (P2PKH) + TX_SCRIPTHASH, // Pay to script hash (P2SH) + TX_MULTISIG, // Multisignature + TX_NULL_DATA, // OP_RETURN + TX_WITNESS_V0_SCRIPTHASH, // P2WSH + TX_WITNESS_V0_KEYHASH, // P2WPKH + TX_ZEROCOINMINT, // Zerocoin mint (deprecated) + TX_LELANTUSMINT, // Lelantus mint + TX_LELANTUSJMINT, // Lelantus JMint + TX_SPARKMINT, // Spark mint + TX_SPARKSMINT, // Spark SMint +}; +``` + +--- + +## Examples + +### Example 1: Basic Wallet Operations + +```bash +# Create new address +firo-cli getnewaddress + +# Check balance +firo-cli getbalance + +# Send coins +firo-cli sendtoaddress "aXy123..." 1.0 + +# List transactions +firo-cli listtransactions +``` + +### Example 2: Spark Privacy Operations + +```bash +# Generate Spark address +firo-cli getnewsparkaddress + +# Mint to Spark (anonymize coins) +firo-cli mintspark "[{\"address\":\"sm1...\",\"amount\":10.0,\"memo\":\"savings\"}]" + +# Check Spark balance +firo-cli getsparkbalance + +# Send from Spark (private transaction) +firo-cli spendspark "[{\"address\":\"sm1...\",\"amount\":5.0}]" +``` + +### Example 3: Masternode Operations + +```bash +# List all masternodes +firo-cli evoznode list + +# Get masternode count +firo-cli evoznode count + +# Check sync status +firo-cli evoznsync status + +# Get current winner +firo-cli evoznode current +``` + +### Example 4: Mobile Wallet Integration + +```python +import requests +import base64 + +# Get anonymity set for Spark +def get_spark_anonymity_set(coin_group_id, start_block_hash=""): + result = rpc_call("getsparkanonymityset", [str(coin_group_id), start_block_hash]) + return { + "blockHash": base64.b64decode(result["blockHash"]), + "setHash": base64.b64decode(result["setHash"]), + "coins": result["coins"] + } + +# Get used coin tags +def get_used_tags(start_number): + result = rpc_call("getusedcoinstags", [str(start_number)]) + return [base64.b64decode(tag) for tag in result["tags"]] + +# Check if transaction is locked +def get_mempool_spark_txs(txids): + return rpc_call("getmempoolsparktxs", [{"txids": txids}]) +``` + +### Example 5: Address Index Queries + +```bash +# Get balance for address +firo-cli getaddressbalance '{"addresses": ["aXy123..."]}' + +# Get UTXOs +firo-cli getaddressutxos '{"addresses": ["aXy123..."]}' + +# Get transaction history +firo-cli getaddresstxids '{"addresses": ["aXy123..."], "start": 0, "end": 500000}' +``` + +### Example 6: Mining Pool Integration + +```python +# Get block template +template = rpc_call("getblocktemplate", [{"rules": ["segwit"]}]) + +# For ProgPOW mining, use the pprpcheader +header_hash = template["pprpcheader"] +epoch = template["pprpcepoch"] + +# Submit solution +result = rpc_call("pprpcsb", [header_hash, mix_hash, nonce]) +``` + +--- + +## Error Codes + +| Code | Name | Description | +|------|------|-------------| +| -1 | `RPC_MISC_ERROR` | General error | +| -3 | `RPC_TYPE_ERROR` | Invalid type | +| -4 | `RPC_WALLET_ERROR` | Wallet error | +| -5 | `RPC_INVALID_ADDRESS_OR_KEY` | Invalid address or key | +| -6 | `RPC_WALLET_INSUFFICIENT_FUNDS` | Insufficient funds | +| -8 | `RPC_INVALID_PARAMETER` | Invalid parameter | +| -12 | `RPC_WALLET_KEYPOOL_RAN_OUT` | Keypool exhausted | +| -13 | `RPC_WALLET_UNLOCK_NEEDED` | Wallet locked | +| -14 | `RPC_WALLET_PASSPHRASE_INCORRECT` | Wrong passphrase | +| -17 | `RPC_WALLET_ALREADY_UNLOCKED` | Already unlocked | +| -25 | `RPC_VERIFY_ERROR` | Verification failed | +| -26 | `RPC_VERIFY_REJECTED` | Transaction rejected | +| -27 | `RPC_VERIFY_ALREADY_IN_CHAIN` | Already in blockchain | + +--- + +## Configuration Options + +### Network Options + +| Option | Default | Description | +|--------|---------|-------------| +| `-testnet` | false | Use testnet | +| `-regtest` | false | Use regtest | +| `-port=` | 8168 | P2P port | +| `-rpcport=` | 8888 | RPC port | +| `-maxconnections=` | 125 | Maximum connections | + +### Wallet Options + +| Option | Default | Description | +|--------|---------|-------------| +| `-disablewallet` | false | Disable wallet | +| `-wallet=` | wallet.dat | Wallet filename | +| `-rescan` | false | Rescan blockchain | +| `-zapwallettxes` | 0 | Clear wallet transactions | + +### Privacy Options + +| Option | Default | Description | +|--------|---------|-------------| +| `-enablelelantus` | true | Enable Lelantus | +| `-enablespark` | true | Enable Spark | + +### Index Options + +| Option | Default | Description | +|--------|---------|-------------| +| `-addressindex` | false | Enable address index | +| `-txindex` | false | Enable transaction index | +| `-timestampindex` | false | Enable timestamp index | +| `-spentindex` | false | Enable spent index | + +### Mobile Support + +| Option | Default | Description | +|--------|---------|-------------| +| `-mobile` | false | Enable mobile RPCs | + +--- + +## See Also + +- [Build Instructions](build-unix.md) +- [Developer Notes](developer-notes.md) +- [Release Notes](release-notes.md) +- [ZMQ Documentation](zmq.md) diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 8269d88710..ccdc2a6880 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -104,7 +104,8 @@ Not OK (used plenty in the current source, but not picked up): // ``` -A full list of comment syntaxes picked up by doxygen can be found at http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html, +A full list of comment syntaxes picked up by doxygen can be found at +[the Doxygen docblocks reference](https://www.doxygen.nl/manual/docblocks.html). but if possible use one of the above styles. Development tips and tricks diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index abe9a539d5..0d21ec34a8 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -105,10 +105,6 @@ 'spark_mintspend.py', 'spark_spend_gettransaction.py', 'spark_setmintstatus_validation.py', - 'lelantus_mint.py', - 'lelantus_setmintstatus_validation.py', - 'lelantus_mintspend.py', - 'lelantus_spend_gettransaction.py', 'mempool_doublesend_oneblock.py', 'mempool_reorg.py', 'mempool_spendcoinbase.py', @@ -191,7 +187,6 @@ #, 'dip4-coinbasemerkleroots.py' # bip47 - 'bip47-sendreceive.py', 'bip47-walletrestore.py', 'sendtoaddress.py', diff --git a/qa/rpc-tests/bip47-sendreceive.py b/qa/rpc-tests/bip47-sendreceive.py deleted file mode 100755 index f3af936454..0000000000 --- a/qa/rpc-tests/bip47-sendreceive.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2017-2021 The Firo Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""dip47 sending receiving RPCs QA test. -""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * - -class Bip47SendReceive(BitcoinTestFramework): - - def __init__(self): - super().__init__() - self.setup_clean_chain = True - self.num_nodes = 3 - - def setup_network(self, split=False): - self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) - connect_nodes_bi(self.nodes,0,1) - connect_nodes_bi(self.nodes,1,2) - connect_nodes_bi(self.nodes,0,2) - self.is_network_split=False - self.sync_all() - - def run_test(self): - - self.nodes[1].generate(210) - node0_pcode = self.nodes[0].createrapaddress("node0-pcode0") - - try: - self.nodes[1].setupchannel(node0_pcode) - raise AssertionError('Lelantus balance should be zero') - except JSONRPCException as e: - assert(e.error['code']==-6) - - self.nodes[1].mintlelantus(1) - self.nodes[1].mintlelantus(1) - self.nodes[1].generate(10) - self.nodes[1].setupchannel(node0_pcode) - self.nodes[1].generate(1) - sync_blocks(self.nodes) - self.nodes[1].sendtorapaddress(node0_pcode, 10) - - self.nodes[1].generate(1) - self.sync_all() - - assert_equal(self.nodes[0].getbalance(), Decimal("10.0001")) - - self.nodes[0].sendtoaddress(self.nodes[2].getaccountaddress(""), 9.99) - - self.sync_all() - self.nodes[1].generate(1) - sync_blocks(self.nodes) - - assert_equal(self.nodes[2].getbalance(), Decimal("9.99")) - - -if __name__ == '__main__': - Bip47SendReceive().main() diff --git a/qa/rpc-tests/dip3-deterministicmns.py b/qa/rpc-tests/dip3-deterministicmns.py index f92946fe37..ae5582c677 100755 --- a/qa/rpc-tests/dip3-deterministicmns.py +++ b/qa/rpc-tests/dip3-deterministicmns.py @@ -296,14 +296,23 @@ def assert_mnlists(self, mns): for node in self.nodes: self.assert_mnlist(node, mns) - def assert_mnlist(self, node, mns): - if not self.compare_mnlist(node, mns): - expected = [] - for mn in mns: - expected.append('%s, %d' % (mn.collateral_txid, mn.collateral_vout)) - self.log.error('mnlist: ' + str(node.evoznode('list', 'status'))) - self.log.error('expected: ' + str(expected)) - raise AssertionError("mnlists does not match provided mns") + def assert_mnlist(self, node, mns, timeout=10): + # Poll briefly so we don't race state propagation (e.g. right after + # invalidateblock, where the masternode manager's tip/cache may not + # yet reflect the new active tip on a slow debug build). + deadline = time.time() + timeout + while True: + if self.compare_mnlist(node, mns): + return + if time.time() >= deadline: + break + time.sleep(0.1) + expected = [] + for mn in mns: + expected.append('%s, %d' % (mn.collateral_txid, mn.collateral_vout)) + self.log.error('mnlist: ' + str(node.evoznode('list', 'status'))) + self.log.error('expected: ' + str(expected)) + raise AssertionError("mnlists does not match provided mns") def wait_for_sporks(self, timeout=30): st = time.time() diff --git a/qa/rpc-tests/getblocktemplate_longpoll.py b/qa/rpc-tests/getblocktemplate_longpoll.py index 3cddf4046a..8dc3ef4a30 100755 --- a/qa/rpc-tests/getblocktemplate_longpoll.py +++ b/qa/rpc-tests/getblocktemplate_longpoll.py @@ -34,6 +34,8 @@ def __init__(self): def run_test(self): print("Warning: this test will take about 70 seconds in the best case. Be patient.") self.nodes[0].generate(10) + # Ensure all nodes are synced so that node 1 builds on the same tip in Test 2 + sync_blocks(self.nodes) templat = self.nodes[0].getblocktemplate() longpollid = templat['longpollid'] # longpollid should not change between successive invocations if nothing else happens @@ -45,29 +47,30 @@ def run_test(self): thr.start() # check that thread still lives thr.join(5) # wait 5 seconds or until thread exits - assert(thr.is_alive()) + assert thr.is_alive(), "Test 1: longpoll should not have returned yet" # Test 2: test that longpoll will terminate if another node generates a block self.nodes[1].generate(1) # generate a block on another node - # check that thread will exit now that new transaction entered mempool - thr.join(5) # wait 5 seconds or until thread exits - assert(not thr.is_alive()) + # check that thread will exit after the block propagates to node 0 + thr.join(30) # wait 30 seconds or until thread exits + assert not thr.is_alive(), "Test 2: longpoll did not return after block on node 1" # Test 3: test that longpoll will terminate if we generate a block ourselves thr = LongpollThread(self.nodes[0]) thr.start() - self.nodes[0].generate(1) # generate a block on another node - thr.join(5) # wait 5 seconds or until thread exits - assert(not thr.is_alive()) + self.nodes[0].generate(1) # generate a block on this node + thr.join(30) # wait 30 seconds or until thread exits + assert not thr.is_alive(), "Test 3: longpoll did not return after block on node 0" # Test 4: test that introducing a new transaction into the mempool will terminate the longpoll thr = LongpollThread(self.nodes[0]) thr.start() - # generate a random transaction and submit it - (txid, txhex, fee) = random_transaction(self.nodes, Decimal("1.1"), Decimal("0.0"), Decimal("0.001"), 20) - # after one minute, every 10 seconds the mempool is probed, so in 80 seconds it should have returned - thr.join(60 + 20) - assert(not thr.is_alive()) + # Submit a transaction directly to node 0 (where the longpoll is running) + # to avoid depending on cross-node mempool relay timing + random_transaction([self.nodes[0]], Decimal("1.1"), Decimal("0.0"), Decimal("0.001"), 20) + # after one minute, every 10 seconds the mempool is probed, so in 100 seconds it should have returned + thr.join(60 + 20 + 20) + assert not thr.is_alive(), "Test 4: longpoll did not return after mempool tx" if __name__ == '__main__': GetBlockTemplateLPTest().main() diff --git a/qa/rpc-tests/lelantus_mint.py b/qa/rpc-tests/lelantus_mint.py deleted file mode 100755 index e52c12f07f..0000000000 --- a/qa/rpc-tests/lelantus_mint.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_message, JSONRPCException - -class LelantusMintTest(BitcoinTestFramework): - def __init__(self): - super().__init__() - self.num_nodes = 1 - self.setup_clean_chain = False - - def run_test(self): - # generate coins - amounts = [1, 1.1, 2, 10] - - # 10 confirmations - self.nodes[0].mintlelantus(amounts[0]) - self.nodes[0].mintlelantus(amounts[1]) - self.nodes[0].generate(5) - - # 5 confirmations - self.nodes[0].mintlelantus(amounts[2]) - self.nodes[0].mintlelantus(amounts[3]) - self.nodes[0].generate(5) - - # get all mints and utxos - mints = self.verify_listlelantusmints(amounts) - self.verify_listunspentlelantusmints(amounts) - self.verify_listunspentlelantusmints([], 1000) # [1000, 9999999] - self.verify_listunspentlelantusmints([2, 10], 1, 5) # [1, 5] - self.verify_listunspentlelantusmints([1, 1.1], 6, 10) - self.verify_listunspentlelantusmints([1, 1.1, 2, 10], 5, 10) - assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) - - # state modification test - # mark two coins as used - self.nodes[0].setlelantusmintstatus(mints[2]["serialNumber"], True) - self.nodes[0].setlelantusmintstatus(mints[3]["serialNumber"], True) - - mints = self.verify_listlelantusmints(amounts) - self.verify_listunspentlelantusmints([1, 1.1]) - self.verify_listunspentlelantusmints([], 1000) - self.verify_listunspentlelantusmints([], 1, 5) - self.verify_listunspentlelantusmints([1, 1.1], 6, 10) - self.verify_listunspentlelantusmints([1, 1.1], 5, 10) - assert_equal([False, False, True, True], list(map(lambda m : m["isUsed"], mints))) - - # set a coin as unused - self.nodes[0].setlelantusmintstatus(mints[3]["serialNumber"], False) - mints = self.verify_listlelantusmints(amounts) - self.verify_listunspentlelantusmints([1, 1.1, 10]) - assert_equal([False, False, True, False], list(map(lambda m : m["isUsed"], mints))) - - # reset coins state - self.nodes[0].resetlelantusmint() - mints = self.verify_listlelantusmints(amounts) - self.verify_listunspentlelantusmints(amounts) - assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) - - def verify_listlelantusmints(self, expected_amounts, *args): - mints = self.nodes[0].listlelantusmints(*args) - mints = sorted(mints, key = lambda u: u['amount']) - - assert_equal( - sorted(expected_amounts), - list(map(lambda u: u['amount'] / 1e8, mints))) - - return mints - - def verify_listunspentlelantusmints(self, expected_amounts, *args): - utxos = self.nodes[0].listunspentlelantusmints(*args) - utxos = sorted(utxos, key = lambda u: float(u['amount'])) - - assert_equal( - sorted(expected_amounts), - list(map(lambda u: float(u['amount']), utxos))) - - return utxos - -if __name__ == '__main__': - LelantusMintTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/lelantus_mintspend.py b/qa/rpc-tests/lelantus_mintspend.py deleted file mode 100755 index 689c93cdc9..0000000000 --- a/qa/rpc-tests/lelantus_mintspend.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python3 -from decimal import * - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * - - -class LelantusMintSpendTest(BitcoinTestFramework): - def __init__(self): - super().__init__() - self.num_nodes = 4 - self.setup_clean_chain = False - - def run_test(self): - # Decimal formating: 6 digits for balance will be enought 000.000 - getcontext().prec = 6 - self.nodes[0].generate(11) - self.sync_all() - - start_bal = self.nodes[0].getbalance() - - mint_trans = list() - mint_trans.append(self.nodes[0].mintlelantus(1)) - mint_trans.append(self.nodes[0].mintlelantus(2)) - - # Get fees from both transactions (they may differ) - info1 = self.nodes[0].gettransaction(mint_trans[0][0]) - info2 = self.nodes[0].gettransaction(mint_trans[1][0]) - - # fee in transaction is negative, sum both actual fees - fee = -(info1['fee'] + info2['fee']) - cur_bal = self.nodes[0].getbalance() - start_bal = float(start_bal) - float(fee) - 3 - start_bal = Decimal(format(start_bal, '.8f')) - - assert start_bal == cur_bal, \ - 'Unexpected current balance: {}, should be minus two mints and two fee, ' \ - 'but start was {}'.format(cur_bal, start_bal) - - for tr in mint_trans: - info = self.nodes[0].gettransaction(tr[0]) - confrms = info['confirmations'] - assert confrms == 0, \ - 'Confirmations should be {}, ' \ - 'due to {} blocks was generated after transaction was created,' \ - 'but was {}'.format(0, 0, confrms) - - tr_type = info['details'][0]['category'] - assert tr_type == 'mint', 'Unexpected transaction type: {}'.format(tr_type) - - res = False - args = {'THAYjKnnCsN5xspnEcb1Ztvw4mSPBuwxzU': 1} - try: - res = self.nodes[0].joinsplit(args) - except JSONRPCException as ex: - assert ex.error['message'] == 'Insufficient funds' - - assert not res, 'Did not raise spend exception, but should be.' - - self.nodes[0].generate(1) - self.sync_all() - - # generate last confirmation block - now all transactions should be confimed - self.nodes[0].generate(1) - self.sync_all() - - for tr in mint_trans: - info = self.nodes[0].gettransaction(tr[0]) - confrms = info['confirmations'] - assert confrms == 2, \ - 'Confirmations should be 2, ' \ - 'due to 2 blocks was generated after transaction was created,' \ - 'but was {}.'.format(confrms) - tr_type = info['details'][0]['category'] - assert tr_type == 'mint', 'Unexpected transaction type' - - spend_trans = list() - spend_total = Decimal(0) - - self.sync_all() - - start_bal = self.nodes[0].getbalance() - print(start_bal) - total_spend_fee = 0 - - myaddr = self.nodes[0].listreceivedbyaddress(0, True)[0]['address'] - print(1) - args = {myaddr: 1} - - spend_trans.append(self.nodes[0].joinsplit(args)) - - info = self.nodes[0].gettransaction(spend_trans[-1]) - confrms = info['confirmations'] - tr_type = info['details'][0]['category'] - total_spend_fee += -info['fee'] - print(info['fee']) - print(self.nodes[0].getbalance()) - spend_total = float(spend_total) + 1 - assert confrms == 0, \ - 'Confirmations should be 0, ' \ - 'due to 0 blocks was generated after transaction was created,' \ - 'but was {}.'.format(confrms) - assert tr_type == 'spend', 'Unexpected transaction type' - print(self.nodes[0].getbalance()) - - before_new = self.nodes[0].getbalance() - self.nodes[0].generate(2) - after_new = self.nodes[0].getbalance() - delta = after_new - before_new - self.sync_all() - - # # Start balance increase on generated blocks to confirm - start_bal += delta - cur_bal = Decimal(format(self.nodes[0].getbalance(), '.1f')) - spend_total = Decimal(format(spend_total, '.8f')) - - #TODO check why currently was not minused from total sum - start_bal = start_bal + spend_total - - assert start_bal == cur_bal, \ - 'Unexpected current balance: {}, should increase on {}, ' \ - 'but start was {}'.format(cur_bal, spend_total, start_bal) - - for tr in spend_trans: - info = self.nodes[0].gettransaction(tr) - - confrms = info['confirmations'] - tr_type = info['details'][0]['category'] - assert confrms >= 1, \ - 'Confirmations should be 1 or more, ' \ - 'due to 1 blocks was generated after transaction was created,' \ - 'but was {}.'.format(confrms) - assert tr_type == 'spend', 'Unexpected transaction type' - - -if __name__ == '__main__': - LelantusMintSpendTest().main() diff --git a/qa/rpc-tests/lelantus_setmintstatus_validation.py b/qa/rpc-tests/lelantus_setmintstatus_validation.py deleted file mode 100755 index 61b4d6257e..0000000000 --- a/qa/rpc-tests/lelantus_setmintstatus_validation.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * - -class SetLelantusMintSatusValidationWithFundsTest(BitcoinTestFramework): - def __init__(self): - super().__init__() - self.num_nodes = 4 - self.setup_clean_chain = False - - def setup_nodes(self): - # This test requires mocktime - enable_mocktime() - return start_nodes(self.num_nodes, self.options.tmpdir) - - def run_test(self): - self.nodes[0].generate(100) - self.sync_all() - - txid = self.nodes[0].mintlelantus(10) - - lelantus_mint = self.nodes[0].listlelantusmints() - - assert len(lelantus_mint) == len(txid), 'Should be same number.' - - mint_info = lelantus_mint[0] - - assert not mint_info['isUsed'], \ - 'This mint with txid: {} should not be Used.'.format(txid) - - print('Set mint status from False to True.') - - self.nodes[0].setlelantusmintstatus(mint_info['serialNumber'], True) - - lelantus_mint = self.nodes[0].listlelantusmints() - - assert len(lelantus_mint) == len(txid), 'Should be same number.' - - mint_info = lelantus_mint[0] - - assert mint_info['isUsed'], \ - 'This mint with txid: {} should be Used.'.format(txid) - - print('Set mint status from True to False back.') - - self.nodes[0].setlelantusmintstatus(mint_info['serialNumber'], False) - - lelantus_mint = self.nodes[0].listlelantusmints() - - assert len(lelantus_mint) == len(txid), 'Should be same number.' - - mint_info = lelantus_mint[0] - - assert not mint_info['isUsed'], \ - 'This mint with txid: {} should not be Used.'.format(txid) - - - assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [(mint_info['serialNumber'], "sometext")]) - assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [mint_info['serialNumber']]) - assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, []) - assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, ["sometext"]) - assert_raises(JSONRPCException, self.nodes[0].setlelantusmintstatus, [123]) - - -if __name__ == '__main__': - SetLelantusMintSatusValidationWithFundsTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/lelantus_spend_gettransaction.py b/qa/rpc-tests/lelantus_spend_gettransaction.py deleted file mode 100755 index 73599a2890..0000000000 --- a/qa/rpc-tests/lelantus_spend_gettransaction.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -from decimal import * - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * - -class JoinSplitGettransactionTest(BitcoinTestFramework): - def __init__(self): - super().__init__() - self.num_nodes = 4 - self.setup_clean_chain = True - - def setup_nodes(self): - # This test requires mocktime - enable_mocktime() - return start_nodes(self.num_nodes, self.options.tmpdir) - - def run_test(self): - self.nodes[0].generate(101) - self.sync_all() - - # get a watch only address - watchonly_address = self.nodes[3].getnewaddress() - watchonly_pubkey = self.nodes[3].validateaddress(watchonly_address)["pubkey"] - self.nodes[0].importpubkey(watchonly_pubkey, "", True) - - valid_address = self.nodes[0].getnewaddress() - - for _ in range(10): - self.nodes[0].mintlelantus(1) - - self.nodes[0].generate(10) - self.sync_all() - - # case 1: Spend many with watchonly address - spendto_wo_id = self.nodes[0].joinsplit({watchonly_address: 1}) - spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id) - - assert spendto_wo_tx['amount'] == Decimal('-1') - assert spendto_wo_tx['fee'] < Decimal('0') - assert isinstance(spendto_wo_tx['details'], list) - assert len(spendto_wo_tx['details']) == 1 - assert spendto_wo_tx['details'][0]['involvesWatchonly'] - - # case 2: Spend many with watchonly address and valid address - spendto_wo_and_valid_id = self.nodes[0].joinsplit({watchonly_address: 1, valid_address: 2}) - spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id) - - assert spendto_wo_and_valid_tx['amount'] == Decimal('-1') - assert spendto_wo_and_valid_tx['fee'] < Decimal('0') - assert isinstance(spendto_wo_and_valid_tx['details'], list) - assert len(spendto_wo_and_valid_tx['details']) == 3 - - involves_watch_only_count = 0 - for detial in spendto_wo_and_valid_tx['details']: - if 'involvesWatchonly' in detial and bool(detial['involvesWatchonly']): - involves_watch_only_count += 1 - - assert involves_watch_only_count == 1 - - # case 3: spend many with watchonly address and invalid address - assert_raises(JSONRPCException, self.nodes[0].joinsplit, [{watchonly_address: 1, 'invalidaddress': 2}]) - -if __name__ == '__main__': - JoinSplitGettransactionTest().main() - - diff --git a/qa/rpc-tests/llmq-cl-evospork.py b/qa/rpc-tests/llmq-cl-evospork.py index c22f67c73f..45dd291541 100755 --- a/qa/rpc-tests/llmq-cl-evospork.py +++ b/qa/rpc-tests/llmq-cl-evospork.py @@ -5,8 +5,9 @@ from test_framework.mininode import * from test_framework.test_framework import EvoZnodeTestFramework +from test_framework.authproxy import JSONRPCException from test_framework.util import * -from time import * +from time import time, sleep ''' llmq-chainlocks.py @@ -33,15 +34,16 @@ def run_test(self): # mine single block, wait for chainlock self.nodes[0].generate(1) - - self.wait_for_chainlock_tip_all_nodes() + sync_blocks(self.nodes) + self.wait_for_chainlock_tip_all_nodes(self.nodes[0].getbestblockhash()) self.payment_address = self.nodes[0].getaccountaddress("") self.nodes[0].sendtoaddress(self.payment_address, 1) # mine many blocks, wait for chainlock while self.nodes[0].getblockcount() < 800: self.nodes[0].generate(20) - self.wait_for_chainlock_tip_all_nodes() + sync_blocks(self.nodes, timeout=120) + self.wait_for_chainlock_tip_all_nodes(self.nodes[0].getbestblockhash()) # assert that all blocks up until the tip are chainlocked for h in range(1, self.nodes[0].getblockcount()): @@ -51,13 +53,13 @@ def run_test(self): # cannot invalidate tip current_tip = self.nodes[0].getbestblockhash() self.nodes[0].invalidateblock(current_tip) - sleep(2) - assert(current_tip == self.nodes[0].getbestblockhash()) + self.wait_for_tip(self.nodes[0], current_tip, timeout=15) ##### Disable chainlocks for 10 blocks self.nodes[0].importprivkey(self.sporkprivkey) - self.disable_chainlocks(self.nodes[0].getblockcount() + 10) + reenable_height = self.nodes[0].getblockcount() + 10 + self.disable_chainlocks(reenable_height) self.nodes[0].generate(1) assert(False == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"]) @@ -69,56 +71,80 @@ def run_test(self): ##### Enable chainlocks - self.nodes[0].generate(10) - self.nodes[0].spork('list') - self.wait_for_chainlock_tip_all_nodes() + connected_nodes = [n for n in self.nodes if n != self.nodes[5]] + + # Mine up to the re-enable height, then use the next block for + # chainlock assertions to avoid the reactivation transition edge. + self.nodes[0].generate(reenable_height - self.nodes[0].getblockcount()) + sync_blocks(connected_nodes, timeout=120) sporks = self.nodes[0].spork("list") assert(not sporks["blockchain"]) assert(not sporks["mempool"]) + + self.nodes[0].generate(1) + sync_blocks(connected_nodes, timeout=120) + self.wait_for_chainlock_tip(connected_nodes, self.nodes[0].getbestblockhash(), timeout=90) assert(True == self.nodes[0].getblock(self.nodes[0].getbestblockhash())["chainlock"]) + chainlocked_tip = self.nodes[0].getbestblockhash() # generate a longer chain on the isolated node then reconnect it back and make sure it picks the chainlocked chain self.nodes[5].generate(20) reconnect_isolated_node(self.nodes[5], 1) self.nodes[0].generate(1) current_tip = self.nodes[0].getbestblockhash() - timeout = 15 - while current_tip != self.nodes[5].getbestblockhash(): - if timeout == 0: # retry - self.nodes[0].generate(1) - current_tip = self.nodes[0].getbestblockhash() - timeout = 15 - break - sleep(1) - timeout = timeout - 1 - while current_tip != self.nodes[5].getbestblockhash(): - assert timeout > 0, "Timed out when waiting for a chainlocked chain" - sleep(1) - timeout = timeout - 1 - - - - def wait_for_chainlock_tip_all_nodes(self): - for node in self.nodes: - tip = node.getbestblockhash() - self.wait_for_chainlock(node, tip) - - def wait_for_chainlock_tip(self, node): - tip = node.getbestblockhash() - self.wait_for_chainlock(node, tip) - - def wait_for_chainlock(self, node, block_hash): + assert self.nodes[0].getblock(current_tip)["previousblockhash"] == chainlocked_tip, \ + "Node 0 did not extend the chainlocked tip" + try: + self.wait_for_tip(self.nodes[5], current_tip, timeout=15) + except AssertionError: + self.nodes[0].generate(1) + current_tip = self.nodes[0].getbestblockhash() + self.wait_for_tip(self.nodes[5], current_tip, timeout=15) + assert self.nodes[0].getbestblockhash() == current_tip, \ + "Node 0 did not keep the chainlocked tip" + assert self.nodes[5].getbestblockhash() == current_tip, \ + "Isolated node did not adopt the chainlocked tip" + assert self.nodes[5].getblockcount() == self.nodes[0].getblockcount(), \ + "Node 5 block count diverges from node 0 after reconnect" + + + + def wait_for_chainlock_tip_all_nodes(self, tip_hash=None, timeout=60): + self.wait_for_chainlock_tip(self.nodes, tip_hash, timeout) + + def wait_for_chainlock_tip(self, nodes, tip_hash=None, timeout=60): + if not isinstance(nodes, list): + nodes = [nodes] + if tip_hash is None: + tip_hash = nodes[0].getbestblockhash() + for node in nodes: + self.wait_for_chainlock(node, tip_hash, timeout) + + def wait_for_chainlock(self, node, block_hash, timeout=60): t = time() - while time() - t < 30: + while time() - t < timeout: try: block = node.getblock(block_hash) if block["confirmations"] > 0 and block["chainlock"]: return - except: - # block might not be on the node yet + except JSONRPCException: pass sleep(0.1) - raise AssertionError("wait_for_chainlock timed out") + raise AssertionError("wait_for_chainlock timed out for block %s" % block_hash) + + def wait_for_tip(self, node, expected_tip, timeout=15): + """Wait until node's best block hash equals expected_tip.""" + last_tip = "" + t = time() + while time() - t < timeout: + try: + last_tip = node.getbestblockhash() + if last_tip == expected_tip: + return + except JSONRPCException: + pass + sleep(0.5) + raise AssertionError("wait_for_tip timed out: expected tip %s, got %s" % (expected_tip, last_tip)) def disable_chainlocks(self, till_height): self.nodes[0].spork(self.sporkprivkey, self.payment_address, {"disable":{"chainlocks": till_height}}) diff --git a/qa/rpc-tests/llmq-is-spark.py b/qa/rpc-tests/llmq-is-spark.py index 2c70c5e81a..d59ac343c5 100755 --- a/qa/rpc-tests/llmq-is-spark.py +++ b/qa/rpc-tests/llmq-is-spark.py @@ -18,7 +18,7 @@ Testing Instantsend for Spark transactions ''' -class LLMQ_IS_Lelantus(EvoZnodeTestFramework): +class LLMQ_IS_Spark(EvoZnodeTestFramework): def __init__(self): super().__init__(6, 5, extra_args=[['-debug=instantsend']] * 6 ) self.sporkprivkey = "cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ" @@ -55,4 +55,4 @@ def run_test(self): assert(self.wait_for_instantlock(spendTxid, self.nodes[0])) if __name__ == '__main__': - LLMQ_IS_Lelantus().main() + LLMQ_IS_Spark().main() diff --git a/qa/rpc-tests/proxy_test.py b/qa/rpc-tests/proxy_test.py index 9ccc0ffbb0..37bcbfd30d 100755 --- a/qa/rpc-tests/proxy_test.py +++ b/qa/rpc-tests/proxy_test.py @@ -170,14 +170,21 @@ def networks_dict(d): r[x['name']] = x return r + def assert_network_names(info): + assert_equal(sorted(x['name'] for x in info['networks']), ['ipv4', 'ipv6', 'onion']) + # test RPC getnetworkinfo - n0 = networks_dict(self.nodes[0].getnetworkinfo()) + n0_info = self.nodes[0].getnetworkinfo() + assert_network_names(n0_info) + n0 = networks_dict(n0_info) for net in ['ipv4','ipv6','onion']: assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr)) assert_equal(n0[net]['proxy_randomize_credentials'], True) assert_equal(n0['onion']['reachable'], True) - n1 = networks_dict(self.nodes[1].getnetworkinfo()) + n1_info = self.nodes[1].getnetworkinfo() + assert_network_names(n1_info) + n1 = networks_dict(n1_info) for net in ['ipv4','ipv6']: assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr)) assert_equal(n1[net]['proxy_randomize_credentials'], False) @@ -185,14 +192,18 @@ def networks_dict(d): assert_equal(n1['onion']['proxy_randomize_credentials'], False) assert_equal(n1['onion']['reachable'], True) - n2 = networks_dict(self.nodes[2].getnetworkinfo()) + n2_info = self.nodes[2].getnetworkinfo() + assert_network_names(n2_info) + n2 = networks_dict(n2_info) for net in ['ipv4','ipv6','onion']: assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr)) assert_equal(n2[net]['proxy_randomize_credentials'], True) assert_equal(n2['onion']['reachable'], True) if self.have_ipv6: - n3 = networks_dict(self.nodes[3].getnetworkinfo()) + n3_info = self.nodes[3].getnetworkinfo() + assert_network_names(n3_info) + n3 = networks_dict(n3_info) for net in ['ipv4','ipv6']: assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr)) assert_equal(n3[net]['proxy_randomize_credentials'], False) diff --git a/qa/rpc-tests/spark_mint.py b/qa/rpc-tests/spark_mint.py index e57da982ba..598e0a99f1 100755 --- a/qa/rpc-tests/spark_mint.py +++ b/qa/rpc-tests/spark_mint.py @@ -3,17 +3,15 @@ from test_framework.util import assert_equal, assert_raises_message, JSONRPCException class SparkMintTest(BitcoinTestFramework): - def __init__(self): - super().__init__() + def set_test_params(self): self.num_nodes = 1 - self.setup_clean_chain = False + self.setup_clean_chain = True def run_test(self): assert_raises_message( JSONRPCException, "Spark is not activated yet", self.nodes[0].mintspark, 1) - self.nodes[0].generate(501) # generate coins diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 3e320b592c..399efa9f9f 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -448,6 +448,11 @@ def stop_node(node, i, wait=True): node.stop() except http.client.CannotSendRequest as e: logger.exception("Unable to stop node") + except (ConnectionError, http.client.HTTPException) as e: + # firod may close the RPC socket before sending a response and then + # refuse further connections while shutting down. Treat these as a + # successful stop request; wait_node() below confirms the process exits. + logger.debug("Stop RPC on node %d disconnected (%s); relying on process exit" % (i, e)) if wait: wait_node(i) diff --git a/qa/rpc-tests/wallet-dump.py b/qa/rpc-tests/wallet-dump.py index c1fee55d1c..d495330d8f 100755 --- a/qa/rpc-tests/wallet-dump.py +++ b/qa/rpc-tests/wallet-dump.py @@ -81,8 +81,8 @@ def setup_network(self, split=False): def run_test (self): tmpdir = self.options.tmpdir - # 21 hdmint keys generated initially (0-20) - hdmint_key_count = 21 + # hdmint/sigma mint keys no longer generated (Lelantus stripped) + hdmint_key_count = 0 # generate 20 addresses to compare against the dump test_addr_count = 20 @@ -132,8 +132,8 @@ def run_test (self): assert_equal(found_addr, test_addr_count) assert_equal(found_addr_chg, 50) # 50 block were mined - # Wallet encryption doesn't change master key anymore, therefore we just verify hdmint_key_count is the same as before. - assert_equal(found_addr_sigma, hdmint_key_count) # hdmint keys + # Wallet encryption doesn't change master key anymore; sigma key count unchanged after Lelantus strip (0). + assert_equal(found_addr_sigma, hdmint_key_count) assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) if __name__ == '__main__': diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf9e78a2d8..aa80161a0d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -200,7 +200,6 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/compressor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/core_read.cpp ${CMAKE_CURRENT_SOURCE_DIR}/core_write.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/hdmint/hdmint.cpp ${CMAKE_CURRENT_SOURCE_DIR}/init.cpp ${CMAKE_CURRENT_SOURCE_DIR}/key.cpp ${CMAKE_CURRENT_SOURCE_DIR}/keystore.cpp @@ -270,22 +269,6 @@ add_library(firo_node STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/httprpc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/httpserver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/init.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantus.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/coin.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/innerproduct_proof_generator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/innerproduct_proof_verifier.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/joinsplit.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_primitives.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_prover.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/lelantus_verifier.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/params.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/range_prover.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/range_verifier.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/schnorr_prover.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/schnorr_verifier.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/sigmaextended_prover.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/sigmaextended_verifier.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/liblelantus/spend_metadata.cpp ${CMAKE_CURRENT_SOURCE_DIR}/libspark/aead.cpp ${CMAKE_CURRENT_SOURCE_DIR}/libspark/bech32.cpp ${CMAKE_CURRENT_SOURCE_DIR}/libspark/bpplus.cpp diff --git a/src/arith_uint256.h b/src/arith_uint256.h index b1543a4dea..6f51f2d9bd 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -292,7 +292,7 @@ class arith_uint256 : public base_uint<256> { * complexities of the sign bit and using base 256 are probably an * implementation accident. */ - arith_uint256& SetCompact(uint32_t nCompact, bool *pfNegative = NULL, bool *pfOverflow = NULL); + arith_uint256& SetCompact(uint32_t nCompact, bool* pfNegative = nullptr, bool* pfOverflow = nullptr); uint32_t GetCompact(bool fNegative = false) const; friend uint256 ArithToUint256(const arith_uint256 &); diff --git a/src/batchproof_container.cpp b/src/batchproof_container.cpp index 98b760d89d..e2a2fe040e 100644 --- a/src/batchproof_container.cpp +++ b/src/batchproof_container.cpp @@ -1,8 +1,4 @@ #include "batchproof_container.h" -#include "liblelantus/sigmaextended_verifier.h" -#include "liblelantus/threadpool.h" -#include "liblelantus/range_verifier.h" -#include "lelantus.h" #include "ui_interface.h" #include "spark/state.h" @@ -18,20 +14,11 @@ BatchProofContainer* BatchProofContainer::get_instance() { } void BatchProofContainer::init() { - tempRangeProofs.clear(); tempSparkTransactions.clear(); } void BatchProofContainer::finalize() { if (fCollectProofs) { - for (const auto& itr : tempLelantusSigmaProofs) { - lelantusSigmaProofs[itr.first].insert(lelantusSigmaProofs[itr.first].begin(), itr.second.begin(), itr.second.end()); - } - - for (const auto& itr : tempRangeProofs) { - rangeProofs[itr.first].insert(rangeProofs[itr.first].begin(), itr.second.begin(), itr.second.end()); - } - sparkTransactions.insert(sparkTransactions.end(), tempSparkTransactions.begin(), tempSparkTransactions.end()); } fCollectProofs = false; @@ -39,218 +26,11 @@ void BatchProofContainer::finalize() { void BatchProofContainer::verify() { if (!fCollectProofs) { - batch_lelantus(); - batch_rangeProofs(); batch_spark(); } fCollectProofs = false; } -void BatchProofContainer::add(lelantus::JoinSplit* joinSplit, - const std::map& setSizes, - const Scalar& challenge, - bool fStartLelantusBlacklist) { - const std::vector& sigma_proofs = joinSplit->getLelantusProof().sigma_proofs; - const std::vector& serials = joinSplit->getCoinSerialNumbers(); - const std::vector& groupIds = joinSplit->getCoinGroupIds(); - if (joinSplit->isSigmaToLelantus()) - return; - - for (size_t i = 0; i < sigma_proofs.size(); i++) { - // pair(pair(set id, fAfterFixes), isSigmaToLelantus) - std::pair idAndFlag = std::make_pair(groupIds[i], fStartLelantusBlacklist); - tempLelantusSigmaProofs[idAndFlag].push_back(LelantusSigmaProofData(sigma_proofs[i], serials[i], challenge, setSizes.at(groupIds[i]))); - } -} - - -void BatchProofContainer::add(lelantus::JoinSplit* joinSplit, const std::vector& Cout) { - tempRangeProofs[joinSplit->getVersion()].push_back(std::make_pair(joinSplit->getLelantusProof().bulletproofs, Cout)); -} - -void BatchProofContainer::removeLelantus(std::unordered_map spentSerials) { - for (auto& spendSerial : spentSerials) { - - int id = spendSerial.second; - - // afterFixes bool with the pair of set id is considered separate set identifiers, so try to find in one set, if not found try also in another - std::pair key1 = std::make_pair(id, false); - std::pair key2 = std::make_pair(id, true); - std::vector* vProofs; - if (lelantusSigmaProofs.count(key1) > 0) { - vProofs = &lelantusSigmaProofs[key1]; - erase(vProofs, spendSerial.first); - } - - if (lelantusSigmaProofs.count(key2) > 0) { - vProofs = &lelantusSigmaProofs[key2]; - erase(vProofs, spendSerial.first); - } - } -} - -void BatchProofContainer::remove(const std::vector& rangeProofsToRemove) { - for (auto& itrRemove : rangeProofsToRemove) { - for (auto itrVersions = rangeProofs.begin(); itrVersions != rangeProofs.end(); ++itrVersions) { - bool found = false; - for (auto itr = itrVersions->second.begin(); itr != itrVersions->second.end(); ++itr) { - if (itr->first.T_x1 == itrRemove.T_x1 && itr->first.T_x2 == itrRemove.T_x2 && itr->first.u == itrRemove.u) { - itrVersions->second.erase(itr); - found = true; - break; - } - } - if (itrVersions->second.empty()) { - rangeProofs.erase(itrVersions); - itrVersions--; - } - if (found) - break; - } - } -} - -void BatchProofContainer::erase(std::vector* vProofs, const Scalar& serial) { - vProofs->erase(std::remove_if(vProofs->begin(), - vProofs->end(), - [serial](LelantusSigmaProofData& proof){return proof.serialNumber == serial;}), - vProofs->end()); - -} - -void BatchProofContainer::batch_lelantus() { - if (!lelantusSigmaProofs.empty()){ - LogPrintf("Lelantus batch verification started.\n"); - uiInterface.UpdateProgressBarLabel("Batch verifying Lelantus..."); - } - else - return; - - auto params = lelantus::Params::get_default(); - - DoNotDisturb dnd; - std::size_t threadsMaxCount = std::min((unsigned int)lelantusSigmaProofs.size(), boost::thread::hardware_concurrency()); - std::vector> parallelTasks; - parallelTasks.reserve(threadsMaxCount); - ParallelOpThreadPool threadPool(threadsMaxCount); - auto itr = lelantusSigmaProofs.begin(); - - lelantus::SigmaExtendedVerifier sigmaVerifier(params->get_g(), params->get_sigma_h(), params->get_sigma_n(), - params->get_sigma_m()); - for (std::size_t j = 0; j < lelantusSigmaProofs.size(); j += threadsMaxCount) { - for (std::size_t i = j; i < j + threadsMaxCount; ++i) { - if (i < lelantusSigmaProofs.size()) { - std::vector anonymity_set; - lelantus::CLelantusState* state = lelantus::CLelantusState::GetState(); - std::vector coins; - state->GetAnonymitySet( - itr->first.first, - itr->first.second, - coins); - anonymity_set.reserve(coins.size()); - for (auto& coin : coins) - anonymity_set.emplace_back(coin.getValue()); - - size_t m = itr->second.size(); - std::vector serials; - serials.reserve(m); - std::vector setSizes; - setSizes.reserve(m); - std::vector proofs; - proofs.reserve(m); - std::vector challenges; - challenges.reserve(m); - - for (auto& proofData : itr->second) { - serials.emplace_back(proofData.serialNumber); - setSizes.emplace_back(proofData.anonymitySetSize); - proofs.emplace_back(proofData.lelantusSigmaProof); - challenges.emplace_back(proofData.challenge); - } - - - - parallelTasks.emplace_back(threadPool.PostTask([=]() { - try { - if (!sigmaVerifier.batchverify(anonymity_set, challenges, serials, setSizes, proofs)) - return false; - } catch (const std::exception &) { - return false; - } - return true; - })); - - ++itr; - } - } - bool isFail = false; - for (auto& th : parallelTasks) { - if (!th.get()) - isFail = true; - } - - if (isFail) { - LogPrintf("Lelantus batch verification failed."); - throw std::invalid_argument("Lelantus batch verification failed, please run Firo with -reindex -batching=0"); - } - - parallelTasks.clear(); - } - if (!lelantusSigmaProofs.empty()) - LogPrintf("Lelantus batch verification finished successfully.\n"); - lelantusSigmaProofs.clear(); -} - -void BatchProofContainer::batch_rangeProofs() { - if (!rangeProofs.empty()){ - LogPrintf("RangeProof batch verification started.\n"); - uiInterface.UpdateProgressBarLabel("Batch verifying Range Proofs..."); - } - - auto params = lelantus::Params::get_default(); - for (const auto& itr : rangeProofs) { - lelantus::RangeVerifier rangeVerifier(params->get_h1(), params->get_h0(), params->get_g(), params->get_bulletproofs_g(), params->get_bulletproofs_h(), params->get_bulletproofs_n(), itr.first); - std::vector> V; - std::vector> commitments; - size_t proofSize = itr.second.size(); - V.resize(proofSize); //size of batch - commitments.resize(proofSize); // size of batch - std::vector proofs; - proofs.reserve(proofSize); // size of batch - for (size_t i = 0; i < proofSize; ++i) { - size_t coutSize = itr.second[i].second.size(); - std::size_t m = coutSize * 2; - - while (m & (m - 1)) - m++; - proofs.emplace_back(itr.second[i].first); - V[i].reserve(m); // aggregation size - commitments[i].reserve(2 * coutSize); - commitments[i].resize(coutSize); // prepend zero elements, to match the prover's behavior - auto& Cout = itr.second[i].second; - for (std::size_t j = 0; j < coutSize; ++j) { - V[i].push_back(Cout[j].getValue()); - V[i].push_back(Cout[j].getValue() + params->get_h1_limit_range()); - commitments[i].emplace_back(Cout[j].getValue()); - } - - // Pad with zero elements - for (std::size_t t = coutSize * 2; t < m; ++t) - V[i].push_back(GroupElement()); - } - - if (!rangeVerifier.verify(V, commitments, proofs)) { - LogPrintf("RangeProof batch verification failed.\n"); - throw std::invalid_argument("RangeProof batch verification failed, please run Firo with -reindex -batching=0"); - } - } - - if (!rangeProofs.empty()) - LogPrintf("RangeProof batch verification finished successfully.\n"); - - rangeProofs.clear(); -} - void BatchProofContainer::add(const spark::SpendTransaction& tx) { tempSparkTransactions.push_back(tx); } diff --git a/src/batchproof_container.h b/src/batchproof_container.h index 519c84074c..dc87dcfce2 100644 --- a/src/batchproof_container.h +++ b/src/batchproof_container.h @@ -3,7 +3,6 @@ #include #include "chain.h" -#include "liblelantus/joinsplit.h" #include "libspark/spend_transaction.h" extern CChain chainActive; @@ -12,42 +11,12 @@ class BatchProofContainer { public: static BatchProofContainer* get_instance(); - struct LelantusSigmaProofData { - LelantusSigmaProofData(const lelantus::SigmaExtendedProof& lelantusSigmaProof_, - const Scalar& serialNumber_, - const Scalar& challenge_, - size_t anonymitySetSize_) - : lelantusSigmaProof(lelantusSigmaProof_), - serialNumber(serialNumber_), - challenge(challenge_), - anonymitySetSize(anonymitySetSize_) {} - - lelantus::SigmaExtendedProof lelantusSigmaProof; - Scalar serialNumber; - Scalar challenge; - size_t anonymitySetSize; - }; - void init(); void finalize(); void verify(); - void add(lelantus::JoinSplit* joinSplit, - const std::map& setSizes, - const Scalar& challenge, - bool fStartLelantusBlacklist); - - void add(lelantus::JoinSplit* joinSplit, const std::vector& Cout); - - void removeLelantus(std::unordered_map spentSerials); - void remove(const std::vector& rangeProofsToRemove); - void erase(std::vector* vProofs, const Scalar& serial); - - void batch_lelantus(); - void batch_rangeProofs(); - void add(const spark::SpendTransaction& tx); void remove(const spark::SpendTransaction& tx); void batch_spark(); @@ -56,17 +25,9 @@ class BatchProofContainer { private: static std::unique_ptr instance; - // temp containers, to forget in case block connection fails - // map ((id, afterFixes), fIsSigmaToLelantus) to (sigma proof, serial, set size, challenge) - std::map, std::vector> tempLelantusSigmaProofs; - // map (version to (Range proof, Pubcoins)) - std::map>>> tempRangeProofs; // temp spark transaction proofs std::vector tempSparkTransactions; - // containers to keep proofs for batching - std::map, std::vector> lelantusSigmaProofs; - std::map>>> rangeProofs; // spark transaction proofs std::vector sparkTransactions; }; diff --git a/src/bip47/account.cpp b/src/bip47/account.cpp index 6a0c1f9538..1d75f63c9b 100644 --- a/src/bip47/account.cpp +++ b/src/bip47/account.cpp @@ -6,7 +6,6 @@ #include "util.h" #include "bip47/bip47utils.h" #include "wallet/wallet.h" -#include "lelantus.h" namespace bip47 { @@ -251,27 +250,10 @@ bool CAccountReceiver::acceptMaskedPayload(std::vector const & ma bool CAccountReceiver::acceptMaskedPayload(std::vector const & maskedPayload, CTransaction const & tx) { - std::unique_ptr jsplit; - try { - jsplit = lelantus::ParseLelantusJoinSplit(tx); - }catch (const std::exception &) { - return false; - } - if (!jsplit) - return false; - std::unique_ptr pcode; - CExtKey pcodePrivkey = utils::Derive(privkey, {0}); - try { - CDataStream ds(SER_NETWORK, 0); - ds << jsplit->getCoinSerialNumbers()[0]; - pcode = bip47::utils::PcodeFromMaskedPayload(maskedPayload, (unsigned char const *)ds.vch.data(), ds.vch.size(), pcodePrivkey.key, jsplit->GetEcdsaPubkeys()[0]); - if (!pcode) - return false; - } catch (std::runtime_error const &) { - return false; - } - acceptPcode(*pcode); - return true; + // Lelantus was removed; BIP47 masked payload from Lelantus JoinSplit is no longer parsed. + (void)maskedPayload; + (void)tx; + return false; } CPaymentCode const & CAccountReceiver::lastPcode() const diff --git a/src/bip47/secretpoint.cpp b/src/bip47/secretpoint.cpp index 1c7b97ae9d..3f55039400 100644 --- a/src/bip47/secretpoint.cpp +++ b/src/bip47/secretpoint.cpp @@ -1,6 +1,4 @@ -#include "liblelantus/coin.h" #include "bip47/secretpoint.h" -#include "liblelantus/openssl_context.h" #include "bip47/bip47utils.h" #include "utilstrencodings.h" diff --git a/src/bloom.cpp b/src/bloom.cpp index 9b18b2468f..e41103db7f 100644 --- a/src/bloom.cpp +++ b/src/bloom.cpp @@ -223,8 +223,8 @@ bool CBloomFilter::CheckSpecialTransactionMatchesAndUpdate(const CTransaction &t } case(TRANSACTION_COINBASE): case(TRANSACTION_QUORUM_COMMITMENT): + case(TRANSACTION_LELANTUS): case (TRANSACTION_SPORK): - case (TRANSACTION_LELANTUS): // No aditional checks for this transaction types return false; } diff --git a/src/bls/bls.h b/src/bls/bls.h index 6330d0f95c..a95e552e0a 100644 --- a/src/bls/bls.h +++ b/src/bls/bls.h @@ -13,6 +13,10 @@ // bls-signatures uses relic, which may define DEBUG and ERROR, which leads to many warnings in some build setups #undef ERROR #undef DEBUG +#ifdef __APPLE__ +// macOS defines err_get_code as a macro which conflicts with relic's function declaration +#undef err_get_code +#endif #include #include #include diff --git a/src/chain.h b/src/chain.h index f4629b72c8..76a04b9a05 100644 --- a/src/chain.h +++ b/src/chain.h @@ -14,7 +14,6 @@ #include "bitcoin_bignum/bignum.h" #include #include -#include "liblelantus/coin.h" #include "libspark/coin.h" #include "evo/spork.h" #include "firo_params.h" diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d22e882c0e..653f155ac1 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -485,9 +485,6 @@ class CMainParams : public CChainParams { // Bip39 consensus.nMnemonicBlock = 222400; - // moving lelantus data to v3 payload - consensus.nLelantusV3PayloadStartBlock = 401580; - // ProgPow consensus.nPPSwitchTime = 1635228000; // Tue Oct 26 2021 06:00:00 GMT+0000 consensus.nPPBlockNumber = 419264; @@ -504,6 +501,7 @@ class CMainParams : public CChainParams { consensus.nSparkNamesStartBlock = 1104500; // ~ May 28th 2025 consensus.nSparkNamesFee = standardSparkNamesFee; consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_MAINNET_START_BLOCK; + consensus.nSparkNamesV21StartBlock = SPARK_NAME_V21_MAINNET_START_BLOCK; } virtual bool SkipUndoForBlock(int nHeight) const override { @@ -746,7 +744,6 @@ class CTestNetParams : public CChainParams { consensus.nLelantusStartBlock = ZC_LELANTUS_TESTNET_STARTING_BLOCK; consensus.nLelantusFixesStartBlock = ZC_LELANTUS_TESTNET_FIXES_START_BLOCK; - consensus.nSparkStartBlock = SPARK_TESTNET_START_BLOCK; consensus.nLelantusGracefulPeriod = LELANTUS_TESTNET_GRACEFUL_PERIOD; consensus.nSigmaEndBlock = ZC_SIGMA_TESTNET_END_BLOCK; @@ -802,9 +799,6 @@ class CTestNetParams : public CChainParams { // Bip39 consensus.nMnemonicBlock = 1; - // moving lelantus data to v3 payload - consensus.nLelantusV3PayloadStartBlock = 35000; - // ProgPow consensus.nPPSwitchTime = 1630069200; // August 27 2021, 13:00 UTC consensus.nPPBlockNumber = 37305; @@ -820,6 +814,7 @@ class CTestNetParams : public CChainParams { consensus.nSparkNamesStartBlock = 174000; consensus.nSparkNamesFee = standardSparkNamesFee; consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_TESTNET_START_BLOCK; + consensus.nSparkNamesV21StartBlock = 200000; } }; @@ -1060,9 +1055,6 @@ class CDevNetParams : public CChainParams { // Bip39 consensus.nMnemonicBlock = 1; - // moving lelantus data to v3 payload - consensus.nLelantusV3PayloadStartBlock = 1; - // ProgPow consensus.nPPSwitchTime = 1631261566; // immediately after network start consensus.nPPBlockNumber = 1; @@ -1078,6 +1070,7 @@ class CDevNetParams : public CChainParams { consensus.nSparkNamesStartBlock = 3500; consensus.nSparkNamesFee = standardSparkNamesFee; consensus.nSparkNamesV2StartBlock = SPARK_NAME_TRANSFER_DEVNET_START_BLOCK; + consensus.nSparkNamesV21StartBlock = SPARK_NAME_TRANSFER_DEVNET_START_BLOCK + 200; } }; @@ -1271,9 +1264,9 @@ class CRegTestParams : public CChainParams { consensus.nStartSigmaBlacklist = INT_MAX; consensus.nRestartSigmaWithBlacklistCheck = INT_MAX; consensus.nOldSigmaBanBlock = 1; - consensus.nLelantusStartBlock = 100; - consensus.nLelantusFixesStartBlock = 100; - consensus.nSparkStartBlock = 400; + consensus.nLelantusStartBlock = 1; + consensus.nLelantusFixesStartBlock = 1; + consensus.nSparkStartBlock = 100; consensus.nExchangeAddressStartBlock = 1000; consensus.nLelantusGracefulPeriod = 600; consensus.nSigmaEndBlock = 1; @@ -1317,9 +1310,6 @@ class CRegTestParams : public CChainParams { // Bip39 consensus.nMnemonicBlock = 0; - // moving lelantus data to v3 payload - consensus.nLelantusV3PayloadStartBlock = 800; - // ProgPow // this can be overridden with either -ppswitchtime or -ppswitchtimefromnow flags consensus.nPPSwitchTime = INT_MAX; @@ -1333,6 +1323,7 @@ class CRegTestParams : public CChainParams { consensus.nSparkNamesStartBlock = 2000; consensus.nSparkNamesFee = standardSparkNamesFee; consensus.nSparkNamesV2StartBlock = 2500; + consensus.nSparkNamesV21StartBlock = 2700; } void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout) diff --git a/src/coin_containers.cpp b/src/coin_containers.cpp index d755ee5a49..2069ca4f05 100644 --- a/src/coin_containers.cpp +++ b/src/coin_containers.cpp @@ -4,7 +4,7 @@ #include namespace lelantus { -std::size_t CScalarHash::operator ()(const Scalar& bn) const noexcept { +std::size_t CScalarHash::operator ()(const secp_primitives::Scalar& bn) const noexcept { std::vector bnData(bn.memoryRequired()); bn.serialize(&bnData[0]); @@ -18,7 +18,7 @@ std::size_t CScalarHash::operator ()(const Scalar& bn) const noexcept { } std::size_t CPublicCoinHash::operator ()(const lelantus::PublicCoin& coin) const noexcept { - uint256 hash = coin.getValueHash(); + ::uint256 hash = coin.getValueHash(); std::size_t result; std::memcpy(&result, hash.begin(), sizeof(std::size_t)); diff --git a/src/coin_containers.h b/src/coin_containers.h index 0e162c65b2..4f05d9bb0f 100644 --- a/src/coin_containers.h +++ b/src/coin_containers.h @@ -2,9 +2,14 @@ #define COIN_CONTAINERS_H #include -#include "liblelantus/coin.h" +#include "uint256.h" +#include "serialize.h" #include +#include + +// lelantus::PublicCoin and secp_primitives types +#include "libspark/coin.h" namespace lelantus { diff --git a/src/consensus/params.h b/src/consensus/params.h index f38aaf1d7b..f96281abca 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -278,6 +278,7 @@ struct Params { int nSparkNamesStartBlock; int nSparkNamesV2StartBlock; // v2 enables spark name transfer + int nSparkNamesV21StartBlock; // v2.1 tweaks rules for renewals and transfers std::array nSparkNamesFee; int nLelantusGracefulPeriod; @@ -430,9 +431,6 @@ struct Params { /** block to start reorg depth enforcement */ int nMaxReorgDepthEnforcementBlock; - /** move lelantus data to v3 payload since this block */ - int nLelantusV3PayloadStartBlock; - /** whitelisted transactions */ std::set txidWhitelist; diff --git a/src/crypto/MerkleTreeProof/arith_uint256.h b/src/crypto/MerkleTreeProof/arith_uint256.h index 3a880ce295..3e70255269 100644 --- a/src/crypto/MerkleTreeProof/arith_uint256.h +++ b/src/crypto/MerkleTreeProof/arith_uint256.h @@ -288,7 +288,7 @@ class arith_uint256 : public base_uint<256> { * complexities of the sign bit and using base 256 are probably an * implementation accident. */ - arith_uint256& SetCompact(uint32_t nCompact, bool *pfNegative = NULL, bool *pfOverflow = NULL); + arith_uint256& SetCompact(uint32_t nCompact, bool* pfNegative = nullptr, bool* pfOverflow = nullptr); uint32_t GetCompact(bool fNegative = false) const; friend uint256 ArithToUint256(const arith_uint256 &); diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index dc9b261887..949a756d4e 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -42,11 +42,12 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali return llmq::CheckLLMQCommitment(tx, pindexPrev, state); case TRANSACTION_SPORK: return CheckSporkTx(tx, pindexPrev, state); + case TRANSACTION_LELANTUS: + return true; case TRANSACTION_SPARK: // spark transaction checks are done in other places return true; - case TRANSACTION_LELANTUS: - // lelantus transaction checks are done in other places + case TRANSACTION_ALIAS: return true; } @@ -75,6 +76,8 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida return true; case TRANSACTION_SPARK: return true; + case TRANSACTION_ALIAS: + return true; } return state.DoS(100, false, REJECT_INVALID, "bad-tx-type-proc"); @@ -102,6 +105,8 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return true; case TRANSACTION_SPARK: return true; + case TRANSACTION_ALIAS: + return true; } return false; diff --git a/src/evo/spork.cpp b/src/evo/spork.cpp index 9a0ccfb400..77e5f2080f 100644 --- a/src/evo/spork.cpp +++ b/src/evo/spork.cpp @@ -53,19 +53,7 @@ bool CheckSporkTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValida static bool IsTransactionAllowed(const CTransaction &tx, const ActiveSporkMap &sporkMap, CValidationState &state) { - if (tx.IsLelantusTransaction()) { - if (sporkMap.count(CSporkAction::featureLelantus) > 0) - return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Lelantus transactions are disabled at the moment"); - - if (tx.IsLelantusJoinSplit()) { - const auto &limitSpork = sporkMap.find(CSporkAction::featureLelantusTransparentLimit); - if (limitSpork != sporkMap.cend()) { - if (lelantus::GetSpendTransparentAmount(tx) > (CAmount)limitSpork->second.second) - return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Lelantus transaction is over the transparent limit"); - } - } - } - else if (tx.IsSparkTransaction()) { + if (tx.IsSparkTransaction()) { if (sporkMap.count(CSporkAction::featureSpark) > 0) return state.DoS(100, false, REJECT_CONFLICT, "txn-spark-disabled", false, "Spark transactions are disabled at the moment"); @@ -176,24 +164,7 @@ bool CSporkManager::IsTransactionAllowed(const CTransaction &tx, const ActiveSpo } bool CSporkManager::IsBlockAllowed(const CBlock &block, const CBlockIndex *pindex, CValidationState &state) { - if (pindex->activeDisablingSporks.count(CSporkAction::featureLelantusTransparentLimit) > 0) { - // limit total transparent output of lelantus joinsplit - int64_t limit = pindex->activeDisablingSporks.at(CSporkAction::featureLelantusTransparentLimit).second; - CAmount totalTransparentOutput = 0; - - for (const auto &tx: block.vtx) { - if (!tx->IsLelantusJoinSplit()) - continue; - - totalTransparentOutput += lelantus::GetSpendTransparentAmount(*tx); - } - - if (totalTransparentOutput > CAmount(limit)) - return state.DoS(100, false, REJECT_CONFLICT, "txn-lelantus-disabled", false, "Block is over the transparent output limit because of existing spork"); - } - if (pindex->activeDisablingSporks.count(CSporkAction::featureSparkTransparentLimit) > 0) { - // limit total transparent output of lelantus joinsplit int64_t limit = pindex->activeDisablingSporks.at(CSporkAction::featureSparkTransparentLimit).second; CAmount totalTransparentOutput = 0; diff --git a/src/evo/spork.h b/src/evo/spork.h index 4b4d67fc67..a750c46ebb 100644 --- a/src/evo/spork.h +++ b/src/evo/spork.h @@ -13,8 +13,6 @@ typedef std::map> ActiveSporkMap; // one action to perform. Spork transaction can have multiple actions struct CSporkAction { - static constexpr const char *featureLelantus = "lelantus"; - static constexpr const char *featureLelantusTransparentLimit = "lelantustransparentlimit"; static constexpr const char *featureChainlocks = "chainlocks"; static constexpr const char *featureInstantSend = "instantsend"; static constexpr const char *featureSpark = "spark"; diff --git a/src/firo_params.h b/src/firo_params.h index 5158008f58..9a8d57c0c3 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -193,6 +193,8 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput #define SPARK_NAME_TRANSFER_TESTNET_START_BLOCK 189800 #define SPARK_NAME_TRANSFER_DEVNET_START_BLOCK 3900 +#define SPARK_NAME_V21_MAINNET_START_BLOCK 1329000 // Approx June 22 2026 + // Versions of zerocoin mint/spend transactions #define ZEROCOIN_TX_VERSION_3 30 #define ZEROCOIN_TX_VERSION_3_1 31 diff --git a/src/hdmint/hdmint.cpp b/src/hdmint/hdmint.cpp deleted file mode 100644 index 1a43351a28..0000000000 --- a/src/hdmint/hdmint.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include "hdmint.h" - -/** - * CHDMint empty constructor - */ -CHDMint::CHDMint() -{ - SetNull(); -} - -/** - * CHDMint constructor from given values - */ -CHDMint::CHDMint(const int32_t& nCount, const CKeyID& seedId, const uint256& hashSerial, const GroupElement& pubCoinValue) -{ - SetNull(); - this->nCount = nCount; - this->seedId = seedId; - this->hashSerial = hashSerial; - this->pubCoinValue = pubCoinValue; -} - -/** - * Set HDMint object null - */ -void CHDMint::SetNull() -{ - nCount = 0; - seedId.SetNull(); - hashSerial.SetNull(); - txid.SetNull(); - nHeight = -1; - nId = -1; - amount = 0; - isUsed = false; -} - -/** - * Convert CHDMint object to string - * - * @return CHDMint object as string - */ -std::string CHDMint::ToString() const -{ - return strprintf(" HDMint:\n count=%d\n seedId=%s\n hashSerial=%s\n hashPubCoinValue=%s\n txid=%s\n height=%d\n id=%d\n amount=%d\n isUsed=%d\n", - nCount, seedId.ToString(), hashSerial.GetHex(), GetPubCoinHash().GetHex(), txid.GetHex(), nHeight, nId, amount, isUsed); -} diff --git a/src/hdmint/hdmint.h b/src/hdmint/hdmint.h deleted file mode 100644 index 1831f198c9..0000000000 --- a/src/hdmint/hdmint.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef FIRO_HDMINT_H -#define FIRO_HDMINT_H - -#include "primitives/mint_spend.h" - -/** - * CHDMint object - * - * struct that is safe to store essential mint data, without holding any information that allows for actual spending - * (ie. serial, randomness, private key) - */ -class CHDMint -{ -private: - int32_t nCount; - CKeyID seedId; - uint256 hashSerial; - GroupElement pubCoinValue; - uint256 txid; - int nHeight; - int nId; - int64_t amount; - bool isUsed; - -public: - CHDMint(); - CHDMint(const int32_t& nCount, const CKeyID& seedId, const uint256& hashSerial, const GroupElement& pubCoinValue); - - int64_t GetAmount() const { - return amount; - } - int32_t GetCount() const { return nCount; } - int GetHeight() const { return nHeight; } - int GetId() const { return nId; } - CKeyID GetSeedId() const { return seedId; } - uint256 GetSerialHash() const { return hashSerial; } - GroupElement GetPubcoinValue() const { return pubCoinValue; } - uint256 GetPubCoinHash() const { return primitives::GetPubCoinValueHash(pubCoinValue); } - uint256 GetTxHash() const { return txid; } - bool IsUsed() const { return isUsed; } - void SetAmount(int64_t amount) { this->amount = amount; } - void SetHeight(int nHeight) { this->nHeight = nHeight; } - void SetId(int nId) { this->nId = nId; } - void SetNull(); - void SetTxHash(const uint256& txid) { this->txid = txid; } - void SetUsed(const bool isUsed) { this->isUsed = isUsed; } - void SetPubcoinValue(const GroupElement pubCoinValue) { this->pubCoinValue = pubCoinValue; } - std::string ToString() const; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(nCount); - READWRITE(seedId); - READWRITE(hashSerial); - READWRITE(pubCoinValue); - READWRITE(txid); - READWRITE(nHeight); - READWRITE(nId); - READWRITE(amount); - READWRITE(isUsed); - }; -}; - -#endif //FIRO_HDMINT_H - diff --git a/src/hdmint/mintpool.cpp b/src/hdmint/mintpool.cpp deleted file mode 100644 index 1debd918a1..0000000000 --- a/src/hdmint/mintpool.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include "hdmint/mintpool.h" -#include "lelantus.h" -CMintPool::CMintPool(){} - -/** - * Add a mintpool entry - */ -void CMintPool::Add(std::pair pMint, bool fVerbose) -{ - insert(pMint); - - if (fVerbose) - LogPrintf("%s : add %s count %d to mint pool\n", __func__, pMint.first.GetHex().substr(0, 6), std::get<2>(pMint.second)); -} - -/** - * Sort mintpool entries in terms of the mint count. - * - * @return success - */ -bool SortSmallest(const std::pair& a, const std::pair& b) -{ - return std::get<2>(a.second) < std::get<2>(b.second); -} - -/** - * place the mintpool in listMints. - */ -void CMintPool::List(std::list>& listMints) -{ - for (auto pMint : *(this)) { - listMints.emplace_back(pMint); - } - - listMints.sort(SortSmallest); -} - -/** - * clear the current mintpool - */ -void CMintPool::Reset() -{ - clear(); -} - -bool CMintPool::Get(int32_t nCount, uint160 hashSeedMaster, std::pair& result){ - for (auto pMint : *(this)) { - if(std::get<0>(pMint.second)==hashSeedMaster && std::get<2>(pMint.second)==nCount){ - result = pMint; - return true; - } - } - - return false; - -} - - diff --git a/src/hdmint/mintpool.h b/src/hdmint/mintpool.h deleted file mode 100644 index ca11b546ce..0000000000 --- a/src/hdmint/mintpool.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef FIRO_MINTPOOL_H -#define FIRO_MINTPOOL_H - -#include -#include - -#include "primitives/mint_spend.h" -#include "uint256.h" - -typedef std::tuple MintPoolEntry; - -/** - * The MintPool only contains mint seed values that have not been added to the blockchain yet. - * When a mint is generated by the wallet, or found while syncing, the mint will be removed - * from the MintPool. - * - * The MintPool provides a convenient way to check whether mints in the blockchain belong to a - * wallet's deterministic seed. - */ -class CMintPool : public std::map //hashPubcoin mapped to (hashSeedMaster, seedId, count) -{ - -public: - CMintPool(); - void Add(std::pair pMint, bool fVerbose = false); - void List(std::list>& listMints); - void Reset(); - bool Get(int32_t nCount, uint160 hashSeedMaster, std::pair& result); -}; - -#endif // FIRO_MINTPOOL_H diff --git a/src/hdmint/test/lelantus_tests.cpp b/src/hdmint/test/lelantus_tests.cpp deleted file mode 100644 index 573c43f611..0000000000 --- a/src/hdmint/test/lelantus_tests.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "../../test/fixtures.h" -#include "../wallet.h" - -#include - -class HDMintLelantusTests : public ZerocoinTestingSetup200 -{ -public: - HDMintLelantusTests() : - params(lelantus::Params::get_default()) { - } - -public: - MintPoolEntry FrontMintPool() const { - LOCK(pwalletMain->cs_wallet); - - auto nCountNextUse = pwalletMain->zwallet->GetCount(); - auto mints = CWalletDB(pwalletMain->strWalletFile).ListMintPool(); - - for (auto &m : mints) { - if (std::get<2>(m.second) == nCountNextUse) { - return m.second; - } - } - - throw std::runtime_error("next count is not available"); - } - -public: - lelantus::Params const *params; -}; - -BOOST_FIXTURE_TEST_SUITE(hdmint_lelantus_tests, HDMintLelantusTests) - -BOOST_AUTO_TEST_CASE(deterministic_coin_generation_from_seed) -{ - std::array rawSeed1, rawSeed2; - std::fill(rawSeed1.begin(), rawSeed1.end(), 0); - std::fill(rawSeed2.begin(), rawSeed2.end(), 0); - rawSeed2.back() = 1; - - uint512 seed1(rawSeed1), seed2(rawSeed2); - - lelantus::PrivateCoin - coin1(params, 1), - coin2(params, 1), - coin3(params, 1); - - BOOST_CHECK(pwalletMain->zwallet->SeedToLelantusMint(seed1, coin1)); - BOOST_CHECK(pwalletMain->zwallet->SeedToLelantusMint(seed2, coin2)); - BOOST_CHECK(pwalletMain->zwallet->SeedToLelantusMint(seed1, coin3)); - - // TODO: compare private coin directly instead of comparing via mint - auto pubCoin1 = coin1.getPublicCoin(); - auto pubCoin2 = coin2.getPublicCoin(); - auto pubCoin3 = coin3.getPublicCoin(); - - BOOST_CHECK(pubCoin1 == pubCoin3); - BOOST_CHECK(pubCoin1 != pubCoin2); -} - -BOOST_AUTO_TEST_CASE(lelantus_mint_generation) -{ - lelantus::PrivateCoin - coin1(params, 1), - coin2(params, 1); - - CHDMint mint1, mint2; - - // coin should be valid - CWalletDB walletdb(pwalletMain->strWalletFile); - uint160 seedID; - BOOST_CHECK(pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin1, mint1, seedID)); - BOOST_CHECK(pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin2, mint2, seedID)); - - auto entry = FrontMintPool(); - - BOOST_CHECK_EQUAL(2, std::get<2>(entry)); - - auto pubCoin1 = coin1.getPublicCoin(); - auto pubCoin2 = coin2.getPublicCoin(); - - // verify - BOOST_CHECK(pubCoin1 != pubCoin2); - BOOST_CHECK_EQUAL(0, mint1.GetCount()); - BOOST_CHECK_EQUAL(1, mint2.GetCount()); - - // chain state - BOOST_CHECK_EQUAL(-1, mint1.GetHeight()); - BOOST_CHECK_EQUAL(-1, mint1.GetId()); - BOOST_CHECK(mint1.GetTxHash().IsNull()); - BOOST_CHECK(!mint1.IsUsed()); - - // value should be unique - BOOST_CHECK(mint1.GetSeedId() != mint2.GetSeedId()); - BOOST_CHECK(mint1.GetSerialHash() != mint2.GetSerialHash()); - BOOST_CHECK(mint1.GetPubcoinValue() != mint2.GetPubcoinValue()); - BOOST_CHECK(mint1.GetPubCoinHash() != mint2.GetPubCoinHash()); -} - -BOOST_AUTO_TEST_CASE(lelantus_mint_regeneration) -{ - lelantus::PrivateCoin - coin1(params, 1), - coin2(params, 1); - - CHDMint mint1, mint2; - auto entry1 = FrontMintPool(); - - CWalletDB walletdb(pwalletMain->strWalletFile); - uint160 seedID; - BOOST_CHECK(pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin1, mint1, seedID)); - - // re-generate - BOOST_CHECK(pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin2, mint2, seedID, entry1)); - - auto pubCoin1 = coin1.getPublicCoin(); - auto pubCoin2 = coin2.getPublicCoin(); - - // verify - BOOST_CHECK(pubCoin1 == pubCoin2); - BOOST_CHECK_EQUAL(0, mint1.GetCount()); - BOOST_CHECK_EQUAL(0, mint2.GetCount()); - - // chain state - BOOST_CHECK_EQUAL(-1, mint2.GetHeight()); - BOOST_CHECK_EQUAL(-1, mint2.GetId()); - BOOST_CHECK(mint2.GetTxHash().IsNull()); - BOOST_CHECK(!mint2.IsUsed()); - - // value should be the same - BOOST_CHECK(mint1.GetSeedId() == mint2.GetSeedId()); - BOOST_CHECK(mint1.GetSerialHash() == mint2.GetSerialHash()); - BOOST_CHECK(mint1.GetPubcoinValue() == mint2.GetPubcoinValue()); - BOOST_CHECK(mint1.GetPubCoinHash() == mint2.GetPubCoinHash()); -} - -BOOST_AUTO_TEST_CASE(regenerate_mint) -{ - lelantus::PrivateCoin coin(params, 1); - - CHDMint mint; - CWalletDB walletdb(pwalletMain->strWalletFile); - uint160 seedID; - BOOST_CHECK(pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin, mint, seedID)); - - // Should be generated deterministically - - CLelantusEntry entry1, entry2; - pwalletMain->zwallet->RegenerateMint(walletdb, mint, entry1); - pwalletMain->zwallet->RegenerateMint(walletdb, mint, entry2); - - // verify - BOOST_CHECK(entry1.value == entry2.value); - BOOST_CHECK(entry1.randomness == entry2.randomness); - BOOST_CHECK(entry1.serialNumber == entry2.serialNumber); - BOOST_CHECK(entry1.ecdsaSecretKey == entry2.ecdsaSecretKey); - BOOST_CHECK_EQUAL(false, entry1.IsUsed); - BOOST_CHECK_EQUAL(-1, entry1.nHeight); - BOOST_CHECK_EQUAL(-1, entry1.id); - BOOST_CHECK_EQUAL(1, entry1.amount); - - auto key = coin.getEcdsaSeckey(); // 32 bytes - - BOOST_CHECK(coin.getPublicCoin() == entry1.value); - BOOST_CHECK(coin.getRandomness() == entry1.randomness); - BOOST_CHECK(coin.getSerialNumber() == entry1.serialNumber); - BOOST_CHECK_EQUAL(coin.getV(), entry1.amount); - BOOST_CHECK_EQUAL_COLLECTIONS( - key, key + 32, - entry1.ecdsaSecretKey.begin(), entry1.ecdsaSecretKey.end()); -} - -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/hdmint/tracker.cpp b/src/hdmint/tracker.cpp deleted file mode 100644 index 272724de6d..0000000000 --- a/src/hdmint/tracker.cpp +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include "hdmint/tracker.h" -#include "util.h" -#include "sync.h" -#include "txdb.h" -#include "wallet/wallet.h" -#include "hdmint/wallet.h" -#include "validation.h" -#include "lelantus.h" -#include "txmempool.h" - -using namespace sigma; - -/** - * CHDMintTracker constructor. - * - * Sets the wallet file string and clears the in-memory map of serial hashes -> CMintMeta objects - * and the map of serial hashes -> pending spend txids. - * - * @param strWalletFile wallet file string - */ -CHDMintTracker::CHDMintTracker(std::string strWalletFile) -{ - this->strWalletFile = strWalletFile; - mapLelantusSerialHashes.clear(); - mapPendingSpends.clear(); - fInitialized = false; -} - -/** - * Destroy the CHDMintTracker object. - * - * clears the in-memory map of serial hashes -> CMintMeta objects and the map of - * serial hashes -> pending spend txids. - * - */ -CHDMintTracker::~CHDMintTracker() -{ - mapLelantusSerialHashes.clear(); - mapPendingSpends.clear(); -} - -/** - * Initialize the CHDMintTracker object. - * - * Calls ListMints, which loads all CSigmaEntries and CHDMints from the database. - */ -void CHDMintTracker::Init() -{ - if (!fInitialized) { - ListLelantusMints(false, false, false, true); - fInitialized = true; - } -} - -/** - * Archive a mint. - * - * Ensures the mint exists in the database and then adds it to the archive. - * - * @param meta mint meta object - * @return success - */ - -bool CHDMintTracker::Archive(CLelantusMintMeta& meta) -{ - uint256 hashPubcoin = meta.GetPubCoinValueHash(); - - if (HasLelantusSerialHash(meta.hashSerial)) - mapLelantusSerialHashes.at(meta.hashSerial).isArchived = true; - - CWalletDB walletdb(strWalletFile); - CHDMint dMint; - if (!walletdb.ReadHDMint(hashPubcoin, true, dMint)) - return error("%s: could not find pubcoinhash %s in db", __func__, hashPubcoin.GetHex()); - if (!walletdb.ArchiveDeterministicOrphan(dMint)) - return error("%s: failed to archive deterministic ophaned mint", __func__); - - LogPrintf("%s: archived pubcoinhash %s\n", __func__, hashPubcoin.GetHex()); - return true; -} - -/** - * Unarchives a mint. - * - * @param hashPubcoin reference to mint pubcoin hash - * @return success - */ -bool CHDMintTracker::UnArchive(const uint256& hashPubcoin, bool isDeterministic) -{ - CWalletDB walletdb(strWalletFile); - if (isDeterministic) { - CHDMint dMint; - if (!walletdb.UnarchiveHDMint(hashPubcoin, false, dMint)) - return error("%s: failed to unarchive deterministic mint", __func__); - AddLelantus(walletdb, dMint, false); - } - - LogPrintf("%s: unarchived %s\n", __func__, hashPubcoin.GetHex()); - return true; -} - -/** - * Get a CMintMeta object from memory using mint serial hash - * - * @param hashSerial mint serial hash used to retrieve object - * @param mMeta reference to CMintMeta object - * @return success - */ -bool CHDMintTracker::GetMetaFromSerial(const uint256 &hashSerial, CLelantusMintMeta& mMeta) -{ - auto it = mapLelantusSerialHashes.find(hashSerial); - if(it == mapLelantusSerialHashes.end()) - return false; - - mMeta = mapLelantusSerialHashes.at(hashSerial); - return true; -} - -/** - * Get a CMintMeta object from memory using mint pubcoin hash - * - * @param hashPubcoin mint pubcoin hash used to retrieve object - * @param mMeta reference to CMintMeta object - * @return success - */ -bool CHDMintTracker::GetLelantusMetaFromPubcoin(const uint256& hashPubcoin, CLelantusMintMeta& mMeta) -{ - for (auto it : mapLelantusSerialHashes) { - if (it.second.GetPubCoinValueHash() == hashPubcoin){ - mMeta = it.second; - return true; - } - } - - return false; -} - -/** - * Get the list of non-archiveed, in-memory mint serial hashes. - * - * @return vHashes vector of serial hashes - */ -std::vector CHDMintTracker::GetSerialHashes() -{ - std::vector vHashes; - for (auto it : mapLelantusSerialHashes) { - if (it.second.isArchived) - continue; - - vHashes.emplace_back(it.first); - } - - - return vHashes; -} - -/** - * Does this mint pubcoin hash exist in a CMintMeta object in memory - * - * @param hashPubcoin mint pubcoin hash - * @return success - */ -bool CHDMintTracker::HasPubcoinHash(const uint256& hashPubcoin, CWalletDB& walletdb) const -{ - for (auto const & it : mapLelantusSerialHashes) { - CLelantusMintMeta meta = it.second; - uint256 reducedHash; - walletdb.ReadPubcoinHashes(meta.GetPubCoinValueHash(), reducedHash); - if (reducedHash == hashPubcoin) - return true; - } - - return false; -} - -/** - * Does this mint serial hash map to a CMintMeta object in memory - * - * @param hashSerial mint serial hash - * @return success - */ - -bool CHDMintTracker::HasLelantusSerialHash(const uint256& hashSerial) const -{ - auto it = mapLelantusSerialHashes.find(hashSerial); - return it != mapLelantusSerialHashes.end(); -} - -/** - * Update the tracker state - * - * From the CMintMeta object passed, update the state (both memory and database) accordingly. - * If a CHDMint object does not exist for this mint, fail. - * - * @param meta the CMintMeta object used to update - * @return success - */ - -bool CHDMintTracker::UpdateState(const CLelantusMintMeta& meta) -{ - uint256 hashPubcoin = meta.GetPubCoinValueHash(); - CWalletDB walletdb(strWalletFile); - - CHDMint dMint; - if (!walletdb.ReadHDMint(hashPubcoin, true, dMint)) { - // Check archive just in case - if (!meta.isArchived) - return error("%s: failed to read Lelantus mint from database", __func__); - - // Unarchive this mint since it is being requested and updated - if (!walletdb.UnarchiveHDMint(hashPubcoin, true, dMint)) - return error("%s: failed to unarchive Lelantus mint from database", __func__); - } - - // get coin id & height - int height, id; - if(meta.nHeight<0 || meta.nId <= 0){ - std::tie(height, id) = lelantus::CLelantusState::GetState()->GetMintedCoinHeightAndId(lelantus::PublicCoin(dMint.GetPubcoinValue())); - } - else{ - height = meta.nHeight; - id = meta.nId; - } - - dMint.SetHeight(height); - dMint.SetId(id); - dMint.SetUsed(meta.isUsed); - dMint.SetAmount(meta.amount); - - if (!walletdb.WriteHDMint(meta.GetPubCoinValueHash(), dMint, true)) - return error("%s: failed to update Lelantus mint when writing to db", __func__); - - auto pubcoin = dMint.GetPubcoinValue() + lelantus::Params::get_default()->get_h1() * Scalar(meta.amount).negate(); - walletdb.WritePubcoinHashes(hashPubcoin, primitives::GetPubCoinValueHash(pubcoin)); - - - pwalletMain->NotifyZerocoinChanged( - pwalletMain, - dMint.GetPubcoinValue().GetHex(), - std::string("Update (") + std::to_string((double)dMint.GetAmount() / COIN) + "mint)", - CT_UPDATED); - - mapLelantusSerialHashes[meta.hashSerial] = meta; - - return true; -} - -/** - * Add a mint object to memory. - * - * If this is a new mint, also write the CHDMint object to database. - * Also notifies Qt that a Sigma mint has been added so as to update the balance display correctly. - * This is used to populate memory on startup. - * - * @param dMint CHDMint object to add - * @param isNew set to true if this mint has just been created, also adds mint to database - * @param isArchived set to true if this mint is archived, used to set meta object correctly - */ -void CHDMintTracker::AddLelantus(CWalletDB& walletdb, const CHDMint& dMint, bool isNew, bool isArchived) -{ - CLelantusMintMeta meta; - meta.SetPubCoinValue(dMint.GetPubcoinValue()); - meta.nHeight = dMint.GetHeight(); - meta.nId = dMint.GetId(); - meta.txid = dMint.GetTxHash(); - meta.isUsed = dMint.IsUsed(); - meta.hashSerial = dMint.GetSerialHash(); - meta.amount = dMint.GetAmount(); - meta.isArchived = isArchived; - meta.isSeedCorrect = true; - mapLelantusSerialHashes[meta.hashSerial] = meta; - - pwalletMain->NotifyZerocoinChanged( - pwalletMain, - dMint.GetPubcoinValue().GetHex(), - std::string("Update (") + std::to_string((double)dMint.GetAmount() / COIN) + "mint)", - CT_UPDATED); - - if (isNew) { - walletdb.WriteHDMint(meta.GetPubCoinValueHash(), dMint, true); - auto pubcoin = dMint.GetPubcoinValue() + lelantus::Params::get_default()->get_h1() * Scalar(meta.amount).negate(); - walletdb.WritePubcoinHashes(meta.GetPubCoinValueHash(), primitives::GetPubCoinValueHash(pubcoin)); - } -} - -/** - * Sets a mint as used via it's pubcoin hash. - * - * @param hashPubcoin mint pubcoin hash. Used to retrieve meta object - * @param txid transaction ID of mint - */ -void CHDMintTracker::SetLelantusPubcoinUsed(const uint256& hashPubcoin, const uint256& txid) -{ - CLelantusMintMeta meta; - if(!GetLelantusMetaFromPubcoin(hashPubcoin, meta)) - return; - meta.isUsed = true; - mapPendingSpends.insert(std::make_pair(meta.hashSerial, txid)); - UpdateState(meta); -} - - -/** - * Sets a mint as not used via it's pubcoin hash. - * - * @param hashPubcoin mint pubcoin hash. Used to retrieve meta object - */ - -void CHDMintTracker::SetLelantusPubcoinNotUsed(const uint256& hashPubcoin) -{ - CLelantusMintMeta meta; - if(!GetLelantusMetaFromPubcoin(hashPubcoin, meta)) - return; - meta.isUsed = false; - - if (mapPendingSpends.count(meta.hashSerial)) - mapPendingSpends.erase(meta.hashSerial); - - UpdateState(meta); -} - - -/** - * Check mempool for the spend associated with the mint serial hash passed - * - * @param setMempool the set of txid hashes in the mempool - * @param hashSerial the mint serial hash to check for - * @return success - */ -bool CHDMintTracker::IsMempoolSpendOurs(const std::set& setMempool, const uint256& hashSerial){ - for(auto& mempoolTxid : setMempool){ - CTransactionRef ptx = txpools.get(mempoolTxid); - if(!ptx) { - continue; - } - - const CTransaction &tx = *ptx; - for (const CTxIn& txin : tx.vin) { - if (txin.IsLelantusJoinSplit()) { - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(tx); - } catch (const std::exception &) { - return false; - } - - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - for(const auto& serial: serials) { - uint256 mempoolHashSerial = primitives::GetSerialHash(serial); - if(mempoolHashSerial==hashSerial){ - return true; - } - } - } - } - } - - return false; -} - -/** - * Update the in-memory CMintMeta object for the current mempool - * - * @param setMempool the set of txid hashes in the mempool - * @param mint the CMintMeta object to check for - * @param fSpend if this mint object is being updated as a result of a spend transaction - * @return success - */ -bool CHDMintTracker::UpdateLelantusMetaStatus(const std::set& setMempool, CLelantusMintMeta& mint, bool fSpend) -{ - uint256 hashPubcoin = mint.GetPubCoinValueHash(); - //! Check whether this mint has been spent and is considered 'pending' or 'confirmed' - // If there is not a record of the block height, then look it up and assign it - COutPoint outPoint; - lelantus::PublicCoin pubCoin(mint.GetPubCoinValue()); - bool isMintInChain = GetOutPoint(outPoint, pubCoin); - LogPrintf("UpdateLelantusMetaStatus : isMintInChain: %d\n", isMintInChain); - const uint256& txidMint = outPoint.hash; - - //See if there is internal record of spending this mint (note this is memory only, would reset on restart - next function checks this) - bool isPendingSpend = static_cast(mapPendingSpends.count(mint.hashSerial)); - - // Mempool might hold pending spend - if(!isPendingSpend && fSpend) - isPendingSpend = IsMempoolSpendOurs(setMempool, mint.hashSerial); - - LogPrintf("UpdateLelantusMetaStatus : isPendingSpend: %d\n", isPendingSpend); - - // See if there is a blockchain record of spending this mint - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - Scalar bnSerial; - bool isConfirmedSpend = lelantusState->IsUsedCoinSerialHash(bnSerial, mint.hashSerial); - LogPrintf("UpdateLelantusMetaStatus : isConfirmedSpend: %d\n", isConfirmedSpend); - - bool isUsed = isPendingSpend || isConfirmedSpend; - - if ((mint.nHeight==-1) || (mint.nId==-1) || !isMintInChain || isUsed != mint.isUsed) { - CTransactionRef tx; - uint256 hashBlock; - - // Txid will be marked 0 if there is no knowledge of the final tx hash yet - if (mint.txid.IsNull()) { - if (!isMintInChain) { - if(mint.nHeight>-1) mint.nHeight = -1; - if(mint.nId>-1) mint.nId = -1; - // still want to update this mint later if syncing. else ignore - if(IsInitialBlockDownload()){ - return true; - } - return false; - } - mint.txid = txidMint; - } - - LogPrintf("UpdateLelantusMetaStatus : mint.txid = %d\n", mint.txid.GetHex()); - - if (setMempool.count(mint.txid)) { - if(mint.nHeight>-1) mint.nHeight = -1; - if(mint.nId>-1) mint.nId = -1; - return true; - } - - // Check the transaction associated with this mint - if (!GetTransaction(mint.txid, tx, ::Params().GetConsensus(), hashBlock, true)) { - LogPrintf("%s : Failed to find tx for mint txid=%s\n", __func__, mint.txid.GetHex()); - mint.isArchived = true; - Archive(mint); - return true; - } - - bool isUpdated = false; - - // An orphan tx if hashblock is in mapBlockIndex but not in chain active - if (mapBlockIndex.count(hashBlock)) { - if(!chainActive.Contains(mapBlockIndex.at(hashBlock))) { - LogPrintf("%s : Found orphaned mint txid=%s\n", __func__, mint.txid.GetHex()); - mint.isUsed = false; - mint.nHeight = 0; - - return true; - } else if((mint.nHeight==-1) || (mint.nId<=0)) { // assign nHeight if not present - lelantus::PublicCoin pubcoin(mint.GetPubCoinValue()); - auto MintedCoinHeightAndId = lelantusState->GetMintedCoinHeightAndId(pubcoin); - mint.nHeight = MintedCoinHeightAndId.first; - mint.nId = MintedCoinHeightAndId.second; - LogPrintf("%s : Set mint %s nHeight to %d\n", __func__, hashPubcoin.GetHex(), mint.nHeight); - LogPrintf("%s : Set mint %s nId to %d\n", __func__, hashPubcoin.GetHex(), mint.nId); - isUpdated = true; - } - } - - // Check that the mint has correct used status - if (mint.isUsed != isUsed) { - LogPrintf("%s : Set mint %s isUsed to %d\n", __func__, hashPubcoin.GetHex(), isUsed); - mint.isUsed = isUsed; - isUpdated = true; - } - - if(isUpdated) return true; - } - - return false; -} - -/** - * Update mints found on-chain. - * - * @param mintPoolEntries the set of mint pool entries to update - * @param updatedMeta the CMintMeta objects to update - */ - -void CHDMintTracker::UpdateFromBlock(const std::list>& mintPoolEntries, const std::vector& updatedMeta){ - if (mintPoolEntries.size() > 0) { - pwalletMain->zwallet->SyncWithChain(false, mintPoolEntries); - } - - //overwrite any updates - for (CLelantusMintMeta meta : updatedMeta) - UpdateState(meta); -} - -/** - * Update the state if mint transactions found on-chain exist in the wallet. - * - * We attempt to read a mintpool object from the on-chain data found. If so, update state. - * - * @param mints the set of public coin objects to check for. - */ -void CHDMintTracker::UpdateMintStateFromBlock(const std::vector>>& mints) { - CWalletDB walletdb(strWalletFile); - std::vector updatedMeta; - std::list> mintPoolEntries; - uint160 hashSeedMasterEntry; - CKeyID seedId; - int32_t nCount; - std::set setMempool = GetMempoolTxids(); - for (auto& mint : mints) { - uint256 reducedHash; - if(!walletdb.ReadPubcoinHashes(primitives::GetPubCoinValueHash(mint.first.getValue()), reducedHash)) { - uint64_t amount = mint.second.first; - auto pubcoin = mint.first.getValue() + lelantus::Params::get_default()->get_h1() * Scalar(amount).negate(); - reducedHash = primitives::GetPubCoinValueHash(pubcoin); - } - CLelantusMintMeta meta; - // Check reducedHash in db - if(walletdb.ReadMintPoolPair(reducedHash, hashSeedMasterEntry, seedId, nCount)) { - // If found in db but not in memory - this is likely a resync - if(!GetLelantusMetaFromPubcoin(primitives::GetPubCoinValueHash(mint.first.getValue()), meta)){ - MintPoolEntry mintPoolEntry(hashSeedMasterEntry, seedId, nCount); - mintPoolEntries.push_back(std::make_pair(reducedHash, mintPoolEntry)); - continue; - } - if(UpdateLelantusMetaStatus(setMempool, meta)){ - updatedMeta.emplace_back(meta); - } - } - } - - UpdateFromBlock(mintPoolEntries, updatedMeta); -} - -/** - * Update the state if spend transactions found on-chain exist in the wallet. - * - * We attempt to read a mintpool object from the on-chain data found. If so, update state. - * - * @param spentSerials the set of spent serial objects to check for. - */ -void CHDMintTracker::UpdateSpendStateFromBlock(const std::unordered_map& spentSerials){ - CWalletDB walletdb(strWalletFile); - std::vector updatedMeta; - std::list> mintPoolEntries; - uint160 hashSeedMasterEntry; - CKeyID seedId; - int32_t nCount; - std::set setMempool = GetMempoolTxids(); - for(auto& spentSerial : spentSerials){ - uint256 spentSerialHash = primitives::GetSerialHash(spentSerial.first); - CLelantusMintMeta meta; - GroupElement pubcoin; - // Check serialHash in db - if(walletdb.ReadPubcoin(spentSerialHash, pubcoin)) { - // If found in db but not in memory - this is likely a resync - if(!GetMetaFromSerial(spentSerialHash, meta)){ - uint256 hashPubcoin = primitives::GetPubCoinValueHash(pubcoin); - if(!walletdb.ReadMintPoolPair(hashPubcoin, hashSeedMasterEntry, seedId, nCount)) { - continue; - } - MintPoolEntry mintPoolEntry(hashSeedMasterEntry, seedId, nCount); - mintPoolEntries.push_back(std::make_pair(hashPubcoin, mintPoolEntry)); - continue; - } - if(UpdateLelantusMetaStatus(setMempool, meta, true)){ - updatedMeta.emplace_back(meta); - } - } - } - - UpdateFromBlock(mintPoolEntries, updatedMeta); -} - -/** - * Update the state if mint transactions found in the mempool exist in the wallet. - * - * We attempt to read a mintpool object from the mempool data found. If so, update state. - * - * @param pubCoins the set of public coin objects to check for. - */ - -void CHDMintTracker::UpdateLelantusMintStateFromMempool(const std::vector& pubCoins, const std::vector& amounts) { - CWalletDB walletdb(strWalletFile); - std::vector updatedLelantusMeta; - std::list> mintPoolEntries; - uint160 hashSeedMasterEntry; - CKeyID seedId; - int32_t nCount; - std::set setMempool = GetMempoolTxids(); - int i = 0; - for (auto& pubcoin : pubCoins) { - uint256 reducedHash; - if(!walletdb.ReadPubcoinHashes(primitives::GetPubCoinValueHash(pubcoin), reducedHash)) { - auto pub = pubcoin + lelantus::Params::get_default()->get_h1() * Scalar(amounts[i]).negate(); - reducedHash = primitives::GetPubCoinValueHash(pub); - } - - - LogPrintf("UpdateMintStateFromMempool: hashPubcoin=%d\n", reducedHash.GetHex()); - // Check reducedHash in db - if(walletdb.ReadMintPoolPair(reducedHash, hashSeedMasterEntry, seedId, nCount)){ - // If found in db but not in memory - this is likely a resync - CLelantusMintMeta metaLelantus; - bool skip = !GetLelantusMetaFromPubcoin(primitives::GetPubCoinValueHash(pubcoin), metaLelantus); - - if(skip) { - MintPoolEntry mintPoolEntry(hashSeedMasterEntry, seedId, nCount); - mintPoolEntries.push_back(std::make_pair(reducedHash, mintPoolEntry)); - i++; - continue; - } - - if(UpdateLelantusMetaStatus(setMempool, metaLelantus)){ - updatedLelantusMeta.emplace_back(metaLelantus); - } - - } - i++; - } - - UpdateFromBlock(mintPoolEntries, updatedLelantusMeta); -} - -/** - * Update the state if spend transactions found in the mempool exist in the wallet. - * - * We attempt to read a mintpool object from the mempool data found. If so, update state. - * - * @param spentSerials the set of spent serial objects to check for. - */ - -void CHDMintTracker::UpdateJoinSplitStateFromMempool(const std::vector& spentSerials) { - CWalletDB walletdb(strWalletFile); - std::vector updatedMeta; - std::list> mintPoolEntries; - uint160 hashSeedMasterEntry; - CKeyID seedId; - int32_t nCount; - std::set setMempool = GetMempoolTxids(); - for(auto& spentSerial : spentSerials){ - uint256 spentSerialHash = primitives::GetSerialHash(spentSerial); - CLelantusMintMeta meta; - GroupElement pubcoin; - // Check serialHash in db - if(walletdb.ReadPubcoin(spentSerialHash, pubcoin)) { - // If found in db but not in memory - this is likely a resync - if(!GetMetaFromSerial(spentSerialHash, meta)){ - uint256 hashPubcoin = primitives::GetPubCoinValueHash(pubcoin); - if(!walletdb.ReadMintPoolPair(hashPubcoin, hashSeedMasterEntry, seedId, nCount)) { - continue; - } - MintPoolEntry mintPoolEntry(hashSeedMasterEntry, seedId, nCount); - mintPoolEntries.push_back(std::make_pair(hashPubcoin, mintPoolEntry)); - continue; - } - if(UpdateLelantusMetaStatus(setMempool, meta, true)){ - updatedMeta.emplace_back(meta); - } - } - } - - UpdateFromBlock(mintPoolEntries, updatedMeta); -} - -/** - * Returns the in memory mint objects as CSigmaEntry objects (ie. mints containing private data) - * - * @param fUnusedOnly convert unused mints only - * @param fMatureOnly convert mature (ie. spendable due to sufficient confirmations) mints only - * @return list of CSigmaEntry objects - */ -std::list CHDMintTracker::MintsAsLelantusEntries(bool fUnusedOnly, bool fMatureOnly){ - std::list listCoin; - CWalletDB walletdb(strWalletFile); - std::vector vecMists = ListLelantusMints(fUnusedOnly, fMatureOnly, false); - std::list listMints(vecMists.begin(), vecMists.end()); - for (const CLelantusMintMeta& mint : listMints) { - CLelantusEntry entry; - pwalletMain->GetMint(mint.hashSerial, entry); - listCoin.push_back(entry); - } - return listCoin; -} - -/** - * Sets up the in memory mint objects. - * - * @param fUnusedOnly process mature (ie. spendable due to sufficient confirmations) mints only - * @param fUpdateStatus If the mints should be updated - * @param fLoad If the mints should be loaded from database - * @param fWrongSeed If mints without correct seed should be added - * @return vector of CMintMeta objects - */ -std::vector CHDMintTracker::ListLelantusMints(bool fUnusedOnly, bool fMatureOnly, bool fUpdateStatus, bool fLoad, bool fWrongSeed) -{ - std::vector setMints; - if (fLoad) { - LOCK2(cs_main, pwalletMain->cs_wallet); - CWalletDB walletdb(strWalletFile); - - std::list listDeterministicDB = walletdb.ListHDMints(true); - for (auto& dMint : listDeterministicDB) { - AddLelantus(walletdb, dMint, false, false); - } - LogPrint("zero", "%s: added %d lelantus hdmint from DB\n", __func__, listDeterministicDB.size()); - } - - std::vector vOverWrite; - std::set setMempool = GetMempoolTxids(); - - for (auto& it : mapLelantusSerialHashes) { - CLelantusMintMeta mint = it.second; - - //This is only intended for unarchived coins - if (mint.isArchived) - continue; - - // Update the metadata of the mints if requested - if (fUpdateStatus){ - if(UpdateLelantusMetaStatus(setMempool, mint)) { - if (mint.isArchived) - continue; - - // Mint was updated, queue for overwrite - vOverWrite.emplace_back(mint); - } - } - - if (fUnusedOnly && mint.isUsed) - continue; - - if (fMatureOnly) { - // Not confirmed - if (!mint.nHeight || !(mint.nHeight + (ZC_MINT_CONFIRMATIONS-1) <= chainActive.Height())) - continue; - } - - if (!fWrongSeed && !mint.isSeedCorrect) - continue; - - setMints.push_back(mint); - } - - //overwrite any updates - for (CLelantusMintMeta& meta : vOverWrite) - UpdateState(meta); - - return setMints; -} - -/** - * Get txids of all mempool entries. - * - * @return set of mempool txids - */ -std::set CHDMintTracker::GetMempoolTxids(){ - std::set setMempool; - setMempool.clear(); - { - LOCK(mempool.cs); - txpools.getTransactions(setMempool); - } - return setMempool; -} - -/** - * map of serial hashes -> CMintMeta objects - */ -void CHDMintTracker::Clear() -{ - mapLelantusSerialHashes.clear(); -} diff --git a/src/hdmint/tracker.h b/src/hdmint/tracker.h deleted file mode 100644 index addb3296d6..0000000000 --- a/src/hdmint/tracker.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef FIRO_HDMINTTRACKER_H -#define FIRO_HDMINTTRACKER_H - -#include "primitives/mint_spend.h" -#include "hdmint/mintpool.h" -#include "wallet/walletdb.h" -#include - -class CHDMint; -class CHDMintWallet; - -class CHDMintTracker -{ -private: - bool fInitialized; - std::string strWalletFile; - std::map mapLelantusSerialHashes; - std::map mapPendingSpends; //serialhash, txid of spend - bool IsMempoolSpendOurs(const std::set& setMempool, const uint256& hashSerial); - bool UpdateLelantusMetaStatus(const std::set& setMempool, CLelantusMintMeta& mint, bool fSpend=false); - - std::set GetMempoolTxids(); -public: - CHDMintTracker(std::string strWalletFile); - ~CHDMintTracker(); - void AddLelantus(CWalletDB& walletdb, const CHDMint& dMint, bool isNew = false, bool isArchived = false); - bool Archive(CLelantusMintMeta& meta); - bool HasPubcoinHash(const uint256& hashPubcoin, CWalletDB& walletdb) const; - bool HasLelantusSerialHash(const uint256& hashSerial) const; - bool IsEmpty() const { return mapLelantusSerialHashes.empty(); } - void Init(); - bool GetMetaFromSerial(const uint256& hashSerial, CLelantusMintMeta& mMeta); - bool GetLelantusMetaFromPubcoin(const uint256& hashPubcoin, CLelantusMintMeta& mMeta); - - std::vector GetSerialHashes(); - void UpdateFromBlock(const std::list>& mintPoolEntries, const std::vector& updatedMeta); - void UpdateMintStateFromBlock(const std::vector>>& mints); - void UpdateSpendStateFromBlock(const std::unordered_map& spentSerials); - void UpdateLelantusMintStateFromMempool(const std::vector& pubCoins, const std::vector& amounts); - void UpdateJoinSplitStateFromMempool(const std::vector& spentSerials); - std::list MintsAsLelantusEntries(bool fUnusedOnly = true, bool fMatureOnly = true); - std::vector ListLelantusMints(bool fUnusedOnly = true, bool fMatureOnly = true, bool fUpdateStatus = true, bool fLoad = false, bool fWrongSeed = false); - void SetLelantusPubcoinUsed(const uint256& hashPubcoin, const uint256& txid); - void SetLelantusPubcoinNotUsed(const uint256& hashPubcoin); - bool UnArchive(const uint256& hashPubcoin, bool isDeterministic); - bool UpdateState(const CLelantusMintMeta& meta); - void Clear(); -}; - -#endif //FIRO_HDMINTTRACKER_H diff --git a/src/hdmint/wallet.cpp b/src/hdmint/wallet.cpp deleted file mode 100644 index 2c720644bc..0000000000 --- a/src/hdmint/wallet.cpp +++ /dev/null @@ -1,858 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include "hdmint/wallet.h" -#include "validation.h" -#include "txdb.h" -#include "init.h" -#include "hdmint/hdmint.h" -#include "wallet/walletdb.h" -#include "wallet/wallet.h" -#include "lelantus.h" -#include "crypto/hmac_sha256.h" -#include "crypto/hmac_sha512.h" -#include "keystore.h" -#include -#include "masternode-sync.h" -#include "ui_interface.h" - -/** - * Constructor for CHDMintWallet object. - * - * Sets database values: the wallet file, mintpool, masterseed hash and count values. - * Doesn't set encrypted values if the wallet is locked. - * - * @param strWalletFile wallet file string - */ -CHDMintWallet::CHDMintWallet(const std::string& strWalletFile, bool resetCount) : strWalletFile(strWalletFile), tracker(strWalletFile) -{ - this->mintPool = CMintPool(); - - //Don't try to do anything else if the wallet is locked. - if (pwalletMain->IsLocked()) { - return; - } - - // Use MasterKeyId from HDChain as index for mintpool - uint160 hashSeedMaster = pwalletMain->GetHDChain().masterKeyID; - - if (!SetupWallet(hashSeedMaster, resetCount)) { - LogPrintf("%s: failed to save deterministic seed\n", __func__); - return; - } -} - -/** - * Constructor helper function. - * - * @param hashSeedMaster hash master seed - * @param fResetCount if true, set DB counts to 0. - * @return bool - */ -bool CHDMintWallet::SetupWallet(const uint160& hashSeedMaster, bool fResetCount) -{ - CWalletDB walletdb(strWalletFile); - if (pwalletMain->IsLocked()) - return false; - - if (hashSeedMaster.IsNull()) { - return error("%s: failed to set master seed.", __func__); - } - - this->hashSeedMaster = hashSeedMaster; - - nCountNextUse = COUNT_DEFAULT; - nCountNextGenerate = COUNT_DEFAULT; - - if (fResetCount){ - walletdb.WriteMintCount(nCountNextUse); - walletdb.WriteMintSeedCount(nCountNextGenerate); - }else{ - if (!walletdb.ReadMintCount(nCountNextUse)) - nCountNextUse = COUNT_DEFAULT; - if (!walletdb.ReadMintSeedCount(nCountNextGenerate)) - nCountNextGenerate = COUNT_DEFAULT; - } - - return true; -} - -/** - * Regenerate a MintPoolEntry value from given values. - * - * Doesn't do anything if the wallet is encrypted+locked. - * Attempts to recreate mint seed (512-bit value used for mint generate) from inputs. - * Then recreates mint from the seed, and stores walletdb values appropriately. - * - * @param mintHashSeedMaster hash master seed for this mint - * @param seedId seed ID for the key used to generate mint - * @param nCount count for this mint in the HD chain - * @RETURN pair of for this mint - */ -std::pair CHDMintWallet::RegenerateMintPoolEntry(CWalletDB& walletdb, const uint160& mintHashSeedMaster, CKeyID& seedId, const int32_t& nCount) -{ - // hashPubcoin, hashSerial - std::pair nIndexes; - - //Is locked - if (pwalletMain->IsLocked()) - throw std::runtime_error("Error: Please enter the wallet passphrase with walletpassphrase first."); - - uint512 mintSeed; - if(!CreateMintSeed(walletdb, mintSeed, nCount, seedId)) - throw std::runtime_error("Unable to create seed for mint regeneration."); - - GroupElement commitmentValue; - lelantus::PrivateCoin coin(lelantus::Params::get_default(), 0); - if(!SeedToMint(mintSeed, commitmentValue, coin)) //for lelantus put just part of commit, for checking we will need to reduce h1^v from lelantus mint - throw std::runtime_error("Unable to create sigmamint from seed in mint regeneration."); - - uint256 hashPubcoin = primitives::GetPubCoinValueHash(commitmentValue); - uint256 hashSerial = primitives::GetSerialHash(coin.getSerialNumber()); - - MintPoolEntry mintPoolEntry(mintHashSeedMaster, seedId, nCount); - mintPool.Add(std::make_pair(hashPubcoin, mintPoolEntry)); - walletdb.WritePubcoin(hashSerial, commitmentValue); - walletdb.WriteMintPoolPair(hashPubcoin, mintPoolEntry); - - nIndexes.first = hashPubcoin; - nIndexes.second = hashSerial; - - return nIndexes; - -} - -/** - * Generate the mintpool for the current master seed. - * - * only runs if the current mintpool is exhausted and we need new mints (ie. the next mint to - * generate is the same as the one last used) - * Generates 20 mints at a time. - * Makes the appropriate database entries. - * - * @param nIndex The number of mints to generate. Defaults to 20 if no param passed. - */ -void CHDMintWallet::GenerateMintPool(CWalletDB& walletdb, bool forceGenerate, int32_t nIndex) -{ - //Is locked - if (pwalletMain->IsLocked()) - return; - - // Only generate new values (ie. if last generated less than or the same, proceed) - if(nCountNextGenerate > nCountNextUse && !forceGenerate){ - return; - } - - LOCK(pwalletMain->cs_wallet); - - unsigned int mintpoolsize = std::min((unsigned int)GetArg("-mintpoolsize", DEFAULT_MINTPOOL_SIZE), MAX_MINTPOOL_SIZE); - - int32_t nLastCount = nCountNextGenerate; - int32_t nStop = nLastCount + mintpoolsize; - if(nIndex > 0 && nIndex >= nLastCount) - nStop = nIndex + mintpoolsize; - LogPrintf("%s : nLastCount=%d nStop=%d\n", __func__, nLastCount, nStop - 1); - for (; nLastCount <= nStop; ++nLastCount) { - if (ShutdownRequested()) - return; - - CKeyID seedId; - uint512 mintSeed; - if(!CreateMintSeed(walletdb, mintSeed, nLastCount, seedId, false)) - continue; - - GroupElement commitmentValue; - lelantus::PrivateCoin coin(lelantus::Params::get_default(), 0); - if(!SeedToMint(mintSeed, commitmentValue, coin)) //for lelantus put just part of commit, for checking we will need to reduce h1^v from lelantus mint - continue; - - uint256 hashPubcoin = primitives::GetPubCoinValueHash(commitmentValue); - - MintPoolEntry mintPoolEntry(hashSeedMaster, seedId, nLastCount); - mintPool.Add(std::make_pair(hashPubcoin, mintPoolEntry)); - walletdb.WritePubcoin(primitives::GetSerialHash(coin.getSerialNumber()), commitmentValue); - walletdb.WriteMintPoolPair(hashPubcoin, mintPoolEntry); - } - - // write hdchain back to database - if (!walletdb.WriteHDChain(pwalletMain->GetHDChain())) - throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); - - // Update local + DB entries for count last generated - nCountNextGenerate = nLastCount; - walletdb.WriteMintSeedCount(nCountNextGenerate); - -} - -/** - * Load mintpool values from the database into memory. - * - */ -bool CHDMintWallet::LoadMintPoolFromDB() -{ - CWalletDB walletdb(strWalletFile); - std::vector> listMintPool = walletdb.ListMintPool(); - - for (auto& mintPoolPair : listMintPool){ - mintPool.Add(mintPoolPair); - } - - return true; -} - -/** - * Get the mint serial hash for the mint pubcoin hash given. - * - * We only have a map of serial hashes to pubcoins (from db), so need to traverse the other way in a loop. - * - * @param serialPubcoinPairs a vector of mint hash serials to pubcoin objects. - * @param hashPubcoin mint pubcoin hash - * @param hashSerial reference to a mint serial hash. Is set if found - * @return success - */ -bool CHDMintWallet::GetSerialForPubcoin(const std::vector>& serialPubcoinPairs, const uint256& hashPubcoin, uint256& hashSerial) -{ - bool fFound = false; - for(const auto& serialPubcoinPair : serialPubcoinPairs){ - if(hashPubcoin == primitives::GetPubCoinValueHash(serialPubcoinPair.second)){ - hashSerial = serialPubcoinPair.first; - fFound = true; - break; - } - } - - return fFound; -} - -void CHDMintWallet::SetWalletTransactionBlock(CWalletTx &wtx, const CBlockIndex *blockIndex, const CBlock &block) { - size_t posInBlock = INT_MAX; - uint256 txHash = wtx.tx->GetHash(); - for (size_t i=0; iGetHash() == txHash) - posInBlock = i; - assert(posInBlock < INT_MAX); - wtx.SetMerkleBranch(blockIndex, (int)posInBlock); -} - -/** - * Catch the mint counter up with the chain. - * - * Mints are created deterministically so we can completely regenerate all mints and transaction data for them from chain data. - * Rather than a single pass of listMints, we wrap each pass in an outer while loop, that continues until no updates are found. - * The reason for this is to allow the mint counter in the wallet to update and regenerate more of the mint pool should it need to. - * - * @param fGenerateMintPool whether or not to call GenerateMintPool. defaults to true - * @param listMints An optional value. If passed, only sync the mints in this list. Else get all mints in the mintpool - */ -void CHDMintWallet::SyncWithChain(bool fGenerateMintPool, boost::optional>> listMints) -{ - CWalletDB walletdb(strWalletFile); - bool found = true; - - std::set setAddedTx; - std::set setChecked; - int mintsFound = 1; - bool firstIteration = true; - do { - found = false; - mintsFound = 0; - if (fGenerateMintPool) - GenerateMintPool(walletdb, !firstIteration); - LogPrintf("%s: Mintpool size=%d\n", __func__, mintPool.size()); - - firstIteration = false; - - if(listMints==boost::none){ - listMints = std::list>(); - mintPool.List(listMints.get()); - } - for (std::pair& pMint : listMints.get()) { - if (setChecked.count(pMint.first)) - continue; - setChecked.insert(pMint.first); - uiInterface.UpdateProgressBarLabel("Synchronizing mints..."); - - if (ShutdownRequested()) - return; - uint160& mintHashSeedMaster = std::get<0>(pMint.second); - int32_t& mintCount = std::get<2>(pMint.second); - - // halt processing if mint already in tracker - if (tracker.HasPubcoinHash(pMint.first, walletdb)) - continue; - - uint160 seedId = std::get<1>(pMint.second); - CDataStream ss(SER_GETHASH, 0); - ss << pMint.first; - ss << seedId; - uint256 mintTag = Hash(ss.begin(), ss.end()); - - COutPoint outPoint; - if (!pwalletMain->IsLocked() && lelantus::GetOutPointFromMintTag(outPoint, mintTag)) { - const uint256& txHash = outPoint.hash; - //this mint has already occurred on the chain, increment counter's state to reflect this - LogPrintf("%s : Found wallet coin mint=%s count=%d tx=%s\n", __func__, pMint.first.GetHex(), mintCount, txHash.GetHex()); - found = true; - - uint256 hashBlock; - CTransactionRef tx; - if (!GetTransaction(txHash, tx, Params().GetConsensus(), hashBlock, true)) { - LogPrintf("%s : failed to get transaction for mint %s!\n", __func__, pMint.first.GetHex()); - found = false; - continue; - } - - uint64_t amount = 0; - bool fFoundMint = false; - for (const CTxOut& out : tx->vout) { - if (!out.scriptPubKey.IsLelantusMint() && !out.scriptPubKey.IsLelantusJMint()) - continue; - secp_primitives::GroupElement pubcoin; - try { - if (out.scriptPubKey.IsLelantusMint()) { - amount = out.nValue; - lelantus::ParseLelantusMintScript(out.scriptPubKey, pubcoin); - } else { - std::vector encryptedValue; - lelantus::ParseLelantusJMintScript(out.scriptPubKey, pubcoin, encryptedValue); - if(!pwalletMain->DecryptMintAmount(encryptedValue, pubcoin, amount)) - continue; - } - } catch (std::invalid_argument&) { - continue; - } - if(amount != 0) - pubcoin += lelantus::Params::get_default()->get_h1() * Scalar(amount).negate(); - // See if this is the mint that we are looking for - uint256 hashPubcoin = primitives::GetPubCoinValueHash(pubcoin); - if (pMint.first == hashPubcoin) { - fFoundMint = true; - break; - } - } - - if (!fFoundMint) { - LogPrintf("%s : failed to get mint %s from tx %s!\n", __func__, pMint.first.GetHex(), tx->GetHash().GetHex()); - found = false; - break; - } - - CBlockIndex* pindex = nullptr; - if (mapBlockIndex.count(hashBlock)) - pindex = mapBlockIndex.at(hashBlock); - - if (!setAddedTx.count(txHash)) { - CBlock block; - CWalletTx wtx(pwalletMain, tx); - if (pindex && ReadBlockFromDisk(block, pindex, Params().GetConsensus())) - SetWalletTransactionBlock(wtx, pindex, block); - - //Fill out wtx so that a transaction record can be created - wtx.nTimeReceived = pindex->GetBlockTime(); - pwalletMain->AddToWallet(wtx, false); - setAddedTx.insert(txHash); - } - - if(!SetLelantusMintSeedSeen(walletdb, pMint, pindex->nHeight, txHash, amount)) - continue; - - if (tx->IsLelantusJoinSplit()) { - std::vector serials = lelantus::GetLelantusJoinSplitSerialNumbers(*tx, tx->vin[0]); - for (auto& serial : serials) { - CLelantusMintMeta mMeta; - if (!tracker.GetMetaFromSerial(primitives::GetSerialHash(serial), mMeta)) - continue; - - if (mMeta.isUsed) - continue; - - tracker.SetLelantusPubcoinUsed(mMeta.GetPubCoinValueHash(), tx->GetHash()); - - // add CLelantusSpendEntry - CLelantusSpendEntry spend; - spend.coinSerial = serial; - spend.hashTx = tx->GetHash(); - spend.pubCoin = mMeta.GetPubCoinValue(); - spend.id = mMeta.nId; - spend.amount = mMeta.amount; - if (!walletdb.WriteLelantusSpendSerialEntry(spend)) { - throw std::runtime_error(_("Failed to write coin serial number into wallet")); - } - } - } - - // Only update if the current hashSeedMaster matches the mints' - if(hashSeedMaster == mintHashSeedMaster && mintCount >= GetCount()){ - SetCount(++mintCount); - UpdateCountDB(walletdb); - LogPrint("zero", "%s: updated count to %d\n", __func__, nCountNextUse); - } - } - if (found) - mintsFound++; - } - uiInterface.UpdateProgressBarLabel(""); - // Clear listMints to allow it to be repopulated by the mintPool on the next iteration - if(found) - listMints = boost::none; - if (!fGenerateMintPool) - mintsFound = 0; - } while (found || mintsFound > 0); -} - -/** - * Add the mint from the chain to the mint tracker. - * - * Gets the mint from known values and creates a CHDMint object. Stores it in the tracker. - * If the wallet is not locked, the mint is regenerated from the known values. If regeneration fails, return false. - * If the wallet is locked, we use unencrypted db values to regenerate the object. - * - * @param mintPoolEntryPair pair of pubcoin hash to MintPoolEntry object - * @param nHeight mint txid height - * @param txid mint txid height - * @param denom mint denomination - */ - -bool CHDMintWallet::SetLelantusMintSeedSeen(CWalletDB& walletdb, std::pair mintPoolEntryPair, int nHeight, const uint256& txid, uint64_t amount) -{ - // Regenerate the mint - uint256 hashPubcoin = mintPoolEntryPair.first; - CKeyID seedId = std::get<1>(mintPoolEntryPair.second); - int32_t mintCount = std::get<2>(mintPoolEntryPair.second); - - auto params = lelantus::Params::get_default(); - - GroupElement bnValue; - uint256 hashSerial; - // Can regenerate if unlocked (cheaper) - Scalar serial; - if(!pwalletMain->IsLocked()) { - LogPrintf("%s: Wallet not locked, creating mind seed..\n", __func__); - uint512 mintSeed; - CreateMintSeed(walletdb, mintSeed, mintCount, seedId); - lelantus::PrivateCoin coin(params, amount); - if(!SeedToLelantusMint(mintSeed, coin)) - return false; - hashSerial = primitives::GetSerialHash(coin.getSerialNumber()); - bnValue = coin.getPublicCoin().getValue(); - serial = coin.getSerialNumber(); - } else { - LogPrintf("%s: Wallet locked, retrieving mind seed..\n", __func__); - // Get serial and pubcoin data from the db - std::vector> serialPubcoinPairs = walletdb.ListSerialPubcoinPairs(); - bool fFound = false; - for(auto serialPubcoinPair : serialPubcoinPairs){ - GroupElement pubcoin = serialPubcoinPair.second; - uint256 reducedHash; - walletdb.ReadPubcoinHashes(primitives::GetPubCoinValueHash(pubcoin), reducedHash); - if(hashPubcoin == reducedHash){ - LogPrintf("%s: Found pubcoin and serial hash\n", __func__); - bnValue = pubcoin; - hashSerial = serialPubcoinPair.first; - fFound = true; - break; - } - } - // Not found in DB - if(!fFound){ - LogPrintf("%s: Pubcoin not found in DB. \n", __func__); - return false; - } - } - - LogPrintf("%s: Creating mint object.. \n", __func__); - int height, id; - std::tie(height, id) = lelantus::CLelantusState::GetState()->GetMintedCoinHeightAndId(bnValue); - - // Create mint object - CHDMint dMint(mintCount, seedId, hashSerial, bnValue); - dMint.SetAmount(amount); - dMint.SetHeight(nHeight); - dMint.SetId(id); - - // Check if this is also already spent - int nHeightTx; - uint256 txidSpend; - CTransactionRef txSpend; - bool used = false; - if (IsLelantusSerialInBlockchain(hashSerial, nHeightTx, txidSpend, txSpend)) { - //Find transaction details and make a wallettx and add to wallet - LogPrintf("%s: Mint object is spent. Setting used..\n", __func__); - dMint.SetUsed(true); - CWalletTx wtx(pwalletMain, txSpend); - CBlockIndex* pindex = chainActive[nHeightTx]; - CBlock block; - if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) - SetWalletTransactionBlock(wtx, pindex, block); - - wtx.nTimeReceived = pindex->nTime; - pwalletMain->AddToWallet(wtx, false); - used = true; - } else { - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - // this is for some edge cases, when mint is used but the serial is not at map - Scalar s; - if (lelantusState->IsUsedCoinSerialHash(s, hashSerial)) { - dMint.SetUsed(true); - serial = s; - used = true; - } - } - - // Adding spend entry into db, - if(used && (serial != uint64_t(0))) { - CLelantusSpendEntry spend; - spend.coinSerial = serial; - spend.pubCoin = bnValue; - spend.id = id; - spend.amount = amount; - - if (!walletdb.WriteLelantusSpendSerialEntry(spend)) { - throw std::runtime_error(_("Failed to write coin serial number into wallet")); - } - } - - LogPrintf("%s: Adding mint to tracker.. \n", __func__); - // Add to tracker which also adds to database - tracker.AddLelantus(walletdb, dMint, true); - - return true; -} - -bool CHDMintWallet::SeedToMint(const uint512& mintSeed, GroupElement& commit, lelantus::PrivateCoin& coin) -{ - //convert state seed into a seed for the private key - uint256 nSeedPrivKey = mintSeed.trim256(); - nSeedPrivKey = Hash(nSeedPrivKey.begin(), nSeedPrivKey.end()); - coin.setEcdsaSeckey(nSeedPrivKey); - - // Create a key pair - secp256k1_pubkey pubkey; - if (!secp256k1_ec_pubkey_create(OpenSSLContext::get_context(), &pubkey, coin.getEcdsaSeckey())){ - return false; - } - // Hash the public key in the group to obtain a serial number - Scalar serialNumber = coin.serialNumberFromSerializedPublicKey(OpenSSLContext::get_context(), &pubkey); - coin.setSerialNumber(serialNumber); - - //hash randomness seed with Bottom 256 bits of mintSeed - Scalar randomness; - uint256 nSeedRandomness = ArithToUint512(UintToArith512(mintSeed) >> 256).trim256(); - randomness.memberFromSeed(nSeedRandomness.begin()); - coin.setRandomness(randomness); - - // Generate a Pedersen commitment to the serial number - commit = coin.getParams()->get_g() * coin.getSerialNumber() + coin.getParams()->get_h0() * coin.getRandomness(); - - return true; -} - -/** - * Convert a 512-bit mint seed into a mint. - * - * See https://github.com/firoorg/firo/pull/392 for specification on mint generation. - * - * @param mintSeed uint512 object of seed for mint - * @param coin reference to private coin. Is set in this function - * @return success - */ -bool CHDMintWallet::SeedToLelantusMint(const uint512& mintSeed, lelantus::PrivateCoin& coin) -{ - //convert state seed into a seed for the private key - uint256 nSeedPrivKey = mintSeed.trim256(); - nSeedPrivKey = Hash(nSeedPrivKey.begin(), nSeedPrivKey.end()); - - // Create a key pair - secp256k1_pubkey pubkey; - if (!secp256k1_ec_pubkey_create(OpenSSLContext::get_context(), &pubkey, nSeedPrivKey.begin())) { - return false; - } - - // Hash the public key in the group to obtain a serial number - Scalar serialNumber = coin.serialNumberFromSerializedPublicKey(OpenSSLContext::get_context(), &pubkey); - - // hash randomness seed with Bottom 256 bits of mintSeed - Scalar randomness; - uint256 nSeedRandomness = ArithToUint512(UintToArith512(mintSeed) >> 256).trim256(); - randomness.memberFromSeed(nSeedRandomness.begin()); - - std::vector seckey(nSeedPrivKey.begin(), nSeedPrivKey.end()); - //generating coin - coin = lelantus::PrivateCoin( - coin.getParams(), - serialNumber, - coin.getV(), - randomness, - seckey, - LELANTUS_TX_VERSION_4); - - return true; -} - -/** - * Get seed ID for the key used in mint generation. - * - * See https://github.com/firoorg/firo/pull/392 for specification on mint generation. - * Looks to the mintpool first - if mint doesn't exist, generates new mints in the mintpool. - * - * @param nCount count in the HD Chain of the mint to use. - * @return the seed ID - */ -CKeyID CHDMintWallet::GetMintSeedID(CWalletDB& walletdb, int32_t nCount){ - // Get CKeyID for n from mintpool - uint256 hashPubcoin; - std::pair mintPoolEntryPair; - - if(!mintPool.Get(nCount, hashSeedMaster, mintPoolEntryPair)){ - // Add up to mintPool index + 20 - GenerateMintPool(walletdb, nCount); - if(!mintPool.Get(nCount, hashSeedMaster, mintPoolEntryPair)){ - ResetCount(walletdb); - throw std::runtime_error("Unable to retrieve mint seed ID"); - } - } - - return std::get<1>(mintPoolEntryPair.second); -} - -/** - * Create the mint seed for the count passed. - * - * See https://github.com/firoorg/firo/pull/392 for specification on mint generation. - * We check if the key for the count passed exists. if so retrieve it's seed ID. if not, generate a new key. - * If seedId is passed, use that seedId and ignore key generation section. - * Following that, get the key, and use it to generate the mint seed according to the specification. - * - * @param mintSeed mint seed - * @param nCount (optional) count in the HD Chain of the key to use for mint generation. - * @param seedId (optional) seedId of the key to use for mint generation. - * @return success - */ -bool CHDMintWallet::CreateMintSeed(CWalletDB& walletdb, uint512& mintSeed, const int32_t& nCount, CKeyID& seedId, bool fWriteChain) -{ - LOCK(pwalletMain->cs_wallet); - CKey key; - - if(seedId.IsNull()){ - CPubKey pubKey; - int32_t chainIndex = pwalletMain->GetHDChain().nExternalChainCounters[BIP44_MINT_INDEX]; - if(nCount==chainIndex){ - // If chainIndex is the same as n (ie. we are generating next available key), generate a new key. - pubKey = pwalletMain->GenerateNewKey(BIP44_MINT_INDEX, fWriteChain); - } - else if(nCountGetKeyFromKeypath(BIP44_MINT_INDEX, nCount, secret); - } - else{ - throw std::runtime_error("Unable to retrieve mint seed ID (internal index greater than HDChain index). \n" - "We recommend restarting with -zapwalletmints."); - } - seedId = pubKey.GetID(); - } - - if (!pwalletMain->CCryptoKeyStore::GetKey(seedId, key)){ - ResetCount(walletdb); - throw std::runtime_error("Unable to retrieve generated key for mint seed. Is the wallet locked?"); - } - - // HMAC-SHA512(SHA256(count),key) - unsigned char countHash[CSHA256::OUTPUT_SIZE]; - std::vector result(CSHA512::OUTPUT_SIZE); - - std::string nCountStr = std::to_string(nCount); - CSHA256().Write(reinterpret_cast(nCountStr.c_str()), nCountStr.size()).Finalize(countHash); - - CHMAC_SHA512(countHash, CSHA256().OUTPUT_SIZE).Write(key.begin(), key.size()).Finalize(&result[0]); - - mintSeed = uint512(result); - - return true; -} - -/** - * Get in-memory count of the next mint to use. - * - * @return the count - */ -int32_t CHDMintWallet::GetCount() -{ - return nCountNextUse; -} - -/** - * Reset in-memory count to that of the database value. - * Necessary during transaction creation when fee calcuation causes the creation to reset. - */ -void CHDMintWallet::ResetCount(CWalletDB& walletdb) -{ - walletdb.ReadMintCount(nCountNextUse); -} - -/** - * Set in-memory count to parameter passed - * - * @param nCount count to be set - */ -void CHDMintWallet::SetCount(int32_t nCount) -{ - nCountNextUse = nCount; -} - -/** - * Increment in-memory count of the next mint to use. - */ -void CHDMintWallet::UpdateCountLocal() -{ - nCountNextUse++; - LogPrintf("CHDMintWallet : Updating count local to %s\n",nCountNextUse); -} - -/** - * Increment database count of the next mint to use. - * calls GenerateMintPool, which will run if we have exhausted the mintpool. - */ -void CHDMintWallet::UpdateCountDB(CWalletDB& walletdb) -{ - LogPrintf("CHDMintWallet : Updating count in DB to %s\n",nCountNextUse); - walletdb.WriteMintCount(nCountNextUse); - GenerateMintPool(walletdb); -} - -/** - * Gets a CHDMint object from a mintpool entry. - * - * @param coin reference to private coin object,should keep the value of coin - * @param dMint reference to CHDMint object - * @param mintPoolEntry mintpool data - * @return success - */ -bool CHDMintWallet::GetLelantusHDMintFromMintPoolEntry(CWalletDB& walletdb, lelantus::PrivateCoin& coin, CHDMint& dMint, MintPoolEntry& mintPoolEntry){ - uint512 mintSeed; - CreateMintSeed(walletdb, mintSeed, std::get<2>(mintPoolEntry), std::get<1>(mintPoolEntry)); - - if(!SeedToLelantusMint(mintSeed, coin)){ - return false; - } - - uint256 hashSerial = primitives::GetSerialHash(coin.getSerialNumber()); - dMint = CHDMint(std::get<2>(mintPoolEntry), std::get<1>(mintPoolEntry), hashSerial, coin.getPublicCoin().getValue()); - return true; -} - -/** - * Generate a CHDMint object, taking care of surrounding conditions. - * - * If the chain is not synced, do not proceed, unless fAllowUnsynced is set. - * If passed the mintpool entry, we directly call GetHDMintFromMintPoolEntry and return. - * If not, we assume that this is a new mint being created. - * Following creation, verify the mint does not already exist, in-memory or on-chain. This is to prevent sync issues with the - * mint counter between copies of the same wallet. If it does, increment the count and repeat creation. Continue until an available - * mint is found. - * - * @param coin reference to private coin object, should keep the value of coin - * @param dMint reference to CHDMint object - * @param mintPoolEntry mintpool data - * @param fAllowUnsynced allow mint creation if chain is not synced (for tests) - * @return success - */ -bool CHDMintWallet::GenerateLelantusMint(CWalletDB& walletdb, lelantus::PrivateCoin& coin, CHDMint& dMint, uint160& seedIdOut, boost::optional mintPoolEntry, bool fAllowUnsynced) -{ - if(!masternodeSync.IsBlockchainSynced() && !fAllowUnsynced && !(Params().NetworkIDString() == CBaseChainParams::REGTEST)) - throw std::runtime_error("Unable to generate mint: Blockchain not yet synced."); - - if(mintPoolEntry!=boost::none) - return GetLelantusHDMintFromMintPoolEntry(walletdb, coin, dMint, mintPoolEntry.get()); - - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - while(true) { - if(hashSeedMaster.IsNull()) - throw std::runtime_error("Unable to generate mint: HashSeedMaster not set"); - CKeyID seedId = GetMintSeedID(walletdb, nCountNextUse); - seedIdOut = seedId; - mintPoolEntry = MintPoolEntry(hashSeedMaster, seedId, nCountNextUse); - // Empty mintPoolEntry implies this is a new mint being created, so update nCountNextUse - UpdateCountLocal(); - - if(!GetLelantusHDMintFromMintPoolEntry(walletdb, coin, dMint, mintPoolEntry.get())) - return false; - - // New HDMint exists, try new count - if(walletdb.HasHDMint(dMint.GetPubcoinValue()) - || lelantusState->HasCoin(coin.getPublicCoin())) { - LogPrintf("%s: Coin detected used, trying next. count: %d\n", __func__, std::get<2>(mintPoolEntry.get())); - }else{ - LogPrintf("%s: Found unused coin, count: %d\n", __func__, std::get<2>(mintPoolEntry.get())); - break; - } - } - - dMint.SetAmount(coin.getV()); - return true; -} - - -bool CHDMintWallet::RegenerateMint(CWalletDB& walletdb, const CHDMint& dMint, CLelantusEntry& lelantusEntry, bool forEstimation) -{ - //Generate the coin - lelantus::PrivateCoin coin(lelantus::Params::get_default(), dMint.GetAmount()); - CHDMint dMintDummy; - CKeyID seedId = dMint.GetSeedId(); - int32_t nCount = dMint.GetCount(); - MintPoolEntry mintPoolEntry(hashSeedMaster, seedId, nCount); - uint160 dummySeedId; - if(!forEstimation) - GenerateLelantusMint(walletdb, coin, dMintDummy, dummySeedId, mintPoolEntry, true); - - //Fill in the lelantus object's details - GroupElement bnValue = coin.getPublicCoin().getValue(); - if (primitives::GetPubCoinValueHash(bnValue) != dMint.GetPubCoinHash() && !forEstimation) - return error("%s: failed to correctly generate lelantus mint, pubcoin hash mismatch", __func__); - if(forEstimation) - lelantusEntry.value = dMint.GetPubcoinValue(); - else - lelantusEntry.value = bnValue; - - Scalar bnSerial = coin.getSerialNumber(); - if (primitives::GetSerialHash(bnSerial) != dMint.GetSerialHash() && !forEstimation) - return error("%s: failed to correctly generate lelantus mint, serial hash mismatch", __func__); - - lelantusEntry.amount = dMint.GetAmount(); - lelantusEntry.randomness = coin.getRandomness(); - lelantusEntry.serialNumber = bnSerial; - lelantusEntry.IsUsed = dMint.IsUsed(); - lelantusEntry.nHeight = dMint.GetHeight(); - lelantusEntry.id = dMint.GetId(); - lelantusEntry.ecdsaSecretKey = std::vector(&coin.getEcdsaSeckey()[0],&coin.getEcdsaSeckey()[32]); - - return true; -} - -/** - * Checks to see if serial passed is on-chain (ie. a check on whether the mint for the serial is spent) - * - * @param hashSerial mint serial hash - * @param nHeightTx transaction height on-chain - * @param txidSpend transaction hash - * @param tx full transaction object - * @return success - */ - -bool CHDMintWallet::IsLelantusSerialInBlockchain(const uint256& hashSerial, int& nHeightTx, uint256& txidSpend, CTransactionRef & tx) -{ - txidSpend.SetNull(); - CLelantusMintMeta mMeta; - Scalar bnSerial; - - if (!lelantus::CLelantusState::GetState()->IsUsedCoinSerialHash(bnSerial, hashSerial)) - return false; - - if(!tracker.GetMetaFromSerial(hashSerial, mMeta)) - return false; - - txidSpend = mMeta.txid; - - return IsTransactionInChain(txidSpend, nHeightTx, tx); -} diff --git a/src/hdmint/wallet.h b/src/hdmint/wallet.h deleted file mode 100644 index ddee0cb973..0000000000 --- a/src/hdmint/wallet.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2019 The Firo Core Developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef FIRO_HDMINTWALLET_H -#define FIRO_HDMINTWALLET_H - -#include -#include "hdmint/mintpool.h" -#include "uint256.h" -#include "primitives/mint_spend.h" -#include "wallet/wallet.h" -#include "tracker.h" - -class CHDMint; - -static const unsigned int DEFAULT_MINTPOOL_SIZE = 20; -static const unsigned int MAX_MINTPOOL_SIZE = 200; - -class CHDMintWallet -{ -private: - int32_t nCountNextUse; - int32_t nCountNextGenerate; - const std::string& strWalletFile; - CMintPool mintPool; - CHDMintTracker tracker; - uint160 hashSeedMaster; - -public: - int static const COUNT_DEFAULT = 0; - - CHDMintWallet(const std::string& strWalletFile, bool resetCount=false); - - bool SetupWallet(const uint160& hashSeedMaster, bool fResetCount=false); - void SyncWithChain(bool fGenerateMintPool = true, boost::optional>> listMints = boost::none); - bool GetLelantusHDMintFromMintPoolEntry(CWalletDB& walletdb, lelantus::PrivateCoin& coin, CHDMint& dMint, MintPoolEntry& mintPoolEntry); - bool GenerateLelantusMint(CWalletDB& walletdb, lelantus::PrivateCoin& coin, CHDMint& dMint, uint160& seedIdOut, boost::optional mintPoolEntry = boost::none, bool fAllowUnsynced=false); - bool LoadMintPoolFromDB(); - bool RegenerateMint(CWalletDB& walletdb, const CHDMint& dMint, CLelantusEntry& sigma, bool forEstimation = false); - bool GetSerialForPubcoin(const std::vector>& serialPubcoinPairs, const uint256& hashPubcoin, uint256& hashSerial); - bool IsSerialInBlockchain(const uint256& hashSerial, int& nHeightTx, uint256& txidSpend, CTransactionRef & tx); - bool IsLelantusSerialInBlockchain(const uint256& hashSerial, int& nHeightTx, uint256& txidSpend, CTransactionRef & tx); - std::pair RegenerateMintPoolEntry(CWalletDB& walletdb, const uint160& mintHashSeedMaster, CKeyID& seedId, const int32_t& nCount); - void GenerateMintPool(CWalletDB& walletdb, bool forceGenerate = false, int32_t nIndex = 0); - bool SetLelantusMintSeedSeen(CWalletDB& walletdb, std::pair mintPoolEntryPair, int nHeight, const uint256& txid, uint64_t amount); - bool SeedToMint(const uint512& mintSeed, GroupElement& bnValue, lelantus::PrivateCoin& coin); - bool SeedToLelantusMint(const uint512& mintSeed, lelantus::PrivateCoin& coin); - - // Count updating functions - int32_t GetCount(); - CHDMintTracker& GetTracker() { return tracker; } - void ResetCount(CWalletDB& walletdb); - void SetCount(int32_t nCount); - void UpdateCountLocal(); - void UpdateCountDB(CWalletDB& walletdb); - void SetWalletTransactionBlock(CWalletTx &wtx, const CBlockIndex *blockIndex, const CBlock &block); - -private: - CKeyID GetMintSeedID(CWalletDB& walletdb, int32_t nCount); - bool CreateMintSeed(CWalletDB& walletdb, uint512& mintSeed, const int32_t& n, CKeyID& seedId, bool nWriteChain = true); -}; - -#endif //FIRO_HDMINTWALLET_H diff --git a/src/immer/detail/type_traits.hpp b/src/immer/detail/type_traits.hpp index 091fe04dc2..12bfcdaf6e 100644 --- a/src/immer/detail/type_traits.hpp +++ b/src/immer/detail/type_traits.hpp @@ -133,7 +133,7 @@ template struct is_iterator : std::false_type {}; -// See http://en.cppreference.com/w/cpp/concept/Iterator +// See https://en.cppreference.com/w/cpp/iterator.html template struct is_iterator< T, diff --git a/src/init.cpp b/src/init.cpp index 78bb0ccc7a..f80bd18562 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -219,6 +219,10 @@ static CCoinsViewDB *pcoinsdbview = NULL; static CCoinsViewErrorCatcher *pcoinscatcher = NULL; static std::unique_ptr globalVerifyHandle; +// Forward declaration: definition is further down alongside the -torsetup +// embedded Tor helpers. File-local because no other TU calls it. +static void InterruptTorEnabled(); + void Interrupt(boost::thread_group& threadGroup) { InterruptHTTPServer(); @@ -226,6 +230,13 @@ void Interrupt(boost::thread_group& threadGroup) InterruptRPC(); InterruptREST(); InterruptTorControl(); + // Best-effort interrupt for the -torsetup embedded Tor thread. tor_main() + // runs its own blocking event loop inside TorEnabledThread, so this only + // primes our wrapping event_base to exit once tor_main eventually returns + // (on process signal). We cannot cleanly stop embedded Tor from outside + // because no control port is configured; that's why Shutdown() does not + // call StopTorEnabled() -- joining the thread would deadlock. + InterruptTorEnabled(); llmq::InterruptLLMQSystem(); if (g_connman) g_connman->Interrupt(); @@ -264,9 +275,24 @@ void Shutdown() MapPort(false); UnregisterValidationInterface(peerLogic.get()); peerLogic.reset(); - g_connman.reset(); + // Stop the Tor control thread before tearing down CConnman. The current + // auth_cb only touches global state (SetProxy, mapLocalHost via AddLocal, + // GetArg/GetListenPort) and does not dereference g_connman, so strictly + // speaking the order is not required today. Kept as defense-in-depth in + // case a future TorController callback needs to query live peers or + // listeners (the dedicated onion listener is now bound at init time + // instead of from auth_cb, so that particular path no longer applies). StopTorControl(); + // Note: there is intentionally no matching StopTorEnabled() call here + // for the -torsetup embedded Tor thread. RunTor() invokes tor_main() + // which runs Tor's own blocking event loop and does not return until + // the whole process is signaled; we don't configure a control port on + // the embedded Tor, so there is no in-band way to stop it. Joining + // torEnabledThread would deadlock Shutdown(). InterruptTorEnabled() in + // Interrupt() primes our wrapping event_base for post-tor_main + // cleanup; the embedded Tor itself is torn down by process exit. + g_connman.reset(); UnregisterNodeSignals(GetNodeSignals()); if (fDumpMempoolLater) DumpMempool(); @@ -465,7 +491,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, *1000 bytes (default: %u)"), DEFAULT_MAXSENDBUFFER)); strUsage += HelpMessageOpt("-maxtimeadjustment", strprintf(_("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)"), DEFAULT_MAX_TIME_ADJUSTMENT)); strUsage += HelpMessageOpt("-onion=", strprintf(_("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)"), "-proxy")); - strUsage += HelpMessageOpt("-onlynet=", _("Only connect to nodes in network (ipv4, ipv6 or onion)")); + strUsage += HelpMessageOpt("-onlynet=", _("Make automatic outbound connections only to network (ipv4, ipv6 or onion). Can be specified multiple times to allow multiple networks.")); strUsage += HelpMessageOpt("-permitbaremultisig", strprintf(_("Relay non-P2SH multisig (default: %u)"), DEFAULT_PERMIT_BAREMULTISIG)); strUsage += HelpMessageOpt("-peerbloomfilters", strprintf(_("Support filtering of blocks and transaction with bloom filters (default: %u)"), DEFAULT_PEERBLOOMFILTERS)); strUsage += HelpMessageOpt("-port=", strprintf(_("Listen for connections on (default: %u or testnet: %u)"), Params(CBaseChainParams::MAIN).GetDefaultPort(), Params(CBaseChainParams::TESTNET).GetDefaultPort())); @@ -711,12 +737,7 @@ void CleanupBlockRevFiles() void ThreadImport(std::vector vImportFiles) { #ifdef ENABLE_WALLET - if (!GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - //Load zerocoin mint hashes to memory - LogPrintf("Loading mints to wallet..\n"); - pwalletMain->zwallet->GetTracker().Init(); - pwalletMain->zwallet->LoadMintPoolFromDB(); - } + // Lelantus wallet (zwallet) was removed; mint loading skipped #endif const CChainParams &chainparams = Params(); @@ -806,15 +827,6 @@ void ThreadImport(std::vector vImportFiles) { LoadMempool(); } -#ifdef ENABLE_WALLET - if (!GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - pwalletMain->zwallet->SyncWithChain(); - } - // Need this to restore Sigma spend state - if (GetBoolArg("-rescan", false) && !GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - pwalletMain->zwallet->GetTracker().ListLelantusMints(); - } -#endif fDumpMempoolLater = !fRequestShutdown; } @@ -969,7 +981,7 @@ static std::string ResolveErrMsg(const char *const optname, const std::string &s return strprintf(_("Cannot resolve -%s address: '%s'"), optname, strBind); } -void RunTor(){ +void RunTor(unsigned short onion_local_port){ printf("TOR thread started.\n"); boost::optional < std::string > clientTransportPlugin; @@ -997,7 +1009,17 @@ void RunTor(){ argv.push_back("--HiddenServiceDir"); argv.push_back((tor_dir / "onion").string()); argv.push_back("--HiddenServicePort"); - argv.push_back("8168"); + // When a dedicated onion listener was bound at init time, point the + // hidden service at it so inbound traffic is accepted on a socket + // flagged is_onion_listener (and therefore classified as NET_ONION). + // Otherwise fall back to the single-port form, which Tor interprets + // as VIRTPORT -> localhost:VIRTPORT and lands inbound peers on the + // main listener (same as pre-fix behavior). + if (onion_local_port != 0) { + argv.push_back(strprintf("8168 127.0.0.1:%u", onion_local_port)); + } else { + argv.push_back("8168"); + } if (clientTransportPlugin) { printf("Using OBFS4.\n"); @@ -1018,15 +1040,20 @@ void RunTor(){ struct event_base *baseTor; boost::thread torEnabledThread; +/** Port for the dedicated onion-forwarded local listener, captured by + * StartTorEnabled() and consumed by TorEnabledThread() when it launches + * the embedded Tor. 0 means no dedicated listener was bound. */ +static unsigned short g_tor_enabled_onion_local_port = 0; static void TorEnabledThread() { - RunTor(); + RunTor(g_tor_enabled_onion_local_port); event_base_dispatch(baseTor); } -void StartTorEnabled(boost::thread_group& threadGroup, CScheduler& scheduler) +void StartTorEnabled(boost::thread_group& threadGroup, CScheduler& scheduler, + unsigned short onion_local_port = 0) { assert(!baseTor); #ifdef WIN32 @@ -1040,10 +1067,18 @@ void StartTorEnabled(boost::thread_group& threadGroup, CScheduler& scheduler) return; } + g_tor_enabled_onion_local_port = onion_local_port; torEnabledThread = boost::thread(boost::bind(&TraceThread, "torcontrol", &TorEnabledThread)); } -void InterruptTorEnabled() +// Best-effort interrupt of the embedded Tor wrapper event_base. This does +// NOT stop tor_main() itself -- Tor runs its own event loop inside +// TorEnabledThread, and without a control port there is no in-band way to +// request shutdown. Calling this only makes the wrapping event_base_dispatch +// that follows tor_main() return immediately once tor_main does return on +// process exit. Safe to call even while tor_main is still running. +// File-local (forward-declared near Interrupt()). +static void InterruptTorEnabled() { if (baseTor) { LogPrintf("tor: Thread interrupt\n"); @@ -1051,6 +1086,14 @@ void InterruptTorEnabled() } } +// WARNING: this will deadlock if called while tor_main() is still running +// inside TorEnabledThread (the common case at shutdown), because +// torEnabledThread.join() blocks until tor_main returns and embedded Tor +// has no in-band shutdown path. Intentionally not called from Shutdown(); +// the embedded Tor thread is left to be torn down by process exit. Kept +// as a no-op-ish symbol in case a future change adds a control port (e.g. +// via tor_api_run_main + tor_shutdown_event_loop_for_controller_()) that +// makes clean shutdown possible. void StopTorEnabled() { if (baseTor) { @@ -1529,18 +1572,33 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) strSubVersion.size(), MAX_SUBVERSION_LENGTH)); } - if (mapMultiArgs.count("-onlynet")) { - std::set nets; + bool fOnlyNet = mapMultiArgs.count("-onlynet"); + std::set onlyNetNets; + if (fOnlyNet) { BOOST_FOREACH(const std::string& snet, mapMultiArgs.at("-onlynet")) { enum Network net = ParseNetwork(snet); if (net == NET_UNROUTABLE) return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet)); - nets.insert(net); + onlyNetNets.insert(net); } - for (int n = 0; n < NET_MAX; n++) { - enum Network net = (enum Network)n; - if (!nets.count(net)) + // Intentional narrowing relative to Bitcoin Core: Firo only enforces + // -onlynet over the user-selectable networks (ipv4, ipv6, onion). + // NET_I2P / NET_CJDNS aren't supported as -onlynet targets (they have + // no proxy/transport plumbing in Firo, ParseNetwork() doesn't accept + // them, and getnetworkinfo doesn't surface them). If Firo adds + // support for either, extend this list, GetNetworksInfo() in + // src/rpc/net.cpp, and the qa/rpc-tests/proxy_test.py expected set. + for (const auto net : {NET_IPV4, NET_IPV6, NET_ONION}) { + if (!onlyNetNets.count(net)) { SetLimited(net); + } + } + // The "explicitly limited" flag is consulted by background setup paths + // that may try to (re)mark a network reachable after init finishes + // (today only TorController::auth_cb for NET_ONION). Other networks + // have no such async setup, so flagging them here would be dead. + if (!onlyNetNets.count(NET_ONION)) { + SetNetworkExplicitlyLimited(NET_ONION); } } @@ -1556,28 +1614,73 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) // start tor bool torEnabled = GetBoolArg("-torsetup", DEFAULT_TOR_SETUP); + bool listenOnion = GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION); + + // Bind a dedicated 127.0.0.1: listener used only to receive + // traffic forwarded by Tor from our hidden service. Doing this once at + // init (mirroring Bitcoin Core's pattern, ref bitcoin#16702) means: + // * the Tor control thread never has to call BindListenPort at + // runtime (it just receives the resolved port); and + // * -torsetup's embedded Tor can point its --HiddenServicePort target + // at this socket too, so both paths classify inbound peers as + // NET_ONION instead of IPv4 127.0.0.1. + // The listener is flagged is_onion_listener so AcceptConnection tags + // accepted peers accordingly. + // + // Note on -listen=0 interaction: -listen=0 soft-forces -listenonion=0 + // (see the parameter-interaction block above), but does *not* force + // -torsetup=0. So with "-listen=0 -torsetup=1" we still bind this + // dedicated local listener and the embedded Tor's hidden service will + // route inbound traffic to it. This differs from pre-PR behavior where + // -listen=0 + -torsetup=1 effectively disabled inbound onion (the + // embedded Tor forwarded to 127.0.0.1:8168 where nothing was + // listening). The new behavior is closer to user intent ("-torsetup=1 + // implies I want inbound via onion") while still honoring -listen=0 + // for clearnet listeners. + unsigned short onion_local_port = 0; + if (torEnabled || listenOnion) { + std::string strBindError; + CService onionBindAddr(LookupNumeric("127.0.0.1", 0)); + if (!connman.BindListenPort(onionBindAddr, strBindError, + /*fWhitelisted=*/false, + /*is_onion_listener=*/true, + &onion_local_port) || onion_local_port == 0) { + LogPrintf("Warning: failed to bind dedicated local listener for Tor hidden service (%s); " + "inbound onion peers will appear as IPv4 127.0.0.1\n", strBindError); + onion_local_port = 0; + } + } + if(torEnabled){ - StartTorEnabled(threadGroup, scheduler); - SetLimited(NET_ONION); - SetLimited(NET_IPV4); - SetLimited(NET_IPV6); + StartTorEnabled(threadGroup, scheduler, onion_local_port); proxyType addrProxy = proxyType(LookupNumeric("127.0.0.1", 9050), true); SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); - SetLimited(NET_IPV4, false); - SetLimited(NET_IPV6, false); - SetLimited(NET_ONION, false); + SetNameProxy(addrProxy); + if (!fOnlyNet || onlyNetNets.count(NET_IPV4)) + SetLimited(NET_IPV4, false); + if (!fOnlyNet || onlyNetNets.count(NET_IPV6)) + SetLimited(NET_IPV6, false); + if (!fOnlyNet || onlyNetNets.count(NET_ONION)) + SetLimited(NET_ONION, false); } bool proxyRandomize = GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE); // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = GetArg("-proxy", ""); - // Only set NET_ONION as limited by default if -onlynet was not specified. - // If -onlynet was specified, the network limitations have already been set correctly above. - if (!mapMultiArgs.count("-onlynet")) { + // Default NET_ONION to limited unless -onlynet already set the limits + // explicitly above OR -torsetup=1 has already configured the embedded + // Tor SOCKS proxy (and unlimited NET_ONION) in the torEnabled block. + // Without the !torEnabled guard, -torsetup=1 alone would end up with + // NET_ONION limited because this block runs after the torEnabled + // unlimit -- and post-PR that limit is actually enforced in + // OpenNetworkConnection / the masternode filter, blocking every + // outbound onion connection. -proxy and -onion below still get their + // chance to limit/unlimit explicitly. + if (!fOnlyNet && !torEnabled) { SetLimited(NET_ONION); } if (proxyArg != "" && proxyArg != "0") { @@ -1590,7 +1693,8 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); - SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later + if (!fOnlyNet || onlyNetNets.count(NET_ONION)) + SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later } // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses @@ -1599,6 +1703,10 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) std::string onionArg = GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 + if (onlyNetNets.count(NET_ONION)) { + return InitError( + _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0")); + } SetLimited(NET_ONION); // set onions as unreachable } else { CService resolved(LookupNumeric(onionArg.c_str(), 9050)); @@ -1606,25 +1714,32 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg)); SetProxy(NET_ONION, addrOnion); - SetLimited(NET_ONION, false); + if (!fOnlyNet || onlyNetNets.count(NET_ONION)) + SetLimited(NET_ONION, false); + // When onlynet=onion and a dedicated -onion proxy is set (but no -proxy), + // also set it as the name proxy so DNS resolution goes through Tor + // and doesn't leak over clearnet. + if (fOnlyNet && onlyNetNets.count(NET_ONION) && !HaveNameProxy()) + SetNameProxy(addrOnion); } } // Check if -onlynet=onion was specified but no proxy is configured to reach the Tor network. - // This check is similar to Bitcoin Core's approach to provide a helpful error message. - if (mapMultiArgs.count("-onlynet")) { - bool onlynetIncludesOnion = false; - for (const std::string& snet : mapMultiArgs.at("-onlynet")) { - if (ParseNetwork(snet) == NET_ONION) { - onlynetIncludesOnion = true; - break; - } - } - if (onlynetIncludesOnion) { + // Matches Bitcoin Core: if -listenonion is enabled we will connect to the + // Tor control port later from the torcontrol thread and auth_cb will + // configure the onion proxy automatically, so that path is also accepted. + // The -listenonion bypass only helps when -torcontrol is actually usable; + // treat an empty or "0" value (including -notorcontrol) as disabled so we + // don't let init succeed into a state with no working onion path. + if (fOnlyNet) { + if (onlyNetNets.count(NET_ONION)) { proxyType onionProxy; bool haveOnionProxy = GetProxy(NET_ONION, onionProxy) && onionProxy.IsValid(); - if (!haveOnionProxy && !torEnabled) { - return InitError(_("Outbound connections restricted to Tor (-onlynet=onion) but no proxy for reaching the Tor network is provided. Use -proxy, -onion, or -torsetup to configure a Tor proxy.")); + const std::string torControlAddr = GetArg("-torcontrol", DEFAULT_TOR_CONTROL); + const bool torControlUsable = !torControlAddr.empty() && torControlAddr != "0"; + const bool canAutoConfigureOnion = listenOnion && torControlUsable; + if (!haveOnionProxy && !torEnabled && !canAutoConfigureOnion) { + return InitError(_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion, -torsetup or -listenonion (with a usable -torcontrol) is given.")); } } } @@ -1830,11 +1945,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) CBlockIndex *tip = chainActive.Tip(); if (tip) { int lastBlockIndexVersion = pblocktree->GetBlockIndexVersion(*tip->phashBlock); - if ((tip->nHeight >= chainparams.GetConsensus().nLelantusStartBlock && - lastBlockIndexVersion < LELANTUS_PROTOCOL_ENABLEMENT_VERSION) || - (tip->nHeight >= chainparams.GetConsensus().nEvoSporkStartBlock && - lastBlockIndexVersion < EVOSPORK_MIN_VERSION)) - { + bool evoReindex = (tip->nHeight >= chainparams.GetConsensus().nEvoSporkStartBlock && + lastBlockIndexVersion < EVOSPORK_MIN_VERSION); + if (evoReindex) { strLoadError = _( "Block index is outdated, reindex required\n"); break; @@ -2105,8 +2218,11 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) //// debug print LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); LogPrintf("nBestHeight = %d\n", chainActive.Height()); - if (GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) - StartTorControl(threadGroup, scheduler); + if (listenOnion) { + // The dedicated onion listener (if any) was bound earlier, shared + // with the -torsetup path; reuse its port here. + StartTorControl(threadGroup, scheduler, onion_local_port); + } Discover(threadGroup); diff --git a/src/init.h b/src/init.h index 47d80ab45a..8222794374 100644 --- a/src/init.h +++ b/src/init.h @@ -10,7 +10,6 @@ class CScheduler; class CWallet; -class CHDMintWallet; namespace boost { diff --git a/src/key.h b/src/key.h index 37c74e8732..4516304da1 100644 --- a/src/key.h +++ b/src/key.h @@ -175,6 +175,8 @@ struct CExtKey { { unsigned int len = ::ReadCompactSize(s); unsigned char code[BIP32_EXTKEY_SIZE]; + if (len != BIP32_EXTKEY_SIZE) + throw std::runtime_error("Invalid extended key size"); s.read((char *)&code[0], len); Decode(code); } diff --git a/src/lelantus.cpp b/src/lelantus.cpp deleted file mode 100644 index f23d35f098..0000000000 --- a/src/lelantus.cpp +++ /dev/null @@ -1,1770 +0,0 @@ -#include "validation.h" -#include "lelantus.h" -#include "timedata.h" -#include "chainparams.h" -#include "util.h" -#include "base58.h" -#include "definition.h" -#include "txmempool.h" -#ifdef ENABLE_WALLET -#include "wallet/wallet.h" -#include "wallet/walletdb.h" -#endif // ENABLE_WALLET -#include "crypto/sha256.h" -#include "liblelantus/coin.h" -#include "liblelantus/schnorr_prover.h" -#include "liblelantus/schnorr_verifier.h" -#include "primitives/mint_spend.h" -#include "liblelantus/challenge_generator_impl.h" -#include "policy/policy.h" -#include "coins.h" -#include "batchproof_container.h" - -#include -#include -#include - -#include -#include - -#include - -int64_t nMinimumInputValue = DUST_HARD_LIMIT; - -namespace lelantus { - -static CLelantusState lelantusState; - -static bool CheckLelantusSpendSerial( - CValidationState &state, - CLelantusTxInfo *lelantusTxInfo, - const Scalar &serial, - int nHeight, - bool fConnectTip) { - // check for Lelantus transaction in this block as well - if (lelantusTxInfo && - !lelantusTxInfo->fInfoIsComplete && - lelantusTxInfo->spentSerials.find(serial) != lelantusTxInfo->spentSerials.end()) - return state.DoS(0, error("CTransaction::CheckTransaction() : two or more joinsplits with same serial in the same block")); - - // check for used serials in lelantusState - if (lelantusState.IsUsedCoinSerial(serial)) { - // Proceed with checks ONLY if we're accepting tx into the memory pool or connecting block to the existing blockchain - if (nHeight == INT_MAX || fConnectTip) { - return state.DoS(0, error("CTransaction::CheckTransaction() : The lelantus JoinSplit serial has been used")); - } - } - return true; -} - -/* - * Util funtions - */ -size_t CountCoinInBlock(CBlockIndex *index, int id) { - return index->lelantusMintedPubCoins.count(id) > 0 - ? index->lelantusMintedPubCoins[id].size() : 0; -} - -std::vector GetAnonymitySetHash(CBlockIndex *index, int group_id, bool generation = false) { - std::vector out_hash; - - CLelantusState::LelantusCoinGroupInfo coinGroup; - if (!lelantusState.GetCoinGroupInfo(group_id, coinGroup)) - return out_hash; - - if ((coinGroup.firstBlock == coinGroup.lastBlock && generation) || (coinGroup.nCoins == 0)) - return out_hash; - - while (index != coinGroup.firstBlock) { - if (index->anonymitySetHash.count(group_id) > 0) { - out_hash = index->anonymitySetHash[group_id]; - break; - } - index = index->pprev; - } - return out_hash; -} - -bool IsLelantusAllowed() -{ - LOCK(cs_main); - return IsLelantusAllowed(chainActive.Height()); -} - -bool IsLelantusAllowed(int height) -{ - return height >= ::Params().GetConsensus().nLelantusStartBlock && height < ::Params().GetConsensus().nSparkStartBlock; -} - -bool IsLelantusGraceFulPeriod() -{ - LOCK(cs_main); - int height = chainActive.Height(); - return height >= ::Params().GetConsensus().nLelantusStartBlock && height < ::Params().GetConsensus().nLelantusGracefulPeriod; -} - -bool IsAvailableToMint(const CAmount& amount) -{ - return amount <= ::Params().GetConsensus().nMaxValueLelantusMint; -} - -void GenerateMintSchnorrProof(const lelantus::PrivateCoin& coin, CDataStream& serializedSchnorrProof) -{ - auto params = lelantus::Params::get_default(); - - LOCK(cs_main); - SchnorrProof schnorrProof; - - // after nLelantusFixesStartBlock block start to pass whole data to transcript - bool afterFixes = chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock; - SchnorrProver schnorrProver(params->get_g(), params->get_h0(), afterFixes); - Scalar v = coin.getVScalar(); - secp_primitives::GroupElement commit = coin.getPublicCoin().getValue(); - secp_primitives::GroupElement comm = commit + (params->get_h1() * v.negate()); - - std::unique_ptr challengeGenerator; - if (afterFixes) { - // start to use CHash256 which is more secure - challengeGenerator = std::make_unique>(1); - } else { - challengeGenerator = std::make_unique>(0); - } - - // commit (G^s*H1^v*H2^r), comm (G^s*H2^r), and H1^v are used in challenge generation if nLelantusFixesStartBlock is passed - schnorrProver.proof(coin.getSerialNumber(), coin.getRandomness(), comm, commit, (params->get_h1() * v), challengeGenerator, schnorrProof); - - serializedSchnorrProof << schnorrProof; -} - -bool VerifyMintSchnorrProof(const uint64_t& v, const secp_primitives::GroupElement& commit, const SchnorrProof& schnorrProof) -{ - auto params = lelantus::Params::get_default(); - - LOCK(cs_main); - // after nLelantusFixesStartBlock block start to pass whole data to transcript - bool afterFixes = chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock; - secp_primitives::GroupElement comm = commit + (params->get_h1() * Scalar(v).negate()); - SchnorrVerifier verifier(params->get_g(), params->get_h0(), afterFixes); - std::unique_ptr challengeGenerator; - if (afterFixes) { - // start to use CHash256 which is more secure - challengeGenerator = std::make_unique>(1); - } else { - challengeGenerator = std::make_unique>(0); - } - - // commit (G^s*H1^v*H2^r), comm (G^s*H2^r), and H1^v are used in challenge generation if nLelantusFixesStartBlock is passed - return verifier.verify(comm, commit, (params->get_h1() * Scalar(v)), schnorrProof, challengeGenerator); -} - -void ParseLelantusMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, SchnorrProof& schnorrProof, uint256& mintTag) -{ - if (script.size() < 1) { - throw std::invalid_argument("Script is not a valid Lelantus mint"); - } - - std::vector serialized(script.begin() + 1, script.end()); - if (serialized.size() < (pubcoin.memoryRequired() + schnorrProof.memoryRequired())) { - throw std::invalid_argument("Script is not a valid Lelantus mint"); - } - - bool skipTag = serialized.size() == (pubcoin.memoryRequired() + schnorrProof.memoryRequired()); - - pubcoin.deserialize(serialized.data()); - - CDataStream stream( - std::vector(serialized.begin() + pubcoin.memoryRequired(), serialized.end()), - SER_NETWORK, - PROTOCOL_VERSION - ); - - stream >> schnorrProof; - if(!skipTag) - stream >> mintTag; -} - -void ParseLelantusJMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, std::vector& encryptedValue) -{ - uint256 mintTag; - ParseLelantusJMintScript(script, pubcoin, encryptedValue, mintTag); -} - -void ParseLelantusJMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, std::vector& encryptedValue, uint256& mintTag) -{ - if (script.size() < 1) { - throw std::invalid_argument("Script is not a valid Lelantus jMint"); - } - - std::vector serialized(script.begin() + 1, script.end()); - // 16 is the size of encrypted mint value, 32 is size of mintTag - if (serialized.size() < (pubcoin.memoryRequired() + 16)) { - throw std::invalid_argument("Script is not a valid Lelantus jMint"); - } - - bool skipTag = serialized.size() == (pubcoin.memoryRequired() + 16); - - pubcoin.deserialize(serialized.data()); - encryptedValue.insert(encryptedValue.begin(), serialized.begin() + pubcoin.memoryRequired(), serialized.end()); - CDataStream stream( - std::vector(serialized.begin() + pubcoin.memoryRequired() + 16, serialized.end()), - SER_NETWORK, - PROTOCOL_VERSION - ); - if(!skipTag) - stream >> mintTag; -} - - -void ParseLelantusMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin) -{ - uint256 mintTag; - if(script.IsLelantusMint()) { - SchnorrProof schnorrProof; - ParseLelantusMintScript(script, pubcoin, schnorrProof, mintTag); - } else if (script.IsLelantusJMint()) { - std::vector encryptedValue; - ParseLelantusJMintScript(script, pubcoin, encryptedValue, mintTag); - } -} - -std::unique_ptr ParseLelantusJoinSplit(const CTransaction &tx) -{ - if (tx.vin.size() != 1 || tx.vin[0].scriptSig.size() < 1) { - throw CBadTxIn(); - } - - CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - - if (tx.vin[0].scriptSig[0] == OP_LELANTUSJOINSPLIT) { - serialized.write((const char *)tx.vin[0].scriptSig.data()+1, tx.vin[0].scriptSig.size()-1); - } - else if (tx.vin[0].scriptSig[0] == OP_LELANTUSJOINSPLITPAYLOAD && tx.nVersion >= 3 && tx.nType == TRANSACTION_LELANTUS) { - serialized.write((const char *)tx.vExtraPayload.data(), tx.vExtraPayload.size()); - } - else - throw CBadTxIn(); - - return std::make_unique(lelantus::Params::get_default(), serialized); -} - -bool CheckLelantusBlock(CValidationState &state, const CBlock& block) { - auto& consensus = ::Params().GetConsensus(); - - size_t blockSpendsAmount = 0; - CAmount blockSpendsValue(0); - - for (const auto& tx : block.vtx) { - auto txSpendsValue = GetSpendTransparentAmount(*tx); - size_t txSpendNumber = GetSpendInputs(*tx); - - if (txSpendNumber > consensus.nMaxLelantusInputPerTransaction) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-lelantus-spend-invalid"); - } - - if (txSpendsValue > consensus.nMaxValueLelantusSpendPerTransaction) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-lelantus-spend-invalid"); - } - - blockSpendsAmount += txSpendNumber; - blockSpendsValue += txSpendsValue; - } - - if (blockSpendsAmount > consensus.nMaxLelantusInputPerBlock) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-lelantus-spend-invalid"); - } - - if (blockSpendsValue > consensus.nMaxValueLelantusSpendPerBlock) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-lelantus-spend-invalid"); - } - - return true; -} - -bool CheckLelantusJMintTransaction( - const CTxOut &txout, - CValidationState &state, - uint256 hashTx, - bool fStatefulSigmaCheck, - std::vector& Cout, - CLelantusTxInfo* lelantusTxInfo) { - - LogPrintf("CheckLelantusJMintTransaction txHash = %s\n", txout.GetHash().ToString()); - - secp_primitives::GroupElement pubCoinValue; - uint256 mintTag; - std::vector encryptedValue; - try { - ParseLelantusJMintScript(txout.scriptPubKey, pubCoinValue, encryptedValue, mintTag); - } catch (std::invalid_argument&) { - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CTransaction::CheckTransaction() : Mint parsing failure."); - } - - lelantus::PublicCoin pubCoin(pubCoinValue); - - //checking whether commitment is valid - if(!pubCoin.validate()) - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckLelantusMintTransaction : PubCoin validation failed"); - - bool hasCoin = lelantusState.HasCoin(pubCoin); - - if (!hasCoin && lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete) { - BOOST_FOREACH(const auto& mint, lelantusTxInfo->mints) { - if (mint.first == pubCoin) { - hasCoin = true; - break; - } - } - } - - if (hasCoin && fStatefulSigmaCheck) { - LogPrintf("CheckLelantusMintTransaction: double mint, tx=%s\n", - txout.GetHash().ToString()); - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckLelantusMintTransaction: double mint"); - } - - uint64_t amount = 0; -#ifdef ENABLE_WALLET - if (!GetBoolArg("-disablewallet", false)) { - if (!pwalletMain->DecryptMintAmount(encryptedValue, pubCoinValue, amount)) - amount = 0; - } -#endif - if (lelantusTxInfo != NULL && !lelantusTxInfo->fInfoIsComplete) { - - // Update public coin list in the info - lelantusTxInfo->mints.push_back(std::make_pair(pubCoin, std::make_pair(amount, mintTag))); - lelantusTxInfo->encryptedJmintValues.insert(std::make_pair(pubCoin, encryptedValue)); - lelantusTxInfo->zcTransactions.insert(hashTx); - } - - Cout.push_back(pubCoin); - - return true; -} - -bool CheckLelantusJoinSplitTransaction( - const CTransaction &tx, - CValidationState &state, - uint256 hashTx, - bool isVerifyDB, - int nHeight, - int realHeight, - bool isCheckWallet, - bool fStatefulSigmaCheck, - CLelantusTxInfo* lelantusTxInfo) { - std::unordered_set txSerials; - - Consensus::Params const & params = ::Params().GetConsensus(); - - if(tx.vin.size() != 1 || !tx.vin[0].scriptSig.IsLelantusJoinSplit()) { - // mixing lelantus spend input with non-lelantus inputs is prohibited - return state.DoS(100, false, - REJECT_MALFORMED, - "CheckLelantusJoinSplitTransaction: can't mix lelantus spend input with other tx types or have more than one spend"); - } - - int height = nHeight == INT_MAX ? chainActive.Height()+1 : nHeight; - if (!isVerifyDB) { - if (height >= params.nLelantusV3PayloadStartBlock) { - // data should be moved to v3 payload - if (tx.nVersion < 3 || tx.nType != TRANSACTION_LELANTUS) - return state.DoS(100, false, NSEQUENCE_INCORRECT, - "CheckLelantusJoinSplitTransaction: lelantus data should reside in transaction payload"); - } - else { - if (tx.nVersion >= 3 && tx.nType != TRANSACTION_NORMAL) - return state.DoS(100, false, NSEQUENCE_INCORRECT, - "CheckLelantusJoinSplitTransaction: network hasn't yet switched over to lelantus payload data"); - } - } - FIRO_UNUSED const CTxIn &txin = tx.vin[0]; - std::unique_ptr joinsplit; - - try { - joinsplit = ParseLelantusJoinSplit(tx); - } - catch (CBadTxIn&) { - return state.DoS(100, - false, - REJECT_MALFORMED, - "CheckLelantusJoinSplitTransaction: invalid joinsplit transaction"); - } - catch (const std::exception &) { - return state.DoS(100, - false, - REJECT_MALFORMED, - "CheckLelantusJoinSplitTransaction: failed to deserialize joinsplit"); - } - - int jSplitVersion = joinsplit->getVersion(); - - if (jSplitVersion < LELANTUS_TX_VERSION_4 || - (!isVerifyDB && - ((height >= params.nLelantusFixesStartBlock && height < params.nLelantusV3PayloadStartBlock && jSplitVersion != LELANTUS_TX_VERSION_4_5 && jSplitVersion != SIGMA_TO_LELANTUS_JOINSPLIT_FIXED) || - (height >= params.nLelantusV3PayloadStartBlock && jSplitVersion != LELANTUS_TX_TPAYLOAD && jSplitVersion != SIGMA_TO_LELANTUS_TX_TPAYLOAD)))) { - return state.DoS(100, - false, - NSEQUENCE_INCORRECT, - "CTransaction::CheckLelantusJoinSplitTransaction() : Error: incorrect joinsplit transaction verion"); - } - - if (joinsplit->isSigmaToLelantus() && height >= params.nSigmaEndBlock) { - return state.DoS(100, - false, - NSEQUENCE_INCORRECT, - "CTransaction::CheckLelantusJoinSplitTransaction() : Sigma pool already closed."); - } - - uint256 txHashForMetadata; - - // Obtain the hash of the transaction sans the zerocoin part - CMutableTransaction txTemp = tx; - txTemp.vin[0].scriptSig.clear(); - txTemp.vExtraPayload.clear(); - - txHashForMetadata = txTemp.GetHash(); - - LogPrintf("CheckLelantusJoinSplitTransaction: tx version=%d, tx metadata hash=%s\n", - jSplitVersion, txHashForMetadata.ToString()); - - if (!fStatefulSigmaCheck) { - return true; - } - - bool passVerify = false; - std::map> anonymity_sets; - std::vector Cout; - uint64_t Vout = 0; - - for (const CTxOut &txout : tx.vout) { - if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsLelantusJMint()) { - try { - if (!CheckLelantusJMintTransaction(txout, state, hashTx, fStatefulSigmaCheck, Cout, lelantusTxInfo)) - return false; - } - catch (const std::exception &x) { - return state.Error(x.what()); - } - } else if(txout.scriptPubKey.IsLelantusMint()) { - return false; //putting regular mints at JoinSplit transactions is not allowed - } else { - Vout += txout.nValue; - } - } - - std::vector> anonymity_set_hashes; - const std::vector& ids = joinsplit->getCoinGroupIds(); - - if (joinsplit->isSigmaToLelantus()) { - passVerify = true; - goto skipSigmaToLelantus; - } - - for (auto& idAndHash : joinsplit->getIdAndBlockHashes()) { - auto& anonymity_set = anonymity_sets[idAndHash.first]; - { - CLelantusState::LelantusCoinGroupInfo coinGroup; - if (!lelantusState.GetCoinGroupInfo(idAndHash.first, coinGroup)) - return state.DoS(100, false, NO_MINT_ZEROCOIN, - "CheckLelantusJoinSplitTransaction: Error: no coins were minted with such parameters"); - - CBlockIndex *index = coinGroup.lastBlock; - - - // find index for block with hash of accumulatorBlockHash or set index to the coinGroup.firstBlock if not found - while (index != coinGroup.firstBlock && index->GetBlockHash() != idAndHash.second) - index = index->pprev; - - // take the hash from last block of anonymity set, it is used at challenge generation if nLelantusFixesStartBlock is passed - if (nHeight >= params.nLelantusFixesStartBlock) { - std::vector set_hash = GetAnonymitySetHash(index, idAndHash.first); - if (!set_hash.empty()) - anonymity_set_hashes.push_back(set_hash); - } - // Build a vector with all the public coins with given id before - // the block on which the spend occured. - // This list of public coins is required by function "Verify" of JoinSplit. - - while (true) { - int id = 0; - if (CountCoinInBlock(index, idAndHash.first)) { - id = idAndHash.first; - } else if (CountCoinInBlock(index, idAndHash.first - 1)) { - id = idAndHash.first - 1; - } - if (id) { - if(index->lelantusMintedPubCoins.count(id) > 0) { - BOOST_FOREACH( - const auto& pubCoinValue, - index->lelantusMintedPubCoins[id]) { - // skip mints from blacklist if nLelantusFixesStartBlock is passed - if (chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock) { - if (::Params().GetConsensus().lelantusBlacklist.count(pubCoinValue.first.getValue()) > 0) { - continue; - } - } - anonymity_set.push_back(pubCoinValue.first); - } - } - } - if (index == coinGroup.firstBlock) - break; - index = index->pprev; - } - } - anonymity_sets[idAndHash.first] = anonymity_set; - } - - { - for (const auto& id: ids) { - if (!anonymity_sets.count(id)) - return state.DoS(100, - error("CheckLelantusJoinSplitTransaction: No anonymity set found.")); - } - - BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); - bool useBatching = batchProofContainer->fCollectProofs && !isVerifyDB && !isCheckWallet && lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete; - - Scalar challenge; - // if we are collecting proofs, skip verification and collect proofs - passVerify = joinsplit->Verify(anonymity_sets, anonymity_set_hashes, Cout, Vout, txHashForMetadata, challenge, useBatching); - - // add proofs into container - if(useBatching) { - std::map idAndSizes; - - for(auto itr : anonymity_sets) - idAndSizes[itr.first] = itr.second.size(); - - batchProofContainer->add(joinsplit.get(), Cout); - } - } - -skipSigmaToLelantus: - - if (passVerify) { - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - if (serials.size() != ids.size()) { - return state.DoS(100, - error("CheckLelantusJoinSplitTransaction: sized of serials and group ids don't match.")); - } - - // do not check for duplicates in case we've seen exact copy of this tx in this block before - if (!(lelantusTxInfo && lelantusTxInfo->zcTransactions.count(hashTx) > 0)) { - for (size_t i = 0; i < serials.size(); ++i) { - if (!CheckLelantusSpendSerial( - state, lelantusTxInfo, serials[i], nHeight, false)) { - LogPrintf("CheckLelantusJoinSplitTransaction: serial check failed, serial=%s\n", serials[i].GetHex()); - return false; - } - } - } - - // check duplicated serials in same transaction. - for (const auto &serial : serials) { - if (!txSerials.insert(serial).second) { - return state.DoS(100, - error("CheckLelantusJoinSplitTransaction: two or more spends with same serial in the same transaction")); - } - } - - if (!isVerifyDB && !isCheckWallet) { - // add spend information to the index - if (lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete) { - for (size_t i = 0; i < serials.size(); i++) { - lelantusTxInfo->spentSerials.insert(std::make_pair(serials[i], ids[i])); - } - } - } - } - else { - LogPrintf("CheckLelantusJoinSplitTransaction: verification failed at block %d\n", nHeight); - return false; - } - - if(!isVerifyDB && !isCheckWallet) { - if (lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete) { - lelantusTxInfo->zcTransactions.insert(hashTx); - } - } - - return true; -} - -bool CheckLelantusMintTransaction( - const CTxOut &txout, - CValidationState &state, - uint256 hashTx, - bool fStatefulSigmaCheck, - CLelantusTxInfo* lelantusTxInfo) { - secp_primitives::GroupElement pubCoinValue; - uint256 mintTag; - SchnorrProof schnorrProof; - - LogPrintf("CheckLelantusMintTransaction txHash = %s\n", txout.GetHash().ToString()); - LogPrintf("nValue = %d\n", txout.nValue); - if(txout.nValue > ::Params().GetConsensus().nMaxValueLelantusMint) - return state.DoS(100, - false, - REJECT_INVALID, - "CTransaction::CheckTransaction() : Mint is out of limit."); - - try { - ParseLelantusMintScript(txout.scriptPubKey, pubCoinValue, schnorrProof, mintTag); - } catch (std::invalid_argument&) { - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CTransaction::CheckTransaction() : Mint parsing failure."); - } - - lelantus::PublicCoin pubCoin(pubCoinValue); - - //checking whether commitment is valid - if(!VerifyMintSchnorrProof(txout.nValue, pubCoinValue, schnorrProof) || !pubCoin.validate()) - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckLelantusMintTransaction : PubCoin validation failed"); - - - bool hasCoin = lelantusState.HasCoin(pubCoin); - - if (!hasCoin && lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete) { - BOOST_FOREACH(const auto& mint, lelantusTxInfo->mints) { - if (mint.first == pubCoin) { - hasCoin = true; - break; - } - } - } - - if (hasCoin && fStatefulSigmaCheck) { - LogPrintf("CheckLelantusMintTransaction: double mint, tx=%s\n", - txout.GetHash().ToString()); - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckLelantusMintTransaction: double mint"); - } - - if (lelantusTxInfo != NULL && !lelantusTxInfo->fInfoIsComplete) { - // Update public coin list in the info - lelantusTxInfo->mints.push_back(std::make_pair(pubCoin, std::make_pair(txout.nValue, mintTag))); - lelantusTxInfo->zcTransactions.insert(hashTx); - } - - return true; -} - -bool CheckLelantusTransaction( - const CTransaction &tx, - CValidationState &state, - uint256 hashTx, - bool isVerifyDB, - int nHeight, - bool isCheckWallet, - bool fStatefulSigmaCheck, - CLelantusTxInfo* lelantusTxInfo) -{ - Consensus::Params const & consensus = ::Params().GetConsensus(); - - int realHeight = nHeight; - - if (realHeight == INT_MAX) { - LOCK(cs_main); - realHeight = chainActive.Height(); - } - - // accept Lelantus mint tx into 5 more blocks, to allow mempool cleared - if (!isVerifyDB && realHeight >= (::Params().GetConsensus().nSparkStartBlock + 5)) { - if (tx.IsLelantusMint() && !tx.IsLelantusJoinSplit()) - return state.DoS(100, false, - REJECT_INVALID, - "Lelantus already is not available, start using Spark."); - } - - // accept lelantus spends until nLelantusGracefulPeriod passed, to allow migration of funds from lelantus to spark - if (!isVerifyDB && realHeight >= (::Params().GetConsensus().nLelantusGracefulPeriod)) { - if (tx.IsLelantusJoinSplit()) - return state.DoS(100, false, - REJECT_INVALID, - "Lelantus is fully disabled."); - } - - bool const allowLelantus = (realHeight >= consensus.nLelantusStartBlock); - - if (!isVerifyDB && !isCheckWallet) { - if (allowLelantus && lelantusState.IsSurgeConditionDetected()) { - return state.DoS(100, false, - REJECT_INVALID, - "Lelantus surge protection is ON."); - } - } - - // Check Mint Lelantus Transaction - if (allowLelantus && !isVerifyDB) { - for (const CTxOut &txout : tx.vout) { - if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsLelantusMint()) { - try { - if (!CheckLelantusMintTransaction(txout, state, hashTx, fStatefulSigmaCheck, lelantusTxInfo)) - return false; - } - catch (const std::exception &x) { - return state.Error(x.what()); - } - } - } - } - - // Check Lelantus JoinSplit Transaction - if(tx.IsLelantusJoinSplit()) { - // First check number of inputs does not exceed transaction limit - if (GetSpendInputs(tx) > consensus.nMaxLelantusInputPerTransaction) { - return state.DoS(100, false, - REJECT_INVALID, - "bad-txns-spend-invalid"); - } - - if (GetSpendTransparentAmount(tx) > consensus.nMaxValueLelantusSpendPerTransaction) { - return state.DoS(100, false, - REJECT_INVALID, - "bad-txns-spend-invalid"); - } - - if (!isVerifyDB) { - try { - if (!CheckLelantusJoinSplitTransaction( - tx, state, hashTx, isVerifyDB, nHeight, realHeight, - isCheckWallet, fStatefulSigmaCheck, lelantusTxInfo)) { - return false; - } - } - catch (const std::exception &x) { - return state.Error(x.what()); - } - } - } - - return true; -} - -void RemoveLelantusJoinSplitReferencingBlock(CTxMemPool& pool, CBlockIndex* blockIndex) { - LOCK2(cs_main, pool.cs); - std::vector txn_to_remove; - for (CTxMemPool::txiter mi = pool.mapTx.begin(); mi != pool.mapTx.end(); ++mi) { - const CTransaction& tx = mi->GetTx(); - if (tx.IsLelantusJoinSplit()) { - // Run over all the inputs, check if their CoinGroup block hash is equal to - // block removed. If any one is equal, remove txn from mempool. - for (const CTxIn& txin : tx.vin) { - if (txin.IsLelantusJoinSplit()) { - std::unique_ptr joinsplit; - - try { - joinsplit = ParseLelantusJoinSplit(tx); - } - catch (const std::exception &) { - txn_to_remove.push_back(tx); - break; - } - - const std::vector>& coinGroupIdAndBlockHash = joinsplit->getIdAndBlockHashes(); - for(const auto& idAndHash : coinGroupIdAndBlockHash) { - if (idAndHash.second == blockIndex->GetBlockHash()) { - // Do not remove transaction immediately, that will invalidate iterator mi. - txn_to_remove.push_back(tx); - break; - } - } - } - } - } - } - for (const CTransaction& tx: txn_to_remove) { - // Remove txn from mempool. - pool.removeRecursive(tx); - LogPrintf("DisconnectTipLelantus: removed lelantus joinsplit which referenced a removed blockchain tip."); - } -} - -void DisconnectTipLelantus(CBlock& block, CBlockIndex *pindexDelete) { - lelantusState.RemoveBlock(pindexDelete); - - // Also remove from mempool lelantus joinsplits that reference given block hash. - RemoveLelantusJoinSplitReferencingBlock(mempool, pindexDelete); - RemoveLelantusJoinSplitReferencingBlock(txpools.getStemTxPool(), pindexDelete); -} - -std::vector GetLelantusJoinSplitSerialNumbers(const CTransaction &tx, const CTxIn &txin) { - if (!tx.IsLelantusJoinSplit()) - return std::vector(); - - try { - return ParseLelantusJoinSplit(tx)->getCoinSerialNumbers(); - } - catch (const std::exception &) { - return std::vector(); - } -} - -std::vector GetLelantusJoinSplitIds(const CTransaction &tx, const CTxIn &txin) { - if (!tx.IsLelantusJoinSplit()) - return std::vector(); - - try { - return ParseLelantusJoinSplit(tx)->getCoinGroupIds(); - } - catch (const std::exception &) { - return std::vector(); - } -} - -size_t GetSpendInputs(const CTransaction &tx, const CTxIn& in) { - return in.IsLelantusJoinSplit() ? - GetLelantusJoinSplitSerialNumbers(tx, in).size() : 0; -} - -size_t GetSpendInputs(const CTransaction &tx) { - size_t sum = 0; - for (const auto& vin : tx.vin) { - sum += GetSpendInputs(tx, vin); - } - return sum; -} - -CAmount GetSpendTransparentAmount(const CTransaction& tx) { - CAmount result = 0; - if(!tx.IsLelantusJoinSplit()) - return 0; - - for (const CTxOut &txout : tx.vout) - result += txout.nValue; - return result; -} - -/** - * Connect a new ZCblock to chainActive. pblock is either NULL or a pointer to a CBlock - * corresponding to pindexNew, to bypass loading it again from disk. - */ -bool ConnectBlockLelantus( - CValidationState &state, - const CChainParams &chainparams, - CBlockIndex *pindexNew, - const CBlock *pblock, - bool fJustCheck) { - // Add lelantus transaction information to index - if (pblock && pblock->lelantusTxInfo) { - if (!fJustCheck) { - pindexNew->lelantusMintedPubCoins.clear(); - pindexNew->lelantusSpentSerials.clear(); - pindexNew->anonymitySetHash.clear(); - } - - if (!CheckLelantusBlock(state, *pblock)) { - return false; - } - - BOOST_FOREACH(auto& serial, pblock->lelantusTxInfo->spentSerials) { - if (!CheckLelantusSpendSerial( - state, - pblock->lelantusTxInfo.get(), - serial.first, - pindexNew->nHeight, - true /* fConnectTip */ - )) { - return false; - } - } - - if (!fJustCheck) { - BOOST_FOREACH(auto& serial, pblock->lelantusTxInfo->spentSerials) { - pindexNew->lelantusSpentSerials.insert(serial); - lelantusState.AddSpend(serial.first, serial.second); - } - } - else { - return true; - } - - const auto& params = ::Params().GetConsensus(); - CHash256 hash; - std::vector data(GroupElement::serialize_size); - bool updateHash = false; - - // create first anonymity set hash with whole existing set, at HF block - if (pindexNew->nHeight == params.nLelantusFixesStartBlock) { - updateHash = true; - std::vector coins; - lelantusState.GetAnonymitySet(1, false, coins); - for (auto &coin : coins) { - coin.getValue().serialize(data.data()); - hash.Write(data.data(), data.size()); - } - } - - if (!pblock->lelantusTxInfo->mints.empty()) { - lelantusState.AddMintsToStateAndBlockIndex(pindexNew, pblock); - int latestCoinId = lelantusState.GetLatestCoinID(); - // add coins into hasher, for generating set hash - // if this is HF block just add mint from this block too, - // else hasher is supposed to be empty, so add previous hash first, then coins - if (pindexNew->nHeight >= params.nLelantusFixesStartBlock) { - updateHash = true; - - if (pindexNew->nHeight > params.nLelantusFixesStartBlock) { - // get previous hash of the set, if there is no such, don't write anything - std::vector prev_hash = GetAnonymitySetHash(pindexNew->pprev, latestCoinId, true); - if (!prev_hash.empty()) - hash.Write(prev_hash.data(), 32); - else { - if(latestCoinId > 1) { - prev_hash = GetAnonymitySetHash(pindexNew->pprev, latestCoinId - 1, true); - hash.Write(prev_hash.data(), 32); - } - } - } - - for (auto &coin : pindexNew->lelantusMintedPubCoins[latestCoinId]) { - coin.first.getValue().serialize(data.data()); - hash.Write(data.data(), data.size()); - } - } - } - - // generate hash if we need it - if (updateHash) { - unsigned char hash_result[CSHA256::OUTPUT_SIZE]; - hash.Finalize(hash_result); - auto &out_hash = pindexNew->anonymitySetHash[lelantusState.GetLatestCoinID()]; - out_hash.clear(); - out_hash.insert(out_hash.begin(), std::begin(hash_result), std::end(hash_result)); - } - } - else if (!fJustCheck) { - lelantusState.AddBlock(pindexNew); - } - return true; -} - -bool GetOutPointFromBlock(COutPoint& outPoint, const GroupElement &pubCoinValue, const CBlock &block) { - secp_primitives::GroupElement txPubCoinValue; - // cycle transaction hashes, looking for this pubcoin. - BOOST_FOREACH(CTransactionRef tx, block.vtx){ - uint32_t nIndex = 0; - for (const CTxOut &txout: tx->vout) { - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - try { - ParseLelantusMintScript(txout.scriptPubKey, txPubCoinValue); - } - catch (const std::exception &) { - continue; - } - if(pubCoinValue==txPubCoinValue){ - outPoint = COutPoint(tx->GetHash(), nIndex); - return true; - } - } - nIndex++; - } - } - - return false; -} - -uint256 GetTxHashFromPubcoin(const lelantus::PublicCoin& pubCoin) { - COutPoint outPoint; - GetOutPoint(outPoint, pubCoin.getValue()); - return outPoint.hash; -} - -bool GetOutPoint(COutPoint& outPoint, const lelantus::PublicCoin &pubCoin) { - - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - auto mintedCoinHeightAndId = lelantusState->GetMintedCoinHeightAndId(pubCoin); - int mintHeight = mintedCoinHeightAndId.first; - int coinId = mintedCoinHeightAndId.second; - - if(mintHeight==-1 && coinId==-1) - return false; - - // get block containing mint - CBlockIndex *mintBlock = chainActive[mintHeight]; - CBlock block; - if(!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) - LogPrintf("can't read block from disk.\n"); - - return GetOutPointFromBlock(outPoint, pubCoin.getValue(), block); -} - -bool GetOutPoint(COutPoint& outPoint, const GroupElement &pubCoinValue) { - lelantus::PublicCoin pubCoin(pubCoinValue); - - return GetOutPoint(outPoint, pubCoin); -} - -bool GetOutPoint(COutPoint& outPoint, const uint256 &pubCoinValueHash) { - GroupElement pubCoinValue; - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - if(!lelantusState->HasCoinHash(pubCoinValue, pubCoinValueHash)){ - return false; - } - - return GetOutPoint(outPoint, pubCoinValue); -} - -bool GetOutPointFromMintTag(COutPoint& outPoint, const uint256 &pubCoinTag) { - GroupElement pubCoinValue; - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - if(!lelantusState->HasCoinTag(pubCoinValue, pubCoinTag)){ - return false; - } - - return GetOutPoint(outPoint, pubCoinValue); -} - -bool BuildLelantusStateFromIndex(CChain *chain) { - for (CBlockIndex *blockIndex = chain->Genesis(); blockIndex; blockIndex=chain->Next(blockIndex)) - { - lelantusState.AddBlock(blockIndex); - } - // DEBUG - LogPrintf( - "Latest ID for Lelantus coin group %d\n", - lelantusState.GetLatestCoinID()); - return true; -} - -// CLelantusTxInfo -void CLelantusTxInfo::Complete() { - // We need to sort mints lexicographically by serialized value of pubCoin. That's the way old code - // works, we need to stick to it. - sort(mints.begin(), mints.end(), - [](decltype(mints)::const_reference m1, decltype(mints)::const_reference m2)->bool { - CDataStream ds1(SER_DISK, CLIENT_VERSION), ds2(SER_DISK, CLIENT_VERSION); - ds1 << m1; - ds2 << m2; - return ds1.str() < ds2.str(); - }); - - // Mark this info as complete - fInfoIsComplete = true; -} - -/******************************************************************************/ -// CLelantusState::Containers -/******************************************************************************/ - -CLelantusState::Containers::Containers(std::atomic & surgeCondition) -: surgeCondition(surgeCondition) -{} - -void CLelantusState::Containers::AddMint(lelantus::PublicCoin const & pubCoin, CMintedCoinInfo const & coinInfo, const uint256& tag) { - mintedPubCoins.insert(std::make_pair(pubCoin, coinInfo)); - tagToPublicCoin.insert(std::make_pair(tag, pubCoin)); - mintMetaInfo[coinInfo.coinGroupId] += 1; - CheckSurgeCondition(); -} - -void CLelantusState::Containers::RemoveMint(lelantus::PublicCoin const & pubCoin) { - mint_info_container::const_iterator iter = mintedPubCoins.find(pubCoin); - if (iter != mintedPubCoins.end()) { - for(auto hashPair = tagToPublicCoin.begin(); hashPair != tagToPublicCoin.end(); hashPair++) - if(hashPair->second == pubCoin) { - tagToPublicCoin.erase(hashPair); - break; - } - - mintMetaInfo[iter->second.coinGroupId] -= 1; - mintedPubCoins.erase(iter); - CheckSurgeCondition(); - } -} - -void CLelantusState::Containers::AddSpend(Scalar const & serial, int coinGroupId) { - if (mintMetaInfo.count(coinGroupId) > 0) { - usedCoinSerials[serial] = coinGroupId; - spendMetaInfo[coinGroupId] += 1; - CheckSurgeCondition(); - } -} - -void CLelantusState::Containers::RemoveSpend(Scalar const & serial) { - auto iter = usedCoinSerials.find(serial); - if (iter != usedCoinSerials.end()) { - spendMetaInfo[iter->second] -= 1; - usedCoinSerials.erase(iter); - CheckSurgeCondition(); - } -} - -void CLelantusState::Containers::AddExtendedMints(int group, size_t mints) { - extendedMintMetaInfo[group] = mints; - CheckSurgeCondition(); -} - -void CLelantusState::Containers::RemoveExtendedMints(int group) { - extendedMintMetaInfo.erase(group); - CheckSurgeCondition(); -} - -mint_info_container const & CLelantusState::Containers::GetMints() const { - return mintedPubCoins; -} - -std::unordered_map& CLelantusState::Containers::GetTagToPublicCoin() { - return tagToPublicCoin; -} - -std::unordered_map const & CLelantusState::Containers::GetSpends() const { - return usedCoinSerials; -} - -bool CLelantusState::Containers::IsSurgeCondition() const { - return surgeCondition; -} - -void CLelantusState::Containers::Reset() { - mintedPubCoins.clear(); - usedCoinSerials.clear(); - mintMetaInfo.clear(); - spendMetaInfo.clear(); - tagToPublicCoin.clear(); - surgeCondition = false; -} - -void CLelantusState::Containers::CheckSurgeCondition() { - bool result = false; - - // find a range of groups that sum of serials larger than sum of mints - size_t serials = 0; - size_t mints = 0; - int start = 0; - - for (auto it = mintMetaInfo.begin(); it != mintMetaInfo.end(); it++) { - auto id = it->first; - - // include serials and mints to accumulators - serials += spendMetaInfo.count(id) ? spendMetaInfo[id] : 0; - mints += it->second; - - // serials exceed mints then trigger - if (serials > mints) { - result = true; - - std::ostringstream ostr; - ostr << "Turning Lelantus surge protection ON: in group range: " << start << " - " << id << '\n'; - error(ostr.str().c_str()); - - break; - } - - auto extendedMints = extendedMintMetaInfo.count(id + 1) ? - extendedMintMetaInfo[id + 1] : 0; - - if (serials <= mints - extendedMints) { - start = id + 1; - serials = 0; - mints = extendedMints; - } - } - - surgeCondition = result; -} - -/******************************************************************************/ -// CLelantusState -/******************************************************************************/ - -CLelantusState::CLelantusState( - size_t maxCoinInGroup, - size_t startGroupSize) - : - maxCoinInGroup(maxCoinInGroup), - startGroupSize(startGroupSize), - containers(surgeCondition) -{ - Reset(); -} - -void CLelantusState::AddMintsToStateAndBlockIndex( - CBlockIndex *index, - const CBlock* pblock) { - - std::vector> blockMints; - std::unordered_map lelantusMintData; - - for (const auto& mint : pblock->lelantusTxInfo->mints) { - if (GetBoolArg("-mobile", false)) { - lelantus::MintValueData mintdata; - mintdata.amount = mint.second.first; - if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { - mintdata.isJMint = true; - mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; - } - - COutPoint outPoint; - GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); - mintdata.txHash = outPoint.hash; - lelantusMintData[mint.first.getValue()] = mintdata; - } - blockMints.push_back(std::make_pair(mint.first, mint.second.second)); - } - - latestCoinId = std::max(1, latestCoinId); - - auto &coinGroup = coinGroups[latestCoinId]; - - if (coinGroup.nCoins + blockMints.size() <= maxCoinInGroup) { - if (coinGroup.nCoins == 0) { - // first group of coins - assert(coinGroup.firstBlock == nullptr); - assert(coinGroup.lastBlock == nullptr); - - coinGroup.firstBlock = coinGroup.lastBlock = index; - } else { - assert(coinGroup.firstBlock != nullptr); - assert(coinGroup.lastBlock != nullptr); - assert(coinGroup.lastBlock->nHeight <= index->nHeight); - - coinGroup.lastBlock = index; - } - coinGroup.nCoins += blockMints.size(); - } else { - auto& newCoinGroup = coinGroups[++latestCoinId]; - - CBlockIndex *first; - auto coins = CountLastNCoins(latestCoinId - 1, startGroupSize, first); - newCoinGroup.firstBlock = first ? first : index; - newCoinGroup.lastBlock = index; - newCoinGroup.nCoins = coins + blockMints.size(); - - containers.AddExtendedMints(latestCoinId, coins); - } - - for (const auto& mint : blockMints) { - containers.AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight), mint.second); - - LogPrintf("AddMintsToStateAndBlockIndex: Lelantus mint added id=%d\n", latestCoinId); - index->lelantusMintedPubCoins[latestCoinId].push_back(mint); - - if (GetBoolArg("-mobile", false)) { - index->lelantusMintData[mint.first.getValue()] = lelantusMintData[mint.first.getValue()]; - } - } -} - -void CLelantusState::AddSpend(const Scalar &serial, int coinGroupId) { - containers.AddSpend(serial, coinGroupId); -} - -void CLelantusState::AddBlock(CBlockIndex *index) { - for (auto const &pubCoins : index->lelantusMintedPubCoins) { - - if (pubCoins.second.empty()) - continue; - - auto &coinGroup = coinGroups[pubCoins.first]; - - if (coinGroup.firstBlock == nullptr) { - coinGroup.firstBlock = index; - - if (pubCoins.first > 1) { - CBlockIndex *first; - coinGroup.nCoins = CountLastNCoins(pubCoins.first - 1, startGroupSize, first); - coinGroup.firstBlock = first ? first : index; - - containers.AddExtendedMints(pubCoins.first, coinGroup.nCoins); - } - } - coinGroup.lastBlock = index; - coinGroup.nCoins += pubCoins.second.size(); - - latestCoinId = pubCoins.first; - for (auto const &coin : pubCoins.second) { - containers.AddMint(coin.first, CMintedCoinInfo::make(pubCoins.first, index->nHeight), coin.second); - } - } - - for (auto const &serial : index->lelantusSpentSerials) { - AddSpend(serial.first, serial.second); - } -} - -void CLelantusState::RemoveBlock(CBlockIndex *index) { - // roll back coin group updates - for (auto &coins : index->lelantusMintedPubCoins) - { - if (coinGroups.count(coins.first) == 0) - continue; - - LelantusCoinGroupInfo& coinGroup = coinGroups[coins.first]; - auto nMintsToForget = coins.second.size(); - - if (nMintsToForget == 0) - continue; - - assert(cmp::greater_equal(coinGroup.nCoins, nMintsToForget)); - auto isExtended = coins.first > 1; - coinGroup.nCoins -= nMintsToForget; - - // if `index` is edged block we need to erase group - auto isEdgedBlock = false; - if (isExtended) { - auto prevBlockContainMints = index; - size_t prevGroupCount = 0; - - // find block that contain some Lelantus mints - do { - prevBlockContainMints = prevBlockContainMints->pprev; - } while (prevBlockContainMints - && CountCoinInBlock(prevBlockContainMints, coins.first) == 0 - && (prevGroupCount = CountCoinInBlock(prevBlockContainMints, coins.first - 1)) == 0); - - isEdgedBlock = prevGroupCount > 0 && (coinGroup.nCoins - prevGroupCount) < startGroupSize; - } - - if ((!isExtended && coinGroup.nCoins == 0) || (isExtended && isEdgedBlock)) { - // all the coins of this group have been erased, remove the group altogether - coinGroups.erase(coins.first); - // decrease pubcoin id - latestCoinId--; - // erase from containers - containers.RemoveExtendedMints(coins.first); - } else { - // roll back lastBlock to previous position - assert(coinGroup.lastBlock == index); - - do { - assert(coinGroup.lastBlock != coinGroup.firstBlock); - coinGroup.lastBlock = coinGroup.lastBlock->pprev; - } while (coinGroup.lastBlock->lelantusMintedPubCoins.count(coins.first) == 0); - } - } - - // roll back mints - for (auto const &pubCoins : index->lelantusMintedPubCoins) { - for (auto const &coin : pubCoins.second) { - auto coins = containers.GetMints().equal_range(coin.first); - auto coinIt = find_if( - coins.first, coins.second, - [&pubCoins](const mint_info_container::value_type &v) { - return v.second.coinGroupId == pubCoins.first; - }); - assert(coinIt != coins.second); - containers.RemoveMint(coinIt->first); - } - } - - // roll back spends - for (auto const &serial : index->lelantusSpentSerials) { - containers.RemoveSpend(serial.first); - } -} - -bool CLelantusState::GetCoinGroupInfo( - int group_id, - LelantusCoinGroupInfo& result) { - if (coinGroups.count(group_id) == 0) - return false; - - result = coinGroups[group_id]; - return true; -} - -bool CLelantusState::IsUsedCoinSerial(const Scalar &coinSerial) { - return containers.GetSpends().count(coinSerial) != 0; -} - -bool CLelantusState::IsUsedCoinSerialHash(Scalar &coinSerial, const uint256 &coinSerialHash) { - for ( auto it = GetSpends().begin(); it != GetSpends().end(); ++it ){ - if(primitives::GetSerialHash(it->first)==coinSerialHash){ - coinSerial = it->first; - return true; - } - } - return false; -} - -bool CLelantusState::HasCoin(const lelantus::PublicCoin& pubCoin) { - return containers.GetMints().find(pubCoin) != containers.GetMints().end(); -} - -bool CLelantusState::HasCoinHash(GroupElement &pubCoinValue, const uint256 &pubCoinValueHash) { - for ( auto it = GetMints().begin(); it != GetMints().end(); ++it ){ - const lelantus::PublicCoin & pubCoin = (*it).first; - if(pubCoin.getValueHash()==pubCoinValueHash){ - pubCoinValue = pubCoin.getValue(); - return true; - } - } - return false; -} - -bool CLelantusState::HasCoinTag(GroupElement& pubCoinValue, const uint256& pubCoinTag) { - auto const& mints = containers.GetTagToPublicCoin(); - if(mints.count(pubCoinTag) > 0) { - pubCoinValue = mints.at(pubCoinTag).getValue(); - return true; - } - return false; -} - -int CLelantusState::GetCoinSetForSpend( - CChain *chain, - int maxHeight, - int coinGroupID, - uint256& blockHash_out, - std::vector& coins_out, - std::vector& setHash_out, - std::string start_block_hash) { - - coins_out.clear(); - - if (coinGroups.count(coinGroupID) == 0) { - return 0; - } - - LelantusCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; - - int numberOfCoins = 0; - for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { - - // ignore block heigher than max height - if (block->nHeight > maxHeight) { - continue; - } - - if (block->GetBlockHash().GetHex() == start_block_hash) { - break ; - } - - // check coins in group coinGroupID - 1 in the case that using coins from prev group. - int id = 0; - if (CountCoinInBlock(block, coinGroupID)) { - id = coinGroupID; - } else if (CountCoinInBlock(block, coinGroupID - 1)) { - id = coinGroupID - 1; - } - - if (id) { - if (numberOfCoins == 0) { - // latest block satisfying given conditions - // remember block hash and set hash - blockHash_out = block->GetBlockHash(); - setHash_out = GetAnonymitySetHash(block, id); - } - numberOfCoins += block->lelantusMintedPubCoins[id].size(); - if (block->lelantusMintedPubCoins.count(id) > 0) { - for (const auto &coin : block->lelantusMintedPubCoins[id]) { - LOCK(cs_main); - // skip mints from blacklist if nLelantusFixesStartBlock is passed - if (chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock) { - if (::Params().GetConsensus().lelantusBlacklist.count(coin.first.getValue()) > 0) { - continue; - } - } - coins_out.push_back(coin.first); - } - } - } - - if (block == coinGroup.firstBlock) { - break ; - } - } - - return numberOfCoins; -} - -void CLelantusState::GetCoinsForRecovery( - CChain *chain, - int maxHeight, - int coinGroupID, - std::string start_block_hash, - uint256& blockHash_out, - std::vector>>& coins, - std::vector& setHash_out) { - - coins.clear(); - if (coinGroups.count(coinGroupID) == 0) { - return; - } - - LelantusCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; - - int numberOfCoins = 0; - for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { - // ignore block heigher than max height - if (block->nHeight > maxHeight) { - continue; - } - - if (block->GetBlockHash().GetHex() == start_block_hash) { - break; - } - - // check coins in group coinGroupID - 1 in the case that using coins from prev group. - int id = 0; - if (CountCoinInBlock(block, coinGroupID)) { - id = coinGroupID; - } else if (CountCoinInBlock(block, coinGroupID - 1)) { - id = coinGroupID - 1; - } - - if (id) { - if (numberOfCoins == 0) { - // latest block satisfying given conditions - // remember block hash and set hash - blockHash_out = block->GetBlockHash(); - setHash_out = GetAnonymitySetHash(block, id); - } - - numberOfCoins += block->lelantusMintedPubCoins[id].size(); - if (block->lelantusMintedPubCoins.count(id) > 0) { - for (const auto &coin : block->lelantusMintedPubCoins[id]) { - LOCK(cs_main); - // skip mints from blacklist if nLelantusFixesStartBlock is passed - if (chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock) { - if (::Params().GetConsensus().lelantusBlacklist.count(coin.first.getValue()) > 0) { - continue; - } - } - - lelantus::MintValueData lelantusMintData; - if (block->lelantusMintData.count(coin.first.getValue())) - lelantusMintData = block->lelantusMintData[coin.first.getValue()]; - coins.push_back(std::make_pair(coin.first, std::make_pair(lelantusMintData, coin.second))); - - } - } - } - - if (block == coinGroup.firstBlock) { - break ; - } - } - -} - -void CLelantusState::GetAnonymitySet( - int coinGroupID, - bool fStartLelantusBlacklist, - std::vector& coins_out) { - - coins_out.clear(); - - if (coinGroups.count(coinGroupID) == 0) { - return; - } - - LelantusCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; - const auto ¶ms = ::Params().GetConsensus(); - LOCK(cs_main); - int maxHeight = fStartLelantusBlacklist ? (chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1)) : (params.nLelantusFixesStartBlock - 1); - - for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { - - // ignore block heigher than max height - if (block->nHeight > maxHeight) { - continue; - } - - // check coins in group coinGroupID - 1 in the case that using coins from prev group. - int id = 0; - if (CountCoinInBlock(block, coinGroupID)) { - id = coinGroupID; - } else if (CountCoinInBlock(block, coinGroupID - 1)) { - id = coinGroupID - 1; - } - - if (id) { - if(block->lelantusMintedPubCoins.count(id) > 0) { - for (const auto &coin : block->lelantusMintedPubCoins[id]) { - if (fStartLelantusBlacklist && - chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock) { - std::vector vch = coin.first.getValue().getvch(); - if (::Params().GetConsensus().lelantusBlacklist.count(coin.first.getValue()) > 0) { - continue; - } - } - coins_out.push_back(coin.first); - } - } - } - - if (block == coinGroup.firstBlock) { - break ; - } - } -} - -std::pair CLelantusState::GetMintedCoinHeightAndId( - const lelantus::PublicCoin& pubCoin) { - auto coinIt = containers.GetMints().find(pubCoin); - - if (coinIt != containers.GetMints().end()) { - return std::make_pair(coinIt->second.nHeight, coinIt->second.coinGroupId); - } - return std::make_pair(-1, -1); -} - -bool CLelantusState::AddSpendToMempool(const std::vector &coinSerials, uint256 txHash) { - LOCK(mempool.cs); - BOOST_FOREACH(const Scalar& coinSerial, coinSerials){ - if (IsUsedCoinSerial(coinSerial) || mempool.lelantusState.HasCoinSerial(coinSerial)) - return false; - - mempool.lelantusState.AddSpendToMempool(coinSerial, txHash); - } - - return true; -} - -void CLelantusState::RemoveSpendFromMempool(const std::vector &coinSerials) { - LOCK(mempool.cs); - BOOST_FOREACH(const Scalar& coinSerial, coinSerials) { - mempool.lelantusState.RemoveSpendFromMempool(coinSerial); - } -} - -void CLelantusState::AddMintsToMempool(const std::vector& pubCoins) { - LOCK(mempool.cs); - BOOST_FOREACH(const GroupElement& pubCoin, pubCoins) { - mempool.lelantusState.AddMintToMempool(pubCoin); - } -} - -void CLelantusState::RemoveMintFromMempool(const GroupElement& pubCoin) { - LOCK(mempool.cs); - mempool.lelantusState.RemoveMintFromMempool(pubCoin); -} - -uint256 CLelantusState::GetMempoolConflictingTxHash(const Scalar& coinSerial) { - LOCK(mempool.cs); - return mempool.lelantusState.GetMempoolConflictingTxHash(coinSerial); -} - -bool CLelantusState::CanAddSpendToMempool(const Scalar& coinSerial) { - LOCK(mempool.cs); - return !IsUsedCoinSerial(coinSerial) && !mempool.lelantusState.HasCoinSerial(coinSerial); -} - -bool CLelantusState::CanAddMintToMempool(const GroupElement& pubCoin){ - LOCK(mempool.cs); - return !HasCoin(pubCoin) && !mempool.lelantusState.HasMint(pubCoin); -} - -void CLelantusState::Reset() { - coinGroups.clear(); - latestCoinId = 0; - containers.Reset(); -} - -CLelantusState* CLelantusState::GetState() { - return &lelantusState; -} - -int CLelantusState::GetLatestCoinID() const { - return latestCoinId; -} - -bool CLelantusState::IsSurgeConditionDetected() const { - return surgeCondition; -} - -mint_info_container const & CLelantusState::GetMints() const { - return containers.GetMints(); -} - -std::unordered_map const & CLelantusState::GetSpends() const { - return containers.GetSpends(); -} - -std::unordered_map const & CLelantusState::GetCoinGroups() const { - return coinGroups; -} - -std::unordered_map const & CLelantusState::GetMempoolCoinSerials() const { - LOCK(mempool.cs); - return mempool.lelantusState.GetMempoolCoinSerials(); -} - -// private -size_t CLelantusState::CountLastNCoins(int groupId, size_t required, CBlockIndex* &first) { - first = nullptr; - size_t coins = 0; - - if (coinGroups.count(groupId)) { - auto &group = coinGroups[groupId]; - - for (auto block = group.lastBlock - ; coins < required && block - ; block = block->pprev) { - - size_t inBlock; - if (block->lelantusMintedPubCoins.count(groupId) - && (inBlock = block->lelantusMintedPubCoins[groupId].size())) { - - coins += inBlock; - first = block; - } - } - } - - return coins; -} - -// CLelantusMempoolState - -bool CLelantusMempoolState::HasCoinSerial(const Scalar& coinSerial) { - return mempoolCoinSerials.count(coinSerial) > 0; -} - -bool CLelantusMempoolState::HasMint(const GroupElement& pubCoin) { - return mempoolMints.count(pubCoin) > 0; -} - -bool CLelantusMempoolState::AddSpendToMempool(const Scalar &coinSerial, uint256 txHash) { - return mempoolCoinSerials.insert({coinSerial, txHash}).second; -} - -void CLelantusMempoolState::AddMintToMempool(const GroupElement& pubCoin) { - mempoolMints.insert(pubCoin); -} - -void CLelantusMempoolState::RemoveMintFromMempool(const GroupElement& pubCoin) { - mempoolMints.erase(pubCoin); -} - -uint256 CLelantusMempoolState::GetMempoolConflictingTxHash(const Scalar& coinSerial) { - if (mempoolCoinSerials.count(coinSerial) == 0) - return uint256(); - - return mempoolCoinSerials[coinSerial]; -} - -void CLelantusMempoolState::RemoveSpendFromMempool(const Scalar &coinSerial) { - mempoolCoinSerials.erase(coinSerial); -} - -void CLelantusMempoolState::Reset() { - mempoolCoinSerials.clear(); - mempoolMints.clear(); -} - - -} // end of namespace lelantus. diff --git a/src/lelantus.h b/src/lelantus.h deleted file mode 100644 index 58e79f97bf..0000000000 --- a/src/lelantus.h +++ /dev/null @@ -1,311 +0,0 @@ -#ifndef _MAIN_LELANTUS_H__ -#define _MAIN_LELANTUS_H__ - -#include "amount.h" -#include "chain.h" -#include "liblelantus/coin.h" -#include "liblelantus/joinsplit.h" -#include "consensus/validation.h" -#include -#include -#include "liblelantus/params.h" -#include -#include -#include -#include "coin_containers.h" - -namespace lelantus_mintspend { struct lelantus_mintspend_test; } - -namespace lelantus { - -// Lelantus transaction info, added to the CBlock to ensure zerocoin mint/spend transactions got their info stored into index -class CLelantusTxInfo { -public: - // all the zerocoin transactions encountered so far - std::set zcTransactions; - - // Vector of > for all the mints. - std::vector>> mints; - - std::unordered_map, lelantus::CPublicCoinHash> encryptedJmintValues; - - // serial for every spend (map from serial to coin group id) - std::unordered_map spentSerials; - - // information about transactions in the block is complete - bool fInfoIsComplete; - - CLelantusTxInfo(): fInfoIsComplete(false) {} - - // finalize everything - void Complete(); -}; - -bool IsLelantusAllowed(); -bool IsLelantusAllowed(int height); -bool IsLelantusGraceFulPeriod(); - -bool IsAvailableToMint(const CAmount& amount); - -void GenerateMintSchnorrProof(const lelantus::PrivateCoin& coin, CDataStream& serializedSchnorrProof); -bool VerifyMintSchnorrProof(const uint64_t& v, const secp_primitives::GroupElement& commit, const SchnorrProof& schnorrProof); -void ParseLelantusMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, SchnorrProof& schnorrProof, uint256& mintTag); -void ParseLelantusJMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, std::vector& encryptedValue); -void ParseLelantusJMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin, std::vector& encryptedValue, uint256& mintTag); -void ParseLelantusMintScript(const CScript& script, secp_primitives::GroupElement& pubcoin); -std::unique_ptr ParseLelantusJoinSplit(const CTransaction& tx); - -size_t GetSpendInputs(const CTransaction &tx, const CTxIn& in); -size_t GetSpendInputs(const CTransaction &tx); -CAmount GetSpendTransparentAmount(const CTransaction& tx); - -bool CheckLelantusBlock(CValidationState &state, const CBlock& block); - -bool CheckLelantusTransaction( - const CTransaction &tx, - CValidationState &state, - uint256 hashTx, - bool isVerifyDB, - int nHeight, - bool isCheckWallet, - bool fStatefulSigmaCheck, - CLelantusTxInfo* lelantusTxInfo); - -void DisconnectTipLelantus(CBlock &block, CBlockIndex *pindexDelete); - -bool ConnectBlockLelantus( - CValidationState& state, - const CChainParams& chainparams, - CBlockIndex* pindexNew, - const CBlock *pblock, - bool fJustCheck=false); - -uint256 GetTxHashFromPubcoin(const lelantus::PublicCoin& pubCoin); - -/* - * Get COutPoint(txHash, index) from the chain using pubcoin value alone. - */ -bool GetOutPointFromBlock(COutPoint& outPoint, const GroupElement &pubCoinValue, const CBlock &block); -bool GetOutPoint(COutPoint& outPoint, const lelantus::PublicCoin &pubCoin); -bool GetOutPoint(COutPoint& outPoint, const GroupElement &pubCoinValue); -// This one gets outpoint from hash of full Lelantus commitment -bool GetOutPoint(COutPoint& outPoint, const uint256 &pubCoinValueHash); -// This one gets outpoint from hash of reduced Lelantus commitment -bool GetOutPointFromMintTag(COutPoint& outPoint, const uint256 &pubCoinTag); - - -bool BuildLelantusStateFromIndex(CChain *chain); - -std::vector GetLelantusJoinSplitSerialNumbers(const CTransaction &tx, const CTxIn &txin); -std::vector GetLelantusJoinSplitIds(const CTransaction &tx, const CTxIn &txin); - -/* - * Util functions - */ -size_t CountCoinInBlock(CBlockIndex const *index, int id); - -class CLelantusMempoolState { -private: - // serials of spends currently in the mempool mapped to tx hashes - std::unordered_map mempoolCoinSerials; - // mints in the mempool - std::unordered_set mempoolMints; - -public: - // Check if there is a conflicting tx in the blockchain or mempool - bool HasCoinSerial(const Scalar& coinSerial); - - bool HasMint(const GroupElement& pubCoin); - - // Add spend into the mempool. - bool AddSpendToMempool(const Scalar &coinSerial, uint256 txHash); - - void AddMintToMempool(const GroupElement& pubCoins); - void RemoveMintFromMempool(const GroupElement& pubCoin); - - // Get conflicting tx hash by coin serial number - uint256 GetMempoolConflictingTxHash(const Scalar& coinSerial); - - // Remove spend from the mempool (usually as the result of adding tx to the block) - void RemoveSpendFromMempool(const Scalar& coinSerial); - - std::unordered_map const & GetMempoolCoinSerials() const { return mempoolCoinSerials; } - - void Reset(); -}; - -/* - * State of minted/spent coins as extracted from the index - */ -class CLelantusState { -friend bool BuildLelantusStateFromIndex(CChain *, std::set &); -public: - // First and last block where mint with given id was seen - struct LelantusCoinGroupInfo { - LelantusCoinGroupInfo() : firstBlock(nullptr), lastBlock(nullptr), nCoins(0) {} - - // first and last blocks having coins with given id minted - CBlockIndex *firstBlock; - CBlockIndex *lastBlock; - // total number of minted coins with such parameters - int nCoins; - }; - -public: - CLelantusState( - size_t maxCoinInGroup = ZC_LELANTUS_MAX_MINT_NUM, - size_t startGroupSize = ZC_LELANTUS_SET_START_SIZE); - - // Add mints in block, automatically assigning id to it - void AddMintsToStateAndBlockIndex(CBlockIndex *index, const CBlock* pblock); - - // Add serial to the list of used ones - void AddSpend(const Scalar &serial, int coinGroupId); - - // Add everything from the block to the state - void AddBlock(CBlockIndex *index); - - // Disconnect block from the chain rolling back mints and spends - void RemoveBlock(CBlockIndex *index); - - // Query coin group with given id - bool GetCoinGroupInfo(int group_id, LelantusCoinGroupInfo &result); - - // Query if the coin serial was previously used - bool IsUsedCoinSerial(const Scalar& coinSerial); - // Query if the hash of a coin serial was previously used. If so, store preimage in coinSerial param - bool IsUsedCoinSerialHash(Scalar &coinSerial, const uint256 &coinSerialHash); - - // Query if there is a coin with given pubCoin value - bool HasCoin(const lelantus::PublicCoin& pubCoin); - // Query if there is a coin with given hash of a pubCoin value. If so, store preimage in pubCoin param - bool HasCoinHash(GroupElement &pubCoinValue, const uint256 &pubCoinValueHash); - // Query if there is a coin with given tag - bool HasCoinTag(GroupElement &pubCoinValue, const uint256 &pubCoinTag); - - - // Given id returns latest anonymity set and corresponding block hash - // Do not take into account coins with height more than maxHeight - // Returns number of coins satisfying conditions - int GetCoinSetForSpend( - CChain *chain, - int maxHeight, - int id, - uint256& blockHash_out, - std::vector& coins_out, - std::vector& setHash_out, - std::string start_block_hash = ""); - - void GetAnonymitySet( - int coinGroupID, - bool fStartLelantusBlacklist, - std::vector& coins_out); - - void GetCoinsForRecovery( - CChain *chain, - int maxHeight, - int coinGroupID, - std::string start_block_hash, - uint256& blockHash_out, - std::vector>>& coins, - std::vector& setHash_out); - - // Return height of mint transaction and id of minted coin - std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); - - // Reset to initial values - void Reset(); - - // Check if there is a conflicting tx in the blockchain or mempool - bool CanAddSpendToMempool(const Scalar& coinSerial); - - bool CanAddMintToMempool(const GroupElement& pubCoin); - - // Add spend into the mempool. - // Check if there is a coin with such serial in either blockchain or mempool - bool AddSpendToMempool(const std::vector& coinSerials, uint256 txHash); - - void AddMintsToMempool(const std::vector& pubCoins); - void RemoveMintFromMempool(const GroupElement& pubCoin); - - // Get conflicting tx hash by coin serial number - uint256 GetMempoolConflictingTxHash(const Scalar& coinSerial); - - // Remove spend from the mempool (usually as the result of adding tx to the block) - void RemoveSpendFromMempool(const std::vector& coinSerials); - - static CLelantusState* GetState(); - - int GetLatestCoinID() const; - - mint_info_container const & GetMints() const; - std::unordered_map const & GetSpends() const; - std::unordered_map const & GetCoinGroups() const ; - std::unordered_map const & GetMempoolCoinSerials() const; - - std::size_t GetTotalCoins() const { return GetMints().size(); } - - bool IsSurgeConditionDetected() const; - -private: - size_t CountLastNCoins(int groupId, size_t required, CBlockIndex* &first); - -private: - // Group Limit - size_t maxCoinInGroup; - size_t startGroupSize; - - // Collection of coin groups. Map from id to LelantusCoinGroupInfo structure - std::unordered_map coinGroups; - - // Latest anonymity set id; - int latestCoinId; - - std::atomic surgeCondition; - - struct Containers { - Containers(std::atomic & surgeCondition); - - void AddMint(lelantus::PublicCoin const & pubCoin, CMintedCoinInfo const & coinInfo, const uint256& tag); - void RemoveMint(lelantus::PublicCoin const & pubCoin); - - void AddSpend(Scalar const & serial, int coinGroupId); - void RemoveSpend(Scalar const & serial); - - void AddExtendedMints(int group, size_t mints); - void RemoveExtendedMints(int group); - - void Reset(); - - mint_info_container const & GetMints() const; - std::unordered_map const & GetSpends() const; - std::unordered_map& GetTagToPublicCoin(); - bool IsSurgeCondition() const; - private: - // Set of all minted pubCoin values, keyed by the public coin. - // Used for checking if the given coin already exists. - mint_info_container mintedPubCoins; - // Set of all used coin serials. - std::unordered_map usedCoinSerials; - - //this map keeps hash(G^s*H0^r|seedId) to G^s*H0^r*H1^v - std::unordered_map tagToPublicCoin; - - std::atomic & surgeCondition; - - typedef std::map metainfo_container_t; - metainfo_container_t extendedMintMetaInfo, mintMetaInfo, spendMetaInfo; - - void CheckSurgeCondition(); - - friend struct lelantus_mintspend::lelantus_mintspend_test; - }; - - Containers containers; - - friend struct lelantus_mintspend::lelantus_mintspend_test; -}; - -} // end of namespace lelantus - -#endif // _MAIN_LELANTUS_H__ diff --git a/src/liblelantus/challenge_generator.h b/src/liblelantus/challenge_generator.h deleted file mode 100644 index d599d54525..0000000000 --- a/src/liblelantus/challenge_generator.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef FIRO_LELANTUS_CHALLENGE_GENERATOR_H -#define FIRO_LELANTUS_CHALLENGE_GENERATOR_H -#include -#include -namespace lelantus { -using namespace secp_primitives; - -class ChallengeGenerator { -public: - virtual ~ChallengeGenerator() {}; - virtual void add(const GroupElement& group_element) = 0; - virtual void add(const std::vector& group_elements) = 0; - virtual void add(const Scalar& scalar) = 0; - virtual void add(const std::vector& scalars) = 0; - virtual void add(const std::vector& data_) = 0; - virtual void get_challenge(Scalar& result_out) = 0; -}; - -}// namespace lelantus - -#endif //FIRO_LELANTUS_CHALLENGE_GENERATOR_H diff --git a/src/liblelantus/challenge_generator_impl.h b/src/liblelantus/challenge_generator_impl.h deleted file mode 100644 index c584a19a3c..0000000000 --- a/src/liblelantus/challenge_generator_impl.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef FIRO_LELANTUS_CHALLENGE_GENERATOR_IMPL_H -#define FIRO_LELANTUS_CHALLENGE_GENERATOR_IMPL_H - -#include -#include -#include "../../crypto/sha256.h" -#include "../hash.h" -#include "challenge_generator.h" -#include -namespace lelantus { - -using namespace secp_primitives; - -template -class ChallengeGeneratorImpl : public ChallengeGenerator { - -public: - ChallengeGeneratorImpl(int version_ = 0) { - version = version_; - data.resize(GroupElement::serialize_size); - scalar_data.resize(32); - } - - void add(const GroupElement& group_element) override { - group_element.serialize(data.data()); - hash.Write(data.data(), data.size()); - } - - void add(const std::vector& group_elements) override { - addSize(group_elements.size()); - for (size_t i = 0; i < group_elements.size(); ++i) { - add(group_elements[i]); - } - } - - void add(const Scalar& scalar) override { - scalar.serialize(scalar_data.data()); - hash.Write(scalar_data.data(), scalar_data.size()); - } - - void add(const std::vector& scalars) override { - addSize(scalars.size()); - for (size_t i = 0; i < scalars.size(); ++i) { - add(scalars[i]); - } - } - - void add(const std::vector& data_) override { - hash.Write(data_.data(), data_.size()); - } - - void get_challenge(Scalar& result_out) override { - unsigned char result_data[CSHA256::OUTPUT_SIZE]; - do { - Hasher temp_hash = hash; - hash.Finalize(result_data); - hash = temp_hash; - result_out = result_data; - add(result_out); - } while (result_out.isZero() || !result_out.isMember()); - } - -private: - void addSize(uint32_t size) { - if (version >= 1) { - Scalar s(size); - add(s); - } - } -private: - int version; - Hasher hash; - std::vector data; - std::vector scalar_data; -}; - -}// namespace lelantus - -#endif //FIRO_LELANTUS_CHALLENGE_GENERATOR_IMPL_H diff --git a/src/liblelantus/coin.cpp b/src/liblelantus/coin.cpp deleted file mode 100644 index 8448696158..0000000000 --- a/src/liblelantus/coin.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include "coin.h" -#include "primitives/mint_spend.h" -#include "lelantus_primitives.h" -#include -#include - -namespace lelantus { - -static std::string zpts("PUBLICKEY_TO_SERIALNUMBER"); - -//class PublicCoin -PublicCoin::PublicCoin() { -} - -PublicCoin::PublicCoin(const GroupElement& coin): - value(coin) { -} - -const GroupElement& PublicCoin::getValue() const { - return this->value; -} - -uint256 PublicCoin::getValueHash() const { - return primitives::GetPubCoinValueHash(value); -} - -bool PublicCoin::operator==(const PublicCoin& other) const { - return (*this).value == other.value; -} - -bool PublicCoin::operator!=(const PublicCoin& other) const { - return (*this).value != other.value; -} - -bool PublicCoin::validate() const { - return this->value.isMember() && !this->value.isInfinity(); -} - -size_t PublicCoin::GetSerializeSize() const { - return value.memoryRequired(); -} - -//class PrivateCoin -PrivateCoin::PrivateCoin(const Params* p, uint64_t v): - params(p) { - this->randomize(); - this->mintCoin(v); -} - -PrivateCoin::PrivateCoin( - const Params* p, - const Scalar& serial, - uint64_t v, - const Scalar& random, - const std::vector& seckey, - int version_) : - params(p), - serialNumber(serial), - randomness(random), - version(version_) { - this->setEcdsaSeckey(seckey); - this->mintCoin(v); -} - -const Params* PrivateCoin::getParams() const { - return this->params; -} - -const PublicCoin& PrivateCoin::getPublicCoin() const { - return this->publicCoin; -} - -const Scalar& PrivateCoin::getSerialNumber() const { - return this->serialNumber; -} - -const Scalar& PrivateCoin::getRandomness() const { - return this->randomness; -} - -uint64_t PrivateCoin::getV() const { - return this->value; -} - -Scalar PrivateCoin::getVScalar() const { - return Scalar(this->value); -} - -unsigned int PrivateCoin::getVersion() const { - return this->version; -} - -void PrivateCoin::setPublicCoin(const PublicCoin& p) { - publicCoin = p; -} - -void PrivateCoin::setRandomness(const Scalar& n) { - randomness = n; -} - -const unsigned char* PrivateCoin::getEcdsaSeckey() const { - return this->ecdsaSeckey; -} - -void PrivateCoin::setEcdsaSeckey(const std::vector &seckey) { - if (seckey.size() == sizeof(ecdsaSeckey)) - std::copy(seckey.cbegin(), seckey.cend(), &ecdsaSeckey[0]); - else - throw std::invalid_argument("EcdsaSeckey size does not match."); -} - -void PrivateCoin::setEcdsaSeckey(const uint256& seckey) { - if (seckey.size() == sizeof(ecdsaSeckey)) - std::copy(seckey.begin(), seckey.end(), &ecdsaSeckey[0]); - else - throw std::invalid_argument("EcdsaSeckey size does not match."); -} - -void PrivateCoin::setSerialNumber(const Scalar& n) { - serialNumber = n; -} - -void PrivateCoin::setV(uint64_t n) { - value = n; -} - -void PrivateCoin::setVersion(unsigned int nVersion) { - version = nVersion; -} - -void PrivateCoin::randomize() { - // Create a key pair - secp256k1_pubkey pubkey; - do { - if (RAND_bytes(this->ecdsaSeckey, sizeof(this->ecdsaSeckey)) != 1) { - throw std::invalid_argument("Unable to generate randomness"); - } - } while (!secp256k1_ec_pubkey_create( - OpenSSLContext::get_context(), &pubkey, this->ecdsaSeckey)); - - // Hash the public key in the group to obtain a serial number - serialNumber = serialNumberFromSerializedPublicKey( - OpenSSLContext::get_context(), &pubkey); - - randomness.randomize(); -} - -void PrivateCoin::mintCoin(uint64_t v) { - value = v; - GroupElement commit = LelantusPrimitives::double_commit( - params->get_g(), serialNumber, params->get_h1(), getVScalar(), params->get_h0(), randomness); - publicCoin = PublicCoin(commit); -} - -Scalar PrivateCoin::serialNumberFromSerializedPublicKey( - const secp256k1_context *context, - secp256k1_pubkey *pubkey) { - std::vector pubkey_hash(32, 0); - - static const unsigned char one[32] = { - 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, 0x01 - }; - - // We use secp256k1_ecdh instead of secp256k1_serialize_pubkey to avoid a timing channel. - if (1 != secp256k1_ecdh(context, pubkey_hash.data(), pubkey, &one[0])) { - throw std::invalid_argument("Unable to compute public key hash with secp256k1_ecdh."); - } - - std::vector pre(zpts.begin(), zpts.end()); - std::copy(pubkey_hash.begin(), pubkey_hash.end(), std::back_inserter(pre)); - - unsigned char hash[CSHA256::OUTPUT_SIZE]; - CSHA256().Write(pre.data(), pre.size()).Finalize(hash); - - // Use 32 bytes of hash as coin serial. - return Scalar(hash); -} - -} //namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/coin.h b/src/liblelantus/coin.h deleted file mode 100644 index f8723d9de6..0000000000 --- a/src/liblelantus/coin.h +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_COIN_H -#define FIRO_LIBLELANTUS_COIN_H - -#include "params.h" -#include "../firo_params.h" -#include "../uint256.h" -#include "openssl_context.h" -#include "crypto/sha256.h" - -// keep this just to not break old index -namespace sigma { -enum class CoinDenomination : std::uint8_t { - SIGMA_DENOM_0_05 = 5, - SIGMA_DENOM_0_1 = 0, - SIGMA_DENOM_0_5 = 1, - SIGMA_DENOM_1 = 2, - SIGMA_DENOM_10 = 3, - SIGMA_DENOM_25 = 6, - SIGMA_DENOM_100 = 4 -}; -// Serialization support for CoinDenomination - -template -void Serialize(Stream& os, CoinDenomination d) -{ - Serialize(os, static_cast(d)); -} - -template -void Unserialize(Stream& is, CoinDenomination& d) -{ - std::uint8_t v; - Unserialize(is, v); - d = static_cast(v); -} - -class PublicCoin { -public: - PublicCoin() {} - template - inline void Serialize(Stream& s) const { - constexpr int size = GroupElement::memoryRequired(); - unsigned char buffer[size + sizeof(int32_t)]; - value.serialize(buffer); - std::memcpy(buffer + size, &denomination, sizeof(denomination)); - char* b = (char*)buffer; - s.write(b, size + sizeof(int32_t)); - } - - template - inline void Unserialize(Stream& s) { - constexpr int size = GroupElement::memoryRequired(); - unsigned char buffer[size + sizeof(int32_t)]; - char* b = (char*)buffer; - s.read(b, size + sizeof(int32_t)); - value.deserialize(buffer); - std::memcpy(&denomination, buffer + size, sizeof(denomination)); - } - -private: - GroupElement value; - CoinDenomination denomination; -}; - -struct CSpendCoinInfo { - CoinDenomination denomination; - int coinGroupId; - - template - void Serialize(Stream& s) const { - int64_t tmp = uint8_t(denomination); - s << tmp; - tmp = coinGroupId; - s << tmp; - } - template - void Unserialize(Stream& s) { - int64_t tmp; - s >> tmp; denomination = CoinDenomination(tmp); - s >> tmp; coinGroupId = int(tmp); - } - -}; - -struct CScalarHash { - std::size_t operator ()(const Scalar& bn) const noexcept { - std::vector bnData(bn.memoryRequired()); - bn.serialize(&bnData[0]); - unsigned char hash[CSHA256::OUTPUT_SIZE]; - CSHA256().Write(&bnData[0], bnData.size()).Finalize(hash); - // take the first bytes of "hash". - std::size_t result; - std::memcpy(&result, hash, sizeof(std::size_t)); - return result; - } -}; - -using spend_info_container = std::unordered_map; - -} - -namespace lelantus { - -class PublicCoin { -public: - PublicCoin(); - - PublicCoin(const GroupElement& coin); - - const GroupElement& getValue() const; - uint256 getValueHash() const; - bool operator==(const PublicCoin& other) const; - bool operator!=(const PublicCoin& other) const; - bool validate() const; - size_t GetSerializeSize() const; - - template - inline void Serialize(Stream& s) const { - std::vector buffer(GetSerializeSize()); - value.serialize(buffer.data()); - s.write((const char *)buffer.data(), buffer.size()); - } - - template - inline void Unserialize(Stream& s) { - std::vector buffer(GetSerializeSize()); - s.read((char *)buffer.data(), buffer.size()); - value.deserialize(buffer.data()); - } - -private: - GroupElement value; -}; - -class PrivateCoin { -public: - - PrivateCoin(const Params* p, uint64_t v); - PrivateCoin(const Params* p, - const Scalar& serial, - uint64_t v, - const Scalar& random, - const std::vector& seckey, - int version_); - - const Params * getParams() const; - const PublicCoin& getPublicCoin() const; - const Scalar& getSerialNumber() const; - const Scalar& getRandomness() const; - uint64_t getV() const; - Scalar getVScalar() const; - unsigned int getVersion() const; - void setPublicCoin(const PublicCoin& p); - void setRandomness(const Scalar& n); - void setSerialNumber(const Scalar& n); - void setV(uint64_t n); - void setVersion(unsigned int nVersion); - const unsigned char* getEcdsaSeckey() const; - - void setEcdsaSeckey(const std::vector &seckey); - void setEcdsaSeckey(const uint256& seckey); - - static Scalar serialNumberFromSerializedPublicKey( - const secp256k1_context *context, - secp256k1_pubkey *pubkey); - -private: - const Params* params; - PublicCoin publicCoin; - Scalar serialNumber; - uint64_t value; - Scalar randomness; - unsigned int version = 0; - unsigned char ecdsaSeckey[32]; - -private: - void randomize(); - void mintCoin(uint64_t v); -}; - -}// namespace lelantus - -#endif //FIRO_LIBLELANTUS_COIN_H diff --git a/src/liblelantus/innerproduct_proof.h b/src/liblelantus/innerproduct_proof.h deleted file mode 100755 index 5f1c28a362..0000000000 --- a/src/liblelantus/innerproduct_proof.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_INNERPRODUCTPROOF_H -#define FIRO_LIBLELANTUS_INNERPRODUCTPROOF_H - -#include -#include "params.h" - -namespace lelantus { - -// Storage of the proof. -class InnerProductProof { -public: - - inline std::size_t memoryRequired(std::size_t size) const { - return a_.memoryRequired() * 3 + 34 * 2 * size; - } - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(a_); - READWRITE(b_); - READWRITE(c_); - READWRITE(L_); - READWRITE(R_); - } - - Scalar a_; - Scalar b_; - Scalar c_; - std::vector L_; - std::vector R_; -}; - -} // namespace lelantus - -#endif //FIRO_LIBLELANTUS_INNERPRODUCTPROOF_H diff --git a/src/liblelantus/innerproduct_proof_generator.cpp b/src/liblelantus/innerproduct_proof_generator.cpp deleted file mode 100755 index f113f1941e..0000000000 --- a/src/liblelantus/innerproduct_proof_generator.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "innerproduct_proof_generator.h" - -namespace lelantus { - -InnerProductProofGenerator::InnerProductProofGenerator( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - int version) - : g_(g) - , h_(h) - , u_(u) - , version_(version) -{ -} - -InnerProductProofGenerator::InnerProductProofGenerator( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - const GroupElement& P, - int version) - : g_(g) - , h_(h) - , u_(u) - , P_(P) - , version_(version) -{ -} - -void InnerProductProofGenerator::generate_proof( - const std::vector& a, - const std::vector& b, - const Scalar& x, - std::unique_ptr& challengeGenerator, - InnerProductProof& proof_out) { - const Scalar c = LelantusPrimitives::scalar_dot_product(a.begin(), a.end(), b.begin(), b.end()); - compute_P(a, b, P_initial); - u_ *= x; - proof_out.c_ = c; - if (version_ >=3) - challengeGenerator->add(c); - P_ = (P_initial + u_ * c); - generate_proof_util(a, b, challengeGenerator, proof_out); -} - -void InnerProductProofGenerator::generate_proof_util( - const std::vector& a, - const std::vector& b, - std::unique_ptr& challengeGenerator, - InnerProductProof& proof_out) { - - if(a.size() != b.size()) - throw std::runtime_error("Sizes of a and b are not equal."); - - if(a.size() == 1 && b.size() == 1) { //Protocol 2 line 15 - proof_out.a_ = a[0]; - proof_out.b_ = b[0]; - return; - } - - std::size_t n = a.size() / 2; - // Computes cL then L - Scalar cL = LelantusPrimitives::scalar_dot_product(a.begin() ,a.begin() + n, b.begin() + n, b.end()); - GroupElement L; - l(a.begin() ,a.begin() + n, b.begin() + n, b.end(), cL, L); - - //Computes cR then R - Scalar cR = LelantusPrimitives::scalar_dot_product(a.begin() + n, a.end(), b.begin(), b.begin() + n); - GroupElement R; - r(a.begin() + n, a.end(), b.begin(), b.begin() + n, cR, R); - - //Push L and R - proof_out.L_.emplace_back(L); - proof_out.R_.emplace_back(R); - - //Get challenge x - Scalar x; - std::vector group_elements = {L, R}; - - // if(version_ >= 2) we should be using CHash256, - // we want to link transcripts from previous iteration in each step, so we are not restarting in that case, - if (version_ < 2) { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(x); - - //Compute g prime and p prime - std::vector g_p; - LelantusPrimitives::g_prime(g_, x, g_p); - std::vector h_p; - LelantusPrimitives::h_prime(h_, x, h_p); - - //Compute a prime and b prime - std::vector a_p = a_prime(x, a); - std::vector b_p = b_prime(x, b); - - //Compute P prime - GroupElement p_p = LelantusPrimitives::p_prime(P_, L, R, x); - - // Recursive call of protocol 2 - InnerProductProofGenerator(g_p, h_p, u_, p_p, version_).generate_proof_util(a_p, b_p, challengeGenerator, proof_out); -} - -void InnerProductProofGenerator::compute_P( - const std::vector& a, - const std::vector& b, - GroupElement& result_out) { - - secp_primitives::MultiExponent g_mult(g_, a); - secp_primitives::MultiExponent h_mult(h_, b); - GroupElement g = g_mult.get_multiple(); - GroupElement h = h_mult.get_multiple(); - result_out = (g + h); -} - -void InnerProductProofGenerator::l( - typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end, - const Scalar& cL, - GroupElement& result_out) { - std::vector a, b; - std::vector gens_g, gens_h; - gens_g.reserve(g_.size() / 2 + 1); - gens_h.reserve(h_.size() / 2 + 1); - a.reserve(g_.size() / 2 + 1); - b.reserve(h_.size() / 2 + 1); - - gens_g.insert(gens_g.end(), g_.begin() + g_.size() / 2, g_.end()); - a.insert(a.end(), a_start, a_start + g_.size() / 2); - - gens_h.insert(gens_h.end(), h_.begin(), h_.begin() + h_.size() / 2); - b.insert(b.end(), b_start, b_start + h_.size() / 2); - - LelantusPrimitives::commit(u_, cL, gens_g, a, gens_h, b, result_out); -} - -void InnerProductProofGenerator::r( - typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end, - const Scalar& cR, - GroupElement& result_out) { - std::vector a, b; - std::vector gens_g, gens_h; - gens_g.reserve(g_.size() / 2 + 1); - gens_h.reserve(h_.size() / 2 + 1); - a.reserve(g_.size() / 2 + 1); - b.reserve(h_.size() / 2 + 1); - - gens_g.insert(gens_g.end(), g_.begin(), g_.begin() + g_.size() / 2); - a.insert(a.end(), a_start, a_start + g_.size() / 2); - - gens_h.insert(gens_h.end(), h_.begin() + h_.size() / 2, h_.end()); - b.insert(b.end(), b_start, b_start + h_.size() / 2); - - LelantusPrimitives::commit(u_, cR, gens_g, a, gens_h, b, result_out); -} - -std::vector InnerProductProofGenerator::a_prime( - const Scalar& x, - const std::vector& a){ - Scalar x_inverse = x.inverse(); - std::vector result; - result.reserve(a.size() / 2); - for(std::size_t i = 0; i < a.size() / 2; ++i) - { - result.emplace_back(a[i] * x + a[a.size() / 2 + i] * x_inverse); - } - return result; -} - -std::vector InnerProductProofGenerator::b_prime( - const Scalar& x, - const std::vector& b) { - Scalar x_inverse = x.inverse(); - std::vector result; - result.reserve(b.size() / 2); - for(std::size_t i = 0; i < b.size() / 2; ++i) - { - result.emplace_back(b[i] * x_inverse + b[b.size() / 2 + i] * x); - } - return result; -} - -const GroupElement& InnerProductProofGenerator::get_P() { - return P_initial; -} -} // namespace lelantus diff --git a/src/liblelantus/innerproduct_proof_generator.h b/src/liblelantus/innerproduct_proof_generator.h deleted file mode 100755 index 6dcb4119c9..0000000000 --- a/src/liblelantus/innerproduct_proof_generator.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_INNERP_RODUCT_PROOF_GENERATOR_H -#define FIRO_LIBLELANTUS_INNERP_RODUCT_PROOF_GENERATOR_H - -#include "lelantus_primitives.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -class InnerProductProofGenerator { - -public: - //g and h are being kept by reference, be sure it will not be modified from outside - InnerProductProofGenerator( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - int version); // if(version >= 2) we should pass CHash256 in generate_proof function - - void generate_proof( - const std::vector& a, - const std::vector& b, - const Scalar& x, - std::unique_ptr& challengeGenerator, - InnerProductProof& proof_out); - - const GroupElement& get_P(); - -private: - - InnerProductProofGenerator( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - const GroupElement& P, - int version); - - void generate_proof_util( - const std::vector& a, - const std::vector& b, - std::unique_ptr& challengeGenerator, - InnerProductProof& proof_out); - - void l(typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end, - const Scalar& cL, - GroupElement& result_out); - - void r(typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end, - const Scalar& cR, - GroupElement& result_out); - - std::vector a_prime(const Scalar& x, const std::vector& a); - - std::vector b_prime(const Scalar& x, const std::vector& b); - - void compute_P( - const std::vector& a, - const std::vector& b, - GroupElement& result_out); - -private: - const std::vector& g_; - const std::vector& h_; - GroupElement u_; - GroupElement P_; - GroupElement P_initial; - int version_; - -}; - -} // namespace lelantus - -#endif //FIRO_LIBLELANTUS_INNERP_RODUCT_PROOF_GENERATOR_H diff --git a/src/liblelantus/innerproduct_proof_verifier.cpp b/src/liblelantus/innerproduct_proof_verifier.cpp deleted file mode 100755 index 99a6fc08ba..0000000000 --- a/src/liblelantus/innerproduct_proof_verifier.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "innerproduct_proof_verifier.h" - -namespace lelantus { - -InnerProductProofVerifier::InnerProductProofVerifier( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - const GroupElement& P, - int version) - : g_(g) - , h_(h) - , u_(u) - , P_(P) - , version_(version) -{ -} - -bool InnerProductProofVerifier::verify( - const Scalar& x, - const InnerProductProof& proof, - std::unique_ptr& challengeGenerator) { - auto itr_l = proof.L_.begin(); - auto itr_r = proof.R_.begin(); - u_ *= x; - P_ += u_ * proof.c_; - if (version_ >= 3) - challengeGenerator->add(proof.c_); - return verify_util(proof, itr_l, itr_r, challengeGenerator); -} - -bool InnerProductProofVerifier::verify_util( - const InnerProductProof& proof, - typename std::vector::const_iterator itr_l, - typename std::vector::const_iterator itr_r, - std::unique_ptr& challengeGenerator) { - if(itr_l == proof.L_.end()){ - Scalar c = proof.a_ * proof.b_; - GroupElement uc = u_ * c; - GroupElement P = g_[0] * proof.a_ + h_[0] * proof.b_ + uc; - return P_ == P; - } - - //Get challenge x - Scalar x; - std::vector group_elements = {*itr_l, *itr_r}; - - // if(version >= 2) we should be using CHash256, - // we want to link transcripts from previous iteration in each step, so we are not restarting in that case, - if (version_ < 2) { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(x); - - //Compute g prime and p prime - std::vector g_p; - LelantusPrimitives::g_prime(g_, x, g_p); - std::vector h_p; - LelantusPrimitives::h_prime(h_, x, h_p); - - //Compute P prime - GroupElement p_p = LelantusPrimitives::p_prime(P_, *itr_l, *itr_r, x); - return InnerProductProofVerifier(g_p, h_p, u_, p_p, version_).verify_util(proof, itr_l + 1, itr_r + 1, challengeGenerator); -} - -bool InnerProductProofVerifier::verify_fast(std::size_t n, const Scalar& x, const InnerProductProof& proof, std::unique_ptr& challengeGenerator) { - u_ *= x; - P_ += u_ * proof.c_; - return verify_fast_util(n, proof, challengeGenerator); -} - -bool InnerProductProofVerifier::verify_fast_util( - std::size_t n, - const InnerProductProof& proof, - std::unique_ptr& challengeGenerator){ - std::size_t log_n = proof.L_.size(); - std::vector x_j; - x_j.resize(log_n); - for (std::size_t i = 0; i < log_n; ++i) - { - std::vector group_elements = {proof.L_[i], proof.R_[i]}; - - // if(version_ >= 2) we should be using CHash256, - // we want to link transcripts from previous iteration in each step, so we are not restarting in that case, - if (version_ < 2) { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(x_j[i]); - } - std::vector s, s_inv; - s.resize(n); - s_inv.resize(n); - for (std::size_t i = 0; i < n; ++i) - { - Scalar x_i(uint64_t(1)); - for (std::size_t j = 0; j < log_n; ++j) - { - if((i >> j) & 1) { - x_i *= x_j[log_n - j - 1]; - } else{ - x_i *= x_j[log_n - j - 1].inverse(); - } - - } - s[i] = x_i; - s_inv[i] = x_i.inverse(); - } - - secp_primitives::MultiExponent g_mult(g_, s); - secp_primitives::MultiExponent h_mult(h_, s_inv); - GroupElement g = g_mult.get_multiple(); - GroupElement h = h_mult.get_multiple(); - - GroupElement left; - left += g * proof.a_ + h * proof.b_ + u_ * (proof.a_ * proof.b_); - GroupElement right = P_; - GroupElement multi; - for (std::size_t j = 0; j < log_n; ++j) - multi += (proof.L_[j] * (x_j[j].square()) + proof.R_[j] * (x_j[j].square().inverse())); - right += multi; - if(left != right) - return false; - return true; -} - -}// namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/innerproduct_proof_verifier.h b/src/liblelantus/innerproduct_proof_verifier.h deleted file mode 100755 index 1e0880dae8..0000000000 --- a/src/liblelantus/innerproduct_proof_verifier.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_INNER_PRODUCT_PROOF_VERIFIER_H -#define FIRO_LIBLELANTUS_INNER_PRODUCT_PROOF_VERIFIER_H - -#include "lelantus_primitives.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -class InnerProductProofVerifier { - -public: - //g and h are being kept by reference, be sure it will not be modified from outside - InnerProductProofVerifier( - const std::vector& g, - const std::vector& h, - const GroupElement& u, - const GroupElement& P, - int version); // if(version >= 2) we should pass CHash256 in verify - - bool verify(const Scalar& x, const InnerProductProof& proof, std::unique_ptr& challengeGenerator); - bool verify_fast(std::size_t n, const Scalar& x, const InnerProductProof& proof, std::unique_ptr& challengeGenerator); - -private: - bool verify_util( - const InnerProductProof& proof, - typename std::vector::const_iterator ltr_l, - typename std::vector::const_iterator itr_r, - std::unique_ptr& challengeGenerator); - - bool verify_fast_util(std::size_t n, const InnerProductProof& proof, std::unique_ptr& challengeGenerator); - -private: - const std::vector& g_; - const std::vector& h_; - GroupElement u_; - GroupElement P_; - int version_; - -}; - -} // namespace lelantus - -#endif //FIRO_LIBLELANTUS_INNER_PRODUCT_PROOF_VERIFIER_H diff --git a/src/liblelantus/joinsplit.cpp b/src/liblelantus/joinsplit.cpp deleted file mode 100644 index 52d479792e..0000000000 --- a/src/liblelantus/joinsplit.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "joinsplit.h" -#include "lelantus_prover.h" -#include "lelantus_verifier.h" -#include "openssl_context.h" -#include "hash.h" -#include "util.h" - -namespace lelantus { - -JoinSplit::JoinSplit(const Params *p, - const std::vector>& Cin, - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const Scalar& Vout, - const std::vector& Cout, - uint64_t fee, - const std::map& groupBlockHashes, - const uint256& txHash, - unsigned int nVersion) - : - params (p), - version (nVersion), - fee (fee) { - - serialNumbers.reserve(Cin.size()); - for(size_t i = 0; i < Cin.size(); i++) { - serialNumbers.emplace_back(Cin[i].first.getSerialNumber()); - } - - if (!HasValidSerials()) { - throw std::invalid_argument("JoinSplit has invalid serial number"); - } - - std::vector indexes; - for(size_t i = 0; i < Cin.size(); i++) { - size_t index; - const auto& set = anonymity_sets.find(Cin[i].second); - if(set == anonymity_sets.end()) - throw std::invalid_argument("No such anonymity set"); - - if(!getIndex(Cin[i].first.getPublicCoin(), set->second, index)) - throw std::invalid_argument("No such coin in this anonymity set"); - - groupIds.push_back(Cin[i].second); - indexes.emplace_back(index); - } - - coinNum = Cin.size(); - - // generate public keys here, as we need it for challenge generation starting from LELANTUS_TX_VERSION_4_5 - generatePubKeys(Cin); - - LelantusProver prover(p, version); - prover.proof(anonymity_sets, anonymity_set_hashes, uint64_t(0), Cin, indexes, ecdsaPubkeys, Vout, Cout, fee, lelantusProof, qkSchnorrProof); - - if(groupBlockHashes.size() != anonymity_sets.size()) - throw std::invalid_argument("Mismatch blockHashes and anonymity sets sizes."); - - SpendMetaData m(groupBlockHashes, txHash); - - signMetaData(Cin, m, Cout.size()); - - coinGroupIdAndBlockHash = m.coinGroupIdAndBlockHash; -} - -void JoinSplit::generatePubKeys(const std::vector>& Cin) { - ecdsaPubkeys.resize(Cin.size()); - for(size_t i = 0; i < Cin.size(); i++) { - // Sign each spend under the public key associate with the serial number. - secp256k1_pubkey pubkey; - size_t pubkeyLen = 33; - - ecdsaPubkeys[i].resize(pubkeyLen); - // TODO timing channel, since secp256k1_ec_pubkey_serialize does not expect its output to be secret. - // See main_impl.h of ecdh module on secp256k1 - if (!secp256k1_ec_pubkey_create( - OpenSSLContext::get_context(), &pubkey, Cin[i].first.getEcdsaSeckey())) { - throw std::invalid_argument("Invalid secret key"); - } - if (1 != secp256k1_ec_pubkey_serialize( - OpenSSLContext::get_context(), - &this->ecdsaPubkeys[i][0], &pubkeyLen, &pubkey, SECP256K1_EC_COMPRESSED)) { - throw std::invalid_argument("Unable to serialize public key"); - } - } -} - -void JoinSplit::signMetaData(const std::vector>& Cin, const SpendMetaData& m, size_t coutSize) { - // Proves that the coin is correct w.r.t. serial number and hidden coin secret - // (This proof is bound to the coin 'metadata', i.e., transaction hash) - uint256 metahash = signatureHash(m, coutSize); - - - ecdsaSignatures.resize(Cin.size()); - for(size_t i = 0; i < Cin.size(); i++) { - // Sign each spend under the public key associate with the serial number. - secp256k1_ecdsa_signature sig; - ecdsaSignatures[i].resize(64); - if (1 != secp256k1_ecdsa_sign( - OpenSSLContext::get_context(), &sig, - metahash.begin(), Cin[i].first.getEcdsaSeckey(), NULL, NULL)) { - throw std::invalid_argument("Unable to sign with EcdsaSeckey."); - } - if (1 != secp256k1_ecdsa_signature_serialize_compact( - OpenSSLContext::get_context(), &this->ecdsaSignatures[i][0], &sig)) { - throw std::invalid_argument("Unable to serialize ecdsa_signature."); - } - } -} - -bool JoinSplit::Verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& Cout, - uint64_t Vout, - const uint256& txHash) const { - Scalar challenge; - bool fSkipVerification = false; - return Verify(anonymity_sets, anonymity_set_hashes, Cout, Vout, txHash, challenge, fSkipVerification); -} - -bool JoinSplit::Verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& Cout, - uint64_t Vout, - const uint256& txHash, - Scalar& challenge, - bool fSkipVerification ) const { - std::map groupBlockHashes; - - for(const auto& idAndHash : coinGroupIdAndBlockHash) { - groupBlockHashes[idAndHash.first] = idAndHash.second; - } - - - SpendMetaData m(groupBlockHashes, txHash); - - uint256 metahash = signatureHash(m, Cout.size()); - - if(serialNumbers.size() != ecdsaSignatures.size() || serialNumbers.size() != ecdsaPubkeys.size()) { - LogPrintf("Sigma spend failed due to serialNumbers and ecdsaSignatures/ecdsaPubkeys number mismatch."); - return false; - } - - for(size_t i = 0; i < serialNumbers.size(); i++) { - // Verify ecdsa_signature, to make sure someone did not change the output of transaction. - // Check sizes - if (this->ecdsaPubkeys[i].size() != 33 || this->ecdsaSignatures[i].size() != 64) { - LogPrintf("Lelantus joinsplit failed due to incorrect size of ecdsaSignature."); - return false; - } - - // Verify signature - secp256k1_pubkey pubkey; - secp256k1_ecdsa_signature signature; - - if (!secp256k1_ec_pubkey_parse(OpenSSLContext::get_context(), &pubkey, ecdsaPubkeys[i].data(), 33)) { - LogPrintf("Lelantus joinsplit failed due to unable to parse ecdsaPubkey."); - return false; - } - - // Recompute and compare hash of public key - Scalar coinSerialNumberExpected = PrivateCoin::serialNumberFromSerializedPublicKey(OpenSSLContext::get_context(), &pubkey); - if (serialNumbers[i] != coinSerialNumberExpected) { - LogPrintf("Lelantus joinsplit failed due to serial number does not match public key hash."); - return false; - } - - if (1 != secp256k1_ecdsa_signature_parse_compact(OpenSSLContext::get_context(), &signature, ecdsaSignatures[i].data()) ) { - LogPrintf("Lelantus joinsplit failed due to signature cannot be parsed."); - return false; - } - - if (!secp256k1_ecdsa_verify( - OpenSSLContext::get_context(), &signature, metahash.begin(), &pubkey)) { - LogPrintf("Lelantus joinsplit failed due to signature cannot be verified."); - return false; - } - } - - // Now verify lelantus proof - LelantusVerifier verifier(params, version); - return verifier.verify(anonymity_sets, anonymity_set_hashes, serialNumbers, ecdsaPubkeys, groupIds, uint64_t(0),Vout, fee, Cout, lelantusProof, qkSchnorrProof, challenge, fSkipVerification); -} - - -uint256 JoinSplit::signatureHash(const SpendMetaData& m, size_t coutSize) const { - CHashWriter h(0,0); - h << m << lelantusProof; - return h.GetHash(); -} - -const std::vector& JoinSplit::getCoinGroupIds() { - return this->groupIds; -} - -const std::vector>& JoinSplit::getIdAndBlockHashes() { - return this->coinGroupIdAndBlockHash; -} - -const std::vector& JoinSplit::getCoinSerialNumbers() { - return this->serialNumbers; -} - -const LelantusProof& JoinSplit::getLelantusProof() { - return this->lelantusProof; -} - -uint64_t JoinSplit::getFee() { - return this->fee; -} - -bool JoinSplit::getIndex(const PublicCoin& coin, const std::vector& anonymity_set, size_t& index) { - for (std::size_t j = 0; j < anonymity_set.size(); ++j) { - if(anonymity_set[j] == coin){ - index = j; - return true; - } - } - return false; -} - -bool JoinSplit::HasValidSerials() const { - for(size_t i = 0; i < serialNumbers.size(); i++) - if(!serialNumbers[i].isMember() || serialNumbers[i].isZero()) - return false; - return true; -} - -bool JoinSplit::isSigmaToLelantus() const { - return version == SIGMA_TO_LELANTUS_JOINSPLIT || version == SIGMA_TO_LELANTUS_JOINSPLIT_FIXED || version == SIGMA_TO_LELANTUS_TX_TPAYLOAD; -} - -} //namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/joinsplit.h b/src/liblelantus/joinsplit.h deleted file mode 100644 index f290b2a989..0000000000 --- a/src/liblelantus/joinsplit.h +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_JOINSPLIT_H -#define FIRO_LIBLELANTUS_JOINSPLIT_H - -#include "coin.h" -#include "lelantus_proof.h" -#include "spend_metadata.h" - -namespace lelantus { - -class JoinSplit { -public: - template - JoinSplit(const Params* p, Stream& strm): - params(p) { - strm >> *this; - } - - JoinSplit(const Params* p, - const std::vector>& Cin, - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const Scalar& Vout, - const std::vector& Cout, - uint64_t fee, - const std::map& groupBlockHashes, - const uint256& txHash, - unsigned int nVersion); - - bool Verify(const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& Cout, - uint64_t Vout, - const uint256& txHash) const; - - bool Verify(const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& Cout, - uint64_t Vout, - const uint256& txHash, - Scalar& challenge, - bool fSkipVerification = false) const; - - void generatePubKeys(const std::vector>& Cin); - - void signMetaData(const std::vector>& Cin, const SpendMetaData& m, size_t coutSize); - - uint256 signatureHash(const SpendMetaData& m, size_t coutSize) const; - - void setVersion(unsigned int nVersion) { - version = nVersion; - } - - const std::vector& getCoinSerialNumbers(); - - const LelantusProof& getLelantusProof(); - - uint64_t getFee(); - - const std::vector& getCoinGroupIds(); - - const std::vector>& getIdAndBlockHashes(); - - int getVersion() const { - return version; - } - - bool getIndex(const PublicCoin& coin, const std::vector& anonymity_set, size_t& index); - - bool HasValidSerials() const; - - std::vector> const & GetEcdsaPubkeys() const { - return ecdsaPubkeys; - } - - bool isSigmaToLelantus() const; - - ADD_SERIALIZE_METHODS; - template - void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(lelantusProof); - READWRITE(coinNum); - - if (ser_action.ForRead()) - { - groupIds.resize(coinNum); - ecdsaSignatures.resize(coinNum); - ecdsaPubkeys.resize(coinNum); - } - - for(uint8_t i = 0; i < coinNum; i++) - { - READWRITE(groupIds[i]); - size_t sigSize = 64; - size_t pubKeySize = 33; - if (ser_action.ForRead()) - { - ecdsaSignatures[i].resize(sigSize); - ecdsaPubkeys[i].resize(pubKeySize); - } - - for (size_t j = 0; j < sigSize; j++) - READWRITE(ecdsaSignatures[i][j]); - for (size_t j = 0; j < pubKeySize; j++) - READWRITE(ecdsaPubkeys[i][j]); - } - - READWRITE(coinGroupIdAndBlockHash); - READWRITE(fee); - READWRITE(version); - - if (version >= LELANTUS_TX_VERSION_4_5) - READWRITE(qkSchnorrProof); - - if (ser_action.ForRead()) { - serialNumbers.resize(coinNum); - for(size_t i = 0; i < coinNum; i++) { - secp256k1_pubkey pubkey; - if (!secp256k1_ec_pubkey_parse(OpenSSLContext::get_context(), &pubkey, ecdsaPubkeys[i].data(), 33)) { - throw std::invalid_argument("Lelantus joinsplit unserialize failed due to unable to parse ecdsaPubkey."); - } - - serialNumbers[i] = PrivateCoin::serialNumberFromSerializedPublicKey(OpenSSLContext::get_context(), &pubkey); - } - } - } - -private: - const Params* params; - unsigned int version = 0; - LelantusProof lelantusProof; - SchnorrProof qkSchnorrProof; - uint8_t coinNum; - std::vector serialNumbers; - std::vector groupIds; - std::vector> ecdsaSignatures; - std::vector> ecdsaPubkeys; - std::vector> coinGroupIdAndBlockHash; - uint64_t fee; - -}; - -} //namespace lelantus - -#endif //FIRO_LIBLELANTUS_JOINSPLIT_H diff --git a/src/liblelantus/lelantus_primitives.cpp b/src/liblelantus/lelantus_primitives.cpp deleted file mode 100644 index 77d31955dc..0000000000 --- a/src/liblelantus/lelantus_primitives.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "lelantus_primitives.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -static std::string lts("LELANTUS_SIGMA"); - -// Invert a vector of scalars -// -// This _requires_ that all scalars be nonzero! -std::vector LelantusPrimitives::invert(const std::vector& scalars) { - std::size_t n = scalars.size(); - - std::vector result; - result.reserve(n); - result.resize(n); - std::vector scratch; - scratch.reserve(n); - Scalar acc(uint64_t(1)); - Scalar temp; - - for (std::size_t i = 0; i < n; i++) { - if (scalars[i] == Scalar(uint64_t(0))) { - throw std::runtime_error("Cannot invert a zero scalar"); - } - scratch.emplace_back(acc); - acc *= scalars[i]; - } - acc = acc.inverse(); - for (std::size_t i = n; i > 0; i--) { - temp = acc * scalars[i - 1]; - result[i - 1] = acc * scratch[i - 1]; - acc = temp; - } - - return result; -} - -void LelantusPrimitives::generate_challenge( - const std::vector& group_elements, - const std::string& domain_separator, - Scalar& result_out) { - if (group_elements.empty()) - throw std::runtime_error("Group elements empty while generating a challenge."); - - std::unique_ptr challengeGenerator; - if (domain_separator != "") { - challengeGenerator = std::make_unique>(1); - std::vector pre(domain_separator.begin(), domain_separator.end()); - challengeGenerator->add(pre); - } else { - challengeGenerator = std::make_unique>(0); - } - - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(result_out); -} - -void LelantusPrimitives::commit(const GroupElement& g, - const std::vector& h, - const std::vector& exp, - const Scalar& r, - GroupElement& result_out) { - secp_primitives::MultiExponent mult(h, exp); - result_out = g * r + mult.get_multiple(); -} - -GroupElement LelantusPrimitives::commit( - const GroupElement& g, - const Scalar& m, - const GroupElement& h, - const Scalar& r) { - return g * m + h * r; -} - -GroupElement LelantusPrimitives::double_commit( - const GroupElement& g, - const Scalar& m, - const GroupElement& hV, - const Scalar& v, - const GroupElement& hR, - const Scalar& r) { - return g * m + hV * v + hR * r; -} - -void LelantusPrimitives::convert_to_sigma( - std::size_t num, - std::size_t n, - std::size_t m, - std::vector& out) { - out.reserve(n * m); - Scalar one(uint64_t(1)); - Scalar zero(uint64_t(0)); - - for (std::size_t j = 0; j < m; ++j) - { - for (std::size_t i = 0; i < n; ++i) - { - if(i == (num % n)) - out.emplace_back(one); - else - out.emplace_back(zero); - } - num /= n; - } -} - -std::vector LelantusPrimitives::convert_to_nal( - std::size_t num, - std::size_t n, - std::size_t m) { - std::vector result; - result.reserve(m); - while (num != 0) - { - result.emplace_back(num % n); - num /= n; - } - result.resize(m); - return result; -} - -void LelantusPrimitives::generate_Lelantus_challenge( - const std::vector& proofs, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& Cout, - unsigned int version, - std::unique_ptr& challengeGenerator, - Scalar& result_out) { - - result_out = uint64_t(1); - - // starting from LELANTUS_TX_VERSION_4_5 we are using CHash256, and adding domain separator, version, pubkeys and serials into it - if (version >= LELANTUS_TX_VERSION_4_5) { - challengeGenerator = std::make_unique>(1); - std::string domainSeparator = lts + std::to_string(version); - std::vector pre(domainSeparator.begin(), domainSeparator.end()); - challengeGenerator->add(pre); - for (const auto& hash : anonymity_set_hashes) - challengeGenerator->add(hash); - for (const auto& pubkey : ecdsaPubkeys) - challengeGenerator->add(pubkey); - challengeGenerator->add(serialNumbers); - } else { - challengeGenerator = std::make_unique>(0); - } - - if (Cout.size() > 0) { - for (auto coin : Cout) - challengeGenerator->add(coin); - } - - if (proofs.size() > 0) { - for (std::size_t i = 0; i < proofs.size(); ++i) { - challengeGenerator->add(proofs[i].A_); - challengeGenerator->add(proofs[i].B_); - challengeGenerator->add(proofs[i].C_); - challengeGenerator->add(proofs[i].D_); - challengeGenerator->add(proofs[i].Gk_); - challengeGenerator->add(proofs[i].Qk); - } - - challengeGenerator->get_challenge(result_out); - } -} - -void LelantusPrimitives::new_factor( - const Scalar& x, - const Scalar& a, - std::vector& coefficients) { - if(coefficients.empty()) - throw std::runtime_error("Empty coefficients array."); - - std::size_t degree = coefficients.size(); - coefficients.push_back(x * coefficients[degree-1]); - for (std::size_t d = degree-1; d >= 1; --d) - coefficients[d] = a * coefficients[d] + x * coefficients[d-1]; - coefficients[0] *= a; -} - -void LelantusPrimitives::commit( - const GroupElement& h, - const Scalar& h_exp, - const std::vector& g_, - const std::vector& L, - const std::vector& h_, - const std::vector& R, - GroupElement& result_out) { - secp_primitives::MultiExponent g_mult(g_, L); - secp_primitives::MultiExponent h_mult(h_, R); - result_out += h * h_exp + g_mult.get_multiple() + h_mult.get_multiple(); -} - -Scalar LelantusPrimitives::scalar_dot_product( - typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end) { - Scalar result(uint64_t(0)); - auto itr_a = a_start; - auto itr_b = b_start; - while (itr_a != a_end || itr_b != b_end) - { - result += ((*itr_a) * (*itr_b)); - ++itr_a; - ++itr_b; - } - return result; -} - -void LelantusPrimitives::g_prime( - const std::vector& g_, - const Scalar& x, - std::vector& result){ - Scalar x_inverse = x.inverse(); - result.reserve(g_.size() / 2); - for (std::size_t i = 0; i < g_.size() / 2; ++i) - { - result.push_back(((g_[i] * x_inverse) + (g_[g_.size() / 2 + i] * x))); - } -} - -void LelantusPrimitives::h_prime( - const std::vector& h_, - const Scalar& x, - std::vector& result) { - Scalar x_inverse = x.inverse(); - result.reserve(h_.size() / 2); - for (std::size_t i = 0; i < h_.size() / 2; ++i) - { - result.push_back(((h_[i] * x) + (h_[h_.size() / 2 + i] * x_inverse))); - } -} - -GroupElement LelantusPrimitives::p_prime( - const GroupElement& P_, - const GroupElement& L, - const GroupElement& R, - const Scalar& x){ - Scalar x_square = x.square(); - return L * x_square + P_ + R * (x_square.inverse()); -} - -Scalar LelantusPrimitives::delta(const Scalar& y, const Scalar& z, std::size_t n, std::size_t m){ - Scalar y_; - Scalar two_; - NthPower y_n(y); - NthPower two_n(uint64_t(2)); - NthPower z_j(z, z.exponent(uint64_t(3))); - Scalar z_sum(uint64_t(0)); - - for(std::size_t j = 0; j < m; ++j) - { - for(std::size_t i = 0; i < n; ++i) - { - y_ += y_n.pow; - y_n.go_next(); - } - z_sum += z_j.pow; - z_j.go_next(); - } - - for(std::size_t i = 0; i < n; ++i) - { - two_ += two_n.pow; - two_n.go_next(); - } - - return (z - z.square()) * y_ - z_sum * two_; -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/lelantus_primitives.h b/src/liblelantus/lelantus_primitives.h deleted file mode 100644 index 7f28c34d0d..0000000000 --- a/src/liblelantus/lelantus_primitives.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_LELANTUSPRIMITIVES_H -#define FIRO_LIBLELANTUS_LELANTUSPRIMITIVES_H - -#include -#include -#include -#include "sigmaextended_proof.h" -#include "lelantus_proof.h" -#include "schnorr_proof.h" -#include "innerproduct_proof.h" -#include "range_proof.h" -#include "challenge_generator.h" -#include "../firo_params.h" - -#include "serialize.h" - -#include -#include - -namespace lelantus { - -struct NthPower { - Scalar num; - Scalar pow; - - NthPower(const Scalar& num_) : num(num_), pow(uint64_t(1)) {} - NthPower(const Scalar& num_, const Scalar& pow_) : num(num_), pow(pow_) {} - - // be careful and verify that you have catch on upper level - void go_next() { - pow *= num; - if (pow == Scalar(uint64_t(1))) - throw std::invalid_argument("NthPower resulted 1"); - } -}; - -class LelantusPrimitives { - -public: -////common functions - static std::vector invert(const std::vector& scalars); - - static void generate_challenge( - const std::vector& group_elements, - const std::string& domain_separator, - Scalar& result_out); - - static GroupElement commit( - const GroupElement& g, - const Scalar& m, - const GroupElement& h, - const Scalar& r); - - static GroupElement double_commit( - const GroupElement& g, - const Scalar& m, - const GroupElement& hV, - const Scalar& v, - const GroupElement& hR, - const Scalar& r); -////functions for sigma - static void commit( - const GroupElement& g, - const std::vector& h, - const std::vector& exp, - const Scalar& r, - GroupElement& result_out); - - static void convert_to_sigma(std::size_t num, std::size_t n, std::size_t m, std::vector& out); - - static std::vector convert_to_nal(std::size_t num, std::size_t n, std::size_t m); - - static void generate_Lelantus_challenge( - const std::vector& proofs, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& Cout, - unsigned int version, - std::unique_ptr& challengeGenerator, - Scalar& result_out); - - static void new_factor(const Scalar& x, const Scalar& a, std::vector& coefficients); -//// functions for bulletproofs - static void commit( - const GroupElement& h, - const Scalar& h_exp, - const std::vector& g_, - const std::vector& L, - const std::vector& h_, - const std::vector& R, - GroupElement& result_out); - - // computes dot product of two Scalar vectors - static Scalar scalar_dot_product( - typename std::vector::const_iterator a_start, - typename std::vector::const_iterator a_end, - typename std::vector::const_iterator b_start, - typename std::vector::const_iterator b_end); - - static void g_prime( - const std::vector& g_, - const Scalar& x, - std::vector& result); - - static void h_prime( - const std::vector& h_, - const Scalar& x, - std::vector& result); - - static GroupElement p_prime( - const GroupElement& P_, - const GroupElement& L, - const GroupElement& R, - const Scalar& x); - - static Scalar delta(const Scalar& y, const Scalar& z, std::size_t n, std::size_t m); - -}; - -}// namespace lelantus - -#endif //FIRO_LIBLELANTUS_LELANTUSPRIMITIVES_H diff --git a/src/liblelantus/lelantus_proof.h b/src/liblelantus/lelantus_proof.h deleted file mode 100644 index 463c7ac276..0000000000 --- a/src/liblelantus/lelantus_proof.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_LELANTUSPROOF_H -#define FIRO_LIBLELANTUS_LELANTUSPROOF_H - -#include "sigmaextended_proof.h" -#include "schnorr_proof.h" -#include "range_proof.h" -#include "params.h" - -using namespace secp_primitives; - -namespace lelantus { - -class LelantusProof { -public: - //n is the number of input coins, bulletproof_n is number of output coins, - inline std::size_t memoryRequired(int n, int bulletproof_n, int bulletproof_m) const { - return sigma_proofs[0].memoryRequired() * n - + bulletproofs.memoryRequired(bulletproof_n, bulletproof_m) - + schnorrProof.memoryRequired(); - } - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(sigma_proofs); - READWRITE(bulletproofs); - READWRITE(schnorrProof); - } - -public: - std::vector sigma_proofs; - RangeProof bulletproofs; - SchnorrProof schnorrProof; -}; -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_LELANTUSPROOF_H diff --git a/src/liblelantus/lelantus_prover.cpp b/src/liblelantus/lelantus_prover.cpp deleted file mode 100644 index 4450d6fd30..0000000000 --- a/src/liblelantus/lelantus_prover.cpp +++ /dev/null @@ -1,282 +0,0 @@ -#include "lelantus_prover.h" -#include "threadpool.h" -#include "util.h" - -namespace lelantus { - -LelantusProver::LelantusProver(const Params* p, unsigned int v) : params(p), version(v) { -} - -void LelantusProver::proof( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const Scalar& Vin, - const std::vector>& Cin, - const std::vector& indexes, - const std::vector>& ecdsaPubkeys, - const Scalar& Vout, - const std::vector& Cout, - const Scalar& fee, - LelantusProof& proof_out, - SchnorrProof& qkSchnorrProof) { - Scalar input = Vin; - for (std::size_t i = 0; i < Cin.size(); ++i) - input += Cin[i].first.getV(); - - Scalar out = Vout; - for (std::size_t i = 0; i < Cout.size(); ++i) - out += Cout[i].getV(); - out += fee; - - if (input != out) - throw std::runtime_error("Input and output are not equal"); - - Scalar x; - std::vector Yk_sum; - Yk_sum.resize(Cin.size()); - // we are passing challengeGenerator ptr here, as after LELANTUS_TX_VERSION_4_5 we need it back, with filled data, to use in schnorr proof, - std::unique_ptr challengeGenerator; - generate_sigma_proofs(anonymity_sets, anonymity_set_hashes, Cin, Cout, indexes, ecdsaPubkeys, x, challengeGenerator, Yk_sum, proof_out.sigma_proofs, qkSchnorrProof); - - generate_bulletproofs(Cout, proof_out.bulletproofs); - - Scalar x_m = x.exponent(params->get_sigma_m()); - - Scalar X_; - Scalar So; - Scalar Ro; - GroupElement A; - for (std::size_t i = 0; i < Cout.size(); ++i) - { - So += Cout[i].getSerialNumber(); - Ro += Cout[i].getRandomness(); - A += Cout[i].getPublicCoin().getValue(); - } - X_ = So * x_m; - A *= x_m; - A += params->get_h1() * ((Vout + fee) * x_m); - Scalar Y_; - Scalar Ri; - Scalar Vi = Vin; - for (std::size_t i = 0; i < Cin.size(); ++i) - { - Ri += Cin[i].first.getRandomness() * x_m + Yk_sum[i]; - Vi += Cin[i].first.getVScalar(); - } - Y_ = Ro * x_m - Ri; - Vi *= x_m; - // we are calculating A, B amd Y here as after LELANTUS_TX_VERSION_4_5 we need them for challenge generation in schnorr proof - // also we are getting challengeGenerator with filled data from sigma, - GroupElement B = params->get_h1() * Vi + params->get_h0() * Ri; - GroupElement Y = A + B.inverse(); - SchnorrProver schnorrProver(params->get_g(), params->get_h0(), version >= LELANTUS_TX_VERSION_4_5); - schnorrProver.proof(X_, Y_, Y, A, B, challengeGenerator, proof_out.schnorrProof); -} - -void LelantusProver::generate_sigma_proofs( - const std::map>& c, - const std::vector>& anonymity_set_hashes, - const std::vector>& Cin, - const std::vector& Cout, - const std::vector& indexes, - const std::vector>& ecdsaPubkeys, - Scalar& x, - std::unique_ptr& challengeGenerator, - std::vector& Yk_sum, - std::vector& sigma_proofs, - SchnorrProof& qkSchnorrProof) { - SigmaExtendedProver sigmaProver(params->get_g(), params->get_sigma_h(), params->get_sigma_n(), params->get_sigma_m()); - sigma_proofs.resize(Cin.size()); - std::size_t N = Cin.size(); - std::vector rA, rB, rC, rD; - rA.resize(N); - rB.resize(N); - rC.resize(N); - rD.resize(N); - std::vector> sigma; - sigma.resize(N); - std::vector> Tk, Pk, Yk; - Tk.resize(N); - Pk.resize(N); - Yk.resize(N); - std::vector> a; - a.resize(N); - std::vector serialNumbers; - serialNumbers.reserve(N); - - std::size_t threadsMaxCount = std::min((unsigned int)N, boost::thread::hardware_concurrency()); - std::vector> parallelTasks; - parallelTasks.reserve(threadsMaxCount); - ParallelOpThreadPool threadPool(threadsMaxCount); - - std::vector> C_; - C_.resize(N); - DoNotDisturb dnd; - for (std::size_t j = 0; j < N; j += threadsMaxCount) { - for (std::size_t i = j; i < j + threadsMaxCount; ++i) { - if (i < N) { - if (!c.count(Cin[i].second)) - throw std::invalid_argument("No such anonymity set or id is not correct"); - - GroupElement gs = (params->get_g() * Cin[i].first.getSerialNumber().negate()); - serialNumbers.emplace_back(Cin[i].first.getSerialNumber()); - - C_[i].reserve(c.size()); - - const auto& set = c.find(Cin[i].second); - if (set == c.end()) - throw std::invalid_argument("No such anonymity set"); - - for (auto const &coin : set->second) - C_[i].emplace_back(coin.getValue() + gs); - - rA[i].randomize(); - rB[i].randomize(); - rC[i].randomize(); - rD[i].randomize(); - Tk[i].resize(params->get_sigma_m()); - Pk[i].resize(params->get_sigma_m()); - Yk[i].resize(params->get_sigma_m()); - a[i].resize(params->get_sigma_n() * params->get_sigma_m()); - - auto& sigma_i = sigma[i]; - auto& rA_i = rA[i]; - auto& rB_i = rB[i]; - auto& rC_i = rC[i]; - auto& rD_i = rD[i]; - auto& a_i = a[i]; - auto& Tk_i = Tk[i]; - auto& Pk_i = Pk[i]; - auto& Yk_i = Yk[i]; - auto& prover = sigmaProver; - auto& commits = C_[i]; - auto& index = indexes[i]; - auto& proof = sigma_proofs[i]; - parallelTasks.emplace_back(threadPool.PostTask([&]() { - try { - prover.sigma_commit(commits, index, rA_i, rB_i, rC_i, rD_i, a_i, Tk_i, Pk_i, Yk_i, sigma_i, proof); - } catch (const std::exception &) { - return false; - } - return true; - })); - } else - break; - } - - bool isFail = false; - for (auto& th : parallelTasks) { - if (!th.get()) - isFail = true; - } - - if (isFail) - throw std::runtime_error("Lelantus proof creation failed."); - - parallelTasks.clear(); - } - - std::vector PubcoinsOut; - PubcoinsOut.reserve(Cout.size()); - for(auto coin : Cout) - PubcoinsOut.emplace_back(coin.getPublicCoin().getValue()); - LelantusPrimitives::generate_Lelantus_challenge( - sigma_proofs, - anonymity_set_hashes, - serialNumbers, - ecdsaPubkeys, - PubcoinsOut, - version, - challengeGenerator, - x); - - std::vector x_ks; - x_ks.reserve(params->get_sigma_m()); - NthPower x_k(x); - for (int k = 0; k < params->get_sigma_m(); ++k) { - x_ks.emplace_back(x_k.pow); - x_k.go_next(); - } - - for (std::size_t i = 0; i < N; ++i) { - for (int k = 0; k < params->get_sigma_m(); ++k) { - Yk_sum[i] += Yk[i][k] * x_ks[k]; - } - } - - for (std::size_t i = 0; i < N; ++i){ - const Scalar& v = Cin[i].first.getV(); - const Scalar& r = Cin[i].first.getRandomness(); - sigmaProver.sigma_response(sigma[i], a[i], rA[i], rB[i], rC[i], rD[i], v, r, Tk[i], Pk[i], x, sigma_proofs[i]); - } - - // generate schnorr proof to prove that Q_k is generated honestly; - if (version >= LELANTUS_TX_VERSION_4_5) { - Scalar q_k_x; - challengeGenerator->get_challenge(q_k_x); - NthPower qk_x_n(q_k_x); - - Scalar Pk_sum(uint64_t(0)); - Scalar Tk_Yk_sum(uint64_t(0)); - std::vector Qk; - Qk.reserve(N * params->get_sigma_m()); - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = 0; cmp::less(j, params->get_sigma_m()); ++j) { - Pk_sum += (Pk[i][j] * qk_x_n.pow); - Tk_Yk_sum += ((Tk[i][j] + Yk[i][j]) * qk_x_n.pow); - qk_x_n.go_next(); - Qk.emplace_back(sigma_proofs[i].Qk[j]); - } - } - - SchnorrProver schnorrProver(params->get_h1(), params->get_h0(), true); - schnorrProver.proof(Pk_sum, Tk_Yk_sum, Qk, qkSchnorrProof); - } -} - -void LelantusProver::generate_bulletproofs( - const std::vector& Cout, - RangeProof& bulletproofs) { - if (Cout.empty()) - return; - - std::vector v_s, serials, randoms; - std::size_t n = params->get_bulletproofs_n(); - std::size_t m = Cout.size() * 2; - - while (m & (m - 1)) - m++; - - v_s.reserve(m); - serials.reserve(m); - randoms.reserve(m); - // NOTE: this prepends zero-value group elements, apparently as an earlier coding error - // This doesn't hurt anything, and so is retained here for compatibility reasons - std::vector commitments(Cout.size()); - for (std::size_t i = 0; i < Cout.size(); ++i) - { - v_s.push_back(Cout[i].getV()); // Ensure that v >= 0 - v_s.push_back(Cout[i].getVScalar() + params->get_limit_range()); // Ensure that v <= ZC_LELANTUS_MAX_MINT - serials.insert(serials.end(), 2, Cout[i].getSerialNumber()); - randoms.insert(randoms.end(), 2, Cout[i].getRandomness()); - commitments.emplace_back(Cout[i].getPublicCoin().getValue()); - } - - v_s.resize(m); - serials.resize(m); - randoms.resize(m); - - std::vector g_, h_; - g_.reserve(n * m); - h_.reserve(n * m); - - - g_.insert(g_.end(), params->get_bulletproofs_g().begin(), params->get_bulletproofs_g().begin() + (n * m)); - h_.insert(h_.end(), params->get_bulletproofs_h().begin(), params->get_bulletproofs_h().begin() + (n * m)); - - RangeProver rangeProver(params->get_h1(), params->get_h0(), params->get_g(), g_, h_, n, version); - rangeProver.proof(v_s, serials, randoms, commitments, bulletproofs); - -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/lelantus_prover.h b/src/liblelantus/lelantus_prover.h deleted file mode 100644 index c271873b77..0000000000 --- a/src/liblelantus/lelantus_prover.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_LELANTUSPROVER_H -#define FIRO_LIBLELANTUS_LELANTUSPROVER_H - -#include "schnorr_prover.h" -#include "sigmaextended_prover.h" -#include "range_prover.h" -#include "coin.h" - -namespace lelantus { - -class LelantusProver { -public: - LelantusProver(const Params* p, unsigned int v); - void proof( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const Scalar& Vin, - const std::vector>& Cin, - const std::vector & indexes, - const std::vector>& ecdsaPubkeys, - const Scalar& Vout, - const std::vector & Cout, - const Scalar& fee, - LelantusProof& proof_out, - SchnorrProof& qkSchnorrProof); - -private: - void generate_sigma_proofs( - const std::map>& c, - const std::vector>& anonymity_set_hashes, - const std::vector>& Cin, - const std::vector& Cout, - const std::vector& indexes, - const std::vector>& ecdsaPubkeys, - Scalar& x, - std::unique_ptr& challengeGenerator, - std::vector& Yk_sum, - std::vector& sigma_proofs, - SchnorrProof& qkSchnorrProof); - - void generate_bulletproofs( - const std::vector & Cout, - RangeProof& bulletproofs); - -private: - const Params* params; - unsigned int version; -}; -}// namespace lelantus - -#endif //FIRO_LIBLELANTUS_LELANTUSPROVER_H diff --git a/src/liblelantus/lelantus_verifier.cpp b/src/liblelantus/lelantus_verifier.cpp deleted file mode 100644 index 5eebc57d7a..0000000000 --- a/src/liblelantus/lelantus_verifier.cpp +++ /dev/null @@ -1,294 +0,0 @@ -#include "lelantus_verifier.h" -#include "../amount.h" -#include "chainparams.h" -#include "util.h" - -namespace lelantus { - -LelantusVerifier::LelantusVerifier(const Params* p, unsigned int v) : params(p), version(v) { -} - -bool LelantusVerifier::verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& groupIds, - const Scalar& Vin, - uint64_t Vout, - uint64_t fee, - const std::vector& Cout, - const LelantusProof& proof, - const SchnorrProof& qkSchnorrProof) { - Scalar x; - bool fSkipVerification = 0; - return verify(anonymity_sets, anonymity_set_hashes, serialNumbers, ecdsaPubkeys, groupIds, Vin, Vout, fee, Cout, proof, qkSchnorrProof, x, fSkipVerification); -} - -bool LelantusVerifier::verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& groupIds, - const Scalar& Vin, - uint64_t Vout, - uint64_t fee, - const std::vector& Cout, - const LelantusProof& proof, - const SchnorrProof& qkSchnorrProof, - Scalar& x, - bool fSkipVerification) { - //check the overflow of Vout and fee - if (!(Vout <= uint64_t(::Params().GetConsensus().nMaxValueLelantusSpendPerTransaction) && fee < (1000 * CENT))) { // 1000 * CENT is the value of max fee defined at validation.h - LogPrintf("Lelantus verification failed due to transparent values check failed."); - return false; - } - - // number of serials should be equal to number of sigma proofs, we need one proof for each serial - if (serialNumbers.size() != proof.sigma_proofs.size()) { - LogPrintf("Lelantus verification failed due to sizes of serials and sigma proofs are not equal."); - return false; - } - - // max possible number of output coins is 8, - if (cmp::greater(Cout.size(), (params->get_bulletproofs_max_m() / 2))) { - LogPrintf("Number of output coins are more than allowed."); - return false; - } - - std::vector> vAnonymity_sets; - std::vector> vSin; - vAnonymity_sets.reserve(anonymity_sets.size()); - vSin.resize(anonymity_sets.size()); - - size_t i = 0; - auto itr = vSin.begin(); - for (const auto& set : anonymity_sets) { - vAnonymity_sets.emplace_back(set.second); - - while (i < groupIds.size() && groupIds[i] == set.first) { - itr->push_back(serialNumbers[i++]); - } - itr++; - } - - Scalar zV, zR; - std::unique_ptr challengeGenerator; - try { - // we are passing challengeGenerator ptr here, as after LELANTUS_TX_VERSION_4_5 we need it back, with filled data, to use in schnorr proof, - if (!(verify_sigma(vAnonymity_sets, anonymity_set_hashes, vSin, serialNumbers, ecdsaPubkeys, Cout, proof.sigma_proofs, qkSchnorrProof, x, challengeGenerator, zV, zR, fSkipVerification) && - verify_rangeproof(Cout, proof.bulletproofs, fSkipVerification) && - verify_schnorrproof(x, zV, zR, Vin, Vout, fee, Cout, proof, challengeGenerator))) - return false; - } catch (std::invalid_argument&) { - return false; - } - - return true; -} - -bool LelantusVerifier::verify_sigma( - const std::vector>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector>& Sin, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& Cout, - const std::vector &sigma_proofs, - const SchnorrProof& qkSchnorrProof, - Scalar& x, - std::unique_ptr& challengeGenerator, - Scalar& zV, - Scalar& zR, - bool fSkipVerification) { - std::vector PubcoinsOut; - PubcoinsOut.reserve(Cout.size()); - for (auto coin : Cout) - PubcoinsOut.emplace_back(coin.getValue()); - - LelantusPrimitives::generate_Lelantus_challenge( - sigma_proofs, - anonymity_set_hashes, - serialNumbers, - ecdsaPubkeys, - PubcoinsOut, - version, - challengeGenerator, - x); - - SigmaExtendedVerifier sigmaVerifier(params->get_g(), params->get_sigma_h(), params->get_sigma_n(), - params->get_sigma_m()); - - if (Sin.size() != anonymity_sets.size()) - throw std::invalid_argument("Number of anonymity sets and number of vectors containing serial numbers must be equal"); - - int t = 0; - for (std::size_t k = 0; k < Sin.size(); k++) { - - std::vector sigma_proofs_k; - for (std::size_t i = 0; i < Sin[k].size(); ++i, ++t) { - zV += sigma_proofs[t].zV_; - zR += sigma_proofs[t].zR_; - sigma_proofs_k.emplace_back(sigma_proofs[t]); - } - - //skip verification if we are collecting proofs for later batch verification - if (fSkipVerification) - continue; - - std::vector C_; - C_.reserve(anonymity_sets[k].size()); - for (std::size_t j = 0; j < anonymity_sets[k].size(); ++j) - C_.emplace_back(anonymity_sets[k][j].getValue()); - - if (!sigmaVerifier.batchverify(C_, x, Sin[k], sigma_proofs_k)) { - LogPrintf("Lelantus verification failed due sigma verification failed."); - return false; - } - } - - // verify schnorr proof to verify that Q_k is generated honestly; - if (version >= LELANTUS_TX_VERSION_4_5) { - Scalar q_k_x; - challengeGenerator->get_challenge(q_k_x); - - NthPower qK_x_n(q_k_x); - GroupElement Gk_sum; - std::vector Qks; - Qks.reserve(sigma_proofs.size() * params->get_sigma_m()); - for (std::size_t t = 0; t < sigma_proofs.size(); ++t) - { - const std::vector& Qk = sigma_proofs[t].Qk; - for (std::size_t k = 0; k < Qk.size(); ++k) - { - Gk_sum += (Qk[k]) * qK_x_n.pow; - qK_x_n.go_next(); - - Qks.emplace_back(Qk[k]); - } - } - - SchnorrVerifier schnorrVerifier(params->get_h1(), params->get_h0(), version >= LELANTUS_TX_VERSION_4_5); - if (!schnorrVerifier.verify(Gk_sum, Qks, qkSchnorrProof)) { - LogPrintf("Lelantus verification failed due to Qk schnorr proof verification failed."); - return false; - } - } - - return true; -} - -bool LelantusVerifier::verify_rangeproof( - const std::vector& Cout, - const RangeProof& bulletproof, - bool fSkipVerification) { - if (Cout.empty() || fSkipVerification) - return true; - - std::size_t n = params->get_bulletproofs_n(); - std::size_t m = Cout.size() * 2; - - while (m & (m - 1)) - m++; - - // NOTE: for actual deployment, need to ensure this vector is constructed to accommodate the largest proof - std::vector g_, h_; - g_.reserve(n * m); - h_.reserve(n * m); - g_.insert(g_.end(), params->get_bulletproofs_g().begin(), params->get_bulletproofs_g().begin() + (n * m)); - h_.insert(h_.end(), params->get_bulletproofs_h().begin(), params->get_bulletproofs_h().begin() + (n * m)); - - std::vector > V; - V.reserve(1); // size of batch - V.resize(1); - V[0].reserve(m); // aggregation size - std::vector > commitments; - commitments.reserve(1); // size of batch - commitments.resize(1); - commitments[0].reserve(2 * Cout.size()); - commitments[0].resize(Cout.size()); // prepend zero elements, to match the prover's behavior - for (std::size_t i = 0; i < Cout.size(); ++i) { - V[0].push_back(Cout[i].getValue()); - V[0].push_back(Cout[i].getValue() + params->get_h1_limit_range()); - commitments[0].emplace_back(Cout[i].getValue()); - } - - std::vector proofs; - proofs.reserve(1); // size of batch - proofs.emplace_back(bulletproof); - - // Pad with zero elements - for (std::size_t i = Cout.size() * 2; i < m; ++i) - V[0].push_back(GroupElement()); - - RangeVerifier rangeVerifier(params->get_h1(), params->get_h0(), params->get_g(), g_, h_, n, version); - if (!rangeVerifier.verify(V, commitments, proofs)) { - LogPrintf("Lelantus verification failed due range proof verification failed."); - return false; - } - return true; -} - -bool LelantusVerifier::verify_schnorrproof( - const Scalar& x, - const Scalar& zV, - const Scalar& zR, - const Scalar& Vin, - const Scalar& Vout, - const Scalar fee, - const std::vector& Cout, - const LelantusProof& proof, - std::unique_ptr& challengeGenerator) { - // x^m - Scalar x_m = x.exponent(params->get_sigma_m()); - - // A is computed directly, to take advantage of point addition - GroupElement A; - for (std::size_t j = 0; j < Cout.size(); ++j) { - A += Cout[j].getValue(); - } - A += params->get_h1() * (Vout + fee); - A *= x_m; - - // B is computed via multiscalar multiplication - std::vector B_points; - std::vector B_scalars; - B_points.emplace_back(params->get_h1()); - B_scalars.emplace_back(Vin*x_m + zV); - B_points.emplace_back(params->get_h0()); - B_scalars.emplace_back(zR); - - NthPower x_k(x); - std::vector x_ks; - x_ks.reserve(params->get_sigma_m()); - for (int k = 0; k < params->get_sigma_m(); ++k) - { - x_ks.emplace_back(x_k.pow); - x_k.go_next(); - } - - for (std::size_t t = 0; t < proof.sigma_proofs.size(); ++t) - { - const std::vector& Qk = proof.sigma_proofs[t].Qk; - for (std::size_t k = 0; k < Qk.size(); ++k) - { - B_points.emplace_back(Qk[k]); - B_scalars.emplace_back(x_ks[k]); - } - } - - GroupElement B = secp_primitives::MultiExponent(B_points, B_scalars).get_multiple(); - - SchnorrVerifier schnorrVerifier(params->get_g(), params->get_h0(), version >= LELANTUS_TX_VERSION_4_5); - const SchnorrProof& schnorrProof = proof.schnorrProof; - GroupElement Y = A + B * (Scalar(uint64_t(1)).negate()); - // after LELANTUS_TX_VERSION_4_5 we are getting challengeGenerator with filled data from sigma, - if (!schnorrVerifier.verify(Y, A, B, schnorrProof, challengeGenerator)) { - LogPrintf("Lelantus verification failed due schnorr proof verification failed."); - return false; - } - return true; -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/lelantus_verifier.h b/src/liblelantus/lelantus_verifier.h deleted file mode 100644 index 26bd54673b..0000000000 --- a/src/liblelantus/lelantus_verifier.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_LELANTUSVERIFIER_H -#define FIRO_LIBLELANTUS_LELANTUSVERIFIER_H - -#include "schnorr_verifier.h" -#include "sigmaextended_verifier.h" -#include "range_verifier.h" -#include "lelantus_primitives.h" -#include "coin.h" - -namespace lelantus { -class LelantusVerifier { -public: - LelantusVerifier(const Params* p, unsigned int v); - - bool verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& groupIds, - const Scalar& Vin, - uint64_t Vout, - uint64_t fee, - const std::vector& Cout, - const LelantusProof& proof, - const SchnorrProof& qkSchnorrProof); - - bool verify( - const std::map>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& groupIds, - const Scalar& Vin, - uint64_t Vout, - uint64_t fee, - const std::vector& Cout, - const LelantusProof& proof, - const SchnorrProof& qkSchnorrProof, - Scalar& x, - bool fSkipVerification = false); - -private: - bool verify_sigma( - const std::vector>& anonymity_sets, - const std::vector>& anonymity_set_hashes, - const std::vector>& Sin, - const std::vector& serialNumbers, - const std::vector>& ecdsaPubkeys, - const std::vector& Cout, - const std::vector &sigma_proofs, - const SchnorrProof& qkSchnorrProof, - Scalar& x, - std::unique_ptr& challengeGenerator, - Scalar& zV, - Scalar& zR, - bool fSkipVerification = false); - bool verify_rangeproof( - const std::vector& Cout, - const RangeProof& bulletproofs, - bool fSkipVerification); - bool verify_schnorrproof( - const Scalar& x, - const Scalar& zV, - const Scalar& zR, - const Scalar& Vin, - const Scalar& Vout, - const Scalar fee, - const std::vector& Cout, - const LelantusProof& proof, - std::unique_ptr& challengeGenerator); - -private: - const Params* params; - unsigned int version; - -}; -}// namespace lelantus - -#endif //FIRO_LIBLELANTUS_LELANTUSVERIFIER_H diff --git a/src/liblelantus/openssl_context.h b/src/liblelantus/openssl_context.h deleted file mode 100644 index 430cb7c9d0..0000000000 --- a/src/liblelantus/openssl_context.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef _OPENSSL_CONTEXT_H__ -#define _OPENSSL_CONTEXT_H__ - -#include -#include -#include - -// This class is created for creation of a global openSSL context. -class OpenSSLContext { -public: - static secp256k1_context* get_context() { - return get_instance().ctx; - } - static const OpenSSLContext& get_instance() { - static OpenSSLContext instance; - return instance; - } - - OpenSSLContext() { - ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - unsigned char seed[32]; - if (RAND_bytes(seed, sizeof(seed)) != 1) { - throw std::runtime_error("Unable to generate randomness for context"); - } - if (secp256k1_context_randomize(ctx, seed) != 1) { - throw std::runtime_error("Unable to randomize context"); - }; - } - - ~OpenSSLContext(){ - secp256k1_context_destroy(ctx); - } - -private: - secp256k1_context* ctx; - -}; -#endif // _OPENSSL_CONTEXT_H__ diff --git a/src/liblelantus/params.cpp b/src/liblelantus/params.cpp deleted file mode 100644 index 79bf2ba3b2..0000000000 --- a/src/liblelantus/params.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include "params.h" -#include "chainparams.h" -#include -namespace lelantus { - - CCriticalSection Params::cs_instance; - std::unique_ptr Params::instance; - -Params const* Params::get_default() { - if (instance) { - return instance.get(); - } else { - LOCK(cs_instance); - if (instance) { - return instance.get(); - } - - //fixing generator G; - GroupElement g; - if (!(::Params().GetConsensus().IsTestnet())) { - unsigned char buff[32] = {0}; - GroupElement base; - base.set_base_g(); - base.normalSha256(buff); - g.generate(buff); - } - else - g = GroupElement("9216064434961179932092223867844635691966339998754536116709681652691785432045", - "33986433546870000256104618635743654523665060392313886665479090285075695067131"); - - - //fixing n and m; N = n^m = 65,536 - int n = 16; - int m = 4; - - //fixing bulletproof params - int n_rangeProof = 64; - int max_m_rangeProof = 16; - - instance.reset(new Params(g, n, m, n_rangeProof, max_m_rangeProof)); - return instance.get(); - } -} - -Params::Params(const GroupElement& g_, int n_sigma_, int m_sigma_, int n_rangeProof_, int max_m_rangeProof_): - g(g_), - n_sigma(n_sigma_), - m_sigma(m_sigma_), - n_rangeProof(n_rangeProof_), - max_m_rangeProof(max_m_rangeProof_) -{ - - //creating generators for sigma - this->h_sigma.resize(n_sigma * m_sigma); - unsigned char buff0[32] = {0}; - g.normalSha256(buff0); - h_sigma[0].generate(buff0); - for (int i = 1; i < n_sigma * m_sigma; ++i) - { - unsigned char buff[32] = {0}; - h_sigma[i - 1].normalSha256(buff); - h_sigma[i].generate(buff); - } - - //creating generators for bulletproofs - g_rangeProof.resize(n_rangeProof * max_m_rangeProof); - h_rangeProof.resize(n_rangeProof * max_m_rangeProof); - g_rangeProof[0].generate(buff0); - unsigned char buff1[32] = {0}; - g_rangeProof[0].normalSha256(buff1); - h_rangeProof[0].generate(buff1); - for (int i = 1; i < n_rangeProof * max_m_rangeProof; ++i) - { - unsigned char buff[32] = {0}; - h_rangeProof[i-1].normalSha256(buff); - g_rangeProof[i].generate(buff); - unsigned char buff2[32] = {0}; - g_rangeProof[i].normalSha256(buff2); - h_rangeProof[i].generate(buff2); - } - - limit_range = Scalar(uint64_t(2)).exponent(get_bulletproofs_n()) - ::Params().GetConsensus().nMaxValueLelantusMint; - h1_limit_range = get_h1() * limit_range; -} - -const GroupElement& Params::get_g() const { - return g; -} - -const GroupElement& Params::get_h0() const { - return h_sigma[0]; -} - -const GroupElement& Params::get_h1() const { - return h_sigma[1]; -} - -const std::vector& Params::get_sigma_h() const { - return h_sigma; -} - -const std::vector& Params::get_bulletproofs_g() const { - return g_rangeProof; -} -const std::vector& Params::get_bulletproofs_h() const { - return h_rangeProof; -} - -int Params::get_sigma_n() const { - return n_sigma; -} - -int Params::get_sigma_m() const { - return m_sigma; -} - -int Params::get_bulletproofs_n() const { - return n_rangeProof; -} - -int Params::get_bulletproofs_max_m() const { - return max_m_rangeProof; -} - -const Scalar& Params::get_limit_range() const { - return limit_range; -} - -const GroupElement& Params::get_h1_limit_range() const { - return h1_limit_range; -} - -} //namespace lelantus diff --git a/src/liblelantus/params.h b/src/liblelantus/params.h deleted file mode 100644 index a1760f5e58..0000000000 --- a/src/liblelantus/params.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_PARAMS_H -#define FIRO_LIBLELANTUS_PARAMS_H - -#include -#include -#include -#include - -using namespace secp_primitives; - -namespace lelantus { - -class Params { -public: - static Params const* get_default(); - const GroupElement& get_g() const; - const GroupElement& get_h0() const; - const GroupElement& get_h1() const; - const std::vector& get_sigma_h() const; - const std::vector& get_bulletproofs_g() const; - const std::vector& get_bulletproofs_h() const; - int get_sigma_n() const; - int get_sigma_m() const; - int get_bulletproofs_n() const; - int get_bulletproofs_max_m() const; - const Scalar& get_limit_range() const; - const GroupElement& get_h1_limit_range() const; - -private: - Params(const GroupElement& g_sigma_, int n, int m, int n_rangeProof_, int max_m_rangeProof_); - -private: - static CCriticalSection cs_instance; - static std::unique_ptr instance; - - //sigma params - GroupElement g; - std::vector h_sigma; - int n_sigma; - int m_sigma; - - //bulletproof params - int n_rangeProof; - int max_m_rangeProof; - std::vector g_rangeProof; - std::vector h_rangeProof; - Scalar limit_range; - GroupElement h1_limit_range; -}; - -} // namespace lelantus - -#endif // FIRO_LIBLELANTUS_PARAMS_H diff --git a/src/liblelantus/range_proof.h b/src/liblelantus/range_proof.h deleted file mode 100644 index 44bff5ea4e..0000000000 --- a/src/liblelantus/range_proof.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_RANGEPROOF_H -#define FIRO_LIBLELANTUS_RANGEPROOF_H - -#include "innerproduct_proof.h" -#include - -namespace lelantus { - -class RangeProof{ -public: - - static inline int int_log2(uint64_t number) { - assert(number != 0); - - int l2 = 0; - while ((number >>= 1) != 0) - l2++; - - return l2; - } - - inline std::size_t memoryRequired(int n, int m) const { - int size = int_log2(n * m); - return A.memoryRequired() * 4 - + T_x1.memoryRequired() * 3 - + innerProductProof.memoryRequired(size); - } - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(A); - READWRITE(S); - READWRITE(T1); - READWRITE(T2); - READWRITE(T_x1); - READWRITE(T_x2); - READWRITE(u); - READWRITE(innerProductProof); - } - - GroupElement A; - GroupElement S; - GroupElement T1; - GroupElement T2; - Scalar T_x1; - Scalar T_x2; - Scalar u; - InnerProductProof innerProductProof; - -}; -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_RANGE_PROOF_H diff --git a/src/liblelantus/range_prover.cpp b/src/liblelantus/range_prover.cpp deleted file mode 100644 index 66c077be8c..0000000000 --- a/src/liblelantus/range_prover.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "range_prover.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -RangeProver::RangeProver( - const GroupElement& g, - const GroupElement& h1, - const GroupElement& h2, - const std::vector& g_vector, - const std::vector& h_vector, - std::size_t n, - unsigned int v) - : g (g) - , h1 (h1) - , h2 (h2) - , g_(g_vector) - , h_(h_vector) - , n (n) - , version (v) -{} - -void RangeProver::proof( - const std::vector& v, - const std::vector& serialNumbers, - const std::vector& randomness, - const std::vector& commitments, - RangeProof& proof_out) { - // Size checks - std::size_t m = v.size(); - if (m == 0) { - throw std::invalid_argument("Range proof cannot use zero inputs"); - } - if ((m & (m - 1)) != 0) { - throw std::invalid_argument("Range proof requires a power-of-two number of aggregated inputs"); - } - if (v.size() != serialNumbers.size() || v.size() != randomness.size()) { - throw std::invalid_argument("Range proof input vector sizes do not match"); - } - if (g_.size() != n * m || h_.size() != n * m) { - throw std::invalid_argument("Range proof generator vector has incorrect size"); - } - - std::vector> bits; - bits.resize(m); - for (std::size_t i = 0; i < v.size(); i++) - v[i].get_bits(bits[i]); - - std::vector aL, aR; - aL.reserve(n * m); - aR.reserve(n * m); - for (std::size_t j = 0; j < m; ++j) - { - for (std::size_t i = 1; i <= n; ++i) - { - aL.emplace_back(uint64_t(bits[j][bits[j].size() - i])); - aR.emplace_back(Scalar(uint64_t(bits[j][bits[j].size() - i])) - Scalar(uint64_t(1))); - } - } - - Scalar alpha; - alpha.randomize(); - LelantusPrimitives::commit(h1, alpha, g_, aL, h_, aR, proof_out.A); - - std::vector sL, sR; - sL.resize(n * m); - sR.resize(n * m); - for (std::size_t i = 0; i < n * m; ++i) - { - sL[i].randomize(); - sR[i].randomize(); - } - - Scalar ro; - ro.randomize(); - LelantusPrimitives::commit(h1, ro, g_, sL, h_, sR, proof_out.S); - - Scalar y, z; - std::unique_ptr challengeGenerator; - if (version >= LELANTUS_TX_VERSION_4_5) { - challengeGenerator = std::make_unique>(1); - // add domain separator and transaction version into transcript - std::string domain_separator = "RANGE_PROOF" + std::to_string(version); - std::vector pre(domain_separator.begin(), domain_separator.end()); - challengeGenerator->add(pre); - challengeGenerator->add(commitments); - } else { - challengeGenerator = std::make_unique>(0); - } - - challengeGenerator->add({proof_out.A, proof_out.S}); - challengeGenerator->get_challenge(y); - challengeGenerator->get_challenge(z); - - //compute l(x) and r(x) polynomials - std::vector> l_x, r_x; - l_x.resize(n * m); - r_x.resize(n * m); - NthPower y_nm(y); - NthPower z_j(z, z.square()); - Scalar z_sum1(uint64_t(0)); - Scalar z_sum2(uint64_t(0)); - - NthPower two_n_(uint64_t(2)); - std::vector two_n; - two_n.reserve(n); - for (std::size_t k = 0; k < n; ++k) - { - two_n.emplace_back(two_n_.pow); - two_n_.go_next(); - } - - for (std::size_t j = 0; j < m; ++j) - { - for (std::size_t i = 0; i < n; ++i) - { - int index = j * n + i; - l_x[index].emplace_back(aL[index] - z); - l_x[index].emplace_back(sL[index]); - - r_x[index].emplace_back(y_nm.pow * (aR[index] + z) + z_j.pow * two_n[i]); - r_x[index].emplace_back(y_nm.pow * sR[index]); - // - y_nm.go_next(); - } - z_sum1 += z_j.pow * randomness[j]; - z_sum2 += z_j.pow * serialNumbers[j]; - z_j.go_next(); - } - - //compute t1 and t2 coefficients - Scalar t0, t1, t2; - for (std::size_t i = 0; i < n * m; ++i) - { - t0 += l_x[i][0] * r_x[i][0]; - t1 += l_x[i][0] * r_x[i][1] + l_x[i][1] * r_x[i][0]; - t2 += l_x[i][1] * r_x[i][1]; - } - - //computing T11 T12 T21 T22; - Scalar T_11, T_12, T_21, T_22; - T_11.randomize(); - T_12.randomize(); - T_21.randomize(); - T_22.randomize(); - proof_out.T1 = LelantusPrimitives::double_commit(g, t1, h1, T_11, h2, T_21); - proof_out.T2 = LelantusPrimitives::double_commit(g, t2, h1, T_12, h2, T_22); - - Scalar x; - challengeGenerator->add({proof_out.T1, proof_out.T2}); - challengeGenerator->get_challenge(x); - - //computing l and r - std::vector l; - std::vector r; - l.reserve(n * m); - r.reserve(n * m); - for (std::size_t i = 0; i < n * m; i++) - { - l.emplace_back(l_x[i][0] + l_x[i][1] * x); - r.emplace_back(r_x[i][0] + r_x[i][1] * x); - } - - proof_out.T_x1 = T_12 * x.square() + T_11 * x + z_sum1; - proof_out.T_x2 = T_22 * x.square() + T_21 * x + z_sum2; - proof_out.u = alpha + ro * x; - - //compute h' - std::vector h_prime; - h_prime.reserve(h_.size()); - NthPower y_i_inv(y.inverse()); - for (std::size_t i = 0; i < h_.size(); ++i) - { - h_prime.emplace_back(h_[i] * y_i_inv.pow); - y_i_inv.go_next(); - } - - int inner_product_version = version >= LELANTUS_TX_VERSION_4_5 ? 2 : 1; - if (version >= LELANTUS_TX_TPAYLOAD) - inner_product_version = 3; - - InnerProductProofGenerator InnerProductProofGenerator(g_, h_prime, g, inner_product_version); - //t^ is calculated inside inner product proof generation with name c - Scalar x_u; - challengeGenerator->add({proof_out.T_x1, proof_out.T_x2, proof_out.u}); - challengeGenerator->get_challenge(x_u); - - if (version >= LELANTUS_TX_VERSION_4_5) { - // add domain separator in each step - std::string domain_separator = "INNER_PRODUCT"; - std::vector pre(domain_separator.begin(), domain_separator.end()); - challengeGenerator->add(pre); - } - - // if(inner_product_version >= 2) link range proof data to inner product transcript with passing already filled challengeGenerator - InnerProductProofGenerator.generate_proof(l, r, x_u, challengeGenerator, proof_out.innerProductProof); -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/range_prover.h b/src/liblelantus/range_prover.h deleted file mode 100644 index a8151d5aca..0000000000 --- a/src/liblelantus/range_prover.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_RANGE_PROVER_H -#define FIRO_LIBLELANTUS_RANGE_PROVER_H - -#include "innerproduct_proof_generator.h" - -namespace lelantus { - -class RangeProver { -public: - RangeProver( - const GroupElement& g - , const GroupElement& h1 - , const GroupElement& h2 - , const std::vector& g_vector - , const std::vector& h_vector - , std::size_t n - , unsigned int v); - - // commitments are included into transcript if version >= LELANTUS_TX_VERSION_4_5 - void proof( - const std::vector& v - , const std::vector& serialNumbers - , const std::vector& randomness - , const std::vector& commitments - , RangeProof& proof_out); - -private: - GroupElement g; - GroupElement h1; - GroupElement h2; - std::vector g_; - std::vector h_; - std::size_t n; - unsigned int version; - -}; - -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_RANGE_PROVER_H diff --git a/src/liblelantus/range_verifier.cpp b/src/liblelantus/range_verifier.cpp deleted file mode 100644 index e0cb136047..0000000000 --- a/src/liblelantus/range_verifier.cpp +++ /dev/null @@ -1,301 +0,0 @@ -#include "range_verifier.h" -#include "challenge_generator_impl.h" - -#include "../../compat_layer.h" - -// This is based on the 1 Jul 2018 revision of the Bulletproofs preprint: -// https://eprint.iacr.org/2017/1066 - -namespace lelantus { - -RangeVerifier::RangeVerifier( - const GroupElement& g, - const GroupElement& h1, - const GroupElement& h2, - const std::vector& g_vector, - const std::vector& h_vector, - std::size_t n, - unsigned int v) - : g (g) - , h1 (h1) - , h2 (h2) - , g_(g_vector) - , h_(h_vector) - , n (n) - , version (v) -{} - -// Verify a single proof by building a trivial batch -bool RangeVerifier::verify(const std::vector& V, const std::vector& commitments, const RangeProof& proof) { - std::vector > V_batch = {V}; - std::vector > commitments_batch = {commitments}; - std::vector proof_batch = {proof}; - - return verify(V_batch, commitments_batch, proof_batch); -} - -bool RangeVerifier::verify(const std::vector >& V, const std::vector >& commitments, const std::vector& proofs) { - // Preprocess all proofs - if (V.size() != commitments.size() || commitments.size() != proofs.size()) { - return false; - } - std::size_t N_proofs = proofs.size(); - std::size_t max_m = 0; // maximum number of aggregated values - - // Check aggregated input consistency - for (std::size_t k = 0; k < N_proofs; k++) { - std::size_t m = V[k].size(); // number of aggregated inputs - - // Require a power of 2 (if no commitments, valid by default) - if (m == 0) { - return true; - } - if ((m & (m - 1)) != 0) { - return false; - } - - // Track maximum value - if (m > max_m) { - max_m = m; - } - - // Check inner product proof size consistency - std::size_t log_mn = proofs[k].innerProductProof.L_.size(); - if (proofs[k].innerProductProof.R_.size() != log_mn) { - return false; - } - if (cmp::not_equal(RangeProof::int_log2(m*n), log_mn)) { - return false; - } - - // Group membership checks - if (!membership_checks(proofs[k])) { - return false; - } - } - - // Ensure this batch is within bounds - if (max_m*n > g_.size() || max_m*n > h_.size()) { - return false; - } - - // Set up final multiscalar multiplication and common scalars - std::vector points; - std::vector scalars; - Scalar g_scalar(uint64_t(0)); - Scalar h1_scalar(uint64_t(0)); - Scalar h2_scalar(uint64_t(0)); - - // Elements from g- and h-vectors are interleaved in order at the start of the final vectors - for (std::size_t i = 0; i < max_m*n; i++) { - points.emplace_back(g_[i]); - scalars.emplace_back(uint64_t(0)); - points.emplace_back(h_[i]); - scalars.emplace_back(uint64_t(0)); - } - - // Process each proof and add to the batch - for (std::size_t k_proofs = 0; k_proofs < N_proofs; k_proofs++) { - const RangeProof proof = proofs[k_proofs]; - const std::size_t m = V[k_proofs].size(); // number of aggregated inputs - const std::size_t log_mn = proof.innerProductProof.L_.size(); // round count - - // Choose random nonzero weights for batching purposes - // Each weight is used for one of the two verifier equations (98 and 105) - Scalar w1; // equation (98) - w1.randomize(); - Scalar w2; // equation (105) - w2.randomize(); - - // Reconstruct all challenges from this proof - Scalar x, x_u, y, z; - std::unique_ptr challengeGenerator; - - // Newer proofs use domain separation and statement parameters - if (version >= LELANTUS_TX_VERSION_4_5) { - challengeGenerator = std::make_unique>(1); - std::string domain_separator = "RANGE_PROOF" + std::to_string(version); - std::vector pre(domain_separator.begin(), domain_separator.end()); - challengeGenerator->add(pre); - challengeGenerator->add(commitments[k_proofs]); - } else { - challengeGenerator = std::make_unique>(0); - } - challengeGenerator->add({proof.A, proof.S}); - challengeGenerator->get_challenge(y); - challengeGenerator->get_challenge(z); - - challengeGenerator->add({proof.T1, proof.T2}); - challengeGenerator->get_challenge(x); - Scalar x_neg = x.negate(); - - challengeGenerator->add({proof.T_x1, proof.T_x2, proof.u}); - challengeGenerator->get_challenge(x_u); - - const InnerProductProof& innerProductProof = proof.innerProductProof; - std::vector x_j, x_j_inv; - x_j.resize(log_mn); - x_j_inv.reserve(log_mn); - - // Newer proofs use domain separation - if (version >= LELANTUS_TX_VERSION_4_5) { - std::string domain_separator = "INNER_PRODUCT"; - std::vector pre(domain_separator.begin(), domain_separator.end()); - challengeGenerator->add(pre); - } - - if (version >= LELANTUS_TX_TPAYLOAD) - challengeGenerator->add(innerProductProof.c_); - - for (std::size_t i = 0; i < log_mn; ++i) - { - std::vector group_elements_i = {innerProductProof.L_[i], innerProductProof.R_[i]}; - - // if(version >= LELANTUS_TX_VERSION_4_5) we should be using CHash256, - // we want to link transcripts from range proof and from previous iteration in each step, so we are not restarting in that case, - if (version < LELANTUS_TX_VERSION_4_5) { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - - challengeGenerator->add(group_elements_i); - challengeGenerator->get_challenge(x_j[i]); - } - - // In the event of an attempt to invert a zero scalar, the batch is bad - try { - x_j_inv = LelantusPrimitives::invert(x_j); // NOTE: these could be batched across proofs as well for improved efficiency - } catch (const std::runtime_error&) { - return false; - } - - Scalar z_square_neg = (z.square()).negate(); - Scalar delta = LelantusPrimitives::delta(y, z, n, m); - - NthPower z_m(z); - for (std::size_t j = 0; j < m; ++j) - { - points.emplace_back(V[k_proofs][j]); - scalars.emplace_back(z_square_neg * z_m.pow * w1); - z_m.go_next(); - } - - NthPower y_n_(y.inverse()); - NthPower z_j(z, z.square()); - - NthPower two_n_(uint64_t(2)); - std::vector two_n; - two_n.reserve(n); - for (std::size_t k = 0; k < n; ++k) - { - two_n.emplace_back(two_n_.pow); - two_n_.go_next(); - } - - for (std::size_t t = 0; t < m ; ++t) - { - for (std::size_t k = 0; k < n; ++k) - { - std::size_t i = t * n + k; - Scalar x_il(uint64_t(1)); - Scalar x_ir(uint64_t(1)); - for (std::size_t j = 0; j < log_mn; ++j) - { - if ((i >> j) & 1) { - x_il *= x_j[log_mn - j - 1]; - x_ir *= x_j_inv[log_mn - j - 1]; - } else { - x_il *= x_j_inv[log_mn - j - 1]; - x_ir *= x_j[log_mn - j - 1]; - } - - } - - // g-vector - scalars[2*i] += (x_il * innerProductProof.a_ + z) * w2; - - // h-vector - scalars[2*i + 1] += (y_n_.pow * (x_ir * innerProductProof.b_ - (z_j.pow * two_n[k])) - z) * w2; - - y_n_.go_next(); - } - z_j.go_next(); - } - - // Update common scalars - g_scalar += (innerProductProof.c_ - delta) * w1; - g_scalar += x_u * (innerProductProof.a_ * innerProductProof.b_ - innerProductProof.c_) * w2; - h1_scalar += proof.T_x1 * w1; - h1_scalar += proof.u * w2; - h2_scalar += proof.T_x2 * w1; - - // Add per-proof elements - points.emplace_back(proof.A); - scalars.emplace_back(w2.negate()); - points.emplace_back(proof.T1); - scalars.emplace_back(x_neg * w1); - points.emplace_back(proof.T2); - scalars.emplace_back((x.square()).negate() * w1); - points.emplace_back(proof.S); - scalars.emplace_back(x_neg * w2); - - for (std::size_t j = 0; j < log_mn; ++j) - { - points.emplace_back(innerProductProof.L_[j]); - scalars.emplace_back(x_j[j].square().negate() * w2); - points.emplace_back(innerProductProof.R_[j]); - scalars.emplace_back(x_j_inv[j].square().negate() * w2); - } - } - - // Add common elements - points.emplace_back(g); - scalars.emplace_back(g_scalar); - points.emplace_back(h1); - scalars.emplace_back(h1_scalar); - points.emplace_back(h2); - scalars.emplace_back(h2_scalar); - - // Perform the batch check - secp_primitives::MultiExponent mult(points, scalars); - if(!mult.get_multiple().isInfinity()) { - return false; - } - return true; -} - -// Note: the infinity/zero checks are not required by the protocol; they are only included for historical implementation reasons -bool RangeVerifier::membership_checks(const RangeProof& proof) { - if(!(proof.A.isMember() - && proof.S.isMember() - && proof.T1.isMember() - && proof.T2.isMember() - && proof.T_x1.isMember() - && proof.T_x2.isMember() - && proof.u.isMember() - && proof.innerProductProof.a_.isMember() - && proof.innerProductProof.b_.isMember() - && proof.innerProductProof.c_.isMember()) - || proof.A.isInfinity() - || proof.S.isInfinity() - || proof.T1.isInfinity() - || proof.T2.isInfinity() - || proof.T_x1.isZero() - || proof.T_x2.isZero() - || proof.u.isZero() - || proof.innerProductProof.a_.isZero() - || proof.innerProductProof.b_.isZero() - || proof.innerProductProof.c_.isZero()) - return false; - - for (std::size_t i = 0; i < proof.innerProductProof.L_.size(); ++i) - { - if (!(proof.innerProductProof.L_[i].isMember() && proof.innerProductProof.R_[i].isMember()) - || proof.innerProductProof.L_[i].isInfinity() || proof.innerProductProof.R_[i].isInfinity()) { - return false; - } - } - return true; -} - - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/range_verifier.h b/src/liblelantus/range_verifier.h deleted file mode 100644 index c8b4e6e11b..0000000000 --- a/src/liblelantus/range_verifier.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_RANGE_VERIFIER_H -#define FIRO_LIBLELANTUS_RANGE_VERIFIER_H - -#include "innerproduct_proof_verifier.h" - -namespace lelantus { - -class RangeVerifier { -public: - //g_vector and h_vector are being kept by reference, be sure it will not be modified from outside - RangeVerifier( - const GroupElement& g - , const GroupElement& h1 - , const GroupElement& h2 - , const std::vector& g_vector - , const std::vector& h_vector - , std::size_t n - , unsigned int v); - - // commitments are included into transcript if version >= LELANTUS_TX_VERSION_4_5 - bool verify(const std::vector& V, const std::vector& commitments, const RangeProof& proof); // single proof - bool verify(const std::vector >& V, const std::vector >& commitments, const std::vector& proof); // batch of proofs - -private: - bool membership_checks(const RangeProof& proof); - -private: - GroupElement g; - GroupElement h1; - GroupElement h2; - const std::vector& g_; - const std::vector& h_; - std::size_t n; - unsigned int version; -}; - -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_RANGE_VERIFIER_H diff --git a/src/liblelantus/schnorr_proof.h b/src/liblelantus/schnorr_proof.h deleted file mode 100644 index a7894dc800..0000000000 --- a/src/liblelantus/schnorr_proof.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SCHNORR_PROOF_H -#define FIRO_LIBLELANTUS_SCHNORR_PROOF_H - -#include "params.h" - -namespace lelantus { - -class SchnorrProof{ -public: - inline std::size_t memoryRequired() const { - return u.memoryRequired() + P1.memoryRequired() * 2; - } - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(u); - READWRITE(P1); - READWRITE(T1); - } - -public: - GroupElement u; - Scalar P1; - Scalar T1; -}; -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_SCHNORR_PROOF_H diff --git a/src/liblelantus/schnorr_prover.cpp b/src/liblelantus/schnorr_prover.cpp deleted file mode 100644 index a2e5cfb8fb..0000000000 --- a/src/liblelantus/schnorr_prover.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "schnorr_prover.h" -#include "challenge_generator_impl.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -SchnorrProver::SchnorrProver(const GroupElement& g, const GroupElement& h, bool withFixes_): - g_(g), h_(h), withFixes(withFixes_) { -} - -void SchnorrProver::proof( - const Scalar& P, - const Scalar& T, - const GroupElement& y, - const GroupElement& a, - const GroupElement& b, - std::unique_ptr& challengeGenerator, - SchnorrProof& proof_out){ - Scalar P0; - Scalar T0; - P0.randomize(); - T0.randomize(); - GroupElement u = LelantusPrimitives::commit(g_,P0, h_, T0); - proof_out.u = u; - Scalar c; - std::vector group_elements = {u}; - - std::string shts = ""; - if (withFixes) { - shts = "SCHNORR_PROOF"; - std::vector pre(shts.begin(), shts.end()); - group_elements = {u, y, a, b}; - challengeGenerator->add(pre); - } else { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(c); - proof_out.P1 = P0 - c * P; - proof_out.T1 = T0 - c * T; -} - -void SchnorrProver::proof( - const Scalar& P, - const Scalar& T, - const std::vector& group_elements, - SchnorrProof& proof_out){ - Scalar P0; - Scalar T0; - P0.randomize(); - T0.randomize(); - GroupElement u = LelantusPrimitives::commit(g_,P0, h_, T0); - proof_out.u = u; - Scalar c; - - ChallengeGeneratorImpl challengeGenerator(1); - std::string shts = "SCHNORR_PROOF"; - std::vector pre(shts.begin(), shts.end()); - challengeGenerator.add(pre); - challengeGenerator.add(group_elements); - challengeGenerator.add(u); - - challengeGenerator.get_challenge(c); - - proof_out.P1 = P0 - c * P; - proof_out.T1 = T0 - c * T; -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/schnorr_prover.h b/src/liblelantus/schnorr_prover.h deleted file mode 100644 index 8936252623..0000000000 --- a/src/liblelantus/schnorr_prover.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SCHNORR_PROOVER_H -#define FIRO_LIBLELANTUS_SCHNORR_PROOVER_H - -#include "lelantus_primitives.h" - -namespace lelantus { - -class SchnorrProver { -public: - //g and h are being kept by reference, be sure it will not be modified from outside - SchnorrProver(const GroupElement& g, const GroupElement& h, bool withFixes_); - - // values a, b and y are included into transcript if(withFixes_), also better to use CHash256 in that case - void proof(const Scalar& P, const Scalar& T, const GroupElement& y, const GroupElement& a, const GroupElement& b, std::unique_ptr& challengeGenerator, SchnorrProof& proof_out); - void proof(const Scalar& P, const Scalar& T, const std::vector& groupElements, SchnorrProof& proof_out); - -private: - const GroupElement& g_; - const GroupElement& h_; - bool withFixes; -}; - -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_SCHNORR_PROOVER_H diff --git a/src/liblelantus/schnorr_verifier.cpp b/src/liblelantus/schnorr_verifier.cpp deleted file mode 100644 index 549dac54f4..0000000000 --- a/src/liblelantus/schnorr_verifier.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "schnorr_verifier.h" -#include "challenge_generator_impl.h" - -namespace lelantus { - -SchnorrVerifier::SchnorrVerifier(const GroupElement& g, const GroupElement& h, bool withFixes_): - g_(g), h_(h), withFixes(withFixes_) { -} - -bool SchnorrVerifier::verify( - const GroupElement& y, - const GroupElement& a, - const GroupElement& b, - const SchnorrProof& proof, - std::unique_ptr& challengeGenerator){ - - const GroupElement& u = proof.u; - Scalar c; - std::vector group_elements = {u}; - - std::string shts = ""; - if (withFixes) { - shts = "SCHNORR_PROOF"; - std::vector pre(shts.begin(), shts.end()); - group_elements = {u, y, a, b}; - challengeGenerator->add(pre); - } else { - challengeGenerator.reset(new ChallengeGeneratorImpl(0)); - } - challengeGenerator->add(group_elements); - challengeGenerator->get_challenge(c); - - const Scalar P1 = proof.P1; - const Scalar T1 = proof.T1; - - if (!(u.isMember() && y.isMember() && P1.isMember() && T1.isMember()) || - u.isInfinity() || y.isInfinity() || P1.isZero() || T1.isZero()) - return false; - - GroupElement right = y * c + g_ * P1 + h_ * T1; - if (u == right) { - return true; - } - - return false; -} - -bool SchnorrVerifier::verify( - const GroupElement& y, - const std::vector& groupElements, - const SchnorrProof& proof){ - - const GroupElement& u = proof.u; - Scalar c; - - ChallengeGeneratorImpl challengeGenerator(1); - std::string shts = "SCHNORR_PROOF"; - std::vector pre(shts.begin(), shts.end()); - challengeGenerator.add(pre); - challengeGenerator.add(groupElements); - challengeGenerator.add(u); - - challengeGenerator.get_challenge(c); - - const Scalar P1 = proof.P1; - const Scalar T1 = proof.T1; - - if (!(u.isMember() && y.isMember() && P1.isMember() && T1.isMember()) || - u.isInfinity() || y.isInfinity() || P1.isZero() || T1.isZero()) - return false; - - GroupElement right = y * c + g_ * P1 + h_ * T1; - if (u == right) { - return true; - } - - return false; -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/schnorr_verifier.h b/src/liblelantus/schnorr_verifier.h deleted file mode 100644 index 89693eabf9..0000000000 --- a/src/liblelantus/schnorr_verifier.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SCHNORR_VERIFIER_H -#define FIRO_LIBLELANTUS_SCHNORR_VERIFIER_H - -#include "lelantus_primitives.h" - -namespace lelantus { - -class SchnorrVerifier { -public: - //g and h are being kept by reference, be sure it will not be modified from outside - SchnorrVerifier(const GroupElement& g, const GroupElement& h, bool withFixes_); - - // values a, b and y are included into transcript if(withFixes_), also better to use CHash256 in that case - bool verify(const GroupElement& y, const GroupElement& a, const GroupElement& b,const SchnorrProof& proof, std::unique_ptr& challengeGenerator); - bool verify(const GroupElement& y, const std::vector& groupElements,const SchnorrProof& proof); - -private: - const GroupElement& g_; - const GroupElement& h_; - bool withFixes; -}; - -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_SCHNORR_VERIFIER_H diff --git a/src/liblelantus/sigmaextended_proof.h b/src/liblelantus/sigmaextended_proof.h deleted file mode 100644 index 4cf3c02611..0000000000 --- a/src/liblelantus/sigmaextended_proof.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SIGMAEXTENDED_PROOF_H -#define FIRO_LIBLELANTUS_SIGMAEXTENDED_PROOF_H - -#include -#include "params.h" - -namespace lelantus { - -class SigmaExtendedProof{ -public: - SigmaExtendedProof() = default; - - inline std::size_t memoryRequired() const { - return B_.memoryRequired() * 4 - + ZA_.memoryRequired() * (f_.size() + 2) - + B_.memoryRequired() * Gk_.size() * 2 - + zR_.memoryRequired() * 2; - } - - inline std::size_t memoryRequired(int n, int m) const { - return B_.memoryRequired() * 4 - + ZA_.memoryRequired() * (m*(n - 1) + 2) - + B_.memoryRequired() * m * 2 - + zR_.memoryRequired() * 2; - } - - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(A_); - READWRITE(B_); - READWRITE(C_); - READWRITE(D_); - READWRITE(f_); - READWRITE(ZA_); - READWRITE(ZC_); - READWRITE(Gk_); - READWRITE(Qk); - READWRITE(zV_); - READWRITE(zR_); - } - -public: - GroupElement A_; - GroupElement B_; - GroupElement C_; - GroupElement D_; - std::vector f_; - Scalar ZA_; - Scalar ZC_; - std::vector Gk_; - std::vector Qk; - Scalar zV_; - Scalar zR_; -}; - -} //namespace lelantus - -#endif //FIRO_LIBLELANTUS_SIGMAEXTENDED_PROOF_H diff --git a/src/liblelantus/sigmaextended_prover.cpp b/src/liblelantus/sigmaextended_prover.cpp deleted file mode 100644 index 0c8db5b86f..0000000000 --- a/src/liblelantus/sigmaextended_prover.cpp +++ /dev/null @@ -1,212 +0,0 @@ -#include "sigmaextended_prover.h" - -namespace lelantus { - -SigmaExtendedProver::SigmaExtendedProver( - const GroupElement& g, - const std::vector& h_gens, - std::size_t n, - std::size_t m) - : g_(g) - , h_(h_gens) - , n_(n) - , m_(m) { -} - -// Generate the initial portion of a one-of-many proof -// Internal prover state is maintained separately, in order to facilitate the last part of the proof later -void SigmaExtendedProver::sigma_commit( - const std::vector& commits, - std::size_t l, - const Scalar& rA, - const Scalar& rB, - const Scalar& rC, - const Scalar& rD, - std::vector& a, - std::vector& Tk, - std::vector& Pk, - std::vector& Yk, - std::vector& sigma, - SigmaExtendedProof& proof_out) { - // Sanity checks - if (n_ < 2 || m_ < 2) { - throw std::invalid_argument("Prover parameters are invalid"); - } - std::size_t N = (std::size_t)pow(n_, m_); - std::size_t setSize = commits.size(); - if (setSize == 0) { - throw std::invalid_argument("Cannot have empty commitment set"); - } - if (setSize > N) { - throw std::invalid_argument("Commitment set is too large"); - } - if (l >= setSize) { - throw std::invalid_argument("Signing index is out of range"); - } - if (h_.size() != n_ * m_) { - throw std::invalid_argument("Generator vector size is invalid"); - } - - LelantusPrimitives::convert_to_sigma(l, n_, m_, sigma); - for (std::size_t k = 0; k < m_; ++k) - { - Tk[k].randomize(); - Pk[k].randomize(); - Yk[k].randomize(); - } - - //compute B - LelantusPrimitives::commit(g_, h_, sigma, rB, proof_out.B_); - - //compute A - for (std::size_t j = 0; j < m_; ++j) - { - for (std::size_t i = 1; i < n_; ++i) - { - a[j * n_ + i].randomize(); - a[j * n_] -= a[j * n_ + i]; - } - } - LelantusPrimitives::commit(g_, h_, a, rA, proof_out.A_); - - //compute C - std::vector c; - c.resize(n_ * m_); - Scalar one(uint64_t(1)); - Scalar two(uint64_t(2)); - for (std::size_t i = 0; i < n_ * m_; ++i) - { - c[i] = a[i] * (one - two * sigma[i]); - } - LelantusPrimitives::commit(g_,h_, c, rC, proof_out.C_); - - //compute D - std::vector d; - d.resize(n_ * m_); - for (std::size_t i = 0; i < n_ * m_; i++) - { - d[i] = a[i].square().negate(); - } - LelantusPrimitives::commit(g_,h_, d, rD, proof_out.D_); - - std::vector> P_i_k; - P_i_k.resize(setSize); - for (std::size_t i = 0; i < setSize - 1; ++i) - { - std::vector& coefficients = P_i_k[i]; - std::vector I = LelantusPrimitives::convert_to_nal(i, n_, m_); - coefficients.push_back(a[I[0]]); - coefficients.push_back(sigma[I[0]]); - for (std::size_t j = 1; j < m_; ++j) { - LelantusPrimitives::new_factor(sigma[j * n_ + I[j]], a[j * n_ + I[j]], coefficients); - } - } - - /* - * To optimize calculation of sum of all polynomials indices 's' = setSize-1 through 'n^m-1' we use the - * fact that sum of all of elements in each row of 'a' array is zero. Computation is done by going - * through n-ary representation of 's' and increasing "digit" at each position to 'n-1' one by one. - * During every step digits at higher positions are fixed and digits at lower positions go through all - * possible combinations with a total corresponding polynomial sum of 'x^j'. - * - * The math behind optimization (TeX notation): - * - * \sum_{i=s+1}^{N-1}p_i(x) = - * \sum_{j=0}^{m-1} - * \left[ - * \left( \sum_{i=s_j+1}^{n-1}(\delta_{l_j,i}x+a_{j,i}) \right) - * \left( \prod_{k=j}^{m-1}(\delta_{l_k,s_k}x+a_{k,s_k}) \right) - * x^j - * \right] - */ - - std::vector I = LelantusPrimitives::convert_to_nal(setSize - 1, n_, m_); - std::vector lj = LelantusPrimitives::convert_to_nal(l, n_, m_); - - std::vector p_i_sum; - p_i_sum.emplace_back(one); - std::vector> partial_p_s; - - // Pre-calculate product parts and calculate p_s(x) at the same time, put the latter into p_i_sum - for (std::ptrdiff_t j = m_ - 1; j >= 0; j--) { - partial_p_s.push_back(p_i_sum); - LelantusPrimitives::new_factor(sigma[j*n_ + I[j]], a[j*n_ + I[j]], p_i_sum); - } - - for (std::size_t j = 0; j < m_; j++) { - // \sum_{i=s_j+1}^{n-1}(\delta_{l_j,i}x+a_{j,i}) - Scalar a_sum(uint64_t(0)); - for (std::size_t i = I[j] + 1; i < n_; i++) - a_sum += a[j * n_ + i]; - Scalar x_sum(uint64_t(lj[j] >= I[j]+1 ? 1 : 0)); - - // Multiply by \prod_{k=j}^{m-1}(\delta_{l_k,s_k}x+a_{k,s_k}) - std::vector &polynomial = partial_p_s[m_ - j - 1]; - LelantusPrimitives::new_factor(x_sum, a_sum, polynomial); - - // Multiply by x^j and add to the result - for (std::size_t k = 0; k < m_ - j; k++) - p_i_sum[j + k] += polynomial[k]; - } - - P_i_k[setSize - 1] = p_i_sum; - - proof_out.Gk_.reserve(m_); - proof_out.Qk.reserve(m_); - for (std::size_t k = 0; k < m_; ++k) - { - std::vector P_i; - P_i.reserve(setSize); - for (std::size_t i = 0; i < setSize; ++i){ - P_i.emplace_back(P_i_k[i][k]); - } - secp_primitives::MultiExponent mult(commits, P_i); - GroupElement c_k = mult.get_multiple(); - proof_out.Gk_.emplace_back(c_k + h_[0] * Yk[k].negate()); - proof_out.Qk.emplace_back(LelantusPrimitives::double_commit(g_, Scalar(uint64_t(0)), h_[1], Pk[k], h_[0], Tk[k]) + h_[0] * Yk[k]); - - } -} - -// Generate the rest of the one-of-many proof -void SigmaExtendedProver::sigma_response( - const std::vector& sigma, - const std::vector& a, - const Scalar& rA, - const Scalar& rB, - const Scalar& rC, - const Scalar& rD, - const Scalar& v, - const Scalar& r, - const std::vector& Tk, - const std::vector& Pk, - const Scalar& x, - SigmaExtendedProof& proof_out) { - - //f - proof_out.f_.reserve(m_ * (n_ - 1)); - for (std::size_t j = 0; j < m_; j++) - { - for (std::size_t i = 1; i < n_; i++) - proof_out.f_.emplace_back(sigma[(j * n_) + i] * x + a[(j * n_) + i]); - } - //zA, zC - proof_out.ZA_ = rB * x + rA; - proof_out.ZC_ = rC * x + rD; - - //computing z - proof_out.zV_ = v * x.exponent(uint64_t(m_)); - proof_out.zR_ = r * x.exponent(uint64_t(m_)); - Scalar sumV, sumR; - - NthPower x_k(x); - for (std::size_t k = 0; k < m_; ++k) { - sumV += (Pk[k] * x_k.pow); - sumR += (Tk[k] * x_k.pow); - x_k.go_next(); - } - proof_out.zV_ -= sumV; - proof_out.zR_ -= sumR; -} - -}//namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/sigmaextended_prover.h b/src/liblelantus/sigmaextended_prover.h deleted file mode 100644 index fe2e79c914..0000000000 --- a/src/liblelantus/sigmaextended_prover.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SIGMAEXTENDED_PROVER_H -#define FIRO_LIBLELANTUS_SIGMAEXTENDED_PROVER_H - -#include "lelantus_primitives.h" - -namespace lelantus { - -class SigmaExtendedProver{ - -public: - SigmaExtendedProver(const GroupElement& g, - const std::vector& h_gens, std::size_t n, std::size_t m); - - void sigma_commit( - const std::vector& commits, - std::size_t l, - const Scalar& rA, - const Scalar& rB, - const Scalar& rC, - const Scalar& rD, - std::vector& a, - std::vector& Tk, - std::vector& Pk, - std::vector& Yk, - std::vector& sigma, - SigmaExtendedProof& proof_out); - - void sigma_response( - const std::vector& sigma, - const std::vector& a, - const Scalar& rA, - const Scalar& rB, - const Scalar& rC, - const Scalar& rD, - const Scalar& v, - const Scalar& r, - const std::vector& Tk, - const std::vector& Pk, - const Scalar& x, - SigmaExtendedProof& proof_out); - - -private: - GroupElement g_; - std::vector h_; - std::size_t n_; - std::size_t m_; -}; - -}//namespace lelantus - -#endif //FIRO_LIBLELANTUS_SIGMAEXTENDED_PROVER_H diff --git a/src/liblelantus/sigmaextended_verifier.cpp b/src/liblelantus/sigmaextended_verifier.cpp deleted file mode 100644 index 46dcceeb08..0000000000 --- a/src/liblelantus/sigmaextended_verifier.cpp +++ /dev/null @@ -1,438 +0,0 @@ -#include "sigmaextended_verifier.h" -#include "util.h" - -namespace lelantus { - -SigmaExtendedVerifier::SigmaExtendedVerifier( - const GroupElement& g, - const std::vector& h_gens, - std::size_t n, - std::size_t m) - : g_(g) - , h_(h_gens) - , n(n) - , m(m){ -} - -// Verify a single one-of-many proof -// In this case, there is an implied input set size -bool SigmaExtendedVerifier::singleverify( - const std::vector& commits, - const Scalar& x, - const Scalar& serial, - const SigmaExtendedProof& proof) const { - std::vector challenges = { x }; - std::vector serials = { serial }; - std::vector setSizes = { }; - std::vector proofs = { proof }; - - return verify( - commits, - challenges, - serials, - setSizes, - true, - false, - proofs - ); -} - -// Verify a single one-of-many proof -// In this case, there is a specified set size -bool SigmaExtendedVerifier::singleverify( - const std::vector& commits, - const Scalar& x, - const Scalar& serial, - const std::size_t setSize, - const SigmaExtendedProof& proof) const { - std::vector challenges = { x }; - std::vector serials = { serial }; - std::vector setSizes = { setSize }; - std::vector proofs = { proof }; - - return verify( - commits, - challenges, - serials, - setSizes, - true, - true, - proofs - ); -} - -// Verify a batch of one-of-many proofs from the same transaction -// In this case, there is a single common challenge and implied input set size -bool SigmaExtendedVerifier::batchverify( - const std::vector& commits, - const Scalar& x, - const std::vector& serials, - const std::vector& proofs) const { - std::vector challenges = { x }; - std::vector setSizes = { }; - - return verify( - commits, - challenges, - serials, - setSizes, - true, - false, - proofs - ); -} - -// Verify a general batch of one-of-many proofs -// In this case, each proof has a separate challenge and specified set size -bool SigmaExtendedVerifier::batchverify( - const std::vector& commits, - const std::vector& challenges, - const std::vector& serials, - const std::vector& setSizes, - const std::vector& proofs) const { - - return verify( - commits, - challenges, - serials, - setSizes, - false, - true, - proofs - ); -} - -// Verify a batch of one-of-many proofs -bool SigmaExtendedVerifier::verify( - const std::vector& commits, - const std::vector& challenges, - const std::vector& serials, - const std::vector& setSizes, - const bool commonChallenge, - const bool specifiedSetSizes, - const std::vector& proofs) const { - // Sanity checks - if (n < 2 || m < 2) { - LogPrintf("Verifier parameters are invalid"); - return false; - } - std::size_t M = proofs.size(); - std::size_t N = (std::size_t)pow(n, m); - - if (commits.size() == 0) { - LogPrintf("Cannot have empty commitment set"); - return false; - } - if (commits.size() > N) { - LogPrintf("Commitment set is too large"); - return false; - } - if (h_.size() != n * m) { - LogPrintf("Generator vector size is invalid"); - return false; - } - if (serials.size() != M) { - LogPrintf("Invalid number of serials provided"); - return false; - } - - // For separate challenges, we must have enough - if (!commonChallenge && challenges.size() != M) { - LogPrintf("Invalid challenge vector size"); - return false; - } - - // If we have specified set sizes, we must have enough - if (specifiedSetSizes && setSizes.size() != M) { - LogPrintf("Invalid set size vector size"); - return false; - } - - // All proof elements must be valid - for (std::size_t t = 0; t < M; ++t) { - if (!membership_checks(proofs[t])) { - LogPrintf("Sigma verification failed due to membership checks failed."); - return false; - } - } - - // Final batch multiscalar multiplication - Scalar g_scalar = Scalar(uint64_t(0)); // associated to g_ - Scalar h1_scalar = Scalar(uint64_t(0)); // associated to h1 - Scalar h2_scalar = Scalar(uint64_t(0)); // associated to h2 - std::vector h_scalars; // associated to h_ - std::vector commit_scalars; // associated to commitment list - h_scalars.reserve(n * m); - h_scalars.resize(n * m); - for (std::size_t i = 0; i < n * m; i++) { - h_scalars[i] = Scalar(uint64_t(0)); - } - commit_scalars.reserve(commits.size()); - commit_scalars.resize(commits.size()); - for (size_t i = 0; i < commits.size(); i++) { - commit_scalars[i] = Scalar(uint64_t(0)); - } - - // Set up the final batch elements - std::vector points; - std::vector scalars; - std::size_t final_size = 3 + m * n + commits.size(); // g, h1, h2, (h_), (commits) - for (std::size_t t = 0; t < M; t++) { - final_size += 4 + proofs[t].Gk_.size() + proofs[t].Qk.size(); // A, B, C, D, (G), (Q) - } - points.reserve(final_size); - scalars.reserve(final_size); - - // Index decomposition, which is common among all proofs - std::vector > I_; - I_.reserve(commits.size()); - I_.resize(commits.size()); - for (std::size_t i = 0; i < commits.size(); i++) { - I_[i] = LelantusPrimitives::convert_to_nal(i, n, m); - } - - // Process all proofs - for (std::size_t t = 0; t < M; t++) { - SigmaExtendedProof proof = proofs[t]; - - // The challenge depends on whether or not we're in common mode - Scalar x; - if (commonChallenge) { - x = challenges[0]; - } - else { - x = challenges[t]; - } - - // Generate random verifier weights - Scalar w1, w2, w3; - w1.randomize(); - w2.randomize(); - w3.randomize(); - - // Reconstruct f-matrix - std::vector f_; - if (!compute_fs(proof, x, f_)) { - LogPrintf("Invalid matrix reconstruction"); - return false; - } - - // Effective set size - std::size_t setSize; - if (!specifiedSetSizes) { - setSize = commits.size(); - } - else { - setSize = setSizes[t]; - } - - // A, B, C, D (and associated commitments) - points.emplace_back(proof.A_); - scalars.emplace_back(w1.negate()); - points.emplace_back(proof.B_); - scalars.emplace_back(x.negate() * w1); - points.emplace_back(proof.C_); - scalars.emplace_back(x.negate() * w2); - points.emplace_back(proof.D_); - scalars.emplace_back(w2.negate()); - - g_scalar += proof.ZA_ * w1 + proof.ZC_ * w2; - for (std::size_t i = 0; i < m * n; i++) { - h_scalars[i] += f_[i] * (w1 + (x - f_[i]) * w2); - } - - // Input sets - h1_scalar += proof.zV_ * w3.negate(); - h2_scalar += proof.zR_ * w3.negate(); - - Scalar f_i(uint64_t(1)); - Scalar e; - std::vector::iterator ptr; - if (!specifiedSetSizes) { - ptr = commit_scalars.begin(); - compute_batch_fis(f_i, m, f_, w3, e, ptr, ptr, ptr + setSize - 1); - } - else { - ptr = commit_scalars.begin() + commits.size() - setSize; - compute_batch_fis(f_i, m, f_, w3, e, ptr, ptr, ptr + setSize - 1); - } - - Scalar pow(uint64_t(1)); - std::vector f_part_product; - for (std::ptrdiff_t j = m - 1; j >= 0; j--) { - f_part_product.push_back(pow); - pow *= f_[j*n + I_[setSize - 1][j]]; - } - - NthPower xj(x); - for (std::size_t j = 0; j < m; j++) { - Scalar fi_sum(uint64_t(0)); - for (std::size_t i = I_[setSize - 1][j] + 1; i < n; i++) - fi_sum += f_[j*n + i]; - pow += fi_sum * xj.pow * f_part_product[m - j - 1]; - xj.go_next(); - } - - commit_scalars[commits.size() - 1] += pow * w3; - e += pow; - - e *= serials[t] * w3.negate(); - g_scalar += e; - - NthPower x_k(x); - for (std::size_t k = 0; k < m; k++) { - points.emplace_back(proof.Gk_[k]); - scalars.emplace_back(x_k.pow.negate() * w3); - points.emplace_back(proof.Qk[k]); - scalars.emplace_back(x_k.pow.negate() * w3); - x_k.go_next(); - } - } - - // Add common generators - points.emplace_back(g_); - scalars.emplace_back(g_scalar); - points.emplace_back(h_[1]); - scalars.emplace_back(h1_scalar); - points.emplace_back(h_[0]); - scalars.emplace_back(h2_scalar); - for (std::size_t i = 0; i < m * n; i++) { - points.emplace_back(h_[i]); - scalars.emplace_back(h_scalars[i]); - } - for (std::size_t i = 0; i < commits.size(); i++) { - points.emplace_back(commits[i]); - scalars.emplace_back(commit_scalars[i]); - } - - // Verify the batch - secp_primitives::MultiExponent result(points, scalars); - if (result.get_multiple().isInfinity()) { - return true; - } - return false; -} - -bool SigmaExtendedVerifier::membership_checks(const SigmaExtendedProof& proof) const { - if (!(proof.A_.isMember() && - proof.B_.isMember() && - proof.C_.isMember() && - proof.D_.isMember()) || - (proof.A_.isInfinity() || - proof.B_.isInfinity() || - proof.C_.isInfinity() || - proof.D_.isInfinity())) - return false; - - for (std::size_t i = 0; i < proof.f_.size(); i++) - { - if (!proof.f_[i].isMember() || proof.f_[i].isZero()) - return false; - } - const std::vector & Gk = proof.Gk_; - const std::vector & Qk = proof.Qk; - for (std::size_t k = 0; k < m; ++k) - { - if (!(Gk[k].isMember() && Qk[k].isMember()) - || Gk[k].isInfinity() || Qk[k].isInfinity()) - return false; - } - if(!(proof.ZA_.isMember() && - proof.ZC_.isMember() && - proof.zV_.isMember() && - proof.zR_.isMember()) || - (proof.ZA_.isZero() || - proof.ZC_.isZero() || - proof.zV_.isZero() || - proof.zR_.isZero())) - return false; - return true; -} - -bool SigmaExtendedVerifier::compute_fs( - const SigmaExtendedProof& proof, - const Scalar& x, - std::vector& f_) const { - for (std::size_t j = 0; j < proof.f_.size(); ++j) { - if(proof.f_[j] == x) - return false; - } - - f_.reserve(n * m); - for (std::size_t j = 0; j < m; ++j) - { - f_.push_back(Scalar(uint64_t(0))); - Scalar temp; - std::size_t k = n - 1; - for (std::size_t i = 0; i < k; ++i) - { - temp += proof.f_[j * k + i]; - f_.emplace_back(proof.f_[j * k + i]); - } - f_[j * n] = x - temp; - } - return true; -} - -void SigmaExtendedVerifier::compute_fis(int j, const std::vector& f, std::vector& f_i_) const { - Scalar f_i(uint64_t(1)); - std::vector::iterator ptr = f_i_.begin(); - compute_fis(f_i, m, f, ptr, f_i_.end()); -} - -void SigmaExtendedVerifier::compute_fis( - const Scalar& f_i, - int j, - const std::vector& f, - std::vector::iterator& ptr, - std::vector::iterator end_ptr) const { - j--; - if (j == -1) - { - if (ptr < end_ptr) - *ptr++ += f_i; - return; - } - - Scalar t; - - for (std::size_t i = 0; i < n; i++) - { - t = f[j * n + i]; - t *= f_i; - - compute_fis(t, j, f, ptr, end_ptr); - } -} - -void SigmaExtendedVerifier::compute_batch_fis( - const Scalar& f_i, - int j, - const std::vector& f, - const Scalar& y, - Scalar& e, - std::vector::iterator& ptr, - std::vector::iterator start_ptr, - std::vector::iterator end_ptr) const { - j--; - if (j == -1) - { - if(ptr >= start_ptr && ptr < end_ptr){ - *ptr++ += f_i * y; - e += f_i; - } - return; - } - - Scalar t; - - for (std::size_t i = 0; i < n; i++) - { - t = f[j * n + i]; - t *= f_i; - - compute_batch_fis(t, j, f, y, e, ptr, start_ptr, end_ptr); - } -} - -} //namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/sigmaextended_verifier.h b/src/liblelantus/sigmaextended_verifier.h deleted file mode 100644 index f0521a41da..0000000000 --- a/src/liblelantus/sigmaextended_verifier.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SIGMAEXTENDED_VERIFIER_H -#define FIRO_LIBLELANTUS_SIGMAEXTENDED_VERIFIER_H - -#include "lelantus_primitives.h" - -namespace lelantus { - -class SigmaExtendedVerifier{ - -public: - SigmaExtendedVerifier(const GroupElement& g, - const std::vector& h_gens, - std::size_t n_, std::size_t m_); - - // Verify a single one-of-many proof - // In this case, there is an implied input set size - bool singleverify(const std::vector& commits, - const Scalar& x, - const Scalar& serial, - const SigmaExtendedProof& proof) const; - - // Verify a single one-of-many proof - // In this case, there is a specified set size - bool singleverify(const std::vector& commits, - const Scalar& x, - const Scalar& serial, - const size_t setSize, - const SigmaExtendedProof& proof) const; - - // Verify a batch of one-of-many proofs from the same transaction - // In this case, there is a single common challenge and implied input set size - bool batchverify(const std::vector& commits, - const Scalar& x, - const std::vector& serials, - const std::vector& proofs) const; - // Verify a general batch of one-of-many proofs - // In this case, each proof has a separate challenge and specified set size - bool batchverify(const std::vector& commits, - const std::vector& challenges, - const std::vector& serials, - const std::vector& setSizes, - const std::vector& proofs) const; - -private: - // Utility function that actually performs verification - bool verify(const std::vector& commits, - const std::vector& challenges, - const std::vector& serials, - const std::vector& setSizes, - const bool commonChallenge, - const bool specifiedSetSizes, - const std::vector& proofs) const; - //auxiliary functions - bool membership_checks(const SigmaExtendedProof& proof) const; - bool compute_fs( - const SigmaExtendedProof& proof, - const Scalar& x, - std::vector& f_) const; - - void compute_fis(int j, const std::vector& f, std::vector& f_i_) const; - void compute_fis( - const Scalar& f_i, - int j, - const std::vector& f, - std::vector::iterator& ptr, - std::vector::iterator end_ptr) const; - void compute_batch_fis( - const Scalar& f_i, - int j, - const std::vector& f, - const Scalar& y, - Scalar& e, - std::vector::iterator& ptr, - std::vector::iterator start_ptr, - std::vector::iterator end_ptr) const; - -private: - GroupElement g_; - std::vector h_; - std::size_t n; - std::size_t m; -}; - -} // namespace lelantus - -#endif //FIRO_LIBLELANTUS_SIGMAEXTENDED_VERIFIER_H diff --git a/src/liblelantus/spend_metadata.cpp b/src/liblelantus/spend_metadata.cpp deleted file mode 100644 index 64d78ca407..0000000000 --- a/src/liblelantus/spend_metadata.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "spend_metadata.h" - -namespace lelantus { - -SpendMetaData::SpendMetaData( - const std::map& groupBlockHashes, - const uint256& txHash) - : txHash(txHash) { - coinGroupIdAndBlockHash.reserve(groupBlockHashes.size()); - - for(const auto& idAndHash : groupBlockHashes) { - coinGroupIdAndBlockHash.emplace_back(idAndHash.first, idAndHash.second); - } -} - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/spend_metadata.h b/src/liblelantus/spend_metadata.h deleted file mode 100644 index 3141935722..0000000000 --- a/src/liblelantus/spend_metadata.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_SPENDMETADATA_H_ -#define FIRO_LIBLELANTUS_SPENDMETADATA_H_ - -#include "../uint256.h" -#include "../serialize.h" -#include "coin.h" - -namespace lelantus { - -class SpendMetaData { -public: - - SpendMetaData( - const std::map& groupBlockHashes, - const uint256& txHash); - - std::vector> coinGroupIdAndBlockHash; - - uint256 txHash; // The Hash of the rest of the transaction the spend proof is in. - - // Allows us to sign the transaction. - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(coinGroupIdAndBlockHash); - READWRITE(txHash); - } -}; - -} // namespace lelantus - -#endif // FIRO_LIBLELANTUS_SPENDMETADATA_H_ diff --git a/src/liblelantus/test/challenge_generator_tests.cpp b/src/liblelantus/test/challenge_generator_tests.cpp deleted file mode 100644 index 21e7fa5b5d..0000000000 --- a/src/liblelantus/test/challenge_generator_tests.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "lelantus_test_fixture.h" - -#include "../challenge_generator_impl.h" - -#include -#include - -#include - -namespace lelantus { - -class ChallengeGeneratorTests : public LelantusTestingSetup { -public: - ChallengeGeneratorImpl generator; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_challenge_generator_tests, ChallengeGeneratorTests) - -BOOST_AUTO_TEST_CASE(no_input) -{ - Scalar out; - - generator.get_challenge(out); - - // hash of empty - BOOST_CHECK_EQUAL( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - out.GetHex()); -} - -BOOST_AUTO_TEST_CASE(add_one_at_a_time) -{ - auto gs = GenerateGroupElements(2); - - generator.add(gs[0]); - generator.add(gs[1]); - - Scalar out; - generator.get_challenge(out); - - // hash of gs[0] and gs[1] - BOOST_CHECK_EQUAL( - "e89ba7cb6379a9b940ed9ed3cda18e0f2177019938fbac57e38e5e541e080fc3", - out.GetHex()); -} - -BOOST_AUTO_TEST_CASE(add_bulk) -{ - auto gs = GenerateGroupElements(2); - - generator.add(gs); - - Scalar out; - generator.get_challenge(out); - - // hash of gs[0] and gs[1] - BOOST_CHECK_EQUAL( - "e89ba7cb6379a9b940ed9ed3cda18e0f2177019938fbac57e38e5e541e080fc3", - out.GetHex()); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/coin_tests.cpp b/src/liblelantus/test/coin_tests.cpp deleted file mode 100644 index 6abc99b54d..0000000000 --- a/src/liblelantus/test/coin_tests.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "../coin.h" -#include "../params.h" -#include "../../version.h" -#include "../lelantus_primitives.h" - -#include "lelantus_test_fixture.h" - -#include "streams.h" - -#include - -namespace lelantus { - -BOOST_FIXTURE_TEST_SUITE(lelantus_coin_tests, lelantus::LelantusTestingSetup) - -BOOST_AUTO_TEST_CASE(privatecoin) -{ - auto params = Params::get_default(); - - uint64_t v = 1; - - // default constructor - PrivateCoin priv(params, v); - auto &serial = priv.getSerialNumber(); - auto &randomness = priv.getRandomness(); - std::vector seckey(32, 0); - - // calculate commitment - auto commitment = LelantusPrimitives::double_commit( - params->get_g(), serial, - params->get_h1(), priv.getV(), - params->get_h0(), randomness); - - // verify - BOOST_CHECK(serial.isMember()); - BOOST_CHECK(randomness.isMember()); - BOOST_CHECK_EQUAL(0, priv.getVersion()); - BOOST_CHECK_EQUAL(v, priv.getV()); - BOOST_CHECK_EQUAL(commitment, priv.getPublicCoin().getValue()); - - // Construct the identical coin by another constructor - PrivateCoin anotherPriv(params, serial, v, randomness, seckey, 0); - - // verify - BOOST_CHECK_EQUAL(serial, anotherPriv.getSerialNumber()); - BOOST_CHECK_EQUAL(randomness, anotherPriv.getRandomness()); - BOOST_CHECK_EQUAL(0, anotherPriv.getVersion()); - BOOST_CHECK_EQUAL(v, anotherPriv.getV()); - BOOST_CHECK_EQUAL(commitment, anotherPriv.getPublicCoin().getValue()); -} - -BOOST_AUTO_TEST_CASE(privcoin_getset) -{ - auto const params = Params::get_default(); - - PrivateCoin coin1(params, 1); - PrivateCoin coin2(params, 2); - - BOOST_CHECK(coin1.getPublicCoin() != coin2.getPublicCoin()); - BOOST_CHECK(coin1.getRandomness() != coin2.getRandomness()); - BOOST_CHECK(coin1.getSerialNumber() != coin2.getSerialNumber()); - BOOST_CHECK(coin1.getV() != coin2.getV()); - - // set - coin2.setPublicCoin(coin1.getPublicCoin()); - coin2.setRandomness(coin1.getRandomness()); - coin2.setSerialNumber(coin1.getSerialNumber()); - coin2.setV(coin1.getV()); - coin2.setVersion(10); - - // verify - BOOST_CHECK(coin1.getPublicCoin() == coin2.getPublicCoin()); - BOOST_CHECK(coin1.getRandomness() == coin2.getRandomness()); - BOOST_CHECK(coin1.getSerialNumber() == coin2.getSerialNumber()); - BOOST_CHECK(coin1.getV() == coin2.getV()); - BOOST_CHECK(10 == coin2.getVersion()); -} - -BOOST_AUTO_TEST_CASE(publiccoin_constructor) -{ - // default - PublicCoin defaultPub; - - BOOST_CHECK(defaultPub.getValue().isInfinity()); - - // specify commitment - GroupElement coin; - coin.randomize(); - PublicCoin pub(coin); - - BOOST_CHECK_EQUAL(coin, pub.getValue()); -} - -BOOST_AUTO_TEST_CASE(publiccoin_hash) -{ - // seed - std::array seed; // 32 bytes of 0s - std::fill(seed.begin(), seed.end(), 0); - - GroupElement r; - r.generate(seed.data()); - - PublicCoin coin(r); - - BOOST_CHECK(coin.validate()); - - // double hash of 7ed851c84a73fce904dc38fd709836dba446d875846522d4f016bba815a21fc10000 - // the result of generated group element using 32 bytes of 0 as seed - BOOST_CHECK_EQUAL( - "c3ca472773e3334f057875b8268dabd16dcda3d2b4cad3e924ff8c31affacf67", - coin.getValueHash().GetHex()); -} - -BOOST_AUTO_TEST_CASE(validate_publiccoin) -{ - // default pubcoin - BOOST_CHECK(!PublicCoin().validate()); - - // inf coin - GroupElement infCoin; - BOOST_CHECK(infCoin.isMember() && infCoin.isInfinity()); - BOOST_CHECK(!PublicCoin(infCoin).validate()); - - // valid coin - GroupElement coin; - coin.randomize(); - BOOST_CHECK(coin.isMember() && !coin.isInfinity()); - BOOST_CHECK(PublicCoin(coin).validate()); -} - -BOOST_AUTO_TEST_CASE(publiccoin_equality) -{ - GroupElement a, b; - a.randomize(); - b.randomize(); - - PublicCoin pubA(a); - PublicCoin pubB(b); - PublicCoin pubC(a); - - BOOST_CHECK(pubA == pubA); - BOOST_CHECK(pubA == pubC); - BOOST_CHECK(pubA != pubB); -} - -BOOST_AUTO_TEST_CASE(publiccoin_serialization) -{ - GroupElement coin; - coin.randomize(); - - PublicCoin pub(coin); - - CDataStream serialize(SER_NETWORK, PROTOCOL_VERSION); - serialize << pub; - - CDataStream deserialize( - std::vector(serialize.begin(), serialize.end()), - SER_NETWORK, PROTOCOL_VERSION); - - PublicCoin deserializedCoin; - deserialize >> deserializedCoin; - - BOOST_CHECK(pub == deserializedCoin); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/inner_product_test.cpp b/src/liblelantus/test/inner_product_test.cpp deleted file mode 100755 index e197bf8329..0000000000 --- a/src/liblelantus/test/inner_product_test.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "../innerproduct_proof_generator.h" -#include "../innerproduct_proof_verifier.h" -#include "../challenge_generator_impl.h" - -#include "./lelantus_test_fixture.h" - -#include - -namespace lelantus { - -class InnerProductTests : public LelantusTestingSetup { -public: - typedef InnerProductProofGenerator ProofGenerator; - typedef InnerProductProofVerifier ProofVerifier; - typedef InnerProductProof Proof; - -public: - InnerProductTests() {} - -public: - void Generate(size_t n) { - gens_g = RandomizeGroupElements(n); - gens_h = RandomizeGroupElements(n); - a = RandomizeScalars(n); - b = RandomizeScalars(n); - u.randomize(); - } - - Scalar ComputeC() const { - return Primitives::scalar_dot_product(a.begin(), a.end(), b.begin(), b.end()); - } - - GroupElement ComputePInit() const { - return ComputeMultiExponent(gens_g, a) + ComputeMultiExponent(gens_h, b); - } - - GroupElement ComputeP(Scalar const &x) const { - return ComputePInit() + u * ComputeC() * x; - } - -public: - std::vector gens_g; - std::vector gens_h; - std::vector a; - std::vector b; - GroupElement u; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_inner_product_tests, InnerProductTests) - -BOOST_AUTO_TEST_CASE(prove_verify_one) -{ - size_t n = 1; - size_t log2_n = 0; - - Generate(n); - - Scalar x; - x.randomize(); - std::unique_ptr challengeGenerator = std::make_unique>(1); - - // generating proofs - Proof proof; - ProofGenerator prover(gens_g, gens_h, u, 2); - prover.generate_proof(a, b, x, challengeGenerator, proof); - - BOOST_CHECK_EQUAL(ComputePInit(), prover.get_P()); - - // validate - BOOST_CHECK_EQUAL(ComputeC(), proof.c_); - BOOST_CHECK_EQUAL(a.front(), proof.a_); - BOOST_CHECK_EQUAL(b.front(), proof.b_); - BOOST_CHECK_EQUAL(log2_n, proof.L_.size()); - BOOST_CHECK_EQUAL(log2_n, proof.R_.size()); - - // verify - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify(x, proof, challengeGenerator)); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify_fast(n, x, proof, challengeGenerator)); -} - -BOOST_AUTO_TEST_CASE(prove_verify) -{ - size_t n = 32; - size_t log2_n = 5; - - Generate(n); - - Scalar x; - x.randomize(); - std::unique_ptr challengeGenerator = std::make_unique>(1); - - // generating proofs - Proof proof; - ProofGenerator prover(gens_g, gens_h, u, 2); - prover.generate_proof(a, b, x, challengeGenerator, proof); - - BOOST_CHECK_EQUAL(ComputePInit(), prover.get_P()); - - // validate - BOOST_CHECK_EQUAL(ComputeC(), proof.c_); - BOOST_CHECK_EQUAL(log2_n, proof.L_.size()); - BOOST_CHECK_EQUAL(log2_n, proof.R_.size()); - - // verify - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify(x, proof, challengeGenerator)); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify_fast(n, x, proof, challengeGenerator)); -} - -BOOST_AUTO_TEST_CASE(fake_proof_not_verify) -{ - size_t n = 32; - - // generating needed objects - Generate(n); - - Scalar x; - x.randomize(); - std::unique_ptr challengeGenerator = std::make_unique>(1); - - // generating genertor - Proof proof; - ProofGenerator(gens_g, gens_h, u, 2).generate_proof(a, b, x, challengeGenerator, proof); - - // verify with fake P - GroupElement fakeP; - fakeP.randomize(); - - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!ProofVerifier(gens_g, gens_h, u, fakeP, 2).verify(x, proof, challengeGenerator)); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!ProofVerifier(gens_g, gens_h, u, fakeP, 2).verify_fast(n, x, proof, challengeGenerator)); - - // verify with fake proof - auto verify = [&](Scalar const &_x, Proof const &_p) -> void { - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify(_x, _p, challengeGenerator)); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!ProofVerifier(gens_g, gens_h, u, ComputePInit(), 2).verify_fast(n, _x, _p, challengeGenerator)); - }; - - auto fakeProof = proof; - fakeProof.a_.randomize(); - verify(x, fakeProof); - - fakeProof = proof; - fakeProof.b_.randomize(); - verify(x, fakeProof); - - fakeProof = proof; - fakeProof.c_.randomize(); - verify(x, fakeProof); - - fakeProof = proof; - fakeProof.L_[0].randomize(); - verify(x, fakeProof); - - fakeProof = proof; - fakeProof.R_[0].randomize(); - verify(x, fakeProof); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/joinsplit_tests.cpp b/src/liblelantus/test/joinsplit_tests.cpp deleted file mode 100644 index a3bb30d7e7..0000000000 --- a/src/liblelantus/test/joinsplit_tests.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "lelantus_test_fixture.h" - -#include "../openssl_context.h" -#include "../joinsplit.h" - -#include -#include - -namespace lelantus { - -class JoinSplitTests : public LelantusTestingSetup -{ -public: - JoinSplitTests() - { - } - -public: - std::vector GenerateCoins(std::vector const &amounts) { - std::vector privs; - - for (auto a : amounts) { - std::vector ecdsaKey; - ecdsaKey.resize(32); - - // Create a key pair - secp256k1_pubkey pubkey; - do { - if (RAND_bytes(ecdsaKey.data(), ecdsaKey.size()) != 1) { - throw std::runtime_error("Unable to generate randomness"); - } - } while (!secp256k1_ec_pubkey_create( - OpenSSLContext::get_context(), &pubkey, ecdsaKey.data())); - - // Hash the public key in the group to obtain a serial number - auto serial = PrivateCoin::serialNumberFromSerializedPublicKey( - OpenSSLContext::get_context(), &pubkey); - - Scalar randomness; - randomness.randomize(); - - privs.emplace_back(params, serial, a, randomness, ecdsaKey, LELANTUS_TX_VERSION_4); - } - - return privs; - } - - std::vector BuildPublicCoins(std::vector const &es) { - std::vector pubs; - pubs.reserve(es.size()); - - for (auto const &e : es) { - pubs.emplace_back(e); - } - - return pubs; - } -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_joinsplit_tests, JoinSplitTests) - -BOOST_AUTO_TEST_CASE(verify) -{ - auto privs = GenerateCoins({1 * COIN, 10 * COIN, 100 * COIN, 99 * COIN}); - std::vector> cin = { - {privs[0], 1}, - {privs[1], 1}, - {privs[2], 2} - }; - - std::map> anons = { - {1, BuildPublicCoins(GenerateGroupElements(10))}, - {2, BuildPublicCoins(GenerateGroupElements(10))}, - }; - - anons[1][0] = privs[0].getPublicCoin(); - anons[1][1] = privs[1].getPublicCoin(); - anons[2][0] = privs[2].getPublicCoin(); - - std::map groupBlockHashes = { - {1, ArithToUint256(1)}, - {2, ArithToUint256(3)}, - }; - - // inputs = 111 - // outputs = 0.01(fee) + 99(mint) + 11.99(vout) - auto vout = 12 * COIN - CENT; - JoinSplit joinSplit( - params, - cin, - anons, - {}, - vout, // vout - {privs[3]}, // cout - CENT, // fee - groupBlockHashes, - ArithToUint256(3), - 0); - - std::vector expectedGroupIds = {1, 1, 2}; - BOOST_CHECK(expectedGroupIds == joinSplit.getCoinGroupIds()); - BOOST_CHECK(joinSplit.Verify(anons, {}, {privs[3].getPublicCoin()}, vout, ArithToUint256(3))); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/lelantus_primitives_tests.cpp b/src/liblelantus/test/lelantus_primitives_tests.cpp deleted file mode 100644 index f0d1aed34c..0000000000 --- a/src/liblelantus/test/lelantus_primitives_tests.cpp +++ /dev/null @@ -1,275 +0,0 @@ -#include "lelantus_test_fixture.h" - -#include "../lelantus_primitives.h" -#include "../challenge_generator_impl.h" -#include "../../test/test_bitcoin.h" -#include "../../utilstrencodings.h" - -#include -#include - -namespace lelantus { - -typedef LelantusPrimitives Primitives; - -BOOST_FIXTURE_TEST_SUITE(lelantus_primitives_tests, LelantusTestingSetup) - -BOOST_AUTO_TEST_CASE(generate_challenge) -{ - auto gs0 = GenerateGroupElements(10); - auto gs1 = GenerateGroupElements(5); - - secp_primitives::Scalar s0, s1; - Primitives::generate_challenge(gs0, "", s0); - Primitives::generate_challenge(gs1, "", s1); - - BOOST_CHECK_EQUAL( - "7486c200ca76a53a40715a64982705276181c4c8fe6335425607ddc696ca739f", - s0.GetHex()); - - BOOST_CHECK_EQUAL( - "b2eb32c975a2bde90afa85812aea832744f7905759b4edf56b10cf420f848b1e", - s1.GetHex()); -} - -BOOST_AUTO_TEST_CASE(multi_commit) -{ - auto h = GenerateGroupElements(3); - auto g = h.back(); - h.resize(2); - - std::vector exps; - for (int i = 0; i != 3; i++) { - exps.emplace_back(i + 10); - } - auto r = exps.back(); - exps.resize(2); - - GroupElement out; - Primitives::commit(g, h, exps, r, out); - - BOOST_CHECK_EQUAL( - "(6185bcfc7e56b1a66b6a64176c5474befa11047acac0d96043399d729c2523e4," - "4d1930b2ef5d097e0c6f390f7b3818419131a43a15b5d699c9ece70ef162683a)", - out.GetHex()); -} - -BOOST_AUTO_TEST_CASE(commit) -{ - auto gs = GenerateGroupElements(2); - auto g = gs[0]; - auto h = gs[1]; - - Scalar m(10), r(11); - - auto commitment = Primitives::commit(g, m, h, r); - BOOST_CHECK_EQUAL( - "(ef2c9e683a4985e7435993d7b6637254aa5acbd20501aa896815976d35d7bb6e," - "5e84e985869a661f9241dfff097c28eb9deda97fc945f27eadf8adea8c6ae929)", - commitment.GetHex()); -} - -BOOST_AUTO_TEST_CASE(double_commit) -{ - auto gs = GenerateGroupElements(3); - auto g = gs[0]; - auto hV = gs[1]; - auto hR = gs[2]; - - Scalar m(10), v(11), r(12); - - auto commitment = Primitives::double_commit(g, m, hV, v, hR, r); - BOOST_CHECK_EQUAL( - "(6185bcfc7e56b1a66b6a64176c5474befa11047acac0d96043399d729c2523e4," - "4d1930b2ef5d097e0c6f390f7b3818419131a43a15b5d699c9ece70ef162683a)", - commitment.GetHex()); -} - -BOOST_AUTO_TEST_CASE(convert_to_sigma) -{ - uint64_t n = 4, m = 6; - uint64_t num = - 2 + n + 0 + - 3 * std::pow(n, 3) + - 2 * std::pow(n, 4) + - 1 * std::pow(n, 5); - - std::vector rawExpected = - { - 0, 0, 1, 0, - 0, 1, 0, 0, - 1, 0, 0, 0, - 0, 0, 0, 1, - 0, 0, 1, 0, - 0, 1, 0, 0 - }; - - Scalar one(1), zero(uint64_t(0)); - std::vector expected; - expected.reserve(rawExpected.size()); - for (auto const &d : rawExpected) { - expected.push_back(d == 0 ? zero : one); - } - - std::vector out; - Primitives::convert_to_sigma(num, n, m, out); - - BOOST_CHECK(expected == out); -} - -BOOST_AUTO_TEST_CASE(convert_to_nal) -{ - uint64_t n = 4, m = 6; - uint64_t num = - 2 + 3 * n + 0 + - 1 * std::pow(n, 3) + - 2 * std::pow(n, 4); - - std::vector expected = {2, 3, 0, 1, 2, 0}; - auto result = Primitives::convert_to_nal(num, n, m); - - BOOST_CHECK_EQUAL_COLLECTIONS( - expected.begin(), expected.end(), - result.begin(), result.end()); -} - -BOOST_AUTO_TEST_CASE(generate_lelantus_challenge) -{ - std::vector proofs(2); - auto gs = GenerateGroupElements(proofs.size() * 8); - auto it = gs.begin(); - - for (auto &proof : proofs) { - proof.A_ = *it++; - proof.B_ = *it++; - proof.C_ = *it++; - proof.D_ = *it++; - proof.Gk_ = {*it++, *it++}; - proof.Qk = {*it++, *it++}; - } - - Scalar out; - std::unique_ptr challengeGenerator = std::make_unique>(); - Primitives::generate_Lelantus_challenge(proofs, {}, {}, {}, {}, 0, challengeGenerator, out); - - BOOST_CHECK_EQUAL( - "0739d8484b29d53410510c38ffd5b6a43187fa0775175f97d12c61e81147245b", - out.GetHex()); -} - -BOOST_AUTO_TEST_CASE(new_factor) -{ - std::vector coefs = {6, 5, 4, 3, 2, 1}; - Scalar x = 2, a = 4; - - std::vector expected = {24, 32, 26, 20, 14, 8, 2}; - - Primitives::new_factor(x, a, coefs); - - BOOST_CHECK_EQUAL_COLLECTIONS( - expected.begin(), expected.end(), - coefs.begin(), coefs.end()); -} - -BOOST_AUTO_TEST_CASE(commit_two_generators) -{ - auto items = GenerateGroupElements(5); - auto h = items[0]; - std::vector g_(items.begin() + 1, items.begin() + 3); - std::vector h_(items.begin() + 3, items.end()); - - Scalar exp = 10; - std::vector l = {Scalar(11), Scalar(12)}; - std::vector r = {Scalar(13), Scalar(14)}; - - GroupElement out; - Primitives::commit(h, exp, g_, l, h_, r, out); - - BOOST_CHECK_EQUAL( - "(ab6a35cc172ce38d52ad2b5eeda2de4d2c22d18a7dd8bfadbcb839c368a1d8d,75e7dd7a4e098d5eb8f46ad57eb72da067ed826d307d4bff9faea1715d58289a)", - out.GetHex()); -} - -BOOST_AUTO_TEST_CASE(scalar_dot_product) -{ - Scalar x0 = 10, x1 = 20, x2 = 30; - Scalar y0 = 11, y1 = 21, y2 = 31; - - std::vector x = {x0, x1, x2}; - std::vector y = {y0, y1, y2}; - - auto result = Primitives::scalar_dot_product(x.begin(), x.end(), y.begin(), y.end()); - - BOOST_CHECK_EQUAL(x0 * y0 + x1 * y1 + x2 * y2, result); -} - -BOOST_AUTO_TEST_CASE(g_prime) -{ - auto gs = GenerateGroupElements(4); - Scalar x(10); - - auto raw0 = ParseHex("1eddd58977aab5a476bc82ea0f48e11fa5a79419476ddfe5b764540eac7947000100"); - auto raw1 = ParseHex("72771902e20dda39200116e694385fc520b82150fcf8cff12671cdcff68e23f70000"); - std::vector expected(2); - expected[0].deserialize(raw0.data()); - expected[1].deserialize(raw1.data()); - - std::vector result; - Primitives::g_prime(gs, x, result); - - BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), result.begin(), result.end()); -} - -BOOST_AUTO_TEST_CASE(h_prime) -{ - auto h = GenerateGroupElements(4); - Scalar x(10); - - auto raw0 = ParseHex("289d10cc8571a829839c9df9607fc5cc38c06f4d4a0dba069fc384bffc8ae3560000"); - auto raw1 = ParseHex("ceb87b21b9be8161c6200bbd54931582d586e4075819f601bb685b26e7707e280100"); - std::vector expected(2); - expected[0].deserialize(raw0.data()); - expected[1].deserialize(raw1.data()); - - std::vector result; - Primitives::h_prime(h, x, result); - - BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), result.begin(), result.end()); -} - -BOOST_AUTO_TEST_CASE(p_prime) -{ - auto gs = GenerateGroupElements(3); - auto P = gs[0]; - auto L = gs[1]; - auto R = gs[2]; - - Scalar x(10); - - auto result = Primitives::p_prime(P, L, R, x); - - auto raw = ParseHex("7fb5138c96f0e994eb2fca28480e5d6e572fa07ed1b49b6d87d1e475164cac900100"); - GroupElement expected; - expected.deserialize(raw.data()); - - BOOST_CHECK_EQUAL(expected, result); -} - -BOOST_AUTO_TEST_CASE(delta) -{ - Scalar y = 100, z = 200, one = 1, two = 2; - uint64_t n = 4, m = 6; - - auto y_ = (y.exponent(m * n) - 1) * ((y - 1).inverse()); // 1 + y + y^2 + ... + y^(mn - 1) - auto z_ = (z.exponent(m + 3) - 1) * ((z - 1).inverse()) - 1 - z - z * z; // 1 + z + z^2 + z^3 + ... + z^m+2 - 1 - z - z^2 - auto two_ = (two.exponent(n) - 1); // 1 + 2 + 2^2 + ... + 2^(n-1) - - auto expected = (z - z * z) * y_ - z_ * two_; - auto result = Primitives::delta(y, z, n, m); - - BOOST_CHECK_EQUAL(expected, result); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/lelantus_test.cpp b/src/liblelantus/test/lelantus_test.cpp deleted file mode 100644 index 35e856987b..0000000000 --- a/src/liblelantus/test/lelantus_test.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "lelantus_test_fixture.h" - -#include "../lelantus_prover.h" -#include "../lelantus_verifier.h" - -#include - -namespace lelantus { - -class ProtocolTests : public LelantusTestingSetup -{ -public: - ProtocolTests() - : m_params(Params::get_default()) - { - } - -public: - std::vector ExtractPublicCoins(std::vector const &coins) const { - std::vector pubs; - pubs.reserve(coins.size()); - - for (auto const &c : coins) { - pubs.push_back(c.getPublicCoin()); - } - - return pubs; - } - - std::vector ExtractSerials( - size_t anonymitySets, - std::vector> const &Cin, - std::vector& groupIds) const { - std::vector serials; - for (auto const &in : Cin) { - serials.push_back(in.first.getSerialNumber()); - groupIds.push_back(in.second); - } - - return serials; - } - - std::map> GenerateAnonymitySets(std::initializer_list sizes) const { - std::map> sets; - - uint32_t id = 0; - for (size_t s : sizes) { - std::vector set; - GenerateGroupElements(s, std::back_inserter(set)); - sets[id] = set; - id++; - } - - return sets; - } - -public: - Params const *m_params; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_protocol_tests, ProtocolTests) - -BOOST_AUTO_TEST_CASE(prove_verify) -{ - size_t N = 100; - - uint64_t v1(5); - PrivateCoin input_coin1(m_params ,v1); - std::vector> Cin = {{input_coin1, 0}}; - - std::vector indexes = {0}; - - auto anonymity_sets = GenerateAnonymitySets({N}); - anonymity_sets[0][0] = Cin[0].first.getPublicCoin(); - - Scalar Vin(5); - uint64_t Vout(6); - std::vector Cout = {{m_params, 2}, {m_params, 1}}; - - uint64_t f(1); - LelantusProof proof; - SchnorrProof qkSchnorrProof; - - LelantusProver prover(m_params, LELANTUS_TX_VERSION_4_5); - prover.proof(anonymity_sets, {}, Vin, Cin, indexes, {}, Vout, Cout, f, proof, qkSchnorrProof); - - std::vector groupIds; - auto Sin = ExtractSerials(anonymity_sets.size(), Cin, groupIds); - auto Cout_Public = ExtractPublicCoins(Cout); - - lelantus::LelantusVerifier verifier(m_params, LELANTUS_TX_VERSION_4_5); - BOOST_CHECK(verifier.verify(anonymity_sets, {}, Sin, {}, groupIds, Vin, Vout, f, Cout_Public, proof, qkSchnorrProof)); -} - -BOOST_AUTO_TEST_CASE(prove_verify_many_coins) -{ - FIRO_UNUSED size_t N = 100; - - PrivateCoin input1(m_params ,2), input2(m_params, 2), input3(m_params, 1); - std::vector> Cin = { - {input1, 0}, {input2, 0}, {input3, 1} - }; - - std::vector indexes = {0, 1, 0}; - - auto anonymity_sets = GenerateAnonymitySets({N, N}); - anonymity_sets[0][0] = Cin[0].first.getPublicCoin(); - anonymity_sets[0][1] = Cin[1].first.getPublicCoin(); - anonymity_sets[1][0] = Cin[2].first.getPublicCoin(); - - Scalar Vin(5); - uint64_t Vout(6), f(1); - std::vector Cout = {{m_params, 2}, {m_params, 1}}; - - LelantusProof proof; - SchnorrProof qkSchnorrProof; - - LelantusProver prover(m_params, LELANTUS_TX_VERSION_4_5); - prover.proof(anonymity_sets, {}, Vin, Cin, indexes, {}, Vout, Cout, f, proof, qkSchnorrProof); - - std::vector groupIds; - auto Sin = ExtractSerials(anonymity_sets.size(), Cin, groupIds); - auto Cout_Public = ExtractPublicCoins(Cout); - - lelantus::LelantusVerifier verifier(m_params, LELANTUS_TX_VERSION_4_5); - BOOST_CHECK(verifier.verify(anonymity_sets, {}, Sin, {}, groupIds, Vin, Vout, f, Cout_Public, proof, qkSchnorrProof)); - //After Lelantus new update (after version LELANTUS_TX_VERSION_4_5) following 2 verifications will fail, as schnorr proof challenge depends also on Vout, Vin and fee values -// BOOST_CHECK(verifier.verify(anonymity_sets, {}, Sin, {}, groupIds, Vin + 1, Vout + 1, f, Cout_Public, proof)); -// BOOST_CHECK(verifier.verify(anonymity_sets, {}, Sin, {}, groupIds, Vin, Vout + f, uint64_t(0), Cout_Public, proof)); -} - -BOOST_AUTO_TEST_CASE(imbalance_proof_should_fail) -{ - size_t N = 100; - - // Input - PrivateCoin p1(m_params, 3); - std::vector> Cin = {{p1, 0}}; - - std::vector indexs = {0}; - - auto anonymitySets = GenerateAnonymitySets({N}); - anonymitySets[0][0] = Cin[0].first.getPublicCoin(); - - Scalar Vin(2); // Use this to verify - Scalar FakeVin(4); // Use this to generate proof - - // Output - std::vector Cout = {{m_params, 3}}; - uint64_t Vout(3); - uint64_t f(1); - - // Proof - LelantusProof proof; - SchnorrProof qkSchnorrProof; - - // Should be prevent from prover - LelantusProver prover(m_params, LELANTUS_TX_VERSION_4_5); - BOOST_CHECK_THROW(prover.proof(anonymitySets, {}, Vin, Cin, indexs, {}, Vout, Cout, f, proof, qkSchnorrProof), std::runtime_error); - - // Use fake vin - prover.proof(anonymitySets, {}, FakeVin, Cin, indexs, {}, Vout, Cout, f, proof, qkSchnorrProof); - - // Verify - std::vector groupIds; - auto Sin = ExtractSerials(anonymitySets.size(), Cin, groupIds); - auto publicCoins = ExtractPublicCoins(Cout); - - LelantusVerifier verifier(m_params, LELANTUS_TX_VERSION_4_5); - - // input: 2 + 3(anonymous), output: 3 + 3(anonymous) + 1(fee) - BOOST_CHECK(!verifier.verify(anonymitySets, {}, Sin, {}, groupIds, Vin, Vout, f, publicCoins, proof, qkSchnorrProof)); - - // Verify with output which is less than input also should fail - // input: 99 + 3(anonymous), output: 3 + 3(anonymous) + 1(fee) - Scalar newVin(99); - BOOST_CHECK(!verifier.verify(anonymitySets, {}, Sin, {}, groupIds, newVin, Vout, f, publicCoins, proof, qkSchnorrProof)); -} - -BOOST_AUTO_TEST_CASE(other_fail_to_validate) -{ - size_t N = 100; - - // Input - PrivateCoin p1(m_params, 1), p2(m_params, 2); - std::vector> Cin = {{p1, 0}, {p2, 0}}; - - std::vector indexs = {0, 1}; - - auto anonymitySets = GenerateAnonymitySets({N}); - anonymitySets[0][0] = Cin[0].first.getPublicCoin(); - anonymitySets[0][1] = Cin[1].first.getPublicCoin(); - - Scalar Vin(4); - - // Output - std::vector Cout = {{m_params, 1}, {m_params, 2}}; - uint64_t Vout(3); - uint64_t f(1); - - // Proof - LelantusProof proof; - SchnorrProof qkSchnorrProof; - - // Should be prevent from prover - LelantusProver prover(m_params, LELANTUS_TX_VERSION_4_5); - prover.proof(anonymitySets, {}, Vin, Cin, indexs, {}, Vout, Cout, f, proof, qkSchnorrProof); - - // Verify - std::vector groupIds; - auto Sin = ExtractSerials(anonymitySets.size(), Cin, groupIds); - auto publicCoins = ExtractPublicCoins(Cout); - - LelantusVerifier verifier(m_params, LELANTUS_TX_VERSION_4_5); - - BOOST_CHECK(verifier.verify(anonymitySets, {}, Sin, {}, groupIds, Vin, Vout, f, publicCoins, proof, qkSchnorrProof)); - - // Invalid group - auto invalidAnonymitySets = anonymitySets; - invalidAnonymitySets[0].pop_back(); - BOOST_CHECK(!verifier.verify(invalidAnonymitySets, {}, Sin, {}, groupIds, Vin, Vout, f, publicCoins, proof, qkSchnorrProof)); - - invalidAnonymitySets = anonymitySets; - invalidAnonymitySets[0].push_back(PrivateCoin(m_params, 1).getPublicCoin()); - BOOST_CHECK(!verifier.verify(invalidAnonymitySets, {}, Sin, {}, groupIds, Vin, Vout, f, publicCoins, proof, qkSchnorrProof)); - - // Invalid serial - auto invalidSin = Sin; - invalidSin[1].randomize(); - BOOST_CHECK(!verifier.verify(anonymitySets, {}, invalidSin, {}, groupIds, Vin, Vout, f, publicCoins, proof, qkSchnorrProof)); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/lelantus_test_fixture.cpp b/src/liblelantus/test/lelantus_test_fixture.cpp deleted file mode 100644 index 7af6c76121..0000000000 --- a/src/liblelantus/test/lelantus_test_fixture.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "lelantus_test_fixture.h" - -namespace lelantus { - -std::vector LelantusTestingSetup::GenerateGroupElements(size_t size) const { - std::vector gs; - GenerateGroupElements(size, std::back_inserter(gs)); - - return gs; -} - -std::vector LelantusTestingSetup::RandomizeGroupElements(size_t size) const { - std::vector gs(size); - for (auto &g : gs) { - g.randomize(); - } - - return gs; -} - -std::vector LelantusTestingSetup::RandomizeScalars(size_t size) const { - std::vector ss(size); - for (auto &s : ss) { - s.randomize(); - } - - return ss; -} - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/lelantus_test_fixture.h b/src/liblelantus/test/lelantus_test_fixture.h deleted file mode 100644 index 762112d9c1..0000000000 --- a/src/liblelantus/test/lelantus_test_fixture.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef FIRO_LIBLELANTUS_TEST_FIXTURE_H -#define FIRO_LIBLELANTUS_TEST_FIXTURE_H - -#include "../../test/test_bitcoin.h" - -#include "../lelantus_primitives.h" - -#include - -namespace lelantus { - -class LelantusTestingSetup : public BasicTestingSetup { -protected: - typedef LelantusPrimitives Primitives; - -public: - LelantusTestingSetup(const std::string& chainName = CBaseChainParams::MAIN) - : BasicTestingSetup(chainName), params(Params::get_default()) { - } - -public: - GroupElement ComputeMultiExponent(std::vector const &gs, std::vector const &s) const { - return secp_primitives::MultiExponent(gs, s).get_multiple(); - } - - template - void GenerateGroupElements(size_t size, Output output) const { - - std::array seed; - std::fill(seed.begin(), seed.end(), 0); - - for (size_t i = 0; i != size; i++) { - // 'LE' - std::copy( - reinterpret_cast(&i), - reinterpret_cast(&i) + sizeof(i), - seed.begin()); - - GroupElement e; - e.generate(seed.begin()); - - if (!e.isMember() || e.isInfinity()) { - throw std::runtime_error("Fail to generate group elements"); - } - - *output++ = e; - } - } - - // Generate group elements deterministically - std::vector GenerateGroupElements(size_t size) const; - std::vector RandomizeGroupElements(size_t size) const; - std::vector RandomizeScalars(size_t size) const; -public: - Params const *params; -}; - -} // namespace lelantus - -#endif // FIRO_LIBLELANTUS_TEST_FIXTURE_H \ No newline at end of file diff --git a/src/liblelantus/test/range_proof_test.cpp b/src/liblelantus/test/range_proof_test.cpp deleted file mode 100644 index 58794f0987..0000000000 --- a/src/liblelantus/test/range_proof_test.cpp +++ /dev/null @@ -1,346 +0,0 @@ -#include "lelantus_test_fixture.h" - -#include "../range_prover.h" -#include "../range_verifier.h" - -#include - -namespace lelantus { - -// All versions to be tested -unsigned int test_versions[] = { - LELANTUS_TX_VERSION_4, - SIGMA_TO_LELANTUS_JOINSPLIT, - LELANTUS_TX_VERSION_4_5, - SIGMA_TO_LELANTUS_JOINSPLIT_FIXED, - LELANTUS_TX_TPAYLOAD, - SIGMA_TO_LELANTUS_TX_TPAYLOAD -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_range_proof_tests, LelantusTestingSetup) - -// A single valid aggregated range proof -BOOST_AUTO_TEST_CASE(prove_verify_single) -{ - // Parameters - std::size_t n = 64; - std::size_t max_m = 8; - - // Generators - secp_primitives::GroupElement g_gen, h_gen1, h_gen2; - g_gen.randomize(); - h_gen1.randomize(); - h_gen2.randomize(); - - auto prove_verify = [&] (std::size_t m, unsigned int version) { - auto g_ = RandomizeGroupElements(n * m); - auto h_ = RandomizeGroupElements(n * m); - - // Input data - auto serials = RandomizeScalars(m); - auto randoms = RandomizeScalars(m); - - std::vector v_s; - std::vector V; - for (std::size_t i = 0; i < m; ++i){ - v_s.emplace_back(i); - V.push_back(g_gen * v_s.back() + h_gen1 * randoms[i] + h_gen2 * serials[i]); - } - - // Prove - RangeProver rangeProver(g_gen, h_gen1, h_gen2, g_, h_, n, version); - RangeProof proof; - rangeProver.proof(v_s, serials, randoms, V, proof); - - // Verify - RangeVerifier rangeVerifier(g_gen, h_gen1, h_gen2, g_, h_, n, version); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - }; - - // Test powers of 2 - std::size_t i = 1; - while (i <= max_m) { - for (auto version : test_versions) - prove_verify(i, version); - i *= 2; - } - -} - -// A batch of valid aggregated range proofs of different size -BOOST_AUTO_TEST_CASE(prove_verify_batch) -{ - // Parameters - const std::size_t n = 64; - const std::vector m = {1,2,4,8}; - - // Generators - secp_primitives::GroupElement g_gen, h_gen1, h_gen2; - g_gen.randomize(); - h_gen1.randomize(); - h_gen2.randomize(); - std::size_t max_m = *std::max_element(m.begin(), m.end()); - auto g_ = RandomizeGroupElements(n * max_m); - auto h_ = RandomizeGroupElements(n * max_m); - - for (auto version : test_versions) - { - // Proofs - std::vector > V_batch; - V_batch.reserve(m.size()); - std::vector proof_batch; - proof_batch.reserve(m.size()); - for (std::size_t i = 0; i < m.size(); i++) { - RangeProver rangeProver(g_gen, h_gen1, h_gen2, std::vector(g_.begin(), g_.begin() + n * m[i]), std::vector(h_.begin(), h_.begin() + n * m[i]), n, version); - - // Input data - auto serials = RandomizeScalars(m[i]); - auto randoms = RandomizeScalars(m[i]); - - std::vector v_s; - std::vector V; - for (std::size_t j = 0; j < m[i]; ++j){ - v_s.emplace_back(j); - V.push_back(g_gen * v_s.back() + h_gen1 * randoms[j] + h_gen2 * serials[j]); - } - - // Prove - RangeProof proof; - rangeProver.proof(v_s, serials, randoms, V, proof); - V_batch.emplace_back(V); - proof_batch.emplace_back(proof); - } - - // Verify - RangeVerifier rangeVerifier(g_gen, h_gen1, h_gen2, g_, h_, n, version); - BOOST_CHECK(rangeVerifier.verify(V_batch, V_batch, proof_batch)); - } -} - -// A single out-of-range aggregated range proof -BOOST_AUTO_TEST_CASE(out_of_range_single_proof) -{ - // Parameters - std::size_t n = 4; - std::size_t m = 4; - - // Generators - secp_primitives::GroupElement g_gen, h_gen1, h_gen2; - g_gen.randomize(); - h_gen1.randomize(); - h_gen2.randomize(); - auto g_ = RandomizeGroupElements(n * m); - auto h_ = RandomizeGroupElements(n * m); - - // Input data - auto randoms = RandomizeScalars(m); - auto serials = RandomizeScalars(m); - - auto testF = [&] (std::vector const v_s, unsigned int version) { - std::vector V; - for (std::size_t i = 0; i < m; ++i) { - V.push_back(g_gen * v_s[i] + h_gen1 * randoms[i] + h_gen2 * serials[i]); - } - - lelantus::RangeProver rangeProver(g_gen, h_gen1, h_gen2, g_, h_, n, version); - lelantus::RangeProof proof; - rangeProver.proof(v_s, serials, randoms, V, proof ); - - lelantus::RangeVerifier rangeVerifier(g_gen, h_gen1, h_gen2, g_, h_, n, version); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - }; - - for (auto version : test_versions) - { - // All values are out of range - std::vector vs; - for(std::size_t i = 0; i < m; ++i){ - vs.emplace_back((1 << n) + i); - } - testF(vs, version); - - // [0, 2 ^ n - 1] - Scalar l(uint64_t(0)); - Scalar r((1 << n) - 1); - - // One value is out of range - vs = {l, l + 1, r, r + 1}; - testF(vs, version); - - vs = {l - 1, l, r - 1, r}; - testF(vs, version); - } -} - -// A single aggreated range proof, with successively invalid proof elements -BOOST_AUTO_TEST_CASE(invalid_elements) -{ - // Parameters - std::size_t n = 64; - std::size_t m = 4; - - // Generators - secp_primitives::GroupElement g_gen, h_gen1, h_gen2; - g_gen.randomize(); - h_gen1.randomize(); - h_gen2.randomize(); - auto g_ = RandomizeGroupElements(n * m); - auto h_ = RandomizeGroupElements(n * m); - - // Inputs - auto randoms = RandomizeScalars(m); - auto serials = RandomizeScalars(m); - - // Set up valid proof - std::vector v_s; - std::vector V; - for(std::size_t i = 0; i < m; ++i){ - v_s.emplace_back(i); - V.push_back(g_gen * v_s.back() + h_gen1 * randoms[i] + h_gen2 * serials[i]); - } - - for (auto version : test_versions) - { - // Initial correctness check - RangeProver rangeProver(g_gen, h_gen1, h_gen2, g_, h_, n, version); - RangeProof proof; - rangeProver.proof(v_s, serials, randoms, V, proof); - RangeVerifier rangeVerifier(g_gen, h_gen1, h_gen2, g_, h_, n, version); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - // Invalidate successive values and then restore them - GroupElement group; - Scalar scalar; - - group = GroupElement(proof.A); - proof.A.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.A = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - group = GroupElement(proof.S); - proof.S.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.S = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - group = GroupElement(proof.T1); - proof.T1.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.T1 = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - group = GroupElement(proof.T2); - proof.T2.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.T2 = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - for (std::size_t j = 0; j < proof.innerProductProof.L_.size(); j++) { - group = GroupElement(proof.innerProductProof.L_[j]); - proof.innerProductProof.L_[j].randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.innerProductProof.L_[j] = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - group = GroupElement(proof.innerProductProof.R_[j]); - proof.innerProductProof.R_[j].randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.innerProductProof.R_[j] = GroupElement(group); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - } - - scalar = Scalar(proof.T_x1); - proof.T_x1.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.T_x1 = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - scalar = Scalar(proof.T_x2); - proof.T_x2.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.T_x2 = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - scalar = Scalar(proof.u); - proof.u.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.u = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - scalar = Scalar(proof.innerProductProof.a_); - proof.innerProductProof.a_.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.innerProductProof.a_ = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - scalar = Scalar(proof.innerProductProof.b_); - proof.innerProductProof.b_.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.innerProductProof.b_ = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - - scalar = Scalar(proof.innerProductProof.c_); - proof.innerProductProof.c_.randomize(); - BOOST_CHECK(!rangeVerifier.verify(V, V, proof)); - proof.innerProductProof.c_ = Scalar(scalar); - BOOST_CHECK(rangeVerifier.verify(V, V, proof)); - } -} - -// A batch of range proofs, one of which is invalid -BOOST_AUTO_TEST_CASE(invalid_batch) -{ - // Parameters - const std::size_t n = 64; - const std::vector m = {1,2,4,8}; - - // Generators - secp_primitives::GroupElement g_gen, h_gen1, h_gen2; - g_gen.randomize(); - h_gen1.randomize(); - h_gen2.randomize(); - std::size_t max_m = *std::max_element(m.begin(), m.end()); - auto g_ = RandomizeGroupElements(n * max_m); - auto h_ = RandomizeGroupElements(n * max_m); - - for (auto version : test_versions) - { - // Proofs - std::vector > V_batch; - V_batch.reserve(m.size()); - std::vector proof_batch; - proof_batch.reserve(m.size()); - for (std::size_t i = 0; i < m.size(); i++) { - RangeProver rangeProver(g_gen, h_gen1, h_gen2, std::vector(g_.begin(), g_.begin() + n * m[i]), std::vector(h_.begin(), h_.begin() + n * m[i]), n, version); - - // Input data - auto serials = RandomizeScalars(m[i]); - auto randoms = RandomizeScalars(m[i]); - - std::vector v_s; - std::vector V; - for (std::size_t j = 0; j < m[i]; ++j){ - v_s.emplace_back(j); - V.push_back(g_gen * v_s.back() + h_gen1 * randoms[j] + h_gen2 * serials[j]); - } - - // Prove - RangeProof proof; - rangeProver.proof(v_s, serials, randoms, V, proof); - V_batch.emplace_back(V); - proof_batch.emplace_back(proof); - } - - // Invalidate one of the proofs - proof_batch[0].A.randomize(); - - // Verify - RangeVerifier rangeVerifier(g_gen, h_gen1, h_gen2, g_, h_, n, version); - BOOST_CHECK(!rangeVerifier.verify(V_batch, V_batch, proof_batch)); - } -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/schnorr_test.cpp b/src/liblelantus/test/schnorr_test.cpp deleted file mode 100644 index ca92aa7be1..0000000000 --- a/src/liblelantus/test/schnorr_test.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "../schnorr_proof.h" -#include "../schnorr_prover.h" -#include "../schnorr_verifier.h" -#include "../challenge_generator_impl.h" -#include "../../streams.h" -#include "../../version.h" - - -#include - -namespace lelantus { - -class SchnorrProofTests { -public: - SchnorrProofTests() - { - g.randomize(); - h.randomize(); - P.randomize(); - T.randomize(); - a.randomize(); - b.randomize(); - } - -public: - GroupElement g, h, a, b; - Scalar P, T; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_schnorr_proof_tests, SchnorrProofTests) - -BOOST_AUTO_TEST_CASE(serialization) -{ - std::unique_ptr challengeGenerator = std::make_unique>(1); - SchnorrProver prover(g, h, true); - GroupElement y; - y.randomize(); - SchnorrProof proof; - prover.proof(P, T, y, a, b, challengeGenerator, proof); - - CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << proof; - - SchnorrProof deserialized; - serialized >> deserialized; - - BOOST_CHECK(proof.u == deserialized.u); - BOOST_CHECK(proof.P1 == deserialized.P1); - BOOST_CHECK(proof.T1 == deserialized.T1); -} - -BOOST_AUTO_TEST_CASE(prove_verify) -{ - auto y = LelantusPrimitives::commit(g, P, h, T); - std::unique_ptr challengeGenerator = std::make_unique>(1); - - SchnorrProver prover(g, h, true); - SchnorrProof proof; - prover.proof(P, T, y, a, b, challengeGenerator, proof); - - SchnorrVerifier verifier(g, h, true); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(verifier.verify(y, a, b, proof, challengeGenerator)); -} - -BOOST_AUTO_TEST_CASE(fake_prove_not_verify) -{ - auto y = LelantusPrimitives::commit(g, P, h, T); - std::unique_ptr challengeGenerator = std::make_unique>(1); - - SchnorrProver prover(g, h, true); - SchnorrProof proof; - prover.proof(P, T, y, a, b, challengeGenerator, proof); - - GroupElement fakeY; - fakeY.randomize(); - - SchnorrVerifier verifier(g, h, true); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!verifier.verify(fakeY, a, b, proof, challengeGenerator)); - - auto fakeProof = proof; - fakeProof.P1.randomize(); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!verifier.verify(y, a, b, fakeProof, challengeGenerator)); - - fakeProof = proof; - fakeProof.T1.randomize(); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!verifier.verify(y, a, b, fakeProof, challengeGenerator)); - - fakeProof = proof; - fakeProof.u.randomize(); - challengeGenerator.reset(new ChallengeGeneratorImpl(1)); - BOOST_CHECK(!verifier.verify(y, a, b, fakeProof, challengeGenerator)); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/liblelantus/test/serialize_test.cpp b/src/liblelantus/test/serialize_test.cpp deleted file mode 100644 index 70147ca65e..0000000000 --- a/src/liblelantus/test/serialize_test.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "../lelantus_prover.h" -#include "../lelantus_verifier.h" - -#include "streams.h" - -#include "lelantus_test_fixture.h" - -#include - -BOOST_FIXTURE_TEST_SUITE(lelantus_serialize_tests, lelantus::LelantusTestingSetup) - -BOOST_AUTO_TEST_CASE(serialize) -{ - auto params = lelantus::Params::get_default(); - std::map> anonymity_sets; - int N = 100; - - std::vector> Cin; - lelantus::PrivateCoin input_coin1(params, 5); - Cin.emplace_back(std::make_pair(input_coin1, 0)); - std::vector indexes; - indexes.push_back(0); - - std::vector anonymity_set; - anonymity_set.reserve(N); - anonymity_set.push_back(Cin[0].first.getPublicCoin()); - for(int i = 0; i < N; ++i){ - secp_primitives::GroupElement coin; - coin.randomize(); - anonymity_set.emplace_back(lelantus::PublicCoin(coin)); - } - - anonymity_sets[0] = anonymity_set; - - secp_primitives::Scalar Vin(uint64_t(5)); - secp_primitives::Scalar Vout(uint64_t(6)); - std::vector Cout; - Cout.push_back(lelantus::PrivateCoin(params, 2)); - Cout.push_back(lelantus::PrivateCoin(params, 1)); - secp_primitives::Scalar f(uint64_t(1)); - - lelantus::LelantusProof initial_proof; - lelantus::SchnorrProof qkSchnorrProof; - - lelantus::LelantusProver prover(params, LELANTUS_TX_VERSION_4_5); - prover.proof(anonymity_sets, {}, Vin, Cin, indexes, {}, Vout, Cout, f, initial_proof, qkSchnorrProof); - - CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << initial_proof; - - lelantus::LelantusProof resulted_proof; - serialized >> resulted_proof; - - for(size_t i = 0; i < initial_proof.sigma_proofs.size(); ++i){ - BOOST_CHECK(initial_proof.sigma_proofs[i].B_ == resulted_proof.sigma_proofs[i].B_); - BOOST_CHECK(initial_proof.sigma_proofs[i].A_ == resulted_proof.sigma_proofs[i].A_); - BOOST_CHECK(initial_proof.sigma_proofs[i].C_ == resulted_proof.sigma_proofs[i].C_); - BOOST_CHECK(initial_proof.sigma_proofs[i].D_ == resulted_proof.sigma_proofs[i].D_); - BOOST_CHECK(initial_proof.sigma_proofs[i].f_ == resulted_proof.sigma_proofs[i].f_); - BOOST_CHECK(initial_proof.sigma_proofs[i].ZA_ == resulted_proof.sigma_proofs[i].ZA_); - BOOST_CHECK(initial_proof.sigma_proofs[i].ZC_ == resulted_proof.sigma_proofs[i].ZC_); - BOOST_CHECK(initial_proof.sigma_proofs[i].Gk_ == resulted_proof.sigma_proofs[i].Gk_); - BOOST_CHECK(initial_proof.sigma_proofs[i].zV_ == resulted_proof.sigma_proofs[i].zV_); - BOOST_CHECK(initial_proof.sigma_proofs[i].zR_ == resulted_proof.sigma_proofs[i].zR_); - } - - BOOST_CHECK(initial_proof.bulletproofs.A == resulted_proof.bulletproofs.A); - BOOST_CHECK(initial_proof.bulletproofs.S == resulted_proof.bulletproofs.S); - BOOST_CHECK(initial_proof.bulletproofs.T1 == resulted_proof.bulletproofs.T1); - BOOST_CHECK(initial_proof.bulletproofs.T2 == resulted_proof.bulletproofs.T2); - BOOST_CHECK(initial_proof.bulletproofs.T_x1 == resulted_proof.bulletproofs.T_x1); - BOOST_CHECK(initial_proof.bulletproofs.T_x2 == resulted_proof.bulletproofs.T_x2); - BOOST_CHECK(initial_proof.bulletproofs.u == resulted_proof.bulletproofs.u); - BOOST_CHECK(initial_proof.bulletproofs.innerProductProof.a_ == resulted_proof.bulletproofs.innerProductProof.a_); - BOOST_CHECK(initial_proof.bulletproofs.innerProductProof.b_ == resulted_proof.bulletproofs.innerProductProof.b_); - BOOST_CHECK(initial_proof.bulletproofs.innerProductProof.c_ == resulted_proof.bulletproofs.innerProductProof.c_); - BOOST_CHECK(initial_proof.bulletproofs.innerProductProof.L_ == resulted_proof.bulletproofs.innerProductProof.L_); - BOOST_CHECK(initial_proof.bulletproofs.innerProductProof.R_ == resulted_proof.bulletproofs.innerProductProof.R_); - - BOOST_CHECK(initial_proof.schnorrProof.u == resulted_proof.schnorrProof.u); - BOOST_CHECK(initial_proof.schnorrProof.P1 == resulted_proof.schnorrProof.P1); - BOOST_CHECK(initial_proof.schnorrProof.T1 == resulted_proof.schnorrProof.T1); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/liblelantus/test/sigma_extended_test.cpp b/src/liblelantus/test/sigma_extended_test.cpp deleted file mode 100644 index 14ffbe5eb5..0000000000 --- a/src/liblelantus/test/sigma_extended_test.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "../sigmaextended_prover.h" -#include "../sigmaextended_verifier.h" - -#include "lelantus_test_fixture.h" - -#include - -namespace lelantus { - -class SigmaExtendedTests : public LelantusTestingSetup { -public: - struct Secret { - public: - Secret(std::size_t l) : l(l) { - s.randomize(); - v.randomize(); - r.randomize(); - } - - public: - std::size_t l; - Scalar s, v, r; - }; - -public: - typedef SigmaExtendedProver Prover; - typedef SigmaExtendedProof Proof; - typedef SigmaExtendedVerifier Verifier; - -public: - SigmaExtendedTests() {} - -public: - void GenerateParams(std::size_t _N, std::size_t _n, std::size_t _m = 0) { - N = _N; - n = _n; - m = _m; - if (!m) { - if (n <= 1) { - throw std::logic_error("Try to get value of m from invalid n"); - } - - m = (std::size_t)std::round(log(N) / log(n)); - } - - h_gens = RandomizeGroupElements(n * m); - g.randomize(); - } - - void GenerateBatchProof( - Prover &prover, - std::vector const &coins, - std::size_t l, - Scalar const &s, - Scalar const &v, - Scalar const &r, - Scalar const &x, - Proof &proof - ) { - auto gs = g * s.negate(); - std::vector commits(coins.begin(), coins.end()); - for (auto &c : commits) { - c += gs; - } - - Scalar rA, rB, rC, rD; - rA.randomize(); - rB.randomize(); - rC.randomize(); - rD.randomize(); - - std::vector sigma; - std::vector Tk, Pk, Yk; - Tk.resize(m); - Pk.resize(m); - Yk.resize(m); - - std::vector a; - a.resize(n * m); - - prover.sigma_commit( - commits, l, rA, rB, rC, rD, a, Tk, Pk, Yk, sigma, proof); - - prover.sigma_response( - sigma, a, rA, rB, rC, rD, v, r, Tk, Pk, x, proof); - } - -public: - std::size_t N; - std::size_t n; - std::size_t m; - - std::vector h_gens; - GroupElement g; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_sigma_tests, SigmaExtendedTests) - -BOOST_AUTO_TEST_CASE(one_out_of_N_variable_batch) -{ - GenerateParams(64, 4); - - std::size_t commit_size = 60; // require padding - auto commits = RandomizeGroupElements(commit_size); - - // Generate - std::vector secrets; - std::vector indexes = { 0, 1, 3, 59 }; - std::vector set_sizes = { 60, 60, 59, 16 }; - - for (auto index : indexes) { - secrets.emplace_back(index); - - auto &s = secrets.back(); - - commits[index] = Primitives::double_commit( - g, s.s, h_gens[1], s.v, h_gens[0], s.r - ); - } - - Prover prover(g, h_gens, n, m); - Verifier verifier(g, h_gens, n, m); - std::vector proofs; - std::vector serials; - std::vector challenges; - - for (std::size_t i = 0; i < indexes.size(); i++) { - Scalar x; - x.randomize(); - proofs.emplace_back(); - serials.push_back(secrets[i].s); - std::vector commits_(commits.begin() + commit_size - set_sizes[i], commits.end()); - GenerateBatchProof( - prover, - commits_, - secrets[i].l - (commit_size - set_sizes[i]), - secrets[i].s, - secrets[i].v, - secrets[i].r, - x, - proofs.back() - ); - challenges.emplace_back(x); - - // Verify individual proofs as a sanity check - BOOST_CHECK(verifier.singleverify(commits, x, secrets[i].s, set_sizes[i], proofs.back())); - BOOST_CHECK(verifier.singleverify(commits_, x, secrets[i].s, proofs.back())); - } - - BOOST_CHECK(verifier.batchverify(commits, challenges, serials, set_sizes, proofs)); -} - -BOOST_AUTO_TEST_CASE(one_out_of_N_batch) -{ - GenerateParams(16, 4); - - auto commits = RandomizeGroupElements(N); - - // Generate - std::vector secrets; - - for (auto index : {1, 3, 5, 9, 15}) { - secrets.emplace_back(index); - - auto &s = secrets.back(); - - commits[index] = Primitives::double_commit( - g, s.s, h_gens[1], s.v, h_gens[0], s.r); - } - - Prover prover(g, h_gens, n, m); - std::vector proofs; - std::vector serials; - - Scalar x; - x.randomize(); - - for (auto const &s : secrets) { - proofs.emplace_back(); - serials.push_back(s.s); - GenerateBatchProof( - prover, commits, s.l, s.s, s.v, s.r, x, proofs.back()); - } - - Verifier verifier(g, h_gens, n, m); - BOOST_CHECK(verifier.batchverify(commits, x, serials, proofs)); - - // verify subset of valid proofs should success also - serials.pop_back(); - proofs.pop_back(); - BOOST_CHECK(verifier.batchverify(commits, x, serials, proofs)); -} - -BOOST_AUTO_TEST_CASE(one_out_of_N_batch_with_some_invalid_proof) -{ - GenerateParams(16, 4); - - auto commits = RandomizeGroupElements(N); - - // Generate - std::vector secrets; - - for (auto index : {1, 3}) { - secrets.emplace_back(index); - - auto &s = secrets.back(); - - commits[index] = Primitives::double_commit( - g, s.s, h_gens[1], s.v, h_gens[0], s.r); - } - - Prover prover(g, h_gens, n, m); - std::vector proofs; - std::vector serials; - - Scalar x; - x.randomize(); - - for (auto const &s : secrets) { - proofs.emplace_back(); - serials.push_back(s.s); - GenerateBatchProof( - prover, commits, s.l, s.s, s.v, s.r, x, proofs.back()); - } - - // Add an invalid - proofs.push_back(proofs.back()); - - serials.emplace_back(serials.back()); - serials.back().randomize(); - - Verifier verifier(g, h_gens, n, m); - BOOST_CHECK(!verifier.batchverify(commits, x, serials, proofs)); -} - -BOOST_AUTO_TEST_SUITE_END() - -} // namespace lelantus \ No newline at end of file diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp index d6c2973f87..3defe64524 100644 --- a/src/libspark/chaum.cpp +++ b/src/libspark/chaum.cpp @@ -90,13 +90,16 @@ bool Chaum::verify( ) { // Check proof semantics std::size_t n = S.size(); - if (!(T.size() == n && proof.A2.size() == n && proof.t1.size() == n)) { + if (n == 0 || !(T.size() == n && proof.A2.size() == n && proof.t1.size() == n)) { throw std::invalid_argument("Bad Chaum semantics!"); } for (std::size_t i = 0; i < n; i++) { if (S[i].isInfinity()) { throw std::invalid_argument("Bad Chaum input!"); } + if (T[i].isInfinity()) { + throw std::invalid_argument("Bad Chaum input!"); + } } Scalar c = challenge(mu, S, T, proof.A1, proof.A2); diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 664d522643..7339b0497e 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -7,6 +7,8 @@ #include "aead.h" #include "util.h" #include "../uint256.h" +#include "crypto/sha256.h" +#include "primitives/mint_spend.h" namespace spark { @@ -138,6 +140,138 @@ class Coin { } }; +} // namespace spark + +// keep this just to not break old index (chain index serialization) +namespace sigma { +enum class CoinDenomination : std::uint8_t { + SIGMA_DENOM_0_05 = 5, + SIGMA_DENOM_0_1 = 0, + SIGMA_DENOM_0_5 = 1, + SIGMA_DENOM_1 = 2, + SIGMA_DENOM_10 = 3, + SIGMA_DENOM_25 = 6, + SIGMA_DENOM_100 = 4 +}; +// Serialization support for CoinDenomination + +template +void Serialize(Stream& os, CoinDenomination d) +{ + Serialize(os, static_cast(d)); +} + +template +void Unserialize(Stream& is, CoinDenomination& d) +{ + std::uint8_t v; + Unserialize(is, v); + d = static_cast(v); } +class PublicCoin { +public: + PublicCoin() {} + template + inline void Serialize(Stream& s) const { + constexpr int size = secp_primitives::GroupElement::memoryRequired(); + unsigned char buffer[size + sizeof(int32_t)]; + value.serialize(buffer); + int32_t denom32 = static_cast(static_cast(denomination)); + std::memcpy(buffer + size, &denom32, sizeof(denom32)); + char* b = (char*)buffer; + s.write(b, size + sizeof(int32_t)); + } + + template + inline void Unserialize(Stream& s) { + constexpr int size = secp_primitives::GroupElement::memoryRequired(); + unsigned char buffer[size + sizeof(int32_t)]; + char* b = (char*)buffer; + s.read(b, size + sizeof(int32_t)); + value.deserialize(buffer); + int32_t denom32; + std::memcpy(&denom32, buffer + size, sizeof(denom32)); + denomination = static_cast(static_cast(denom32)); + } + +private: + secp_primitives::GroupElement value; + CoinDenomination denomination; +}; + +struct CSpendCoinInfo { + CoinDenomination denomination; + int coinGroupId; + + template + void Serialize(Stream& s) const { + int64_t tmp = uint8_t(denomination); + s << tmp; + tmp = coinGroupId; + s << tmp; + } + template + void Unserialize(Stream& s) { + int64_t tmp; + s >> tmp; denomination = CoinDenomination(tmp); + s >> tmp; coinGroupId = int(tmp); + } + +}; + +struct CScalarHash { + std::size_t operator ()(const secp_primitives::Scalar& bn) const noexcept { + std::vector bnData(bn.memoryRequired()); + bn.serialize(&bnData[0]); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + CSHA256().Write(&bnData[0], bnData.size()).Finalize(hash); + // take the first bytes of "hash". + std::size_t result; + std::memcpy(&result, hash, sizeof(std::size_t)); + return result; + } +}; + +using spend_info_container = std::unordered_map; + +} + +namespace lelantus { + +using namespace secp_primitives; + +// Stub for chain index serialization only (Lelantus protocol removed). +class PublicCoin { +public: + PublicCoin() : value() {} + PublicCoin(const GroupElement& coin) : value(coin) {} + + const GroupElement& getValue() const { return value; } + uint256 getValueHash() const { return primitives::GetPubCoinValueHash(value); } + bool operator==(const PublicCoin& other) const { return value == other.value; } + bool operator!=(const PublicCoin& other) const { return !(*this == other); } + bool validate() const { return value.isMember() && !value.isInfinity(); } + size_t GetSerializeSize() const { return value.memoryRequired(); } + + template + inline void Serialize(Stream& s) const { + std::vector buffer(GetSerializeSize()); + value.serialize(buffer.data()); + s.write((const char *)buffer.data(), buffer.size()); + } + + template + inline void Unserialize(Stream& s) { + std::vector buffer(GetSerializeSize()); + s.read((char *)buffer.data(), buffer.size()); + value.deserialize(buffer.data()); + } + +private: + GroupElement value; +}; + +} // namespace lelantus + #endif diff --git a/src/libspark/grootle.cpp b/src/libspark/grootle.cpp index 26c8085641..9d34a32c97 100644 --- a/src/libspark/grootle.cpp +++ b/src/libspark/grootle.cpp @@ -426,13 +426,10 @@ bool Grootle::verify( } } - // Commitment binding weight; intentionally restricted range for efficiency, but must be nonzero - // NOTE: this may initialize with a PRNG, which should be sufficient for this use - std::random_device generator; - std::uniform_int_distribution distribution; + // Commitment binding weight (must be nonzero for S/V equation binding) Scalar bind_weight(ZERO); while (bind_weight == ZERO) { - bind_weight = Scalar(distribution(generator)); + bind_weight.randomize(); } // Bind the commitment lists diff --git a/src/libspark/grootle.h b/src/libspark/grootle.h index f0d5dd4b27..e0efbffa0f 100644 --- a/src/libspark/grootle.h +++ b/src/libspark/grootle.h @@ -1,9 +1,11 @@ #ifndef FIRO_LIBSPARK_GROOTLE_H #define FIRO_LIBSPARK_GROOTLE_H +// Include standard library first to avoid macro conflicts with system headers (e.g. ) +#include + #include "grootle_proof.h" #include -#include #include "util.h" namespace spark { diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 3e30b64a4c..8673934642 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -37,6 +37,11 @@ SpendTransaction::SpendTransaction( this->f = f; // fee this->vout = vout; // transparent output value + // Defense-in-depth: guard against uint64_t overflow of fee + vout (Schnorr proof also constrains this) + if (vout > 0 && f > std::numeric_limits::max() - vout) { + throw std::invalid_argument("fee + vout overflow"); + } + // Prepare Chaum vectors std::vector chaum_x, chaum_y, chaum_z; @@ -266,6 +271,11 @@ bool SpendTransaction::verify( const std::size_t t = tx.out_coins.size(); // number of generated coins const std::size_t N = (std::size_t) std::pow(params->get_n_grootle(), params->get_m_grootle()); // size of cover sets + // Defense-in-depth: reject attacker-controlled fee + vout that would overflow (balance proof would fail anyway) + if (tx.vout > 0 && tx.f > std::numeric_limits::max() - tx.vout) { + return false; + } + // Consumed coin semantics if (tx.S1.size() != w || tx.C1.size() != w || diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 220a3d580c..49cefb8c03 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -1,5 +1,11 @@ #ifndef FIRO_SPARK_SPEND_TRANSACTION_H #define FIRO_SPARK_SPEND_TRANSACTION_H + + +#include +#include + +#include "grootle_proof.h" #include "keys.h" #include "coin.h" #include "schnorr.h" @@ -104,6 +110,10 @@ class SpendTransaction { } void setVout(const uint64_t& vout_) { + // Defense-in-depth: guard against uint64_t overflow of fee + vout + if (vout_ > 0 && this->f > std::numeric_limits::max() - vout_) { + throw std::invalid_argument("fee + vout overflow"); + } this->vout = vout_; } diff --git a/src/libspark/test/chaum_test.cpp b/src/libspark/test/chaum_test.cpp index 26281438bd..52caccaddc 100644 --- a/src/libspark/test/chaum_test.cpp +++ b/src/libspark/test/chaum_test.cpp @@ -175,6 +175,23 @@ BOOST_AUTO_TEST_CASE(bad_proofs) BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); } +BOOST_AUTO_TEST_CASE(empty_input_vectors_rejected) +{ + GroupElement F, G, H, U; + F.randomize(); + G.randomize(); + H.randomize(); + U.randomize(); + Scalar mu; + mu.randomize(); + std::vector S; + std::vector T; + ChaumProof proof; + Chaum chaum(F, G, H, U); + BOOST_CHECK_THROW(chaum.verify(mu, S, T, proof), std::invalid_argument); + } + + BOOST_AUTO_TEST_SUITE_END() } diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index 427b749c08..08f67e139d 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -312,6 +312,10 @@ std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqTyp std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount) { + if (!pindexStart) { + return {}; + } + auto& params = Params().GetConsensus().llmqs.at(llmqType); auto cacheKey = std::make_pair(llmqType, pindexStart->GetBlockHash()); diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index ebc0302027..3a802356de 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -490,23 +490,19 @@ bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, return false; } - if (tx.nType == isutils::INSTANTSEND_ADAPTED_TX ) { - if (tx.IsLelantusJoinSplit()) { - for (CTxIn const & in : tx.vin) { - Scalar serial; - serial.deserialize(&in.scriptSig.front()); - LOCK(cs_main); - if (lelantus::CLelantusState::GetState()->IsUsedCoinSerial(serial)) - return false; - } - } else if (tx.IsSparkSpend()) { - for (CTxIn const & in : tx.vin) { - GroupElement lTag; - lTag.deserialize(&in.scriptSig.front()); - LOCK(cs_main); - if (spark::CSparkState::GetState()->IsUsedLTag(lTag)) - return false; - } + if (tx.IsLelantusJoinSplit()) { + return false; + } + + if (tx.nType == isutils::INSTANTSEND_ADAPTED_TX) { + LOCK(cs_main); + for (CTxIn const & in : tx.vin) { + if (in.scriptSig.empty()) + return false; + GroupElement lTag; + lTag.deserialize(&in.scriptSig.front()); + if (spark::CSparkState::GetState()->IsUsedLTag(lTag)) + return false; } return true; } @@ -617,7 +613,7 @@ void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& re return; } - if (tx && (tx->IsLelantusJoinSplit() || tx->IsSparkSpend())) { + if (tx && tx->IsSparkSpend()) { tx = MakeTransactionRef(isutils::AdaptPrivateTx(*tx)); } @@ -1023,7 +1019,7 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx_, const CBlockI return; } - CTransaction const & tx{(tx_.IsLelantusJoinSplit() || tx_.IsSparkSpend()) ? isutils::AdaptPrivateTx(tx_) : tx_}; + CTransaction const & tx{tx_.IsSparkSpend() ? isutils::AdaptPrivateTx(tx_) : tx_}; bool inMempool = mempool.get(tx.GetHash()) != nullptr; bool isDisconnect = pindex && posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK; @@ -1532,7 +1528,7 @@ CInstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& return nullptr; } - CTransaction const & tx{(tx_.IsLelantusJoinSplit() || tx_.IsSparkSpend()) ? isutils::AdaptPrivateTx(tx_) : tx_}; + CTransaction const & tx{tx_.IsSparkSpend() ? isutils::AdaptPrivateTx(tx_) : tx_}; LOCK(cs); for (const auto& in : tx.vin) { @@ -1577,31 +1573,6 @@ bool CInstantSendManager::IsNewInstantSendEnabled() const namespace isutils { -CTransaction AdaptJsplitTx(CTransaction const & tx) -{ - static size_t const jsplitSerialSize = 32; - - CTransaction result{tx}; - std::unique_ptr jsplit; - try { - jsplit = lelantus::ParseLelantusJoinSplit(tx); - } - catch (...) { - return result; - } - const_cast*>(&result.vin)->clear(); //This const_cast was done intentionally as the current design allows for this way only - for (Scalar const & serial : jsplit->getCoinSerialNumbers()) { - CTxIn newin; - newin.scriptSig.resize(jsplitSerialSize); - serial.serialize(&newin.scriptSig.front()); - newin.prevout.hash = primitives::GetSerialHash(serial); - newin.prevout.n = 0; - const_cast*>(&result.vin)->push_back(newin); - } - *const_cast(&result.nType) = INSTANTSEND_ADAPTED_TX; - assert(result.GetHash() == tx.GetHash()); - return result; -} CTransaction AdaptSparkTx(CTransaction const & tx) { @@ -1632,9 +1603,7 @@ CTransaction AdaptSparkTx(CTransaction const & tx) CTransaction AdaptPrivateTx(CTransaction const & tx) { - if (tx.IsLelantusJoinSplit()) { - return AdaptJsplitTx(tx); - } else if (tx.IsSparkSpend()) { + if (tx.IsSparkSpend()) { return AdaptSparkTx(tx); } return tx; diff --git a/src/llmq/quorums_signing.cpp b/src/llmq/quorums_signing.cpp index 202cc00ff0..ca61dea31d 100644 --- a/src/llmq/quorums_signing.cpp +++ b/src/llmq/quorums_signing.cpp @@ -870,10 +870,13 @@ std::vector CSigningManager::GetActiveQuorumSet(Consensus::LLMQType { LOCK(cs_main); int startBlockHeight = signHeight - SIGN_HEIGHT_OFFSET; - if (startBlockHeight > chainActive.Height()) { + if (startBlockHeight < 0 || startBlockHeight > chainActive.Height()) { return {}; } pindexStart = chainActive[startBlockHeight]; + if (!pindexStart) { + return {}; + } } return quorumManager->ScanQuorums(llmqType, pindexStart, poolSize); diff --git a/src/llmq/quorums_signing_shares.cpp b/src/llmq/quorums_signing_shares.cpp index 0815f5dd6c..47bc74871b 100644 --- a/src/llmq/quorums_signing_shares.cpp +++ b/src/llmq/quorums_signing_shares.cpp @@ -237,6 +237,14 @@ void CSigSharesManager::ProcessMessage(CNode* pfrom, const std::string& strComma return; } + { + LOCK(cs); + auto it = nodeStates.find(pfrom->id); + if (it != nodeStates.end() && it->second.banned) { + return; + } + } + if (strCommand == NetMsgType::QSIGSESANN) { std::vector msgs; vRecv >> msgs; @@ -323,7 +331,11 @@ bool CSigSharesManager::ProcessMessageSigSesAnn(CNode* pfrom, const CSigSesAnn& FIRO_UNUSED auto signHash = CLLMQUtils::BuildSignHash(llmqType, ann.quorumHash, ann.id, ann.msgHash); LOCK(cs); - auto& nodeState = nodeStates[pfrom->id]; + auto it = nodeStates.find(pfrom->id); + if (it != nodeStates.end() && it->second.banned) { + return true; + } + auto& nodeState = it != nodeStates.end() ? it->second : nodeStates[pfrom->id]; auto& session = nodeState.GetOrCreateSessionFromAnn(ann); nodeState.sessionByRecvId.erase(session.recvSessionId); nodeState.sessionByRecvId.erase(ann.sessionId); @@ -371,7 +383,11 @@ bool CSigSharesManager::ProcessMessageSigSharesInv(CNode* pfrom, const CSigShare } LOCK(cs); - auto& nodeState = nodeStates[pfrom->id]; + auto it = nodeStates.find(pfrom->id); + if (it == nodeStates.end() || it->second.banned) { + return true; + } + auto& nodeState = it->second; auto session = nodeState.GetSessionByRecvId(inv.sessionId); if (!session) { return true; @@ -401,7 +417,11 @@ bool CSigSharesManager::ProcessMessageGetSigShares(CNode* pfrom, const CSigShare sessionInfo.signHash.ToString(), inv.ToString(), pfrom->id); LOCK(cs); - auto& nodeState = nodeStates[pfrom->id]; + auto it = nodeStates.find(pfrom->id); + if (it == nodeStates.end() || it->second.banned) { + return true; + } + auto& nodeState = it->second; auto session = nodeState.GetSessionByRecvId(inv.sessionId); if (!session) { return true; @@ -428,7 +448,11 @@ bool CSigSharesManager::ProcessMessageBatchedSigShares(CNode* pfrom, const CBatc { LOCK(cs); - auto& nodeState = nodeStates[pfrom->id]; + auto it = nodeStates.find(pfrom->id); + if (it == nodeStates.end() || it->second.banned) { + return true; + } + auto& nodeState = it->second; for (size_t i = 0; i < batchedSigShares.sigShares.size(); i++) { CSigShare sigShare = RebuildSigShare(sessionInfo, batchedSigShares, i); @@ -459,7 +483,11 @@ bool CSigSharesManager::ProcessMessageBatchedSigShares(CNode* pfrom, const CBatc } LOCK(cs); - auto& nodeState = nodeStates[pfrom->id]; + auto it = nodeStates.find(pfrom->id); + if (it == nodeStates.end() || it->second.banned) { + return true; + } + auto& nodeState = it->second; for (auto& s : sigShares) { nodeState.pendingIncomingSigShares.Add(s.GetKey(), s); } @@ -529,6 +557,10 @@ void CSigSharesManager::CollectPendingSigSharesToVerify( CLLMQUtils::IterateNodesRandom(nodeStates, [&]() { return uniqueSignHashes.size() < maxUniqueSessions; }, [&](NodeId nodeId, CSigSharesNodeState& ns) { + if (ns.banned) { + ns.pendingIncomingSigShares.Clear(); + return false; + } if (ns.pendingIncomingSigShares.Empty()) { return false; } @@ -647,8 +679,6 @@ void CSigSharesManager::ProcessPendingSigSharesFromNode(NodeId nodeId, const std::unordered_map, CQuorumCPtr, StaticSaltedHasher>& quorums, CConnman& connman) { - FIRO_UNUSED auto& nodeState = nodeStates[nodeId]; - cxxtimer::Timer t(true); for (auto& sigShare : sigShares) { auto quorumKey = std::make_pair((Consensus::LLMQType)sigShare.llmqType, sigShare.quorumHash); @@ -1255,8 +1285,11 @@ void CSigSharesManager::Cleanup() // Find node states for peers that disappeared from CConnman std::unordered_set nodeStatesToDelete; - for (auto& p : nodeStates) { - nodeStatesToDelete.emplace(p.first); + { + LOCK(cs); + for (const auto& p : nodeStates) { + nodeStatesToDelete.emplace(p.first); + } } g_connman->ForEachNode([&](CNode* pnode) { nodeStatesToDelete.erase(pnode->id); @@ -1265,12 +1298,17 @@ void CSigSharesManager::Cleanup() // Now delete these node states LOCK(cs); for (auto nodeId : nodeStatesToDelete) { - auto& nodeState = nodeStates[nodeId]; + auto it = nodeStates.find(nodeId); + if (it == nodeStates.end()) { + continue; + } + + auto& nodeState = it->second; // remove global requested state to force a re-request from another node nodeState.requestedSigShares.ForEach([&](const SigShareKey& k, bool) { sigSharesRequested.Erase(k); }); - nodeStates.erase(nodeId); + nodeStates.erase(it); } lastCleanupTime = GetAdjustedTime(); @@ -1278,6 +1316,8 @@ void CSigSharesManager::Cleanup() void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash) { + AssertLockHeld(cs); + for (auto& p : nodeStates) { auto& ns = p.second; ns.RemoveSession(signHash); @@ -1291,34 +1331,40 @@ void CSigSharesManager::RemoveSigSharesForSession(const uint256& signHash) void CSigSharesManager::RemoveBannedNodeStates() { - // Called regularly to cleanup local node states for banned nodes - - LOCK2(cs_main, cs); - std::unordered_set toRemove; - for (auto it = nodeStates.begin(); it != nodeStates.end();) { - if (IsBanned(it->first)) { - // re-request sigshares from other nodes - it->second.requestedSigShares.ForEach([&](const SigShareKey& k, int64_t) { - sigSharesRequested.Erase(k); - }); - it = nodeStates.erase(it); - } else { - ++it; + // Called regularly to cleanup local node states for banned peers after MarkNodeBanned already removed the + // request/session state which affects correctness. Keep markers for still-connected peers so they stay ignored. + + std::unordered_set nodeStatesToDelete; + { + LOCK(cs); + for (const auto& p : nodeStates) { + if (p.second.banned) { + nodeStatesToDelete.emplace(p.first); + } + } + } + + g_connman->ForEachNode([&](CNode* pnode) { + if (!pnode->fDisconnect) { + nodeStatesToDelete.erase(pnode->id); + } + }); + + LOCK(cs); + for (auto nodeId : nodeStatesToDelete) { + auto it = nodeStates.find(nodeId); + if (it != nodeStates.end() && it->second.banned) { + nodeStates.erase(it); } } } -void CSigSharesManager::BanNode(NodeId nodeId) +void CSigSharesManager::MarkNodeBanned(NodeId nodeId) { if (nodeId == -1) { return; } - { - LOCK(cs_main); - Misbehaving(nodeId, 100); - } - LOCK(cs); auto it = nodeStates.find(nodeId); if (it == nodeStates.end()) { @@ -1326,18 +1372,37 @@ void CSigSharesManager::BanNode(NodeId nodeId) } auto& nodeState = it->second; - // Whatever we requested from him, let's request it from someone else now + // Whatever we requested from him, let's request it from someone else now. nodeState.requestedSigShares.ForEach([&](const SigShareKey& k, int64_t) { sigSharesRequested.Erase(k); }); nodeState.requestedSigShares.Clear(); + // Drop all cached sessions and pending work from banned peers immediately. + nodeState.pendingIncomingSigShares.Clear(); + nodeState.sessions.clear(); + nodeState.sessionByRecvId.clear(); nodeState.banned = true; } +void CSigSharesManager::BanNode(NodeId nodeId) +{ + if (nodeId == -1) { + return; + } + + { + LOCK(cs_main); + Misbehaving(nodeId, 100); + } + + MarkNodeBanned(nodeId); +} + void CSigSharesManager::WorkThreadMain() { int64_t lastSendTime = 0; + int64_t lastRemoveBannedNodeStatesTime = 0; while (!workInterrupt) { if (!quorumSigningManager || !g_connman) { @@ -1349,7 +1414,13 @@ void CSigSharesManager::WorkThreadMain() bool didWork = false; - RemoveBannedNodeStates(); + // MarkNodeBanned handles the correctness-sensitive cleanup immediately, so this periodic pass only reclaims the + // remaining per-node state for banned peers. + if (GetTimeMillis() - lastRemoveBannedNodeStatesTime > 30000 /* 30s */) { + RemoveBannedNodeStates(); + lastRemoveBannedNodeStatesTime = GetTimeMillis(); + } + didWork |= quorumSigningManager->ProcessPendingRecoveredSigs(*g_connman); didWork |= ProcessPendingSigShares(*g_connman); didWork |= SignPendingSigShares(); @@ -1364,7 +1435,7 @@ void CSigSharesManager::WorkThreadMain() // TODO Wakeup when pending signing is needed? if (!didWork) { - if (!workInterrupt.sleep_for(std::chrono::milliseconds(100))) { + if (!workInterrupt.sleep_for(std::chrono::milliseconds(250))) { return; } } diff --git a/src/llmq/quorums_signing_shares.h b/src/llmq/quorums_signing_shares.h index 329ba63392..cdb2b3b9f6 100644 --- a/src/llmq/quorums_signing_shares.h +++ b/src/llmq/quorums_signing_shares.h @@ -379,6 +379,7 @@ class CSigSharesManager : public CRecoveredSigsListener void AsyncSign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash); void Sign(const CQuorumCPtr& quorum, const uint256& id, const uint256& msgHash); void ForceReAnnouncement(const CQuorumCPtr& quorum, Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash); + void MarkNodeBanned(NodeId nodeId); void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override; diff --git a/src/miner.cpp b/src/miner.cpp index 86817cf6de..eba49030e2 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -33,7 +33,6 @@ #include "crypto/MerkleTreeProof/mtp.h" #include "crypto/Lyra2Z/Lyra2Z.h" #include "crypto/Lyra2Z/Lyra2.h" -#include "lelantus.h" #include "evo/spork.h" #include #include @@ -430,22 +429,6 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter) const CTransaction &tx = iter->GetTx(); - // Check transaction against lelantus limits - if(tx.IsLelantusJoinSplit()) { - CAmount spendAmount = lelantus::GetSpendTransparentAmount(tx); - size_t spendNumber = lelantus::GetSpendInputs(tx); - const auto ¶ms = chainparams.GetConsensus(); - - if (spendNumber > params.nMaxLelantusInputPerTransaction || spendAmount > params.nMaxValueLelantusSpendPerTransaction) - return false; - - if (spendNumber + nLelantusSpendInputs > params.nMaxLelantusInputPerBlock) - return false; - - if (spendAmount + nLelantusSpendAmount > params.nMaxValueLelantusSpendPerBlock) - return false; - } - // Check transaction against spark limits if(tx.IsSparkSpend()) { CAmount spendAmount = spark::GetSpendTransparentAmount(tx); @@ -465,21 +448,6 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) { const CTransaction &tx = iter->GetTx(); - if(tx.IsLelantusJoinSplit()) { - CAmount spendAmount = lelantus::GetSpendTransparentAmount(tx); - size_t spendNumber = lelantus::GetSpendInputs(tx); - const auto ¶ms = chainparams.GetConsensus(); - - if (spendAmount > params.nMaxValueLelantusSpendPerTransaction) - return; - - if ((nLelantusSpendAmount += spendAmount) > params.nMaxValueLelantusSpendPerBlock) - return; - - if ((nLelantusSpendInputs += spendNumber) > params.nMaxLelantusInputPerBlock) - return; - } - if(tx.IsSparkSpend()) { CAmount spendAmount = spark::GetSpendTransparentAmount(tx); const auto ¶ms = chainparams.GetConsensus(); @@ -987,13 +955,6 @@ void BlockAssembler::FillBlackListForBlockTemplate() { } } - // Now if we have limit on lelantus transparent outputs scan mempool and drop all the transactions exceeding the limit - if (sporkMap.count(CSporkAction::featureLelantusTransparentLimit) > 0) { - BlacklistTxsExceedingLimit(sporkMap[CSporkAction::featureLelantusTransparentLimit].second, - [](const CTransaction &tx)->bool { return tx.IsLelantusJoinSplit(); }, - [](const CTransaction &tx)->CAmount { return lelantus::GetSpendTransparentAmount(tx); }); - } - // Same for spark spends if (sporkMap.count(CSporkAction::featureSparkTransparentLimit) > 0) { BlacklistTxsExceedingLimit(sporkMap[CSporkAction::featureSparkTransparentLimit].second, diff --git a/src/net.cpp b/src/net.cpp index bf247873e0..2909b4e85f 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -109,6 +109,7 @@ bool fRelayTxes = true; CCriticalSection cs_mapLocalHost; std::map mapLocalHost; static bool vfLimited[NET_MAX] = {}; +static bool vfExplicitlyLimited[NET_MAX] = {}; std::string strSubVersion; limitedmap mapAlreadyAskedFor(MAX_INV_SZ); @@ -289,6 +290,22 @@ bool IsLimited(const CNetAddr &addr) return IsLimited(addr.GetNetwork()); } +void SetNetworkExplicitlyLimited(enum Network net, bool fLimited) +{ + if (net == NET_UNROUTABLE || net >= NET_MAX) + return; + LOCK(cs_mapLocalHost); + vfExplicitlyLimited[net] = fLimited; +} + +bool IsNetworkExplicitlyLimited(enum Network net) +{ + if (net == NET_UNROUTABLE || net >= NET_MAX) + return false; + LOCK(cs_mapLocalHost); + return vfExplicitlyLimited[net]; +} + /** vote for a local address */ bool SeenLocal(const CService& addr) { @@ -684,6 +701,7 @@ void CNode::copyStats(CNodeStats &stats) X(cleanSubVer); } X(fInbound); + X(m_inbound_onion); X(fAddnode); X(nStartingHeight); { @@ -1250,12 +1268,13 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); - CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, "", true); + CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr, CalculateKeyedNetGroup(addr), nonce, "", true, hListenSocket.is_onion_listener); pnode->AddRef(); pnode->fWhitelisted = whitelisted; GetNodeSignals().InitializeNode(pnode, *this); - LogPrint("net", "connection from %s accepted\n", addr.ToString()); + LogPrint("net", "connection from %s accepted%s\n", addr.ToString(), + hListenSocket.is_onion_listener ? " (inbound via onion)" : ""); { LOCK(cs_vNodes); @@ -1359,26 +1378,32 @@ void CConnman::ThreadSocketHandler() // { LOCK(cs_vNodes); - // Disconnect unused nodes - std::vector vNodesCopy = vNodes; - BOOST_FOREACH(CNode* pnode, vNodesCopy) + + // Only copy nodes which are actually disconnected so as to minimise allocations. + std::vector vDisconnectedNodes; + BOOST_FOREACH(CNode* pnode, vNodes) { if (pnode->fDisconnect) { - // remove from vNodes - vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); + vDisconnectedNodes.push_back(pnode); + } + } + + BOOST_FOREACH(CNode* pnode, vDisconnectedNodes) + { + // remove from vNodes + vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); - // release outbound grant (if any) - pnode->grantOutbound.Release(); - pnode->grantMasternodeOutbound.Release(); + // release outbound grant (if any) + pnode->grantOutbound.Release(); + pnode->grantMasternodeOutbound.Release(); - // close socket and cleanup - pnode->CloseSocketDisconnect(); + // close socket and cleanup + pnode->CloseSocketDisconnect(); - // hold in disconnected pool until all refs are released - pnode->Release(); - vNodesDisconnected.push_back(pnode); - } + // hold in disconnected pool until all refs are released + pnode->Release(); + vNodesDisconnected.push_back(pnode); } } { @@ -1434,7 +1459,17 @@ void CConnman::ThreadSocketHandler() SOCKET hSocketMax = 0; bool have_fds = false; - BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) { + // Snapshot the listen sockets under the lock, then work with the + // copy. In the current call graph vhListenSocket is only mutated at + // init (before this thread starts) and in Stop() (after this thread + // joins), so this is defense-in-depth against a future runtime + // BindListenPort caller invalidating iterators mid-loop. + std::vector vhListenSocketSnapshot; + { + LOCK(cs_vhListenSocket); + vhListenSocketSnapshot = vhListenSocket; + } + BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocketSnapshot) { FD_SET(hListenSocket.socket, &fdsetRecv); hSocketMax = std::max(hSocketMax, hListenSocket.socket); have_fds = true; @@ -1503,7 +1538,7 @@ void CConnman::ThreadSocketHandler() // // Accept new connections // - BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocket) + BOOST_FOREACH(const ListenSocket& hListenSocket, vhListenSocketSnapshot) { if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) { @@ -1826,12 +1861,30 @@ void CConnman::ThreadDNSAddressSeed() LogPrintf("Loading addresses from DNS seeds (could take a while)\n"); + // Skip the direct DNS seed lookup only when *all* clearnet networks are + // unreachable, i.e. the user has restricted us to Tor/onion. In that case + // a DNS query would leak over clearnet and any resolved A/AAAA addresses + // would be on limited networks anyway. In mixed configurations where at + // least one of IPv4/IPv6 is still reachable (e.g. -onlynet=ipv6), keep + // doing the direct lookup -- getaddrinfo() returns both A and AAAA + // records and the unreachable-family addresses are filtered out later at + // connect time, so the lookup still produces useful peers. + // * If we have a name proxy, route the seed through it via AddOneShot + // regardless, so resolution happens privately. + // * If we have no name proxy and no clearnet reachability, skip the + // seed rather than fall back to system DNS. + const bool fSkipDNSLookup = !IsReachable(NET_IPV4) && !IsReachable(NET_IPV6); + BOOST_FOREACH(const CDNSSeedData &seed, vSeeds) { if (interruptNet) { return; } if (HaveNameProxy()) { AddOneShot(seed.host); + } else if (fSkipDNSLookup) { + LogPrintf("Skipping DNS seed %s: no clearnet network reachable and no name proxy " + "configured (would leak DNS resolution over clearnet)\n", seed.host); + continue; } else { std::vector vIPs; std::vector vAdd; @@ -2234,6 +2287,12 @@ void CConnman::ThreadOpenMasternodeConnections() } } + // Respect -onlynet: filter out masternodes on limited networks + // before selecting, so we don't waste the iteration or lose + // dequeued vPendingMasternodes entries. + pending.erase(std::remove_if(pending.begin(), pending.end(), + [](const CService& s) { return IsLimited(s); }), pending.end()); + if (pending.empty()) { // nothing to do, keep waiting continue; @@ -2267,6 +2326,10 @@ bool CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai if (!fNetworkActive) { return false; } + // Respect -onlynet: don't connect to addresses on limited networks + if (!pszDest && addrConnect.IsValid() && IsLimited(addrConnect)) { + return false; + } bool fAllowLocal = fMasternodeMode; if (!pszDest) { // banned or exact match? @@ -2378,7 +2441,8 @@ void CConnman::ThreadMessageHandler() -bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted) +bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, bool fWhitelisted, + bool is_onion_listener, unsigned short* out_port) { strError = ""; int nOne = 1; @@ -2457,7 +2521,25 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b CloseSocket(hListenSocket); return false; } - LogPrintf("Bound to %s\n", addrBind.ToString()); + + // Resolve the actual bound address via getsockname. The input addrBind + // may have used port 0 (e.g. the dedicated Tor onion listener requests + // an ephemeral port), in which case the kernel has just assigned a real + // port and addrBind.ToString() would misleadingly show ":0". Use the + // resolved address for the log line below and for the optional out_port + // result. getsockname is best-effort for logging; only a hard failure + // when the caller actually needs out_port rejects the bind. + CService resolvedBind; + bool haveResolved = false; + { + struct sockaddr_storage boundAddr; + socklen_t boundLen = sizeof(boundAddr); + if (getsockname(hListenSocket, (struct sockaddr*)&boundAddr, &boundLen) == 0 && + resolvedBind.SetSockAddr((const struct sockaddr*)&boundAddr)) { + haveResolved = true; + } + } + LogPrintf("Bound to %s\n", haveResolved ? resolvedBind.ToString() : addrBind.ToString()); // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) @@ -2468,9 +2550,29 @@ bool CConnman::BindListenPort(const CService &addrBind, std::string& strError, b return false; } - vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted)); + // If the caller needs the resolved port (e.g. to tell Tor where to + // forward hidden-service traffic), failing to resolve is fatal -- we + // must not publish a listener whose port the caller can't address. + if (out_port != nullptr) { + if (!haveResolved) { + strError = strprintf("BindListenPort: could not resolve bound address for %s", + addrBind.ToString()); + LogPrintf("%s\n", strError); + CloseSocket(hListenSocket); + return false; + } + *out_port = resolvedBind.GetPort(); + } + + { + LOCK(cs_vhListenSocket); + vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted, is_onion_listener)); + } - if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) + // The dedicated local listener used to receive Tor-forwarded hidden + // service traffic is bound to 127.0.0.1; we must not advertise it as a + // local reachable address. + if (addrBind.IsRoutable() && fDiscover && !fWhitelisted && !is_onion_listener) AddLocal(addrBind, LOCAL_BIND); return true; @@ -2838,10 +2940,17 @@ void CConnman::Stop() // Close sockets BOOST_FOREACH(CNode* pnode, vNodes) pnode->CloseSocketDisconnect(); - BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket) - if (hListenSocket.socket != INVALID_SOCKET) - if (!CloseSocket(hListenSocket.socket)) - LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); + { + // Match BindListenPort's locking as defense-in-depth. In the current + // call graph all BindListenPort calls happen at init, so nothing + // else is mutating vhListenSocket here; this keeps the invariant + // stable if a runtime caller is reintroduced. + LOCK(cs_vhListenSocket); + BOOST_FOREACH(ListenSocket& hListenSocket, vhListenSocket) + if (hListenSocket.socket != INVALID_SOCKET) + if (!CloseSocket(hListenSocket.socket)) + LogPrintf("CloseSocket(hListenSocket) failed with error %s\n", NetworkErrorString(WSAGetLastError())); + } // clean up some globals (to help leak detection) BOOST_FOREACH(CNode *pnode, vNodes) { @@ -2852,7 +2961,10 @@ void CConnman::Stop() } vNodes.clear(); vNodesDisconnected.clear(); - vhListenSocket.clear(); + { + LOCK(cs_vhListenSocket); + vhListenSocket.clear(); + } delete semOutbound; semOutbound = NULL; delete semAddnode; @@ -3355,16 +3467,18 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } unsigned int CConnman::GetSendBufferSize() const{ return nSendBufferMaxSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string& addrNameIn, bool fInboundIn) : +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string& addrNameIn, bool fInboundIn, bool inbound_onion) : nTimeConnected(GetSystemTimeInSeconds()), nTimeFirstMessageReceived(0), fFirstMessageIsMNAUTH(false), addr(addrIn), fInbound(fInboundIn), + m_inbound_onion(inbound_onion), id(idIn), nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001), filterInventoryKnown(50000, 0.000001), + filterDandelionInventoryKnown(50000, 0.000001), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), nMyStartingHeight(nMyStartingHeightIn), @@ -3397,6 +3511,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn hashContinue = uint256(); nStartingHeight = -1; filterInventoryKnown.reset(); + filterDandelionInventoryKnown.reset(); fSendMempool = false; fGetAddr = false; nNextLocalAddrSend = 0; diff --git a/src/net.h b/src/net.h index 4e0c3c5772..42621f4ec4 100644 --- a/src/net.h +++ b/src/net.h @@ -178,7 +178,8 @@ class CConnman bool Start(CScheduler& scheduler, std::string& strNodeError, Options options); void Stop(); void Interrupt(); - bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); + bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false, + bool is_onion_listener = false, unsigned short* out_port = nullptr); bool GetNetworkActive() const { return fNetworkActive; }; void SetNetworkActive(bool active); bool OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false, bool fFeeler = false, bool fAddnode = false, bool fConnectToMasternode = false); @@ -420,8 +421,14 @@ class CConnman struct ListenSocket { SOCKET socket; bool whitelisted; - - ListenSocket(SOCKET socket_, bool whitelisted_) : socket(socket_), whitelisted(whitelisted_) {} + /** Whether this socket is the local endpoint that receives inbound + * connections forwarded from our Tor hidden service. Connections + * accepted on such a socket originate from a peer that reached us + * over Tor, even though the socket-level peer address is 127.0.0.1. */ + bool is_onion_listener; + + ListenSocket(SOCKET socket_, bool whitelisted_, bool is_onion_listener_ = false) + : socket(socket_), whitelisted(whitelisted_), is_onion_listener(is_onion_listener_) {} }; void ThreadOpenAddedConnections(); @@ -489,6 +496,14 @@ class CConnman unsigned int nReceiveFloodSize; std::vector vhListenSocket; + /** Guards vhListenSocket. All current callers of BindListenPort run at + * init time before ThreadSocketHandler starts, and Stop() runs after it + * joins, so under the current call graph there is no concurrent access. + * The lock is kept as defense-in-depth against a future runtime caller + * (e.g. if the dedicated onion bind ever moves back to the Tor control + * thread) mutating the vector while the socket handler or Stop() is + * iterating it. */ + mutable CCriticalSection cs_vhListenSocket; std::atomic fNetworkActive; banmap_t setBanned; CCriticalSection cs_setBanned; @@ -598,6 +613,8 @@ void AdvertiseLocal(CNode *pnode); void SetLimited(enum Network net, bool fLimited = true); bool IsLimited(enum Network net); bool IsLimited(const CNetAddr& addr); +void SetNetworkExplicitlyLimited(enum Network net, bool fLimited = true); +bool IsNetworkExplicitlyLimited(enum Network net); bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); bool RemoveLocal(const CService& addr); @@ -641,6 +658,10 @@ class CNodeStats int nVersion; std::string cleanSubVer; bool fInbound; + /** True when the peer connected to us over our Tor hidden service. The + * socket-level address will be 127.0.0.1, but the connection really came + * in through the onion listener, so we classify it as an onion peer. */ + bool m_inbound_onion; bool fAddnode; int nStartingHeight; uint64_t nSendBytes; @@ -756,6 +777,9 @@ class CNode bool fAddnode; bool fClient; const bool fInbound; + /** True when this peer reached us through our Tor hidden service (inbound + * on the dedicated onion listener). See CNodeStats::m_inbound_onion. */ + const bool m_inbound_onion; std::atomic_bool fSuccessfullyConnected; std::atomic_bool fDisconnect; // We use fRelayTxes for two purposes - @@ -805,8 +829,8 @@ class CNode // inventory based relay CRollingBloomFilter filterInventoryKnown; - // Set of Dandelion transactions that should be known to this peer - std::set setDandelionInventoryKnown; + // Bounded bloom filter for Dandelion inventory known (same semantics as filterInventoryKnown) + CRollingBloomFilter filterDandelionInventoryKnown; // Set of transaction ids we still have to announce. // They are sorted by the mempool before relay, so the order is not important. std::set setInventoryTxToSend; @@ -871,7 +895,7 @@ class CNode // If true, we will send and receive ADDRV2 messages (BIP155) std::atomic m_wants_addrv2{false}; - CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string &addrNameIn = "", bool fInboundIn = false); + CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool inbound_onion = false); ~CNode(); private: @@ -992,7 +1016,7 @@ class CNode setInventoryForcedToSend.insert(inv.hash); } } else if (inv.type == MSG_DANDELION_TX) { - if (fForce || setDandelionInventoryKnown.count(inv.hash) == 0) { + if (fForce || !filterDandelionInventoryKnown.contains(inv.hash)) { vInventoryDandelionTxToSend.push_back(inv.hash); } } else if (inv.type == MSG_BLOCK) { diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 375749c939..28ed856143 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1193,14 +1193,17 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam uint256 dandelionServiceDiscoveryHash; dandelionServiceDiscoveryHash.SetHex( "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - if (txinfo.tx && !CNode::isDandelionInbound(pfrom) && - pfrom->setDandelionInventoryKnown.count(inv.hash) != 0) { - connman.PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::DANDELIONTX, *txinfo.tx)); - push = true; - } else if (inv.hash == dandelionServiceDiscoveryHash && - pfrom->setDandelionInventoryKnown.count(inv.hash) != 0) { - pfrom->fSupportsDandelion = true; - push = true; + { + LOCK(pfrom->cs_inventory); + if (txinfo.tx && !CNode::isDandelionInbound(pfrom) && + pfrom->filterDandelionInventoryKnown.contains(inv.hash)) { + connman.PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::DANDELIONTX, *txinfo.tx)); + push = true; + } else if (inv.hash == dandelionServiceDiscoveryHash && + pfrom->filterDandelionInventoryKnown.contains(inv.hash)) { + pfrom->fSupportsDandelion = true; + push = true; + } } } if (!push) { @@ -1910,8 +1913,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } else if (inv.type == MSG_DANDELION_TX) { - auto result = pfrom->setDandelionInventoryKnown.insert(inv.hash); - fAlreadyHave = !result.second; + { + LOCK(pfrom->cs_inventory); + fAlreadyHave = pfrom->filterDandelionInventoryKnown.contains(inv.hash); + pfrom->filterDandelionInventoryKnown.insert(inv.hash); + } uint256 dandelionServiceDiscoveryHash; dandelionServiceDiscoveryHash.SetHex( "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -3167,6 +3173,9 @@ static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman& connman) else if (pnode->fAddnode) LogPrintf("Warning: not punishing addnoded peer %s!\n", pnode->addr.ToString()); else { + if (llmq::quorumSigSharesManager) { + llmq::quorumSigSharesManager->MarkNodeBanned(pnode->GetId()); + } pnode->fDisconnect = true; if (pnode->addr.IsLocal()) LogPrintf("Warning: not banning local peer %s!\n", pnode->addr.ToString()); @@ -3611,10 +3620,10 @@ bool SendMessages(CNode* pto, CConnman& connman, const std::atomic& interr // Add Dandelion transactions for (const uint256& hash : pto->vInventoryDandelionTxToSend) { - pto->setDandelionInventoryKnown.insert(hash); + pto->filterDandelionInventoryKnown.insert(hash); uint256 dandelionServiceDiscoveryHash; dandelionServiceDiscoveryHash.SetHex( - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); if (!pto->fSupportsDandelion && hash != dandelionServiceDiscoveryHash) { //LogPrintf("Pushing transaction MSG_TX %s to %s.", // hash.ToString(), pto->addr.ToString()); diff --git a/src/netbase.cpp b/src/netbase.cpp index 31bab57a26..4673e42a07 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -44,18 +44,32 @@ enum Network ParseNetwork(std::string net) { boost::to_lower(net); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; - if (net == "tor" || net == "onion") return NET_ONION; + if (net == "tor") { + static bool warned = false; + if (!warned) { + LogPrintf("Warning: net name 'tor' is deprecated and will be removed in the future. You should use 'onion' instead.\n"); + warned = true; + } + return NET_ONION; + } + if (net == "onion") return NET_ONION; return NET_UNROUTABLE; } std::string GetNetworkName(enum Network net) { switch(net) { + case NET_UNROUTABLE: return "not_publicly_routable"; case NET_IPV4: return "ipv4"; case NET_IPV6: return "ipv6"; case NET_ONION: return "onion"; - default: return ""; + case NET_I2P: return "i2p"; + case NET_CJDNS: return "cjdns"; + case NET_INTERNAL: return "internal"; + case NET_MAX: break; } + assert(!"unexpected Network value"); + return "unknown"; } void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { diff --git a/src/primitives/mint_spend.cpp b/src/primitives/mint_spend.cpp index 486ca75996..ca55d43cb5 100644 --- a/src/primitives/mint_spend.cpp +++ b/src/primitives/mint_spend.cpp @@ -1,24 +1,4 @@ #include "mint_spend.h" -#include "../liblelantus/coin.h" - -GroupElement const & MintMeta::GetPubCoinValue() const { - return pubCoinValue; -} - - -void MintMeta::SetPubCoinValue(GroupElement const & other) { - if (other == pubCoinValue) - return; - pubCoinValueHash.reset(); - pubCoinValue = other; -} - - -uint256 MintMeta::GetPubCoinValueHash() const { - if(!pubCoinValueHash) - pubCoinValueHash.reset(primitives::GetPubCoinValueHash(pubCoinValue)); - return *pubCoinValueHash; -} namespace primitives { diff --git a/src/primitives/mint_spend.h b/src/primitives/mint_spend.h index 9e55d06f78..28371a210c 100644 --- a/src/primitives/mint_spend.h +++ b/src/primitives/mint_spend.h @@ -10,87 +10,9 @@ #include #include #include "key.h" -#include "liblelantus/coin.h" #include "serialize.h" #include "firo_params.h" - -//struct that is safe to store essential mint data, without holding any information that allows for actual spending (serial, randomness, private key) -struct MintMeta -{ - int nHeight; - int nId; - GroupElement const & GetPubCoinValue() const; - void SetPubCoinValue(GroupElement const & other); - uint256 GetPubCoinValueHash() const ; - uint256 hashSerial; - uint8_t nVersion; - uint256 txid; - bool isUsed; - bool isArchived; - bool isSeedCorrect; -protected: - GroupElement pubCoinValue; - mutable boost::optional pubCoinValueHash; -}; - -struct CLelantusMintMeta : MintMeta -{ - uint64_t amount; -}; - -struct CLelantusEntry { - //public - GroupElement value; - - //private - Scalar randomness; - Scalar serialNumber; - - // Signature over partial transaction - // to make sure the outputs are not changed by attacker. - std::vector ecdsaSecretKey; - - bool IsUsed; - int nHeight; - int id; - - // Starting from Version 3 == sigma, this number is coin value * COIN, - // I.E. it is set to 100.000.000 for 1 firo. - int64_t amount; -}; - -class CLelantusSpendEntry -{ -public: - Scalar coinSerial; - uint256 hashTx; - GroupElement pubCoin; - int id; - int64_t amount; - - CLelantusSpendEntry() - { - SetNull(); - } - - void SetNull() - { - coinSerial = Scalar(uint64_t(0)); - pubCoin = GroupElement(); - id = 0; - amount = 0; - } - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(coinSerial); - READWRITE(hashTx); - READWRITE(pubCoin); - READWRITE(id); - READWRITE(amount); - } -}; +#include "../secp256k1/include/GroupElement.h" namespace primitives { uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial); diff --git a/src/pubkey.h b/src/pubkey.h index 0da1cae33a..b487e4c76e 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -242,7 +242,7 @@ struct CExtPubKey { unsigned int len = ::ReadCompactSize(s); unsigned char code[BIP32_EXTKEY_SIZE]; if (len != BIP32_EXTKEY_SIZE) - throw std::runtime_error("Invalid extended key size\n"); + throw std::runtime_error("Invalid extended key size"); s.read((char *)&code[0], len); Decode(code); } diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 3d66a78e5c..5fbcc58fa7 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -13,11 +13,38 @@ endif() get_target_property(qt_lib_type Qt6::Core TYPE) +if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD)$" AND TARGET Qt6::QWaylandIntegrationPlugin) + set(HAVE_WAYLAND TRUE) +endif() + +if(qt_lib_type STREQUAL "STATIC_LIBRARY" AND HAVE_WAYLAND) + find_library(WAYLAND_FFI_LIBRARY NAMES ffi) + if(NOT WAYLAND_FFI_LIBRARY) + message(FATAL_ERROR "Static Qt Wayland plugins require libffi, but it was not found") + endif() + if(TARGET Wayland::Client) + set_property(TARGET Wayland::Client APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${WAYLAND_FFI_LIBRARY}") + endif() +endif() + function(import_plugins target) if(qt_lib_type STREQUAL "STATIC_LIBRARY") set(plugins Qt6::QMinimalIntegrationPlugin) if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD)$") list(APPEND plugins Qt6::QXcbIntegrationPlugin) + if(TARGET Qt6::QWaylandIntegrationPlugin) + list(APPEND plugins Qt6::QWaylandIntegrationPlugin) + list(APPEND plugins Qt6::QWaylandEglPlatformIntegrationPlugin) + if(TARGET Qt6::QWaylandXdgShellIntegrationPlugin) + list(APPEND plugins Qt6::QWaylandXdgShellIntegrationPlugin) + endif() + if(TARGET Qt6::QWaylandEglClientBufferPlugin) + list(APPEND plugins Qt6::QWaylandEglClientBufferPlugin) + endif() + if(TARGET Qt6::QWaylandBradientDecorationPlugin) + list(APPEND plugins Qt6::QWaylandBradientDecorationPlugin) + endif() + endif() elseif(WIN32) list(APPEND plugins Qt6::QWindowsIntegrationPlugin Qt6::QModernWindowsStylePlugin) elseif(APPLE) @@ -147,6 +174,10 @@ target_compile_definitions(firoqt PUBLIC QT_NO_KEYWORDS QT_USE_QSTRINGBUILDER + $<$:HAVE_WAYLAND> + $<$:HAVE_QT_WAYLAND_XDG_SHELL_INTEGRATION_PLUGIN> + $<$:HAVE_QT_WAYLAND_EGL_CLIENT_BUFFER_PLUGIN> + $<$:HAVE_QT_WAYLAND_BRADIENT_DECORATION_PLUGIN> ) target_include_directories(firoqt PUBLIC diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 18709b7b04..ee4d891a57 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -24,27 +24,32 @@ #include #include -AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent, bool isReused) : +AddressBookPage::AddressBookPage(const PlatformStyle *_platformStyle, Mode _mode, Tabs _tab, QWidget *parent, bool isReused) : QDialog(parent), ui(new Ui::AddressBookPage), + platformStyle(_platformStyle), model(0), mode(_mode), - tab(_tab) + tab(_tab), + initialAddressType(-1) { ui->setupUi(this); this->isReused = isReused; - if (!platformStyle->getImagesOnButtons()) { + if (!_platformStyle->getImagesOnButtons()) { ui->newAddress->setIcon(QIcon()); + ui->extendAddress->setIcon(QIcon()); ui->copyAddress->setIcon(QIcon()); ui->deleteAddress->setIcon(QIcon()); ui->exportButton->setIcon(QIcon()); } else { - ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add")); - ui->copyAddress->setIcon(platformStyle->SingleColorIcon(":/icons/editcopy")); - ui->deleteAddress->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export")); + ui->newAddress->setIcon(_platformStyle->SingleColorIcon(":/icons/add")); + ui->extendAddress->setIcon(_platformStyle->SingleColorIcon(":/icons/plus")); + ui->copyAddress->setIcon(_platformStyle->SingleColorIcon(":/icons/editcopy")); + ui->deleteAddress->setIcon(_platformStyle->SingleColorIcon(":/icons/remove")); + ui->exportButton->setIcon(_platformStyle->SingleColorIcon(":/icons/export")); } + ui->extendAddress->setVisible(false); // hide extend address button for now switch(mode) { @@ -85,6 +90,7 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, QAction *copyLabelAction = new QAction(tr("Copy &Label"), this); QAction *editAction = new QAction(tr("&Edit"), this); deleteAction = new QAction(ui->deleteAddress->text(), this); + QAction *extendAction = new QAction(tr("&Extend"), this); // Build context menu contextMenu = new QMenu(this); @@ -100,6 +106,7 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, connect(copyLabelAction, &QAction::triggered, this, &AddressBookPage::onCopyLabelAction); connect(editAction, &QAction::triggered, this, &AddressBookPage::onEditAction); connect(deleteAction, &QAction::triggered, this, &AddressBookPage::on_deleteAddress_clicked); + connect(extendAction, &QAction::triggered, this, &AddressBookPage::on_extendAddress_clicked); connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); @@ -111,47 +118,64 @@ AddressBookPage::~AddressBookPage() delete ui; } -void AddressBookPage::setModel(AddressTableModel *_model) +void AddressBookPage::populateAddressTypes(bool sparkAllowed) { - this->model = _model; - if(!_model) - return; - bool spark = this->model->IsSparkAllowed(); + ui->addressType->clear(); + ui->addressType->show(); - if (tab == SendingTab) { - if (spark) + if (tab == SendingTab || (tab == ReceivingTab && !this->isReused)) { + if (sparkAllowed) ui->addressType->addItem(tr("Spark"), Spark); - ui->addressType->addItem(tr("Transparent"), Transparent); - if (spark) { + ui->addressType->addItem(tr("Transparent"), Transparent); + if (sparkAllowed) { ui->addressType->addItem(tr("Spark names"), SparkName); ui->addressType->addItem(tr("My own spark names"), SparkNameMine); } - } else if(tab == ReceivingTab && !this->isReused) { - if (spark) { - ui->addressType->addItem(tr("Spark"), Spark); - } - ui->addressType->addItem(tr("Transparent"), Transparent); } else { ui->addressType->addItem(tr(""), Transparent); ui->addressType->addItem(tr("Transparent"), Transparent); ui->addressType->hide(); } +} + +int AddressBookPage::currentAddressType() const +{ + return ui->addressType->currentData().toInt(); +} + +bool AddressBookPage::isSparkNameType(int type) +{ + return type == (int)SparkName || type == (int)SparkNameMine; +} + +void AddressBookPage::setModel(AddressTableModel *_model) +{ + this->model = _model; + if(!_model) + return; + populateAddressTypes(this->model->IsSparkAllowed()); proxyModel = new QSortFilterProxyModel(this); fproxyModel = new AddressBookFilterProxy(this); proxyModel->setSourceModel(model); - switch(tab) - { - case ReceivingTab: - // Receive filter - proxyModel->setFilterRole(AddressTableModel::TypeRole); - proxyModel->setFilterFixedString(AddressTableModel::Receive); - break; - case SendingTab: - // Send filter - proxyModel->setFilterRole(AddressTableModel::TypeRole); - proxyModel->setFilterFixedString(AddressTableModel::Send); - break; + // Spark names are always stored with Send type, so skip the + // Send/Receive filter when we specifically want spark names. + if (initialAddressType == SparkName || initialAddressType == SparkNameMine) { + // No TypeRole filter — let fproxyModel handle filtering by address type + } else { + switch(tab) + { + case ReceivingTab: + // Receive filter + proxyModel->setFilterRole(AddressTableModel::TypeRole); + proxyModel->setFilterFixedString(AddressTableModel::Receive); + break; + case SendingTab: + // Send filter + proxyModel->setFilterRole(AddressTableModel::TypeRole); + proxyModel->setFilterFixedString(AddressTableModel::Send); + break; + } } proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -179,25 +203,31 @@ void AddressBookPage::setModel(AddressTableModel *_model) connect(model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress); selectionChanged(); - chooseAddressType(0); + int startIdx = 0; + if (initialAddressType >= 0) { + for (int i = 0; i < ui->addressType->count(); ++i) { + if (ui->addressType->itemData(i).toInt() == initialAddressType) { + startIdx = i; + ui->addressType->setCurrentIndex(i); + break; + } + } + } + chooseAddressType(startIdx); connect(ui->addressType, qOverload(&QComboBox::activated), this, &AddressBookPage::chooseAddressType); } -void AddressBookPage::updateSpark() { - ui->addressType->clear(); - if (tab == SendingTab) { - ui->addressType->addItem(tr("Spark"), Spark); - ui->addressType->addItem(tr("Transparent"), Transparent); - } else if(tab == ReceivingTab && !this->isReused) { - ui->addressType->addItem(tr("Spark"), Spark); - ui->addressType->addItem(tr("Transparent"), Transparent); - } else { - ui->addressType->addItem(tr(""), Transparent); - ui->addressType->addItem(tr("Transparent"), Transparent); - ui->addressType->hide(); - } +bool AddressBookPage::updateSpark() +{ + const bool sparkAllowed = model && model->IsSparkAllowed(); + populateAddressTypes(sparkAllowed); chooseAddressType(0); + return sparkAllowed; +} + +void AddressBookPage::setInitialAddressType(AddressTypeEnum type) { + initialAddressType = static_cast(type); } void AddressBookPage::on_copyAddress_clicked() @@ -213,14 +243,15 @@ void AddressBookPage::onCopyLabelAction() void AddressBookPage::onEditAction() { QModelIndexList indexes; + const int selectedType = currentAddressType(); - if (ui->addressType->currentText() == AddressTableModel::SparkName) + if (isSparkNameType(selectedType)) return; EditAddressDialog::Mode mode; AddressTableModel * pmodel; pmodel = model; - if (ui->addressType->currentText() == AddressTableModel::Transparent) { + if (selectedType == (int)Transparent) { mode = tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress; } else { mode = tab == SendingTab ? EditAddressDialog::EditSparkSendingAddress : EditAddressDialog::EditSparkReceivingAddress; @@ -246,7 +277,8 @@ void AddressBookPage::on_newAddress_clicked() if(!model) return; - if (ui->addressType->currentText() == AddressTableModel::SparkName) { + const int selectedType = currentAddressType(); + if (isSparkNameType(selectedType)) { CreateSparkNamePage *dialog = new CreateSparkNamePage(platformStyle, this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModel(model->getWalletModel()); @@ -257,7 +289,7 @@ void AddressBookPage::on_newAddress_clicked() AddressTableModel *pmodel; EditAddressDialog::Mode mode; pmodel = model; - if (ui->addressType->currentText() == AddressTableModel::Spark) { + if (selectedType == (int)Spark) { mode = tab == SendingTab ? EditAddressDialog::NewSparkSendingAddress : EditAddressDialog::NewSparkReceivingAddress; } else { mode = tab == SendingTab ? EditAddressDialog::NewSendingAddress : EditAddressDialog::NewReceivingAddress; @@ -275,8 +307,9 @@ void AddressBookPage::on_deleteAddress_clicked() { QTableView *table; table = ui->tableView; + const int selectedType = currentAddressType(); - if(!table->selectionModel() || ui->addressType->currentText() == AddressTableModel::SparkName) + if(!table->selectionModel() || isSparkNameType(selectedType)) return; QModelIndexList indexes = table->selectionModel()->selectedRows(); @@ -287,6 +320,29 @@ void AddressBookPage::on_deleteAddress_clicked() } } +void AddressBookPage::on_extendAddress_clicked() +{ + if (!model) + return; + if (!ui->tableView || !ui->tableView->selectionModel()) + return; + + QModelIndexList selectionLabel = ui->tableView->selectionModel()->selectedRows(AddressTableModel::Label); + QModelIndexList selectionAddress = ui->tableView->selectionModel()->selectedRows(AddressTableModel::Address); + + if (selectionLabel.isEmpty() || selectionAddress.isEmpty()) + return; + + QString rawLabel = selectionLabel.at(0).data(Qt::EditRole).toString(); + QString name = rawLabel.startsWith('@') ? rawLabel.mid(1) : rawLabel; + QString address = selectionAddress.at(0).data(Qt::EditRole).toString(); + CreateSparkNamePage *dialog = new CreateSparkNamePage(platformStyle, this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setModel(model->getWalletModel()); + dialog->setExtendMode(name, address); + dialog->show(); +} + void AddressBookPage::selectionChanged() { // Set button states based on selected tab and selection @@ -298,12 +354,12 @@ void AddressBookPage::selectionChanged() if(table->selectionModel()->hasSelection()) { - bool fSparkNames = ui->addressType->currentText() == AddressTableModel::SparkName; + bool fSparkNames = isSparkNameType(currentAddressType()); switch(tab) { case SendingTab: // In sending tab, allow deletion of selection - ui->deleteAddress->setEnabled(true); + ui->deleteAddress->setEnabled(!fSparkNames); ui->deleteAddress->setVisible(!fSparkNames); deleteAction->setEnabled(!fSparkNames); break; @@ -316,11 +372,13 @@ void AddressBookPage::selectionChanged() } ui->copyAddress->setEnabled(true); + ui->extendAddress->setEnabled(true); } else { ui->deleteAddress->setEnabled(false); ui->copyAddress->setEnabled(false); + ui->extendAddress->setEnabled(false); } } @@ -334,12 +392,18 @@ void AddressBookPage::done(int retval) // Figure out which address was selected, and return it QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); + QModelIndexList labelIndexes = table->selectionModel()->selectedRows(AddressTableModel::Label); for (const QModelIndex& index : indexes) { QVariant address = table->model()->data(index); returnValue = address.toString(); } + for (const QModelIndex& index : labelIndexes) { + QVariant label = table->model()->data(index); + returnLabel = label.toString(); + } + if(returnValue.isEmpty()) { // If no address entry selected, return rejected @@ -363,7 +427,8 @@ void AddressBookPage::on_exportButton_clicked() FIRO_UNUSED QTableView *table; writer.setModel(proxyModel); - if (ui->addressType->currentText() == AddressTableModel::Transparent) { + const int selectedType = currentAddressType(); + if (selectedType == (int)Transparent) { writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("Transparent Address", AddressTableModel::Address, Qt::EditRole); writer.addColumn("Address Type", AddressTableModel::AddressType, Qt::EditRole); @@ -412,17 +477,42 @@ void AddressBookPage::chooseAddressType(int idx) if(!proxyModel) return; - if (idx == 2) { + const int selectedType = ui->addressType->itemData(idx).toInt(); + + if (isSparkNameType(selectedType)) { model->ProcessPendingSparkNameChanges(); ui->deleteAddress->setEnabled(false); + ui->deleteAddress->setVisible(false); deleteAction->setEnabled(false); - } - else { + // Remove TypeRole filter so spark names (stored as Send) are visible. + proxyModel->setFilterRole(0); + proxyModel->setFilterFixedString(QString()); + } else { + ui->deleteAddress->setVisible(tab == SendingTab); + // Restore TypeRole filter for non-spark-name types. + switch(tab) + { + case ReceivingTab: + proxyModel->setFilterRole(AddressTableModel::TypeRole); + proxyModel->setFilterFixedString(AddressTableModel::Receive); + break; + case SendingTab: + proxyModel->setFilterRole(AddressTableModel::TypeRole); + proxyModel->setFilterFixedString(AddressTableModel::Send); + break; + } selectionChanged(); } + + if (selectedType == (int)SparkNameMine) { + ui->newAddress->setVisible(false); + ui->extendAddress->setVisible(true); + } else { + ui->extendAddress->setVisible(false); + ui->newAddress->setVisible(true); + } - fproxyModel->setTypeFilter( - ui->addressType->itemData(idx).toInt()); + fproxyModel->setTypeFilter(selectedType); } AddressBookFilterProxy::AddressBookFilterProxy(QObject *parent) : diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 18ad864d9e..d50c950760 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -55,8 +55,11 @@ class AddressBookPage : public QDialog void setModel(AddressTableModel *model); const QString &getReturnValue() const { return returnValue; } + const QString &getReturnLabel() const { return returnLabel; } - void updateSpark(); + void setInitialAddressType(AddressTypeEnum type); + + bool updateSpark(); public Q_SLOTS: void done(int retval) override; @@ -68,6 +71,8 @@ public Q_SLOTS: Mode mode; Tabs tab; QString returnValue; + QString returnLabel; + int initialAddressType; QSortFilterProxyModel *proxyModel; AddressBookFilterProxy *fproxyModel; QMenu *contextMenu; @@ -75,12 +80,17 @@ public Q_SLOTS: QAction *deleteAction; // to be able to explicitly disable it QString newAddressToSelect; bool isReused; + void populateAddressTypes(bool sparkAllowed); + int currentAddressType() const; + static bool isSparkNameType(int type); private Q_SLOTS: /** Delete currently selected address entry */ void on_deleteAddress_clicked(); /** Create a new address for receiving coins and / or add a new address book entry */ void on_newAddress_clicked(); + /** Extend address expiration date (spark names only) */ + void on_extendAddress_clicked(); /** Copy address of currently selected address entry to clipboard */ void on_copyAddress_clicked(); /** Copy label of currently selected address entry to clipboard (no button) */ diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 493967cc6e..bbc9233e7a 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -18,6 +18,7 @@ #include #include +#include const QString AddressTableModel::Send = "S"; const QString AddressTableModel::Receive = "R"; @@ -100,18 +101,35 @@ class AddressTablePriv CSparkNameBlockIndexData sparkNameData; }; QList pendingSparkNameChanges; + bool pendingSparkNameDrainScheduled = false; CCriticalSection cs_pendingSparkNameChanges; private: + void queueProcessPendingSparkNameChanges() { + // Caller must hold cs_pendingSparkNameChanges. Coalesce bursts into a + // single queued drain so a block full of spark-name mutations doesn't + // flood the Qt event loop. + if (pendingSparkNameDrainScheduled) + return; + pendingSparkNameDrainScheduled = true; + QMetaObject::invokeMethod(parent, "ProcessPendingSparkNameChanges", Qt::QueuedConnection); + } + void sparkNameAdded(const CSparkNameBlockIndexData &sparkNameData) { + if (!parent->AutoProcessPendingSparkNameChanges()) + return; LOCK(cs_pendingSparkNameChanges); pendingSparkNameChanges.append(PendingSparkNameChange{CT_NEW, sparkNameData}); + queueProcessPendingSparkNameChanges(); } void sparkNameRemoved(const CSparkNameBlockIndexData &sparkNameData) { + if (!parent->AutoProcessPendingSparkNameChanges()) + return; LOCK(cs_pendingSparkNameChanges); pendingSparkNameChanges.append(PendingSparkNameChange{CT_DELETED, sparkNameData}); + queueProcessPendingSparkNameChanges(); } public: @@ -321,6 +339,7 @@ class AddressTablePriv LOCK(cs_pendingSparkNameChanges); pendingChanges = pendingSparkNameChanges; pendingSparkNameChanges.clear(); + pendingSparkNameDrainScheduled = false; } LOCK(wallet->cs_wallet); diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 5ecfccd079..f9a0667d2b 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -91,7 +91,8 @@ class AddressTableModel : public QAbstractTableModel PcodeAddressTableModel * getPcodeAddressTableModel(); bool IsSparkAllowed(); - void ProcessPendingSparkNameChanges(); + Q_INVOKABLE void ProcessPendingSparkNameChanges(); + virtual bool AutoProcessPendingSparkNameChanges() const { return true; } WalletModel *getWalletModel() const { return walletModel; } protected: @@ -140,6 +141,7 @@ class PcodeAddressTableModel : public AddressTableModel /*@}*/ QString addRow(const QString &type, const QString &label, const QString &address, const QString &addressType) override; + bool AutoProcessPendingSparkNameChanges() const override { return false; } AddressTableModel::EditStatus getEditStatus() const { return editStatus; } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 42e082b6e9..832120de2e 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -46,10 +46,13 @@ #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -70,6 +73,19 @@ Q_IMPORT_PLUGIN(AccessibleFactory) #endif #if defined(QT_QPA_PLATFORM_XCB) Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); +#ifdef HAVE_WAYLAND +Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin); +Q_IMPORT_PLUGIN(QWaylandEglPlatformIntegrationPlugin); +#ifdef HAVE_QT_WAYLAND_XDG_SHELL_INTEGRATION_PLUGIN +Q_IMPORT_PLUGIN(QWaylandXdgShellIntegrationPlugin); +#endif +#ifdef HAVE_QT_WAYLAND_EGL_CLIENT_BUFFER_PLUGIN +Q_IMPORT_PLUGIN(QWaylandEglClientBufferPlugin); +#endif +#ifdef HAVE_QT_WAYLAND_BRADIENT_DECORATION_PLUGIN +Q_IMPORT_PLUGIN(QWaylandBradientDecorationPlugin); +#endif +#endif #elif defined(QT_QPA_PLATFORM_WINDOWS) Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) @@ -674,6 +690,37 @@ void BitcoinApplication::migrateToFiro() } } +#if defined(Q_OS_LINUX) +// Write the app icon and desktop file to the user's XDG data directory so the +// Wayland compositor can find them without a system-wide installation. +static void RegisterXdgResources() +{ + const QString dataDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + + const QString iconDir = dataDir + "/icons/hicolor/scalable/apps"; + QDir().mkpath(iconDir); + const QString iconDst = iconDir + "/firo-qt.svg"; + // Always overwrite so the icon stays in sync with the running binary version. + QFile::remove(iconDst); + QFile::copy(":/icons/firo_svg", iconDst); + + const QString appDir = dataDir + "/applications"; + QDir().mkpath(appDir); + QFile desktopFile(appDir + "/firo-qt.desktop"); + if (desktopFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&desktopFile); + out << "[Desktop Entry]\n" + << "Name=Firo\n" + << "Comment=Connect to Firo\n" + << "Exec=" << QCoreApplication::applicationFilePath() << " %u\n" + << "Terminal=false\n" + << "Type=Application\n" + << "Icon=firo-qt\n" + << "Categories=Office;Finance;\n"; + } +} +#endif + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -712,6 +759,10 @@ int main(int argc, char *argv[]) BitcoinApplication app(argc, argv); +#if defined(Q_OS_LINUX) + RegisterXdgResources(); +#endif + // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) @@ -725,6 +776,9 @@ int main(int argc, char *argv[]) QApplication::setOrganizationName(QAPP_ORG_NAME); QApplication::setOrganizationDomain(QAPP_ORG_DOMAIN); QApplication::setApplicationName(QAPP_APP_NAME_DEFAULT); + // Required on Wayland: compositor uses the desktop file name to look up the + // application icon from the XDG icon theme instead of the runtime-set window icon. + QGuiApplication::setDesktopFileName("firo-qt"); // GUIUtil::SubstituteFonts(GetLangTerritory()); // use inlcuded fonts below // load included fonts diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index af8ae02326..8e93e280d6 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -1,6 +1,7 @@ res/icons/bitcoin.png + res/icons/firo.svg res/icons/address-book.png res/icons/quit.png res/icons/send.png @@ -59,7 +60,6 @@ res/icons/tools.png res/icons/exchange.png res/icons/balances.png - res/icons/lelantus.png res/icons/paymentcode.png res/icons/ext_add.png res/icons/ext_add_light.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index c6bc97e4cb..fc3a88bb9b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -33,7 +33,6 @@ #include "chainparams.h" #include "init.h" -#include "lelantus.h" #include "util.h" #include "evo/deterministicmns.h" @@ -121,7 +120,6 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *_platformStyle, const NetworkStyle * openRPCConsoleAction(0), openAction(0), showHelpMessageAction(0), - lelantusAction(0), masternodeAction(0), logoAction(0), trayIcon(0), @@ -132,6 +130,9 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *_platformStyle, const NetworkStyle * modalOverlay(0), prevBlocks(0), spinnerFrame(0), +#ifdef ENABLE_WALLET + sparkAddressbookUpdated(false), +#endif platformStyle(_platformStyle) { // load stylesheet @@ -344,14 +345,6 @@ void BitcoinGUI::createActions() tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET - lelantusAction = new QAction(tr("&Lelantus"), this); - lelantusAction->setStatusTip(tr("Anonymize your coins")); - lelantusAction->setToolTip(lelantusAction->statusTip()); - lelantusAction->setCheckable(true); - lelantusAction->setShortcut(QKeySequence(QString("Alt+%1").arg(key++))); - tabGroup->addAction(lelantusAction); - lelantusAction->setVisible(false); - // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. masternodeAction = new QAction(tr("&Masternodes"), this); @@ -524,7 +517,6 @@ void BitcoinGUI::createToolBars() toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); - toolbar->addAction(lelantusAction); toolbar->addAction(masternodeAction); logoLabel = new QLabel(); @@ -619,14 +611,22 @@ bool BitcoinGUI::addWallet(const QString& name, WalletModel *walletModel) if(!walletFrame) return false; setWalletActionsEnabled(true); - return walletFrame->addWallet(name, walletModel); + const bool walletAdded = walletFrame->addWallet(name, walletModel); + if (walletAdded && clientModel && !sparkAddressbookUpdated) { + sparkAddressbookUpdated = walletFrame->updateAddressbook(); + } + return walletAdded; } bool BitcoinGUI::setCurrentWallet(const QString& name) { if(!walletFrame) return false; - return walletFrame->setCurrentWallet(name); + const bool walletSelected = walletFrame->setCurrentWallet(name); + if (walletSelected && clientModel && !sparkAddressbookUpdated) { + sparkAddressbookUpdated = walletFrame->updateAddressbook(); + } + return walletSelected; } void BitcoinGUI::removeAllWallets() @@ -646,7 +646,6 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); - lelantusAction->setEnabled(enabled); masternodeAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); @@ -973,6 +972,9 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer #ifdef ENABLE_WALLET checkZnodeVisibility(count); + if (!header && walletFrame && !sparkAddressbookUpdated && count >= ::Params().GetConsensus().nSparkStartBlock) { + sparkAddressbookUpdated = walletFrame->updateAddressbook(); + } #endif // ENABLE_WALLET } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 933d0898cb..1b49ac89f9 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -122,7 +122,6 @@ class BitcoinGUI : public QMainWindow QAction *openRPCConsoleAction; QAction *openAction; QAction *showHelpMessageAction; - QAction *lelantusAction; QAction *masternodeAction; QAction *logoAction; QToolBar *toolbar; @@ -137,6 +136,9 @@ class BitcoinGUI : public QMainWindow /** Keep track of previous number of blocks, to detect progress */ int prevBlocks; int spinnerFrame; +#ifdef ENABLE_WALLET + bool sparkAddressbookUpdated; +#endif const PlatformStyle *platformStyle; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 0b7d35c85c..161da36f9e 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -509,7 +509,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog, bool a nQuantity++; // Amount - if(out.tx->tx->vout[out.i].scriptPubKey.IsLelantusJMint() || out.tx->tx->vout[out.i].scriptPubKey.IsSparkSMint()) { + if(out.tx->tx->vout[out.i].scriptPubKey.IsSparkSMint()) { nAmount += model->GetJMintCredit(out.tx->tx->vout[out.i]); } else { nAmount += out.tx->tx->vout[out.i].nValue; @@ -733,7 +733,7 @@ void CoinControlDialog::updateView() int nChildren = 0; BOOST_FOREACH(const COutput& out, coins.second) { CAmount amount; - if(out.tx->tx->vout[out.i].scriptPubKey.IsLelantusJMint() || out.tx->tx->vout[out.i].scriptPubKey.IsSparkSMint()) { + if(out.tx->tx->vout[out.i].scriptPubKey.IsSparkSMint()) { amount = model->GetJMintCredit(out.tx->tx->vout[out.i]); } else { amount = out.tx->tx->vout[out.i].nValue; diff --git a/src/qt/createsparknamepage.cpp b/src/qt/createsparknamepage.cpp index 617479dec3..5e81b49f01 100644 --- a/src/qt/createsparknamepage.cpp +++ b/src/qt/createsparknamepage.cpp @@ -9,10 +9,12 @@ #include "platformstyle.h" #include "validation.h" +#include "sparkname.h" #include "compat_layer.h" #include #include +#include #define SEND_CONFIRM_DELAY 3 @@ -24,7 +26,7 @@ CreateSparkNamePage::CreateSparkNamePage(const PlatformStyle *platformStyle, QWi feeText = ui->feeTextLabel->text(); ui->numberOfYearsEdit->setValue(1); - ui->numberOfYearsEdit->setRange(1, 10); + ui->numberOfYearsEdit->setRange(1, 15); updateFee(); } @@ -45,6 +47,18 @@ void CreateSparkNamePage::setModel(WalletModel *model) this, &CreateSparkNamePage::checkSparkBalance, Qt::UniqueConnection); } +void CreateSparkNamePage::setExtendMode(const QString &name, const QString &address) +{ + extendMode = true; + ui->sparkNameEdit->setText(name); + ui->sparkNameEdit->setEnabled(false); + ui->sparkAddressEdit->setText(address); + ui->sparkAddressEdit->setEnabled(false); + ui->generateButton->setEnabled(false); + this->setWindowTitle(tr("Extend Spark Name")); + updateFee(); +} + void CreateSparkNamePage::on_generateButton_clicked() { QString newSparkAddress = model->generateSparkAddress(); @@ -87,10 +101,42 @@ void CreateSparkNamePage::updateFee() { QString sparkName = ui->sparkNameEdit->text(); int numberOfYears = ui->numberOfYearsEdit->value(); - if (sparkName.isEmpty() || cmp::greater(sparkName.length(), CSparkNameManager::maximumSparkNameLength) || numberOfYears == 0 || numberOfYears > 10) + if (sparkName.isEmpty() || cmp::greater(sparkName.length(), CSparkNameManager::maximumSparkNameLength) || numberOfYears == 0 || numberOfYears > 15) { ui->feeTextLabel->setText(feeText.arg("?")); - else - ui->feeTextLabel->setText(feeText.arg(QString::number(Params().GetConsensus().nSparkNamesFee[sparkName.length()]*numberOfYears))); + return; + } + + int fee = Params().GetConsensus().nSparkNamesFee[sparkName.length()] * numberOfYears; + QString label; + + if (extendMode) { + try { + constexpr int nBlocksPerHour = 24; + int newValidityBlocks = numberOfYears * 365 * 24 * nBlocksPerHour; + + LOCK(cs_main); + int currentHeight = chainActive.Height(); + int currentExpirationHeight = CSparkNameManager::GetInstance()->GetSparkNameBlockHeight( + CSparkNameManager::ToUpper(sparkName.toStdString())); + + int remainingBlocks = currentExpirationHeight - currentHeight; + int totalValidityBlocks = newValidityBlocks + std::max(0, remainingBlocks); + int blocksFromNow = totalValidityBlocks; + + QDateTime expirationDate = QDateTime::currentDateTime().addSecs( + (qint64)blocksFromNow * 3600 / nBlocksPerHour); + + label = tr("Fee: %1 FIRO. New estimated expiration: %2") + .arg(fee) + .arg(expirationDate.toString("MMMM d, yyyy")); + } catch (const std::runtime_error&) { + label = feeText.arg(QString::number(fee)); + } + } else { + label = feeText.arg(QString::number(fee)); + } + + ui->feeTextLabel->setText(label); } bool CreateSparkNamePage::CreateSparkNameTransaction(const std::string &name, const std::string &address, int numberOfYears, const std::string &additionalInfo) diff --git a/src/qt/createsparknamepage.h b/src/qt/createsparknamepage.h index 3c8ac4dc26..27766d6a5b 100644 --- a/src/qt/createsparknamepage.h +++ b/src/qt/createsparknamepage.h @@ -21,11 +21,14 @@ class CreateSparkNamePage : public QDialog private: QString feeText; + bool extendMode = false; public: explicit CreateSparkNamePage(const PlatformStyle *platformStyle, QWidget *parent = 0); ~CreateSparkNamePage(); + void setExtendMode(const QString &name, const QString &address); + void setModel(WalletModel *model); void accept() override; diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index 43101aadb6..74f27e3f71 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -86,6 +86,19 @@ + + + Extend address expiration date + + + &Extend + + + false + + + + Copy the currently selected address to the system clipboard diff --git a/src/qt/forms/createsparkname.ui b/src/qt/forms/createsparkname.ui index 16d54ebb81..1512fb2336 100644 --- a/src/qt/forms/createsparkname.ui +++ b/src/qt/forms/createsparkname.ui @@ -271,8 +271,7 @@ Qt::RichText - <html><head/><body><p>To get this spark name you must pay a fee of <span style=" font-weight:600;">%1 FIRO(s)</span>. Fee depends on spark name lengths, shorter names are more expensive. Fee can be paid only with private funds.</p></body></html> - + <html><head/><body><p>To get this spark name you must pay a fee of <span style=" font-weight:600;">%1 FIRO(s)</span>. Fee depends on spark name lengths, shorter names are more expensive. Fee can be paid only with private funds.</p></body></html> true diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index b567badcce..b3de4370d3 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -164,12 +164,12 @@ - + - Restore all Lelantus transactions following a reindex. + Restore Spark and wallet transaction data following a full reindex (deletes Spark mint records from the wallet, then reindexes the chain and reapplies wallet transactions). This can take several hours. - &Reindex Lelantus + &Reindex Spark wallet data @@ -177,13 +177,16 @@ - + - Lelantus + Spark - + + + When enabled, the wallet can prompt to anonymize transparent funds using Spark. + Enable &auto-anonymize features @@ -191,15 +194,21 @@ + + Split outputs when minting to Spark for better privacy. + Enable &splitting when minting - + + + Show the Spark manual anonymize controls in the overview. + - Enable &lelantus manual-anonymize page + Enable Spark &manual-anonymize page diff --git a/src/qt/forms/overviewpage.h b/src/qt/forms/overviewpage.h deleted file mode 100644 index a8052f5d48..0000000000 --- a/src/qt/forms/overviewpage.h +++ /dev/null @@ -1,408 +0,0 @@ -/******************************************************************************** -** Form generated from reading UI file 'overviewpage.ui' -** -** Created by: Qt User Interface Compiler version 4.8.7 -** -** WARNING! All changes made in this file will be lost when recompiling UI file! -********************************************************************************/ - -#ifndef OVERVIEWPAGE_H -#define OVERVIEWPAGE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class Ui_OverviewPage -{ -public: - QVBoxLayout *topLayout; - QLabel *labelAlerts; - QHBoxLayout *horizontalLayout; - QVBoxLayout *verticalLayout_2; - QFrame *frame; - QVBoxLayout *verticalLayout_4; - QHBoxLayout *horizontalLayout_4; - QLabel *label_5; - QPushButton *labelWalletStatus; - QSpacerItem *horizontalSpacer_3; - QGridLayout *gridLayout; - QLabel *labelWatchPending; - QLabel *labelUnconfirmed; - QLabel *labelWatchImmature; - QFrame *line; - QFrame *lineWatchBalance; - QLabel *labelTotalText; - QLabel *labelImmature; - QSpacerItem *horizontalSpacer_2; - QLabel *labelImmatureText; - QLabel *labelTotal; - QLabel *labelWatchTotal; - QLabel *labelWatchonly; - QLabel *labelBalanceText; - QLabel *labelBalance; - QLabel *labelWatchAvailable; - QLabel *labelPendingText; - QLabel *labelSpendable; - QSpacerItem *verticalSpacer; - QCheckBox *checkboxEnabledTor; - QVBoxLayout *verticalLayout_3; - QFrame *frame_2; - QVBoxLayout *verticalLayout; - QHBoxLayout *horizontalLayout_2; - QLabel *label_4; - QPushButton *labelTransactionsStatus; - QSpacerItem *horizontalSpacer; - QListView *listTransactions; - QSpacerItem *verticalSpacer_2; - - void setupUi(QWidget *OverviewPage) - { - if (OverviewPage->objectName().isEmpty()) - OverviewPage->setObjectName(QString::fromUtf8("OverviewPage")); - OverviewPage->resize(642, 342); - topLayout = new QVBoxLayout(OverviewPage); - topLayout->setObjectName(QString::fromUtf8("topLayout")); - labelAlerts = new QLabel(OverviewPage); - labelAlerts->setObjectName(QString::fromUtf8("labelAlerts")); - labelAlerts->setVisible(false); - labelAlerts->setStyleSheet(QString::fromUtf8("QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; }")); - labelAlerts->setWordWrap(true); - labelAlerts->setMargin(3); - labelAlerts->setTextInteractionFlags(Qt::TextSelectableByMouse); - - topLayout->addWidget(labelAlerts); - - horizontalLayout = new QHBoxLayout(); - horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); - verticalLayout_2 = new QVBoxLayout(); - verticalLayout_2->setObjectName(QString::fromUtf8("verticalLayout_2")); - frame = new QFrame(OverviewPage); - frame->setObjectName(QString::fromUtf8("frame")); - frame->setFrameShape(QFrame::StyledPanel); - frame->setFrameShadow(QFrame::Raised); - verticalLayout_4 = new QVBoxLayout(frame); - verticalLayout_4->setObjectName(QString::fromUtf8("verticalLayout_4")); - horizontalLayout_4 = new QHBoxLayout(); - horizontalLayout_4->setObjectName(QString::fromUtf8("horizontalLayout_4")); - label_5 = new QLabel(frame); - label_5->setObjectName(QString::fromUtf8("label_5")); - QFont font; - font.setBold(true); - font.setWeight(75); - label_5->setFont(font); - - horizontalLayout_4->addWidget(label_5); - - labelWalletStatus = new QPushButton(frame); - labelWalletStatus->setObjectName(QString::fromUtf8("labelWalletStatus")); - labelWalletStatus->setEnabled(false); - labelWalletStatus->setMaximumSize(QSize(30, 16777215)); - QIcon icon; - icon.addFile(QString::fromUtf8(":/icons/warning"), QSize(), QIcon::Normal, QIcon::Off); - icon.addFile(QString::fromUtf8(":/icons/warning"), QSize(), QIcon::Disabled, QIcon::Off); - labelWalletStatus->setIcon(icon); - labelWalletStatus->setIconSize(QSize(24, 24)); - labelWalletStatus->setFlat(true); - - horizontalLayout_4->addWidget(labelWalletStatus); - - horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - horizontalLayout_4->addItem(horizontalSpacer_3); - - - verticalLayout_4->addLayout(horizontalLayout_4); - - gridLayout = new QGridLayout(); - gridLayout->setSpacing(12); - gridLayout->setObjectName(QString::fromUtf8("gridLayout")); - labelWatchPending = new QLabel(frame); - labelWatchPending->setObjectName(QString::fromUtf8("labelWatchPending")); - labelWatchPending->setFont(font); - labelWatchPending->setCursor(QCursor(Qt::IBeamCursor)); - labelWatchPending->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelWatchPending->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelWatchPending->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelWatchPending, 2, 2, 1, 1); - - labelUnconfirmed = new QLabel(frame); - labelUnconfirmed->setObjectName(QString::fromUtf8("labelUnconfirmed")); - labelUnconfirmed->setFont(font); - labelUnconfirmed->setCursor(QCursor(Qt::IBeamCursor)); - labelUnconfirmed->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelUnconfirmed->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelUnconfirmed->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelUnconfirmed, 2, 1, 1, 1); - - labelWatchImmature = new QLabel(frame); - labelWatchImmature->setObjectName(QString::fromUtf8("labelWatchImmature")); - labelWatchImmature->setFont(font); - labelWatchImmature->setCursor(QCursor(Qt::IBeamCursor)); - labelWatchImmature->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelWatchImmature->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelWatchImmature->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelWatchImmature, 3, 2, 1, 1); - - line = new QFrame(frame); - line->setObjectName(QString::fromUtf8("line")); - line->setFrameShape(QFrame::HLine); - line->setFrameShadow(QFrame::Sunken); - - gridLayout->addWidget(line, 4, 0, 1, 2); - - lineWatchBalance = new QFrame(frame); - lineWatchBalance->setObjectName(QString::fromUtf8("lineWatchBalance")); - QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(lineWatchBalance->sizePolicy().hasHeightForWidth()); - lineWatchBalance->setSizePolicy(sizePolicy); - lineWatchBalance->setMinimumSize(QSize(140, 0)); - lineWatchBalance->setFrameShape(QFrame::HLine); - lineWatchBalance->setFrameShadow(QFrame::Sunken); - - gridLayout->addWidget(lineWatchBalance, 4, 2, 1, 1); - - labelTotalText = new QLabel(frame); - labelTotalText->setObjectName(QString::fromUtf8("labelTotalText")); - - gridLayout->addWidget(labelTotalText, 5, 0, 1, 1); - - labelImmature = new QLabel(frame); - labelImmature->setObjectName(QString::fromUtf8("labelImmature")); - labelImmature->setFont(font); - labelImmature->setCursor(QCursor(Qt::IBeamCursor)); - labelImmature->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelImmature->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelImmature->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelImmature, 3, 1, 1, 1); - - horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - gridLayout->addItem(horizontalSpacer_2, 2, 3, 1, 1); - - labelImmatureText = new QLabel(frame); - labelImmatureText->setObjectName(QString::fromUtf8("labelImmatureText")); - - gridLayout->addWidget(labelImmatureText, 3, 0, 1, 1); - - labelTotal = new QLabel(frame); - labelTotal->setObjectName(QString::fromUtf8("labelTotal")); - labelTotal->setFont(font); - labelTotal->setCursor(QCursor(Qt::IBeamCursor)); - labelTotal->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelTotal->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelTotal->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelTotal, 5, 1, 1, 1); - - labelWatchTotal = new QLabel(frame); - labelWatchTotal->setObjectName(QString::fromUtf8("labelWatchTotal")); - labelWatchTotal->setFont(font); - labelWatchTotal->setCursor(QCursor(Qt::IBeamCursor)); - labelWatchTotal->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelWatchTotal->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelWatchTotal->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelWatchTotal, 5, 2, 1, 1); - - labelWatchonly = new QLabel(frame); - labelWatchonly->setObjectName(QString::fromUtf8("labelWatchonly")); - labelWatchonly->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - - gridLayout->addWidget(labelWatchonly, 0, 2, 1, 1); - - labelBalanceText = new QLabel(frame); - labelBalanceText->setObjectName(QString::fromUtf8("labelBalanceText")); - - gridLayout->addWidget(labelBalanceText, 1, 0, 1, 1); - - labelBalance = new QLabel(frame); - labelBalance->setObjectName(QString::fromUtf8("labelBalance")); - labelBalance->setFont(font); - labelBalance->setCursor(QCursor(Qt::IBeamCursor)); - labelBalance->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelBalance->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelBalance->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelBalance, 1, 1, 1, 1); - - labelWatchAvailable = new QLabel(frame); - labelWatchAvailable->setObjectName(QString::fromUtf8("labelWatchAvailable")); - labelWatchAvailable->setFont(font); - labelWatchAvailable->setCursor(QCursor(Qt::IBeamCursor)); - labelWatchAvailable->setText(QString::fromUtf8("0.000\342\200\211000\342\200\21100 FIRO")); - labelWatchAvailable->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - labelWatchAvailable->setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse); - - gridLayout->addWidget(labelWatchAvailable, 1, 2, 1, 1); - - labelPendingText = new QLabel(frame); - labelPendingText->setObjectName(QString::fromUtf8("labelPendingText")); - - gridLayout->addWidget(labelPendingText, 2, 0, 1, 1); - - labelSpendable = new QLabel(frame); - labelSpendable->setObjectName(QString::fromUtf8("labelSpendable")); - labelSpendable->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); - - gridLayout->addWidget(labelSpendable, 0, 1, 1, 1); - - - verticalLayout_4->addLayout(gridLayout); - - - verticalLayout_2->addWidget(frame); - - verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); - - verticalLayout_2->addItem(verticalSpacer); - - checkboxEnabledTor = new QCheckBox(OverviewPage); - checkboxEnabledTor->setObjectName(QString::fromUtf8("checkboxEnabledTor")); - - checkboxEnabledTor->setChecked(false); - - verticalLayout_2->addWidget(checkboxEnabledTor); - - - horizontalLayout->addLayout(verticalLayout_2); - - verticalLayout_3 = new QVBoxLayout(); - verticalLayout_3->setObjectName(QString::fromUtf8("verticalLayout_3")); - frame_2 = new QFrame(OverviewPage); - frame_2->setObjectName(QString::fromUtf8("frame_2")); - frame_2->setFrameShape(QFrame::StyledPanel); - frame_2->setFrameShadow(QFrame::Raised); - verticalLayout = new QVBoxLayout(frame_2); - verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); - horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2")); - label_4 = new QLabel(frame_2); - label_4->setObjectName(QString::fromUtf8("label_4")); - label_4->setFont(font); - - horizontalLayout_2->addWidget(label_4); - - labelTransactionsStatus = new QPushButton(frame_2); - labelTransactionsStatus->setObjectName(QString::fromUtf8("labelTransactionsStatus")); - labelTransactionsStatus->setEnabled(false); - labelTransactionsStatus->setMaximumSize(QSize(30, 16777215)); - labelTransactionsStatus->setIcon(icon); - labelTransactionsStatus->setIconSize(QSize(24, 24)); - labelTransactionsStatus->setFlat(true); - - horizontalLayout_2->addWidget(labelTransactionsStatus); - - horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - horizontalLayout_2->addItem(horizontalSpacer); - - - verticalLayout->addLayout(horizontalLayout_2); - - listTransactions = new QListView(frame_2); - listTransactions->setObjectName(QString::fromUtf8("listTransactions")); - listTransactions->setStyleSheet(QString::fromUtf8("QListView { background: transparent; }")); - listTransactions->setFrameShape(QFrame::NoFrame); - listTransactions->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - listTransactions->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - listTransactions->setSelectionMode(QAbstractItemView::NoSelection); - - verticalLayout->addWidget(listTransactions); - - - verticalLayout_3->addWidget(frame_2); - - verticalSpacer_2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); - - verticalLayout_3->addItem(verticalSpacer_2); - - - horizontalLayout->addLayout(verticalLayout_3); - - horizontalLayout->setStretch(0, 1); - horizontalLayout->setStretch(1, 1); - - topLayout->addLayout(horizontalLayout); - - - retranslateUi(OverviewPage); - - QMetaObject::connectSlotsByName(OverviewPage); - } // setupUi - - void retranslateUi(QWidget *OverviewPage) - { - OverviewPage->setWindowTitle(QApplication::translate("OverviewPage", "Form", 0, QApplication::UnicodeUTF8)); - label_5->setText(QApplication::translate("OverviewPage", "Balances", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - labelWalletStatus->setToolTip(QApplication::translate("OverviewPage", "The displayed information may be out of date. Your wallet automatically synchronizes with the Firo network after a connection is established, but this process has not completed yet.", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelWalletStatus->setText(QString()); -#ifndef QT_NO_TOOLTIP - labelWatchPending->setToolTip(QApplication::translate("OverviewPage", "Unconfirmed transactions to watch-only addresses", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP -#ifndef QT_NO_TOOLTIP - labelUnconfirmed->setToolTip(QApplication::translate("OverviewPage", "Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP -#ifndef QT_NO_TOOLTIP - labelWatchImmature->setToolTip(QApplication::translate("OverviewPage", "Mined balance in watch-only addresses that has not yet matured", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelTotalText->setText(QApplication::translate("OverviewPage", "Total:", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - labelImmature->setToolTip(QApplication::translate("OverviewPage", "Mined balance that has not yet matured", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelImmatureText->setText(QApplication::translate("OverviewPage", "Immature:", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - labelTotal->setToolTip(QApplication::translate("OverviewPage", "Your current total balance", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP -#ifndef QT_NO_TOOLTIP - labelWatchTotal->setToolTip(QApplication::translate("OverviewPage", "Current total balance in watch-only addresses", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelWatchonly->setText(QApplication::translate("OverviewPage", "Watch-only:", 0, QApplication::UnicodeUTF8)); - labelBalanceText->setText(QApplication::translate("OverviewPage", "Available:", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - labelBalance->setToolTip(QApplication::translate("OverviewPage", "Your current spendable balance", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP -#ifndef QT_NO_TOOLTIP - labelWatchAvailable->setToolTip(QApplication::translate("OverviewPage", "Your current balance in watch-only addresses", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelPendingText->setText(QApplication::translate("OverviewPage", "Pending:", 0, QApplication::UnicodeUTF8)); - labelSpendable->setText(QApplication::translate("OverviewPage", "Spendable:", 0, QApplication::UnicodeUTF8)); - checkboxEnabledTor->setText(QApplication::translate("OverviewPage", "Anonymous communication with Tor", 0, QApplication::UnicodeUTF8)); - label_4->setText(QApplication::translate("OverviewPage", "Recent transactions", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - labelTransactionsStatus->setToolTip(QApplication::translate("OverviewPage", "The displayed information may be out of date. Your wallet automatically synchronizes with the Firo network after a connection is established, but this process has not completed yet.", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - labelTransactionsStatus->setText(QString()); - } // retranslateUi - -}; - -namespace Ui { - class OverviewPage: public Ui_OverviewPage {}; -} // namespace Ui - -QT_END_NAMESPACE - -#endif // OVERVIEWPAGE_H diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 96b067fe33..4a83e787cf 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -205,6 +205,25 @@ + + + + + 0 + 0 + + + + Browse your spark names to use as label. + + + My Spark names + + + false + + + diff --git a/src/qt/locale/bitcoin_zh_CN.ts b/src/qt/locale/bitcoin_zh_CN.ts index 8eb35134fd..c5f915dbd0 100644 --- a/src/qt/locale/bitcoin_zh_CN.ts +++ b/src/qt/locale/bitcoin_zh_CN.ts @@ -854,10 +854,6 @@ Once this fee is paid, all future sends to this RAP address do not incur any add Send to RAP address 发送到 RAP 地址 - - Lelantus balance - Lelantus 余额 - Status 状态 @@ -880,7 +876,7 @@ Notification transactions use Lelantus facilities to enhance privacy. After the notification transaction is received by the RAP address issuer, funds can be privately sent to the RAP secret addresses. 向 RAP 地址发送资金需要付款人在第一次付款前发送通知交易。 -通知交易使用 Lelantus 功能来增强隐私。 +通知交易使用隐私功能来增强隐私。 在 RAP 地址发布者收到通知交易后,资金可以被私下发送到 RAP 秘密地址。 @@ -1445,22 +1441,10 @@ After the notification transaction is received by the RAP address issuer, funds Enable &auto-anonymize features 启用自动匿名化功能 - - Enable &lelantus manual-anonymize page - 启用 Lelantus 手动匿名化界面 - &Spend unconfirmed change 使用未经确认的零钱(&S) - - &Reindex Lelantus - 重新索引 Lelantus(&R) - - - Restore all Lelantus transactions following a reindex. - 在重新索引后恢复所有 Lelantus 交易。 - Automatically open the Firo client port on the router. This only works when your router supports UPnP and it is enabled. 自动在路由器中打开 Firo 端口。只有当你的路由器开启了 UPnP 选项时此功能才有效。 @@ -1601,10 +1585,6 @@ After the notification transaction is received by the RAP address issuer, funds The supplied proxy address is invalid. 提供的代理服务器地址无效。 - - Confirm Reindex Lelantus - 确认重新索引 Lelantus - Warning: On restart, this setting will wipe your transaction list, reindex the blockchain, and restore the list from the seed in your wallet. This will likely take a few hours. Are you sure? 警告:在重新启动时,该设置将擦除你的交易列表,重新索引区块链,并从你钱包中的种子恢复列表。这可能需要几个小时。你确定吗? @@ -1829,10 +1809,6 @@ After the notification transaction is received by the RAP address issuer, funds %n block(s) %n 个区块 - - Global Lelantus Pool: - 整个 Lelantus 池 - Total: 总数: diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 8eaf288f9f..f5a7fce8f2 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -19,6 +19,7 @@ #ifdef ENABLE_WALLET #include "wallet/wallet.h" // for CWallet::GetRequiredFee() +#include "spark/state.h" #endif #include @@ -164,7 +165,9 @@ void OptionsDialog::setModel(OptionsModel *_model) connect(ui->threadsScriptVerif, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); /* Wallet */ connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); - connect(ui->reindexLelantus, &QCheckBox::clicked, this, &OptionsDialog::handleEnabledZapChanged); +#ifdef ENABLE_WALLET + connect(ui->reindexSpark, &QCheckBox::clicked, this, &OptionsDialog::handleEnabledZapChanged); +#endif /* Network */ connect(ui->allowIncoming, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); @@ -183,17 +186,20 @@ void OptionsDialog::setMapper() /* Wallet */ mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); - mapper->addMapping(ui->reindexLelantus, OptionsModel::ReindexLelantus); +#ifdef ENABLE_WALLET + mapper->addMapping(ui->reindexSpark, OptionsModel::ReindexSpark); +#endif mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); - - /* Lelantus */ mapper->addMapping(ui->autoAnonymize, OptionsModel::AutoAnonymize); mapper->addMapping(ui->fSplit, OptionsModel::Split); - if (!lelantus::IsLelantusAllowed()) { - ui->lelantusPage->setVisible(false); + mapper->addMapping(ui->sparkPage, OptionsModel::SparkPage); +#ifdef ENABLE_WALLET + if (!spark::IsSparkAllowed()) { + ui->sparkGroupBox->setVisible(false); } - mapper->addMapping(ui->lelantusPage, OptionsModel::LelantusPage); - +#else + ui->sparkGroupBox->setVisible(false); +#endif /* Network */ mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP); mapper->addMapping(ui->allowIncoming, OptionsModel::Listen); @@ -266,20 +272,22 @@ void OptionsDialog::on_hideTrayIcon_stateChanged(int fState) ui->minimizeToTray->setEnabled(true); } } -void OptionsDialog::handleEnabledZapChanged(){ - QMessageBox msgBox; - - if(ui->reindexLelantus->isChecked()){ - QMessageBox::StandardButton retval = QMessageBox::warning(this, tr("Confirm Reindex Lelantus"), - tr("Warning: On restart, this setting will wipe your transaction list, reindex the blockchain, and restore the list from the seed in your wallet. This will likely take a few hours. Are you sure?"), - QMessageBox::Yes|QMessageBox::Cancel, - QMessageBox::Cancel); - if(retval == QMessageBox::Cancel) { - ui->reindexLelantus->setChecked(false); - }else { +void OptionsDialog::handleEnabledZapChanged() +{ +#ifdef ENABLE_WALLET + if (ui->reindexSpark->isChecked()) { + QMessageBox::StandardButton retval = QMessageBox::warning(this, tr("Confirm Spark reindex"), + tr("Warning: On restart, this setting will wipe your transaction list, reindex the blockchain, and restore wallet data from your seed. Spark mint records are cleared and rebuilt from the chain. This will likely take a few hours. Are you sure?"), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + if (retval == QMessageBox::Cancel) { + ui->reindexSpark->setChecked(false); + } else { showRestartWarning(); } - }else { + } else +#endif + { clearStatusLabel(); } } diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 8569b0cb81..4c3949c471 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -13,7 +13,7 @@ #include "amount.h" #include "init.h" -#include "validation.h" // For DEFAULT_SCRIPTCHECK_THREADS +#include "validation.h" // For DEFAULT_SCRIPTCHECK_THREADS and DEFAULT_ZAP_WALLET #include "net.h" #include "netbase.h" #include "txdb.h" // for -dbcache defaults @@ -89,10 +89,9 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("fSplit", true); fSplit = settings.value("fSplit", true).toBool(); - if (!settings.contains("fLelantusPage")) - settings.setValue("fLelantusPage", false); - fLelantusPage = settings.value("fLelantusPage", false).toBool(); - + if (!settings.contains("fSparkPage")) + settings.setValue("fSparkPage", true); + fSparkPage = settings.value("fSparkPage", true).toBool(); // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. @@ -123,10 +122,14 @@ void OptionsModel::Init(bool resetSettings) if (!SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) addOverriddenOption("-spendzeroconfchange"); - if (!settings.contains("bReindexLelantus")) - settings.setValue("bReindexLelantus", DEFAULT_ZAP_WALLET); - bool reindexLelantus = settings.value("bReindexLelantus").toBool(); - if (reindexLelantus) { + if (!settings.contains("bReindexSpark")) { + if (settings.contains("bReindexLelantus")) + settings.setValue("bReindexSpark", settings.value("bReindexLelantus")); + else + settings.setValue("bReindexSpark", DEFAULT_ZAP_WALLET); + } + bool reindexSpark = settings.value("bReindexSpark").toBool(); + if (reindexSpark) { if (!SoftSetBoolArg("-zapwalletmints", true)) addOverriddenOption("-zapwalletmints"); if (!SoftSetBoolArg("-reindex", true)) @@ -134,9 +137,7 @@ void OptionsModel::Init(bool resetSettings) if (!SoftSetArg("-zapwallettxes", std::string("1"))) addOverriddenOption("-zapwallettxes"); } - - // Reset the flag to prevent unneeded reindex, - settings.setValue("bReindexLelantus", false); + settings.setValue("bReindexSpark", false); #endif @@ -269,9 +270,6 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const #ifdef ENABLE_WALLET case SpendZeroConfChange: return settings.value("bSpendZeroConfChange"); - - case ReindexLelantus: - return settings.value("bReindexLelantus"); #endif case DisplayUnit: return nDisplayUnit; @@ -285,8 +283,12 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return fAutoAnonymize; case Split: return fSplit; - case LelantusPage: - return fLelantusPage; +#ifdef ENABLE_WALLET + case ReindexSpark: + return settings.value("bReindexSpark"); +#endif + case SparkPage: + return fSparkPage; case DatabaseCache: return settings.value("nDatabaseCache"); case ThreadsScriptVerif: @@ -402,12 +404,6 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in } break; - case ReindexLelantus: - if (settings.value("bReindexLelantus") != value) { - settings.setValue("bReindexLelantus", value); - setRestartRequired(true); - } - break; #endif case DisplayUnit: setDisplayUnit(value); @@ -439,10 +435,18 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in fSplit = value.toBool(); settings.setValue("fSplit", fSplit); break; - case LelantusPage: - fLelantusPage = value.toBool(); - settings.setValue("fLelantusPage", fLelantusPage); - Q_EMIT lelantusPageChanged(fLelantusPage); +#ifdef ENABLE_WALLET + case ReindexSpark: + if (settings.value("bReindexSpark") != value) { + settings.setValue("bReindexSpark", value); + setRestartRequired(true); + } + break; +#endif + case SparkPage: + fSparkPage = value.toBool(); + settings.setValue("fSparkPage", fSparkPage); + Q_EMIT sparkPageChanged(fSparkPage); break; case DatabaseCache: if (settings.value("nDatabaseCache") != value) { diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 9130c53fd3..206e206586 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -45,13 +45,13 @@ class OptionsModel : public QAbstractListModel ThreadsScriptVerif, // int DatabaseCache, // int SpendZeroConfChange, // bool - ReindexLelantus, // bool Listen, // bool TorSetup, // bool AutoAnonymize, // bool Split, // bool - LelantusPage, // bool enableRapAddresses, // bool + ReindexSpark, // bool (wallet: zap Spark mints + reindex; QSettings bReindexSpark) + SparkPage, // bool (show Spark manual-anonymize UI; QSettings fSparkPage) OptionIDRowCount, }; @@ -75,7 +75,7 @@ class OptionsModel : public QAbstractListModel const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; } bool getAutoAnonymize() { return fAutoAnonymize; } bool getfSplit() { return fSplit; } - bool getLelantusPage() {return fLelantusPage; } + bool getSparkPage() { return fSparkPage; } /* Restart flag helper */ void setRestartRequired(bool fRequired); @@ -92,7 +92,7 @@ class OptionsModel : public QAbstractListModel bool fCoinControlFeatures; bool fAutoAnonymize; bool fSplit; - bool fLelantusPage; + bool fSparkPage; bool fenableRapAddresses; /* settings that were overridden by command-line */ @@ -108,7 +108,7 @@ class OptionsModel : public QAbstractListModel void coinControlFeaturesChanged(bool); void enableRapAddressesChanged(bool); void autoAnonymizeChanged(bool); - void lelantusPageChanged(bool); + void sparkPageChanged(bool); void hideTrayIconChanged(bool); }; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 55a91b5dcc..84260c4cd1 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -2,8 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "../lelantus.h" - #include "overviewpage.h" #include "ui_overviewpage.h" @@ -12,6 +10,7 @@ #include "guiconstants.h" #include "guiutil.h" #include "sparkmodel.h" +#include "spark/state.h" #include "optionsmodel.h" #include "platformstyle.h" #include "transactionfilterproxy.h" @@ -168,10 +167,6 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) showOutOfSyncWarning(true); connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); - - connect(&countDownTimer, &QTimer::timeout, this, &OverviewPage::countDown); - countDownTimer.start(30000); - connect(ui->migrateButton, &QPushButton::clicked, this, &OverviewPage::migrateClicked); } void OverviewPage::handleTransactionClicked(const QModelIndex &index) @@ -204,6 +199,60 @@ OverviewPage::~OverviewPage() delete ui; } +void OverviewPage::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + + // Retrieve new dimensions from the resize event + const int newWidth = event->size().width(); + const int newHeight = event->size().height(); + adjustTextSize(newWidth, newHeight); + + // Determine widths for specific widgets as percentages of total width + int labelWidth = static_cast(newWidth * 0.5); + int labelMinWidth = static_cast(newWidth * 0.15); + int labelMaxWidth = static_cast(newWidth * 0.35); + const int labelHeight = 20; + + // Configure the dimensions and constraints of each widget + ui->labelBalance->setFixedWidth(labelWidth); + ui->labelBalance->setMinimumWidth(labelMinWidth); + ui->labelBalance->setMaximumWidth(labelMaxWidth); + ui->labelBalance->setFixedHeight(labelHeight); + + ui->labelUnconfirmed->setFixedWidth(labelWidth); + ui->labelUnconfirmed->setMinimumWidth(labelMinWidth); + ui->labelUnconfirmed->setMaximumWidth(labelMaxWidth); + ui->labelUnconfirmed->setFixedHeight(labelHeight); + + int buttonWidth = static_cast(newWidth * 0.15); + FIRO_UNUSED int buttonHeight = static_cast(newHeight * 0.05); + int buttonMinHeight = static_cast(20); + int buttonMaxHeight = static_cast(45); + + ui->anonymizeButton->setMinimumWidth(buttonWidth); + ui->anonymizeButton->setMaximumWidth(buttonWidth * 2); + ui->anonymizeButton->setMinimumHeight(buttonMinHeight); + ui->anonymizeButton->setMaximumHeight(buttonMaxHeight); + + // Set the minimum width for all label widgets to ensure they maintain a consistent and readable size regardless of window resizing + ui->labelAnonymizable->setMinimumWidth(labelMinWidth); + ui->labelAlerts->setMinimumWidth(labelMinWidth); + ui->label->setMinimumWidth(labelMinWidth); + ui->labelWatchPending->setMinimumWidth(labelMinWidth); + ui->labelBalance->setMinimumWidth(labelMinWidth); + ui->labelSpendable->setMinimumWidth(labelMinWidth); + ui->labelWatchAvailable->setMinimumWidth(labelMinWidth); + ui->labelUnconfirmedPrivate->setMinimumWidth(labelMinWidth); + ui->labelWatchonly->setMinimumWidth(labelMinWidth); + ui->labelTotal->setMinimumWidth(labelMinWidth); + ui->labelWatchTotal->setMinimumWidth(labelMinWidth); + ui->labelUnconfirmed->setMinimumWidth(labelMinWidth); + ui->labelImmature->setMinimumWidth(labelMinWidth); + ui->labelPrivate->setMinimumWidth(labelMinWidth); + ui->label_4->setMinimumWidth(labelMinWidth); +} + void OverviewPage::on_anonymizeButton_clicked() { if (!walletModel) { @@ -248,6 +297,7 @@ void OverviewPage::setBalance( ui->labelAnonymizable->setText(BitcoinUnits::formatWithUnit(unit, anonymizableBalance, false, BitcoinUnits::separatorAlways)); auto wallet = walletModel->getWallet(); + updateSparkAnonymizeRowVisibility(); ui->anonymizeButton->setEnabled(wallet && wallet->sparkWallet && spark::IsSparkAllowed() && anonymizableBalance > 0); // only show immature (newly mined) balance if it's non-zero, so as not to complicate things @@ -280,7 +330,7 @@ void OverviewPage::setClientModel(ClientModel *model) this->clientModel = model; if(model) { - connect(model, &ClientModel::numBlocksChanged, this, &OverviewPage::onRefreshClicked); + connect(model, &ClientModel::numBlocksChanged, this, [this]() { ui->warningFrame->hide(); }); // Show warning if this is a prerelease version connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts); updateAlerts(model->getStatusBarWarnings()); @@ -290,7 +340,7 @@ void OverviewPage::setClientModel(ClientModel *model) void OverviewPage::setWalletModel(WalletModel *model) { this->walletModel = model; - onRefreshClicked(); + ui->warningFrame->hide(); if(model && model->getOptionsModel()) { // Set up transaction list @@ -321,9 +371,11 @@ void OverviewPage::setWalletModel(WalletModel *model) connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance); connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); + connect(model->getOptionsModel(), &OptionsModel::sparkPageChanged, this, &OverviewPage::updateSparkAnonymizeRowVisibility); updateWatchOnlyLabels(model->haveWatchOnly()); connect(model, &WalletModel::notifyWatchonlyChanged, this, &OverviewPage::updateWatchOnlyLabels); + updateSparkAnonymizeRowVisibility(); } // update the display unit, to not use the default ("BTC") @@ -358,190 +410,6 @@ void OverviewPage::showOutOfSyncWarning(bool fShow) ui->labelTransactionsStatus->setVisible(fShow); } -void OverviewPage::countDown() -{ - secDelay--; - if(secDelay <= 0) { - if(walletModel->getAvailableLelantusCoins() && spark::IsSparkAllowed() && chainActive.Height() < ::Params().GetConsensus().nLelantusGracefulPeriod){ - MigrateLelantusToSparkDialog migrate(walletModel); - } - countDownTimer.stop(); - } -} - -void OverviewPage::onRefreshClicked() -{ - auto privateBalance = walletModel->getWallet()->GetPrivateBalance(); - auto lGracefulPeriod = ::Params().GetConsensus().nLelantusGracefulPeriod; - int heightDifference = lGracefulPeriod - chainActive.Height(); - const int approxBlocksPerDay = 570; - int daysUntilMigrationCloses = heightDifference / approxBlocksPerDay; - - if(privateBalance.first > 0 && chainActive.Height() < lGracefulPeriod && spark::IsSparkAllowed()) { - ui->warningFrame->show(); - migrationWindowClosesIn = QString::fromStdString(std::to_string(daysUntilMigrationCloses)); - blocksRemaining = QString::fromStdString(std::to_string(heightDifference)); - migrateAmount = "" + BitcoinUnits::formatHtmlWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), privateBalance.first); - migrateAmount.append(""); - ui->textWarning1->setText(tr("We have detected Lelantus coins that have not been migrated to Spark. Migration window will close in %1 blocks (~ %2 days).").arg(blocksRemaining , migrationWindowClosesIn)); - ui->textWarning2->setText(tr("to migrate %1 ").arg(migrateAmount)); - QFont qFont = ui->migrateButton->font(); - qFont.setUnderline(true); - ui->migrateButton->setFont(qFont); - } else { - ui->warningFrame->hide(); - } -} - -void OverviewPage::migrateClicked() -{ - auto privateBalance = walletModel->getWallet()->GetPrivateBalance(); - FIRO_UNUSED auto lGracefulPeriod = ::Params().GetConsensus().nLelantusGracefulPeriod; - migrateAmount = "" + BitcoinUnits::formatHtmlWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), privateBalance.first); - migrateAmount.append(""); - QString info = tr("Your wallet needs to be unlocked to migrate your funds to Spark."); - - if(walletModel->getEncryptionStatus() == WalletModel::Locked) { - - AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this, info); - dlg.setModel(walletModel); - dlg.exec(); - } - if (walletModel->getEncryptionStatus() == WalletModel::Unlocked){ - if(walletModel->getAvailableLelantusCoins() && spark::IsSparkAllowed() && chainActive.Height() < ::Params().GetConsensus().nLelantusGracefulPeriod){ - MigrateLelantusToSparkDialog migrate(walletModel); - if(!migrate.getClickedButton()){ - ui->warningFrame->hide(); - } - } - } -} -MigrateLelantusToSparkDialog::MigrateLelantusToSparkDialog(WalletModel *_model):QMessageBox() -{ - this->model = _model; - QDialog::setWindowTitle("Migrate funds from Lelantus to Spark"); - QDialog::setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint); - - QLabel *ic = new QLabel(); - QIcon icon_; - icon_.addFile(QString::fromUtf8(":/icons/ic_info"), QSize(), QIcon::Normal, QIcon::On); - ic->setPixmap(icon_.pixmap(18, 18)); - ic->setFixedWidth(90); - ic->setAlignment(Qt::AlignRight); - ic->setStyleSheet("color:#92400E"); - - QLabel *text = new QLabel(); - text->setText(tr("Firo is migrating to Spark. Please migrate your funds.")); - text->setAlignment(Qt::AlignLeft); - text->setWordWrap(true); - text->setStyleSheet("color:#92400E;text-align:center;word-wrap: break-word;"); - - QPushButton *ignore = new QPushButton(this); - ignore->setText("Ignore"); - ignore->setStyleSheet("margin-top:30px;margin-bottom:60px;margin-left:20px;margin-right:50px;"); - QPushButton *migrate = new QPushButton(this); - migrate->setText("Migrate"); - migrate->setStyleSheet("color:#9b1c2e;background-color:none;margin-top:30px;margin-bottom:60px;margin-left:50px;margin-right:20px;border:1px solid #9b1c2e;"); - QHBoxLayout *groupButton = new QHBoxLayout(this); - groupButton->addWidget(ignore); - groupButton->addWidget(migrate); - - QHBoxLayout *hlayout = new QHBoxLayout(this); - hlayout->addWidget(ic); - hlayout->addWidget(text); - - QWidget *layout_ = new QWidget(); - layout_->setLayout(hlayout); - layout_->setStyleSheet("background-color:#FEF3C7;"); - - QVBoxLayout *vlayout = new QVBoxLayout(this); - vlayout->addWidget(layout_); - vlayout->addLayout(groupButton); - vlayout->setContentsMargins(0,0,0,0); - - QWidget *wbody = new QWidget(); - wbody->setLayout(vlayout); - - layout()->addWidget(wbody); - setContentsMargins(0, 0, 0, 0); - setStyleSheet("margin-right:-30px;"); - setStandardButtons(StandardButtons()); - - connect(ignore, &QPushButton::clicked, this, &MigrateLelantusToSparkDialog::onIgnoreClicked); - connect(migrate, &QPushButton::clicked, this, &MigrateLelantusToSparkDialog::onMigrateClicked); - exec(); -} - -void MigrateLelantusToSparkDialog::onIgnoreClicked() -{ - setVisible(false); - clickedButton = true; -} - -void MigrateLelantusToSparkDialog::onMigrateClicked() -{ - setVisible(false); - clickedButton = false; - model->migrateLelantusToSpark(); -} - -bool MigrateLelantusToSparkDialog::getClickedButton() -{ - return clickedButton; -} -void OverviewPage::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - - // Retrieve new dimensions from the resize event - const int newWidth = event->size().width(); - const int newHeight = event->size().height(); - adjustTextSize(newWidth, newHeight); - - // Determine widths for specific widgets as percentages of total width - int labelWidth = static_cast(newWidth * 0.5); - int labelMinWidth = static_cast(newWidth * 0.15); - int labelMaxWidth = static_cast(newWidth * 0.35); - const int labelHeight = 20; - - // Configure the dimensions and constraints of each widget - ui->labelBalance->setFixedWidth(labelWidth); - ui->labelBalance->setMinimumWidth(labelMinWidth); - ui->labelBalance->setMaximumWidth(labelMaxWidth); - ui->labelBalance->setFixedHeight(labelHeight); - - ui->labelUnconfirmed->setFixedWidth(labelWidth); - ui->labelUnconfirmed->setMinimumWidth(labelMinWidth); - ui->labelUnconfirmed->setMaximumWidth(labelMaxWidth); - ui->labelUnconfirmed->setFixedHeight(labelHeight); - - int buttonWidth = static_cast(newWidth * 0.15); - FIRO_UNUSED int buttonHeight = static_cast(newHeight * 0.05); - int buttonMinHeight = static_cast(20); - int buttonMaxHeight = static_cast(45); - - ui->anonymizeButton->setMinimumWidth(buttonWidth); - ui->anonymizeButton->setMaximumWidth(buttonWidth * 2); - ui->anonymizeButton->setMinimumHeight(buttonMinHeight); - ui->anonymizeButton->setMaximumHeight(buttonMaxHeight); - - // Set the minimum width for all label widgets to ensure they maintain a consistent and readable size regardless of window resizing - ui->labelAnonymizable->setMinimumWidth(labelMinWidth); - ui->labelAlerts->setMinimumWidth(labelMinWidth); - ui->label->setMinimumWidth(labelMinWidth); - ui->labelWatchPending->setMinimumWidth(labelMinWidth); - ui->labelBalance->setMinimumWidth(labelMinWidth); - ui->labelSpendable->setMinimumWidth(labelMinWidth); - ui->labelWatchAvailable->setMinimumWidth(labelMinWidth); - ui->labelUnconfirmedPrivate->setMinimumWidth(labelMinWidth); - ui->labelWatchonly->setMinimumWidth(labelMinWidth); - ui->labelTotal->setMinimumWidth(labelMinWidth); - ui->labelWatchTotal->setMinimumWidth(labelMinWidth); - ui->labelUnconfirmed->setMinimumWidth(labelMinWidth); - ui->labelImmature->setMinimumWidth(labelMinWidth); - ui->labelPrivate->setMinimumWidth(labelMinWidth); - ui->label_4->setMinimumWidth(labelMinWidth); -} void OverviewPage::adjustTextSize(int width, int height){ const double fontSizeScalingFactor = 133.0; @@ -588,4 +456,15 @@ void OverviewPage::adjustTextSize(int width, int height){ ui->labelPrivate->setFont(labelFont); ui->label_4->setFont(labelFont); +} + +void OverviewPage::updateSparkAnonymizeRowVisibility() +{ + if (!walletModel || !walletModel->getOptionsModel()) { + return; + } + const bool show = spark::IsSparkAllowed() && walletModel->getOptionsModel()->getSparkPage(); + ui->labelAnonymizableText->setVisible(show); + ui->labelAnonymizable->setVisible(show); + ui->anonymizeButton->setVisible(show); } \ No newline at end of file diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 53a75aa594..7073b40d15 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -50,11 +50,9 @@ class OverviewPage : public QWidget public Q_SLOTS: void on_anonymizeButton_clicked(); - void migrateClicked(); - void onRefreshClicked(); void setBalance( - const CAmount& balance, + const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, const CAmount& watchOnlyBalance, @@ -100,22 +98,7 @@ private Q_SLOTS: void updateAlerts(const QString &warnings); void updateWatchOnlyLabels(bool showWatchOnly); void handleOutOfSyncWarningClicks(); - void countDown(); -}; - -class MigrateLelantusToSparkDialog : public QMessageBox -{ - Q_OBJECT -private: - bool clickedButton; - WalletModel *model; -public: - MigrateLelantusToSparkDialog(WalletModel *model); - bool getClickedButton(); - -private Q_SLOTS: - void onIgnoreClicked(); - void onMigrateClicked(); + void updateSparkAnonymizeRowVisibility(); }; #endif // BITCOIN_QT_OVERVIEWPAGE_H diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 0d7d49e4ce..c5590f9366 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -8,6 +8,7 @@ #include "guiconstants.h" #include "guiutil.h" +#include "netbase.h" // for GetNetworkName #include "validation.h" // for cs_main #include "sync.h" @@ -29,6 +30,14 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine return pLeft->nodeid < pRight->nodeid; case PeerTableModel::Address: return pLeft->addrName.compare(pRight->addrName) < 0; + case PeerTableModel::Network: + { + const std::string leftNet = GetNetworkName( + pLeft->m_inbound_onion ? NET_ONION : pLeft->addr.GetNetwork()); + const std::string rightNet = GetNetworkName( + pRight->m_inbound_onion ? NET_ONION : pRight->addr.GetNetwork()); + return leftNet.compare(rightNet) < 0; + } case PeerTableModel::Subversion: return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; case PeerTableModel::Ping: @@ -114,7 +123,7 @@ PeerTableModel::PeerTableModel(ClientModel *parent) : clientModel(parent), timer(0) { - columns << tr("NodeId") << tr("Node/Service") << tr("User Agent") << tr("Ping"); + columns << tr("NodeId") << tr("Node/Service") << tr("Network") << tr("User Agent") << tr("Ping"); priv.reset(new PeerTablePriv()); // default to unsorted priv->sortColumn = -1; @@ -169,6 +178,13 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const return (qint64)rec->nodeStats.nodeid; case Address: return QString::fromStdString(rec->nodeStats.addrName); + case Network: + // Match the classification used by getpeerinfo: a peer that + // reached us via our Tor hidden service is reported as "onion" + // even though the socket-level address is 127.0.0.1. + return QString::fromStdString(GetNetworkName( + rec->nodeStats.m_inbound_onion ? NET_ONION + : rec->nodeStats.addr.GetNetwork())); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); case Ping: diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 642f3dfaae..cf0a361d61 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -55,8 +55,9 @@ class PeerTableModel : public QAbstractTableModel enum ColumnIndex { NetNodeId = 0, Address = 1, - Subversion = 2, - Ping = 3 + Network = 2, + Subversion = 3, + Ping = 4 }; /** @name Methods overridden from QAbstractTableModel diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 7ffab993fd..15f07cf545 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -15,6 +15,7 @@ #include "recentrequeststablemodel.h" #include "walletmodel.h" #include "createsparknamepage.h" +#include "sparkname.h" #include #include @@ -26,6 +27,9 @@ #include #include #include +#include + +#include ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), @@ -51,12 +55,14 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid ui->addressTypeCombobox->addItem(tr("Spark"), Spark); ui->addressTypeCombobox->addItem(tr("Transparent"), Transparent); - if(ui->addressTypeCombobox->currentText() == "Spark"){ + if(ui->addressTypeCombobox->currentData().toInt() == Spark){ ui->reuseAddress->hide(); ui->createSparkNameButton->setVisible(true); + ui->mySparkNamesButton->setVisible(true); } else { ui->reuseAddress->show(); ui->createSparkNameButton->setVisible(false); + ui->mySparkNamesButton->setVisible(false); } ui->addressTypeHistoryCombobox->addItem(tr("All"), All); @@ -88,6 +94,7 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid connect(ui->addressTypeCombobox, qOverload(&QComboBox::activated), this, &ReceiveCoinsDialog::displayCheckBox); connect(ui->createSparkNameButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::createSparkName); + connect(ui->mySparkNamesButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::mySparkNames); } void ReceiveCoinsDialog::setModel(WalletModel *_model) @@ -127,6 +134,7 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) ui->addressTypeCombobox->removeItem(0); ui->reuseAddress->show(); ui->createSparkNameButton->setVisible(false); + ui->mySparkNamesButton->setVisible(false); } connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, @@ -175,7 +183,8 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() QString address; QString label = ui->reqLabel->text(); QString addressType = ui->addressTypeCombobox->currentText(); - if(ui->reuseAddress->isChecked() && ui->addressTypeCombobox->currentText() == AddressTableModel::Transparent) + const int selectedAddressType = ui->addressTypeCombobox->currentData().toInt(); + if(ui->reuseAddress->isChecked() && selectedAddressType == Transparent) { /* Choose existing receiving address */ AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); @@ -192,17 +201,34 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() } } else { /* Generate new receiving address */ - if(ui->addressTypeCombobox->currentText() == AddressTableModel::Transparent) { + if(selectedAddressType == Transparent) { address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", AddressTableModel::Transparent); - } else if(ui->addressTypeCombobox->currentText() == AddressTableModel::Spark) { - address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", AddressTableModel::Spark); + } else if(selectedAddressType == Spark) { + // Check if label is a @name reference to a spark name + if (label.startsWith("@") && label.size() > 1 && label.size() <= (int)CSparkNameManager::maximumSparkNameLength + 1) { + QString sparkName = label.mid(1); + address = model->getSparkNameAddress(sparkName); + if (address.isEmpty()) { + QMessageBox::critical(this, tr("Error"), tr("Spark name \"%1\" not found or expired.").arg(sparkName)); + return; + } + if (!model->isSparkAddressMine(address)) { + QMessageBox::critical(this, tr("Error"), tr("Spark name \"%1\" does not belong to this wallet.").arg(sparkName)); + return; + } + } else { + address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", AddressTableModel::Spark); + } } } + if(address.isEmpty()) + return; + SendCoinsRecipient info(address, addressType, label, ui->reqAmount->value(), ui->reqMessage->text()); ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setModel(model->getOptionsModel()); + dialog->setModel(model); dialog->setInfo(info); dialog->show(); clear(); @@ -216,7 +242,7 @@ void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex & QModelIndex targetIdx = recentRequestsProxyModel->mapToSource(index); const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel(); ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); - dialog->setModel(model->getOptionsModel()); + dialog->setModel(model); dialog->setInfo(submodel->entry(targetIdx.row()).recipient); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); @@ -243,15 +269,27 @@ void ReceiveCoinsDialog::on_showRequestButton_clicked() void ReceiveCoinsDialog::on_removeRequestButton_clicked() { - if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) + if(!model || !model->getRecentRequestsTableModel() || !recentRequestsProxyModel || !ui->recentRequestsView->selectionModel()) return; QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); if(selection.empty()) return; - // correct for selection mode ContiguousSelection - QModelIndex index = selection.at(0); - QModelIndex firstIndex = recentRequestsProxyModel->mapToSource(index); - model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent()); + + QVector sourceRows; + sourceRows.reserve(selection.size()); + for (const QModelIndex& index : selection) { + QModelIndex sourceIndex = recentRequestsProxyModel->mapToSource(index); + if (sourceIndex.isValid()) + sourceRows.append(sourceIndex.row()); + } + + std::sort(sourceRows.begin(), sourceRows.end(), [](int left, int right) { + return left > right; + }); + + for (int row : sourceRows) { + model->getRecentRequestsTableModel()->removeRows(row, 1); + } } void ReceiveCoinsDialog::keyPressEvent(QKeyEvent *event) @@ -272,14 +310,13 @@ void ReceiveCoinsDialog::keyPressEvent(QKeyEvent *event) QModelIndex ReceiveCoinsDialog::selectedRow() { - if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) + if(!model || !model->getRecentRequestsTableModel() || !recentRequestsProxyModel || !ui->recentRequestsView->selectionModel()) return QModelIndex(); QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); if(selection.empty()) return QModelIndex(); - // correct for selection mode ContiguousSelection QModelIndex firstIndex = selection.at(0); - return firstIndex; + return recentRequestsProxyModel->mapToSource(firstIndex); } // copy column of selected row to clipboard @@ -334,12 +371,14 @@ void ReceiveCoinsDialog::copyAmount() void ReceiveCoinsDialog::displayCheckBox(int idx) { - if(ui->addressTypeCombobox->currentText() == tr("Spark")){ + if(ui->addressTypeCombobox->itemData(idx).toInt() == Spark){ ui->reuseAddress->hide(); ui->createSparkNameButton->setVisible(true); + ui->mySparkNamesButton->setVisible(true); } else { ui->reuseAddress->show(); ui->createSparkNameButton->setVisible(false); + ui->mySparkNamesButton->setVisible(false); } } @@ -432,6 +471,16 @@ void ReceiveCoinsDialog::resizeEvent(QResizeEvent* event) ui->removeRequestButton->setMinimumHeight(buttonMinHeight); ui->removeRequestButton->setMaximumHeight(buttonMaxHeight); + ui->mySparkNamesButton->setMinimumWidth(buttonMinWidth); + ui->mySparkNamesButton->setMaximumWidth(buttonMaxWidth); + ui->mySparkNamesButton->setMinimumHeight(buttonMinHeight); + ui->mySparkNamesButton->setMaximumHeight(buttonMaxHeight); + + ui->createSparkNameButton->setMinimumWidth(buttonMinWidth); + ui->createSparkNameButton->setMaximumWidth(buttonMaxWidth); + ui->createSparkNameButton->setMinimumHeight(buttonMinHeight); + ui->createSparkNameButton->setMaximumHeight(buttonMaxHeight); + // Adjust column widths proportionally int dateColumnWidth = newWidth * 0.25; int labelColumnWidth = newWidth * 0.25; @@ -465,6 +514,8 @@ void ReceiveCoinsDialog::adjustTextSize(int width,int height){ ui->clearButton->setFont(font); ui->showRequestButton->setFont(font); ui->removeRequestButton->setFont(font); + ui->mySparkNamesButton->setFont(font); + ui->createSparkNameButton->setFont(font); ui->addressTypeCombobox->setFont(font); ui->addressTypeHistoryCombobox->setFont(font); ui->recentRequestsView->setFont(font); @@ -477,4 +528,28 @@ void ReceiveCoinsDialog::createSparkName() { dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModel(model); dialog->show(); +} + +void ReceiveCoinsDialog::mySparkNames() { + if (!model || !model->getAddressTableModel()) + return; + + AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this, false); + dlg.setInitialAddressType(AddressBookPage::SparkNameMine); + dlg.setModel(model->getAddressTableModel()); + if (dlg.exec()) { + QString label = dlg.getReturnLabel(); + // Ensure the label is in @name notation + if (!label.isEmpty()) { + if (!label.startsWith("@")) + label = "@" + label; + QString sparkName = label.mid(1); + QString resolvedAddress = model->getSparkNameAddress(sparkName); + if (resolvedAddress.isEmpty() || !model->isSparkAddressMine(resolvedAddress)) { + QMessageBox::critical(this, tr("Error"), tr("Selected spark name \"%1\" does not belong to this wallet.").arg(sparkName)); + return; + } + ui->reqLabel->setText(label); + } + } } \ No newline at end of file diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index ccb9e2fbb8..33a43e93ee 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -85,6 +85,7 @@ private Q_SLOTS: void on_showRequestButton_clicked(); void on_removeRequestButton_clicked(); void createSparkName(); + void mySparkNames(); void on_recentRequestsView_doubleClicked(const QModelIndex &index); void recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void updateDisplayUnit(); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 0c19c5d9c1..53e2c5e8aa 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -90,7 +90,8 @@ void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), - model(0) + model(0), + walletModel(0) { ui->setupUi(this); @@ -107,12 +108,13 @@ ReceiveRequestDialog::~ReceiveRequestDialog() delete ui; } -void ReceiveRequestDialog::setModel(OptionsModel *_model) +void ReceiveRequestDialog::setModel(WalletModel *_walletModel) { - this->model = _model; + this->walletModel = _walletModel; + this->model = _walletModel ? _walletModel->getOptionsModel() : 0; - if (_model) - connect(_model, &OptionsModel::displayUnitChanged, this, &ReceiveRequestDialog::update); + if (model) + connect(model, &OptionsModel::displayUnitChanged, this, &ReceiveRequestDialog::update); // update the display unit if necessary update(); @@ -126,7 +128,7 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) void ReceiveRequestDialog::update() { - if(!model) + if(!model || !walletModel) return; resize(width(), 600); QString target = info.label; diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index bb27a95f37..82d0def8c4 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -53,7 +53,7 @@ class ReceiveRequestDialog : public QDialog explicit ReceiveRequestDialog(QWidget *parent = 0); ~ReceiveRequestDialog(); - void setModel(OptionsModel *model); + void setModel(WalletModel *model); void setInfo(const SendCoinsRecipient &info); private Q_SLOTS: diff --git a/src/qt/res/css/firo.css b/src/qt/res/css/firo.css index 27afcf4324..78847566d7 100644 --- a/src/qt/res/css/firo.css +++ b/src/qt/res/css/firo.css @@ -1215,41 +1215,6 @@ QDialog#Intro QPushButton#ellipsisButton { min-width: 10px; } -/* lelantus */ - -QDialog#LelantusDialog QLabel#labelTotalAnonymizedCoinsText, -QDialog#LelantusDialog QLabel#labelLatestGroupText, -QDialog#LelantusDialog QLabel#labelUnspentText, -QDialog#LelantusDialog QLabel#spendableText, -QDialog#LelantusDialog QLabel#unconfirmedText, -QDialog#LelantusDialog QLabel#totalText { - qproperty-alignment: 'AlignVCenter | AlignRight'; - min-width: 100px; - margin-right: 5px; - padding-right: 5px; -} - -QDialog#LelantusDialog QLabel#labelGlobalLelantusPoolText, -QDialog#LelantusDialog QLabel#labelYourAnoymizedCoinsText { - margin-top: 0; - margin-right: 5px; -} - -QDialog#LelantusDialog QLabel#fallbackFeeWarningLabel, -QDialog#LelantusDialog QLabel#labelFeeHeadline { - qproperty-alignment: 'AlignVCenter'; - height: 28px; - font-weight: bold; -} - -QDialog#LelantusDialog QRadioButton#radioSmartFee { - margin-top: 2.5px; -} - -QDialog#LelantusDialog QRadioButton#radioCustomFee { - margin-top: 7px; -} - /* ManualMintDialog */ QDialog#ManualMintDialog QLabel#availableAmount, @@ -1664,7 +1629,6 @@ QDialog#SendCoinsDialog QLabel#labelCoinControlLowOutputText, QDialog#SendCoinsDialog QLabel#labelCoinControlFeeText, QDialog#SendCoinsDialog QLabel#labelCoinControlAfterFeeText, QDialog#SendCoinsDialog QLabel#labelCoinControlChangeText, -QDialog#SendCoinsDialog QLabel#labelGlobalLelantusPoolText, QDialog#SendCoinsDialog QLabel#labelYourAnoymizedCoinsText, QDialog#SendCoinsDialog QLabel#labelFeeHeadline, QDialog#SendCoinsDialog QLabel#fallbackFeeWarningLabel { diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0dd22b2800..b14f429886 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -574,6 +574,7 @@ void RPCConsole::setClientModel(ClientModel *model) ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu); ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); + ui->peerWidget->setColumnWidth(PeerTableModel::Network, NETWORK_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); ui->peerWidget->horizontalHeader()->setStretchLastSection(true); @@ -1042,6 +1043,8 @@ void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) { // update the detail ui with latest node information QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " "); + if (stats->nodeStats.m_inbound_onion) + peerAddrDetails += tr("(inbound onion)") + " "; peerAddrDetails += tr("(node id: %1)").arg(QString::number(stats->nodeStats.nodeid)); if (!stats->nodeStats.addrLocal.empty()) peerAddrDetails += "
" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal)); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index de1c1d6251..5988d21610 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -135,6 +135,7 @@ public Q_SLOTS: ADDRESS_COLUMN_WIDTH = 200, SUBVERSION_COLUMN_WIDTH = 150, PING_COLUMN_WIDTH = 80, + NETWORK_COLUMN_WIDTH = 80, BANSUBNET_COLUMN_WIDTH = 200, BANTIME_COLUMN_WIDTH = 250 diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 77d431ea11..8bf971e2eb 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -18,7 +18,6 @@ #include "base58.h" #include "chainparams.h" -#include "lelantus.h" #include "wallet/coincontrol.h" #include "validation.h" // mempool and minRelayTxFee #include "txmempool.h" @@ -367,17 +366,7 @@ void SendCoinsDialog::on_sendButton_clicked() CAmount mintSparkAmount = 0; CAmount txFee = 0; CAmount totalAmount = 0; - if (model->getWallet() && - model->getWallet()->GetPrivateBalance().first > 0 && - spark::IsSparkAllowed() && - chainActive.Height() < ::Params().GetConsensus().nLelantusGracefulPeriod) { - MigrateLelantusToSparkDialog migrateLelantusToSpark(model); - bool clickedButton = migrateLelantusToSpark.getClickedButton(); - if(!clickedButton) { - fNewRecipientAllowed = true; - return; - } - } + if ((fAnonymousMode == true) && spark::IsSparkAllowed()) { prepareStatus = model->prepareSpendSparkTransaction(currentTransaction, &ctrl); } else if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount)) { diff --git a/src/qt/sparkmodel.cpp b/src/qt/sparkmodel.cpp index 390eef963d..7eb877d94b 100644 --- a/src/qt/sparkmodel.cpp +++ b/src/qt/sparkmodel.cpp @@ -1,4 +1,3 @@ -#include "../lelantus.h" #include "../validation.h" #include "automintmodel.h" diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index dd2bed0d50..cb29a46aa0 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -253,29 +253,15 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco if (fAllToMe) { - if (wtx.tx->IsLelantusJoinSplit()) { - strHTML += "" + tr("Total debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -wtx.tx->GetValueOut()) + "
"; - strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.tx->GetValueOut()) + "
"; - } else { - // Payment to self - CAmount nChange = wtx.GetChange(); - CAmount nValue = nCredit - nChange; - strHTML += "" + tr("Total debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "
"; - strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "
"; - } + // Payment to self + CAmount nChange = wtx.GetChange(); + CAmount nValue = nCredit - nChange; + strHTML += "" + tr("Total debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "
"; + strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "
"; } CAmount nTxFee = nDebit - wtx.tx->GetValueOut(); - if (wtx.tx->IsLelantusJoinSplit() && wtx.tx->vin.size() > 0) { - try { - nTxFee = lelantus::ParseLelantusJoinSplit(*wtx.tx)->getFee(); - } - catch (const std::exception &) { - //do nothing - } - } - if (wtx.tx->IsSparkSpend() && wtx.tx->vin.size() > 0) { try { nTxFee = spark::ParseSparkSpend(*wtx.tx).getFee(); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index c50b6ec72a..17a6021df5 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -8,7 +8,6 @@ #include "base58.h" #include "consensus/consensus.h" -#include "lelantus.h" #include "validation.h" #include "timedata.h" #include "wallet/wallet.h" @@ -51,20 +50,8 @@ QList TransactionRecord::decomposeTransaction(const CWallet * isAllSigmaSpendFromMe = (wallet->IsMine(wtx.tx->vin[0], *wtx.tx) & ISMINE_SPENDABLE); } - bool isAllJoinSplitFromMe = false; - if (wtx.tx->vin[0].IsLelantusJoinSplit()) { - isAllJoinSplitFromMe = (wallet->IsMine(wtx.tx->vin[0], *wtx.tx) & ISMINE_SPENDABLE); - } - - if (wtx.tx->IsZerocoinSpend() || isAllSigmaSpendFromMe || isAllJoinSplitFromMe) { + if (wtx.tx->IsZerocoinSpend() || isAllSigmaSpendFromMe) { CAmount nTxFee = nDebit - wtx.tx->GetValueOut(); - if (isAllJoinSplitFromMe && wtx.tx->vin.size() > 0) { - try { - nTxFee = lelantus::ParseLelantusJoinSplit(*wtx.tx)->getFee(); - } catch (const std::exception &) { - // do nothing - } - } bool first = true; diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 25291b542c..9ed79dfc26 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -226,9 +226,15 @@ void WalletFrame::outOfSyncWarningClicked() Q_EMIT requestedSyncWarningInfo(); } -void WalletFrame::updateAddressbook() { - WalletView *walletView = currentWalletView(); +bool WalletFrame::updateAddressbook() +{ + bool updated = false; - if (walletView) - walletView->updateAddressbook(); + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { + if (i.value()->updateAddressbook()) + updated = true; + } + + return updated; } diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 0637dd3a23..042059604e 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -102,7 +102,7 @@ public Q_SLOTS: /** Pass on signal over requested out-of-sync-warning information */ void outOfSyncWarningClicked(); - void updateAddressbook(); + bool updateAddressbook(); }; #endif // BITCOIN_QT_WALLETFRAME_H diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 478665431f..7ef66f0689 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -24,7 +24,6 @@ #include "wallet/walletexcept.h" #include "txmempool.h" #include "consensus/validation.h" -#include "lelantus.h" #include "bip47/account.h" #include "bip47/bip47utils.h" #include "cancelpassworddialog.h" @@ -1076,6 +1075,11 @@ bool WalletModel::validateSparkAddress(const QString& address) return network == coinNetwork; } +bool WalletModel::isSparkAddressMine(const QString& address) +{ + return wallet->IsSparkAddressMine(address.toStdString()); +} + QString WalletModel::generateSparkAddress() { const spark::Params* params = spark::Params::get_default(); @@ -1097,30 +1101,6 @@ std::pair WalletModel::getSparkBalance() return wallet->GetSparkBalance(); } -bool WalletModel::getAvailableLelantusCoins() -{ - if (!pwalletMain->zwallet) - return false; - - std::list coins = wallet->GetAvailableLelantusCoins(); - if (coins.size() > 0) { - return true; - } - - return false; -} - -bool WalletModel::migrateLelantusToSpark() -{ - std::string strFailReason; - bool res = wallet->LelantusToSpark(strFailReason); - if (!res) { - Q_EMIT message(tr("Lelantus To Spark"), QString::fromStdString(strFailReason), - CClientUIInterface::MSG_ERROR); - } - return res; -} - WalletModel::SendCoinsReturn WalletModel::prepareMintSparkTransaction(std::vector &transactions, QList recipients, std::vector >& wtxAndFees, std::list& reservekeys, const CCoinControl* coinControl) { CAmount total = 0; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 847f6b6f43..61f4e4250e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -5,9 +5,8 @@ #ifndef BITCOIN_QT_WALLETMODEL_H #define BITCOIN_QT_WALLETMODEL_H -#include "walletmodeltransaction.h" - #include "support/allocators/secure.h" +#include "walletmodeltransaction.h" #ifdef ENABLE_WALLET #include "wallet/walletdb.h" #include "wallet/wallet.h" @@ -35,6 +34,8 @@ class COutput; class CPubKey; class CWallet; class uint256; +class CSparkNameTxData; +class CValidationState; QT_BEGIN_NAMESPACE class QTimer; @@ -153,6 +154,7 @@ class WalletModel : public QObject bool validateAddress(const QString &address); bool validateExchangeAddress(const QString &address); bool validateSparkAddress(const QString &address); + bool isSparkAddressMine(const QString &address); std::pair getSparkBalance(); // Generate spark address @@ -209,10 +211,6 @@ class WalletModel : public QObject std::list &reserveKeys ); - bool migrateLelantusToSpark(); - - bool getAvailableLelantusCoins(); - // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction &transaction); // Wallet encryption diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index f874620af9..be7c14164f 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -2,9 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "walletmodeltransaction.h" +#include "walletmodel.h" #include "policy/policy.h" +#include "spark/state.h" #include "wallet/wallet.h" WalletModelTransaction::WalletModelTransaction(const QList &_recipients) : @@ -99,13 +100,3 @@ CReserveKey *WalletModelTransaction::getPossibleKeyChange() { return keyChange; } - -std::vector& WalletModelTransaction::getSpendCoins() -{ - return spendCoins; -} - -std::vector& WalletModelTransaction::getMintCoins() -{ - return mintCoins; -} \ No newline at end of file diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 6792f6cd7f..17c0a85a6a 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -5,13 +5,9 @@ #ifndef BITCOIN_QT_WALLETMODELTRANSACTION_H #define BITCOIN_QT_WALLETMODELTRANSACTION_H -#include "../hdmint/hdmint.h" -#include "../primitives/mint_spend.h" -#include "spark/state.h" +#include "amount.h" -#include "walletmodel.h" - -#include +#include class SendCoinsRecipient; @@ -41,18 +37,11 @@ class WalletModelTransaction void reassignAmounts(int nChangePosRet); // needed for the subtract-fee-from-amount feature - std::vector& getSpendCoins(); - std::vector& getMintCoins(); - private: QList recipients; CWalletTx *walletTransaction; CReserveKey *keyChange; CAmount fee; - - // lelantus transaction - std::vector spendCoins; - std::vector mintCoins; }; #endif // BITCOIN_QT_WALLETMODELTRANSACTION_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index b18daf132a..c7eeb4afcd 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -375,10 +375,11 @@ void WalletView::usedSendingAddresses() usedSendingAddressesPage->activateWindow(); } -void WalletView::updateAddressbook() +bool WalletView::updateAddressbook() { - usedReceivingAddressesPage->updateSpark(); - usedSendingAddressesPage->updateSpark(); + const bool receivingUpdated = usedReceivingAddressesPage->updateSpark(); + const bool sendingUpdated = usedSendingAddressesPage->updateSpark(); + return receivingUpdated || sendingUpdated; } void WalletView::usedReceivingAddresses() diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 7742b91989..42ab15fe63 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -129,7 +129,7 @@ public Q_SLOTS: /** Show used sending addresses */ void usedSendingAddresses(); - void updateAddressbook(); + bool updateAddressbook(); /** Show used receiving addresses */ void usedReceivingAddresses(); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 81db380844..49d3d33ed9 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -89,7 +89,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listunspent", 0, "minconf" }, { "listunspent", 1, "maxconf" }, { "listunspent", 2, "addresses" }, - { "regeneratemintpool", 0 }, { "listunspentmintzerocoins", 0 }, { "listunspentmintzerocoins", 1 }, { "listunspentmintzerocoins", 2 }, @@ -173,31 +172,20 @@ static const CRPCConvertParam vRPCConvertParams[] = { "setmintzerocoinstatus", 2, {} }, { "setmintzerocoinstatus", 1, {} }, { "setsigmamintstatus", 1, {} }, - { "setlelantusmintstatus", 1, {} }, { "listmintzerocoins", 0, {} }, { "listsigmamints", 0, {} }, - { "listlelantusmints", 0, {} }, { "listpubcoins", 0, {} }, { "listsigmapubcoins", 0, {} }, { "listspendzerocoins", 0, {} }, { "listspendzerocoins", 1, {} }, { "listsigmaspends", 0, {} }, { "listsigmaspends", 1, {} }, - { "listlelantusjoinsplits", 0, {} }, - { "listlelantusjoinsplits", 1, {} }, - { "joinsplit", 0, {} }, - { "joinsplit", 1, {} }, - { "joinsplit", 2, {} }, { "spendallzerocoin", 0, {} }, { "remintzerocointosigma", 0, {} }, - { "getanonymityset", 0, {} }, - { "getmintmetadata", 0, {} }, - { "getusedcoinserials", 0, {} }, - { "getlatestcoinids", 0, {} }, { "getsparkmintmetadata", 0, {} }, { "getmempooltxs", 0, {} }, - //Lelantus + // Spark { "mintspark", 0, {} }, { "mintspark", 1, {} }, { "mintspark", 2, {} }, @@ -207,7 +195,7 @@ static const CRPCConvertParam vRPCConvertParams[] = // Spark names { "registersparkname", 2, {} }, { "getsparknames", 0, {} }, - { "requestsparknametransfer", 3, {} }, + { "requestsparknametransfer", 2, {} }, /* Evo spork */ { "spork", 2, "features"}, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index a2ae0be93f..8d5a72cd2e 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -592,41 +592,6 @@ UniValue signmessagewithprivkey(const JSONRPCRequest& request) return EncodeBase64(&vchSig[0], vchSig.size()); } -UniValue verifyprivatetxown(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 3) - throw std::runtime_error( - "verifyprivatetxown \"txid\" \"signature\" \"message\"\n" - "\nVerify a lelantus tx ownership\n" - "\nArguments:\n" - "1. \"txid\" (string, required) Txid, in which we spend lelantus coins.\n" - "2. \"proof\" (string, required) The signatures of the message encoded in base 64\n" - "3. \"message\" (string, required) The message that was signed.\n" - "\nResult:\n" - "true|false (boolean) If the signature is verified or not.\n" - "\nExamples:\n" - "\nVerify the signature\n" - + HelpExampleCli("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9\" \"signature\" \"my message\"") + - "\nAs json rpc\n" - + HelpExampleRpc("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9\", \"signature\", \"my message\"") - ); - - LOCK(cs_main); - - std::string strTxId = request.params[0].get_str(); - std::string strProof = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); - - uint256 txid = uint256S(strTxId); - bool fInvalid = false; - std::vector vchSig = DecodeBase64(strProof.c_str(), &fInvalid); - - if (fInvalid) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); - - return VerifyPrivateTxOwn(txid, vchSig, strMessage); -} - UniValue setmocktime(const JSONRPCRequest& request) { @@ -1089,180 +1054,6 @@ UniValue getAddressNumWBalance(const JSONRPCRequest& request) return uint64_t(pblocktree->findAddressNumWBalance()); } -UniValue getanonymityset(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( - "getanonymityset\n" - "\nReturns the anonymity set and latest block hash.\n" - "\nArguments:\n" - "{\n" - " \"coinGroupId\" (int)\n" - " \"startBlockHash\" (string)\n" // if this is empty it returns the full set - "}\n" - "\nResult:\n" - "{\n" - " \"blockHash\" (string) Latest block hash for anonymity set\n" - " \"setHash\" (string) Anonymity set hash\n" - " \"mints\" (Pair>) Serialized GroupElements paired with txhash which is paired with mint tag and mint value\n" - "}\n" - + HelpExampleCli("getanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") - + HelpExampleRpc("getanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") - ); - - - int coinGroupId; - std::string startBlockHash; - try { - coinGroupId = std::stol(request.params[0].get_str()); - startBlockHash = request.params[1].get_str(); - } catch (std::logic_error const & e) { - throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); - } - - if(!GetBoolArg("-mobile", false)){ - throw std::runtime_error(std::string("Please rerun Firo with -mobile ")); - } - - uint256 blockHash; - std::vector>> coins; - std::vector setHash; - - { - LOCK(cs_main); - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - lelantusState->GetCoinsForRecovery( - &chainActive, - chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), - coinGroupId, - startBlockHash, - blockHash, - coins, - setHash); - } - - UniValue ret(UniValue::VOBJ); - UniValue mints(UniValue::VARR); - - FIRO_UNUSED int i = 0; - for (const auto& coin : coins) { - std::vector vch = coin.first.getValue().getvch(); - std::vector data; - data.push_back(EncodeBase64(vch.data(), size_t(34))); - data.push_back(EncodeBase64(coin.second.second.begin(), coin.second.second.size())); - if (coin.second.first.isJMint) { - data.push_back(EncodeBase64(coin.second.first.encryptedValue.data(), coin.second.first.encryptedValue.size())); - } else { - data.push_back(coin.second.first.amount); - } - data.push_back(EncodeBase64(coin.second.first.txHash.begin(), coin.second.first.txHash.size())); - - UniValue entity(UniValue::VARR); - entity.push_backV(data); - mints.push_back(entity); - i++; - } - - ret.push_back(Pair("blockHash", EncodeBase64(blockHash.begin(), blockHash.size()))); - ret.push_back(Pair("setHash", UniValue(EncodeBase64(setHash.data(), setHash.size())))); - ret.push_back(Pair("coins", mints)); - - return ret; -} - -UniValue getmintmetadata(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( - "getmintmetadata\n" - "\nReturns the anonymity set id and nHeight of mint.\n" - "\nArguments:\n" - " \"mints\"\n" - " [\n" - " {\n" - " \"pubcoin\" (string) The PubCoin value\n" - " }\n" - " ,...\n" - " ]\n" - "\nResult:\n" - "{\n" - " \"metadata\" (Pair) nHeight and id for each pubcoin\n" - "}\n" - + HelpExampleCli("getmintmetadata", "'{\"mints\": [{\"denom\":5000000, \"pubcoin\":\"b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390\"}]}'") - + HelpExampleRpc("getmintmetadata", "{\"mints\": [{\"denom\":5000000, \"pubcoin\":\"b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390\"}]}") - ); - - UniValue mintValues = find_value(request.params[0].get_obj(), "mints"); - if (!mintValues.isArray()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "mints is expected to be an array"); - } - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - UniValue ret(UniValue::VARR); - for(UniValue const & mintData : mintValues.getValues()){ - std::vector serializedCoin = ParseHex(find_value(mintData, "pubcoin").get_str().c_str()); - - secp_primitives::GroupElement pubCoin; - pubCoin.deserialize(serializedCoin.data()); - - std::pair coinHeightAndId; - { - LOCK(cs_main); - coinHeightAndId = lelantusState->GetMintedCoinHeightAndId(lelantus::PublicCoin(pubCoin)); - } - UniValue metaData(UniValue::VOBJ); - metaData.pushKV(std::to_string(coinHeightAndId.first), coinHeightAndId.second); - ret.push_back(metaData); - } - return ret; -} - -UniValue getusedcoinserials(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( - "getusedcoinserials\n" - "\nReturns the set of used coin serial.\n" - "\nArguments:\n" - "{\n" - " \"startNumber \" (int) Number of elements already existing on user side\n" - "}\n" - "\nResult:\n" - "{\n" - " \"serials\" (std::string[]) array of Serialized Scalars\n" - "}\n" - ); - - int startNumber; - try { - startNumber = std::stol(request.params[0].get_str()); - } catch (std::logic_error const & e) { - throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); - } - - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - std::unordered_map serials; - { - LOCK(cs_main); - serials = lelantusState->GetSpends(); - } - - UniValue serializedSerials(UniValue::VARR); - int i = 0; - for ( auto it = serials.begin(); it != serials.end(); ++it, ++i) { - if (cmp::less((serials.size() - i - 1), startNumber)) - continue; - std::vector serialized; - serialized.resize(32); - it->first.serialize(serialized.data()); - serializedSerials.push_back(EncodeBase64(serialized.data(), 32)); - } - - UniValue ret(UniValue::VOBJ); - ret.push_back(Pair("serials", serializedSerials)); - - return ret; -} - UniValue getfeerate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) @@ -1281,31 +1072,29 @@ UniValue getfeerate(const JSONRPCRequest& request) return ret; } -UniValue getlatestcoinid(const JSONRPCRequest& request) +// Lelantus RPC stubs (protocol removed; throw if called). +UniValue getanonymityset(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - "getlatestcoinid\n" - "\nReturns the set of used coin serial.\n" - "\nResult:\n" - "{\n" - " [\n" - " {\n" - " \"coinGroupId\" (int) The latest group id\n" - " }\n" - " ,...\n" - " ]\n" - "}\n" - ); + (void)request; + throw std::runtime_error("getanonymityset is disabled (Lelantus has been removed). Use getsparkanonymityset for Spark."); +} - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - int latestCoinId; - { - LOCK(cs_main); - latestCoinId = lelantusState->GetLatestCoinID(); - } +UniValue getmintmetadata(const JSONRPCRequest& request) +{ + (void)request; + throw std::runtime_error("getmintmetadata is disabled (Lelantus has been removed). Use getsparkmintmetadata for Spark."); +} - return UniValue(latestCoinId); +UniValue getusedcoinserials(const JSONRPCRequest& request) +{ + (void)request; + throw std::runtime_error("getusedcoinserials is disabled (Lelantus has been removed). Use getusedcoinstags for Spark."); +} + +UniValue getlatestcoinid(const JSONRPCRequest& request) +{ + (void)request; + throw std::runtime_error("getlatestcoinid is disabled (Lelantus has been removed). Use getsparklatestcoinid for Spark."); } UniValue getsparkanonymityset(const JSONRPCRequest& request) @@ -2326,13 +2115,12 @@ static const CRPCCommand commands[] = { "addressindex", "getaddressdeltas", &getaddressdeltas, false, {} }, { "addressindex", "getaddresstxids", &getaddresstxids, false, {} }, { "addressindex", "getaddressbalance", &getaddressbalance, false, {} }, + { "addressindex", "getspentinfo", &getspentinfo, false, {} }, /* Znode features */ { "firo", "znsync", &mnsync, true, {} }, { "firo", "evoznsync", &mnsync, true, {} }, - { "firo", "verifyprivatetxown", &verifyprivatetxown, true, {} }, - /* Not shown in help */ { "hidden", "getinfoex", &getinfoex, false, {} }, { "addressindex", "gettotalsupply", &gettotalsupply, false, {} }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 8cb73ad7ac..5a674f6555 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -94,6 +94,7 @@ UniValue getpeerinfo(const JSONRPCRequest& request) " \"version\": v, (numeric) The peer version, such as 7001\n" " \"subver\": \"/Satoshi:0.8.5/\", (string) The string version\n" " \"inbound\": true|false, (boolean) Inbound (true) or Outbound (false)\n" + " \"network\": \"xxx\", (string) The network the peer is connected through (ipv4, ipv6, onion). For inbound peers reaching us via our Tor hidden service, \"onion\" is reported even though \"addr\" is 127.0.0.1.\n" " \"addnode\": true|false, (boolean) Whether connection was due to addnode and is using an addnode slot\n" " \"startingheight\": n, (numeric) The starting height (block) of the peer\n" " \"banscore\": n, (numeric) The ban score\n" @@ -161,6 +162,11 @@ UniValue getpeerinfo(const JSONRPCRequest& request) // their ver message. obj.push_back(Pair("subver", stats.cleanSubVer)); obj.push_back(Pair("inbound", stats.fInbound)); + // Report "onion" for peers that reached us via our Tor hidden service, + // even though the socket-level address is 127.0.0.1. Otherwise fall + // back to the network class derived from the peer's address. + const Network peerNet = stats.m_inbound_onion ? NET_ONION : stats.addr.GetNetwork(); + obj.push_back(Pair("network", GetNetworkName(peerNet))); obj.push_back(Pair("addnode", stats.fAddnode)); obj.push_back(Pair("startingheight", stats.nStartingHeight)); if (fStateStats) { @@ -384,7 +390,14 @@ static UniValue GetNetworksInfo() for(int n=0; n(n); - if(network == NET_UNROUTABLE) + // Intentional narrowing relative to Bitcoin Core: Firo only supports + // ipv4/ipv6/onion as user-selectable networks (see -onlynet handling + // in init.cpp and ParseNetwork() in netbase.cpp). NET_I2P, NET_CJDNS + // and NET_INTERNAL are skipped here because they have no proxy + // configuration path and the qa/rpc-tests/proxy_test.py harness + // asserts the exact set ['ipv4','ipv6','onion']. If/when Firo adds + // I2P or CJDNS support, this filter and the test must be updated. + if (network != NET_IPV4 && network != NET_IPV6 && network != NET_ONION) continue; proxyType proxy; UniValue obj(UniValue::VOBJ); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b3a7818328..95a53f7533 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -39,7 +39,6 @@ #include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include "evo/providertx.h" -#include "lelantus.h" // Forward declaration with default parameter for calls within this file void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, bool includeChainlock = true); @@ -107,22 +106,6 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) { in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - } else if (txin.IsLelantusJoinSplit()) { - in.push_back("joinsplit"); - fillStdFields(in, txin); - std::unique_ptr jsplit; - try { - jsplit = lelantus::ParseLelantusJoinSplit(tx); - } - catch (const std::exception &) { - continue; - } - in.push_back(Pair("nFees", ValueFromAmount(jsplit->getFee()))); - UniValue serials(UniValue::VARR); - for (Scalar const & serial : jsplit->getCoinSerialNumbers()) { - serials.push_back(serial.GetHex()); - } - in.push_back(Pair("serials", serials)); } else if (tx.IsSparkSpend()) { in.push_back("sparkSpend"); fillStdFields(in, txin); diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index 4805d4c1e7..d34aebcce4 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -1401,10 +1401,8 @@ UniValue spork(const JSONRPCRequest& request) throw std::runtime_error("No spork actions specified"); std::set validFeatureNames { - CSporkAction::featureLelantus, CSporkAction::featureChainlocks, CSporkAction::featureInstantSend, - CSporkAction::featureLelantusTransparentLimit, CSporkAction::featureSpark, CSporkAction::featureSparkTransparentLimit }; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 941ce813bb..4b55e832f0 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -353,6 +353,10 @@ bool EvalScript(std::vector >& stack, const CScript& // otherwise NOOP break; + case OP_SPARKNAMEID: + // NOP - used only to tag fee outputs with spark name data + break; + case OP_CHECKLOCKTIMEVERIFY: { if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 413fe605e1..21a52bc42b 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -91,6 +91,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& } case TX_PUBKEYHASH: case TX_EXCHANGEADDRESS: + case TX_SPARKNAMEFEE: keyID = CKeyID(uint160(vSolutions[0])); if (sigversion != SIGVERSION_BASE) { CPubKey pubkey; diff --git a/src/script/script.cpp b/src/script/script.cpp index 5f7a5951ad..079f16b415 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -158,6 +158,8 @@ const char* GetOpName(opcodetype opcode) case OP_SPARKSPEND : return "OP_SPARKSPEND"; // Super transparent txout script prefix case OP_EXCHANGEADDR : return "OP_EXCHANGEADDR"; + // Spark name fee tag + case OP_SPARKNAMEID : return "OP_SPARKNAMEID"; // Note: // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum @@ -359,6 +361,36 @@ bool CScript::IsSparkSpend() const { (*this)[0] == OP_SPARKSPEND); } +bool CScript::IsSparkNameFee() const { + // Minimum: 25 (P2PKH) + 1 (OP_SPARKNAMEID) + 2 (min push + OP_DROP) + 2 (min push + OP_DROP) + if (this->size() < 30) + return false; + // Must start with standard P2PKH pattern + if ((*this)[0] != OP_DUP || (*this)[1] != OP_HASH160 || (*this)[2] != 0x14 || + (*this)[23] != OP_EQUALVERIFY || (*this)[24] != OP_CHECKSIG) + return false; + // Byte after OP_CHECKSIG must be OP_SPARKNAMEID + if ((*this)[25] != OP_SPARKNAMEID) + return false; + // Validate the full tail: OP_DROP OP_DROP and nothing more + const_iterator pc = begin() + 26; + opcodetype opcode; + std::vector data; + if (!GetOp(pc, opcode, data) || data.empty()) + return false; + if (pc >= end() || *pc != OP_DROP) + return false; + ++pc; + if (!GetOp(pc, opcode, data) || data.empty()) + return false; + if (pc >= end() || *pc != OP_DROP) + return false; + ++pc; + if (pc != end()) + return false; + return true; +} + bool CScript::IsMint() const { return IsZerocoinMint() || IsSigmaMint() || IsZerocoinRemint() || IsLelantusMint() || IsLelantusJMint() || IsSparkMint() || IsSparkSMint(); } diff --git a/src/script/script.h b/src/script/script.h index 3462581956..4496954568 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -209,7 +209,10 @@ enum opcodetype OP_SPARKSPEND = 0xd3, // basically NOP but identifies that sunsequent txout script contains super transparent address - OP_EXCHANGEADDR = 0xe0 + OP_EXCHANGEADDR = 0xe0, + + // NOP suffix that tags a P2PKH output with spark name and spark address data + OP_SPARKNAMEID = 0xe1 }; const char* GetOpName(opcodetype opcode); @@ -689,6 +692,9 @@ class CScript : public CScriptBase bool IsSparkSpend() const; + // Returns true if this script is a P2PKH tagged with spark name data + bool IsSparkNameFee() const; + bool IsZerocoinRemint() const; bool IsMint() const; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index c752febe45..3cea34fdd2 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -90,6 +90,7 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP return Sign1(keyID, creator, scriptPubKey, ret, sigversion); case TX_PUBKEYHASH: case TX_EXCHANGEADDRESS: + case TX_SPARKNAMEFEE: keyID = CKeyID(uint160(vSolutions[0])); if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion)) return false; @@ -327,6 +328,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature case TX_PUBKEY: case TX_PUBKEYHASH: case TX_EXCHANGEADDRESS: + case TX_SPARKNAMEFEE: // Signatures are bigger than placeholders or empty scripts: if (sigs1.script.empty() || sigs1.script[0].empty()) return sigs2; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 8af564f37a..49b1be20b6 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -38,6 +38,7 @@ const char* GetTxnOutputType(txnouttype t) case TX_SPARKMINT: return "sparkmint"; case TX_SPARKSMINT: return "sparksmint"; case TX_EXCHANGEADDRESS: return "exchangeaddress"; + case TX_SPARKNAMEFEE: return "sparknamefee"; } return NULL; } @@ -92,6 +93,15 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector OP_DROP OP_DROP + if (scriptPubKey.IsSparkNameFee()) + { + typeRet = TX_SPARKNAMEFEE; + std::vector hashBytes(scriptPubKey.begin()+3, scriptPubKey.begin()+23); + vSolutionsRet.push_back(hashBytes); + return true; + } + // Zerocoin if (scriptPubKey.IsZerocoinMint()) { @@ -283,6 +293,11 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) addressRet = CExchangeKeyID(uint160(vSolutions[0])); return true; } + else if (whichType == TX_SPARKNAMEFEE) + { + addressRet = CKeyID(uint160(vSolutions[0])); + return true; + } else if (whichType == TX_SCRIPTHASH) { addressRet = CScriptID(uint160(vSolutions[0])); @@ -374,6 +389,59 @@ CScript GetScriptForDestination(const CTxDestination& dest) return script; } +CScript GetScriptForSparkNameFee(const CTxDestination& dest, const std::string& sparkName, const std::string& sparkAddress) +{ + const CKeyID* keyID = boost::get(&dest); + if (!keyID) + return CScript(); + + CScript script; + script << OP_DUP << OP_HASH160 << ToByteVector(*keyID) << OP_EQUALVERIFY << OP_CHECKSIG; + script << OP_SPARKNAMEID; + script << std::vector(sparkName.begin(), sparkName.end()); + script << OP_DROP; + script << std::vector(sparkAddress.begin(), sparkAddress.end()); + script << OP_DROP; + return script; +} + +bool ExtractSparkNameFromScript(const CScript& scriptPubKey, std::string& sparkName, std::string& sparkAddress) +{ + if (!scriptPubKey.IsSparkNameFee()) + return false; + + CScript::const_iterator pc = scriptPubKey.begin() + 26; // skip P2PKH (25 bytes) + OP_SPARKNAMEID (1 byte) + std::vector nameData; + opcodetype opcode; + + if (!scriptPubKey.GetOp(pc, opcode, nameData)) + return false; + if (pc >= scriptPubKey.end() || *pc != OP_DROP) + return false; + ++pc; // skip OP_DROP + + std::vector addrData; + if (!scriptPubKey.GetOp(pc, opcode, addrData)) + return false; + if (pc >= scriptPubKey.end() || *pc != OP_DROP) + return false; + ++pc; // skip OP_DROP + if (pc != scriptPubKey.end()) + return false; + + sparkName.assign(nameData.begin(), nameData.end()); + sparkAddress.assign(addrData.begin(), addrData.end()); + return true; +} + +CScript GetBaseScriptFromSparkNameFee(const CScript& scriptPubKey) +{ + if (!scriptPubKey.IsSparkNameFee()) + return scriptPubKey; + // The first 25 bytes are the standard P2PKH script + return CScript(scriptPubKey.begin(), scriptPubKey.begin() + 25); +} + CScript GetScriptForRawPubKey(const CPubKey& pubKey) { return CScript() << std::vector(pubKey.begin(), pubKey.end()) << OP_CHECKSIG; diff --git a/src/script/standard.h b/src/script/standard.h index 4c49266d83..e4c7ad2ef6 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -12,6 +12,7 @@ #include #include +#include static const bool DEFAULT_ACCEPT_DATACARRIER = true; @@ -59,7 +60,8 @@ enum txnouttype TX_LELANTUSJMINT, TX_SPARKMINT, TX_SPARKSMINT, - TX_EXCHANGEADDRESS + TX_EXCHANGEADDRESS, + TX_SPARKNAMEFEE }; class CNoDestination { @@ -85,6 +87,9 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); CScript GetScriptForDestination(const CTxDestination& dest); +CScript GetScriptForSparkNameFee(const CTxDestination& dest, const std::string& sparkName, const std::string& sparkAddress); +bool ExtractSparkNameFromScript(const CScript& scriptPubKey, std::string& sparkName, std::string& sparkAddress); +CScript GetBaseScriptFromSparkNameFee(const CScript& scriptPubKey); CScript GetScriptForRawPubKey(const CPubKey& pubkey); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); CScript GetScriptForWitness(const CScript& redeemscript); diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 0f1e8f296d..81e251bc60 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1,5 +1,5 @@ -#include "../liblelantus/threadpool.h" #include "sparkwallet.h" +#include "threadpool.h" #include "state.h" #include "../wallet/wallet.h" #include "../wallet/coincontrol.h" @@ -12,6 +12,8 @@ #include "sparkname.h" #include "../chain.h" #include +#include +#include const uint32_t DEFAULT_SPARK_NCOUNT = 1; @@ -40,7 +42,14 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } // Generating spark key set first time - spark::SpendKey spendKey = generateSpendKey(params); + spark::SpendKey spendKey(params); + try { + spendKey = std::move(generateSpendKey(params)); + } catch (const WalletLocked&) { + throw std::runtime_error("Spark wallet creation FAILED, wallet is locked\n"); + } catch (const std::exception&) { + throw std::runtime_error("Spark wallet creation FAILED, unable to generate spend key\n"); + } fullViewKey = generateFullViewKey(spendKey); viewKey = generateIncomingViewKey(fullViewKey); @@ -75,7 +84,9 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } } - threadPool = new ParallelOpThreadPool(boost::thread::hardware_concurrency()); + + unsigned nThreads = std::thread::hardware_concurrency(); + threadPool = new ParallelOpThreadPool(static_cast(nThreads)); if (fWalletJustUnlocked) pwalletMain->Lock(); @@ -83,10 +94,13 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { CSparkWallet::~CSparkWallet() { delete (ParallelOpThreadPool*)threadPool; + threadPool = nullptr; } void CSparkWallet::FinishTasks() { - ((ParallelOpThreadPool*)threadPool)->Shutdown(); + if (threadPool) { + ((ParallelOpThreadPool*)threadPool)->Shutdown(); + } spark::ShutdownSparkState(); } @@ -235,7 +249,7 @@ spark::Address CSparkWallet::getChangeAddress() { spark::SpendKey CSparkWallet::generateSpendKey(const spark::Params* params) { if (pwalletMain->IsLocked()) { LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); - return spark::SpendKey(params); + throw WalletLocked(); } CKey secret; @@ -518,10 +532,10 @@ void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& } void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { - const auto& transactions = block.vtx; + std::vector vtxCopy = block.vtx; ((ParallelOpThreadPool*)threadPool)->PostTask([=]() { LOCK(cs_spark_wallet); - for (const auto& tx : transactions) { + for (const auto& tx : vtxCopy) { if (tx->IsSparkSpend()) { try { spark::SpendTransaction spend = spark::ParseSparkSpend(*tx); @@ -662,14 +676,13 @@ void CSparkWallet::UpdateMintStateFromMempool(const std::vector& co } void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { - const auto& transactions = block.vtx; - - ((ParallelOpThreadPool*)threadPool)->PostTask([=] () mutable { + std::vector vtxCopy = block.vtx; + ((ParallelOpThreadPool*)threadPool)->PostTask([=]() mutable { LOCK(cs_spark_wallet); CWalletDB walletdb(strWalletFile); - for (const auto& tx : transactions) { + for (const auto& tx : vtxCopy) { if (tx->IsSparkTransaction()) { - auto coins = spark::GetSparkMintCoins(*tx); + auto coins = spark::GetSparkMintCoins(*tx); uint256 txHash = tx->GetHash(); UpdateMintState(coins, txHash, walletdb); } @@ -1423,13 +1436,12 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( spark::SpendKey spendKey(params); try { spendKey = std::move(generateSpendKey(params)); - } catch (std::exception& e) { + } catch (const WalletLocked&) { + throw std::runtime_error(_("Unable to generate spend key, wallet is locked.")); + } catch (const std::exception&) { throw std::runtime_error(_("Unable to generate spend key.")); } - if (spendKey == spark::SpendKey(params)) - throw std::runtime_error(_("Unable to generate spend key, looks wallet locked.")); - tx.vin.clear(); tx.vout.clear(); @@ -1662,10 +1674,13 @@ CWalletTx CSparkWallet::CreateSparkNameTransaction(CSparkNameTxData &nameData, C CSparkNameManager *sparkNameManager = CSparkNameManager::GetInstance(); const auto &consensusParams = Params().GetConsensus(); + // Use the next-block height: this transaction will be validated at chainActive.Height() + 1, + // so all height-gated decisions (fee script, inputsHash) must match that height to avoid + // building a transaction the mempool immediately rejects on the activation boundary. int nHeight; { LOCK(cs_main); - nHeight = chainActive.Height(); + nHeight = chainActive.Height() + 1; } std::string payoutAddress = nHeight >= consensusParams.stage41StartBlockDevFundAddressChange ? consensusParams.stage3CommunityFundAddress @@ -1673,7 +1688,7 @@ CWalletTx CSparkWallet::CreateSparkNameTransaction(CSparkNameTxData &nameData, C CRecipient devPayout; devPayout.nAmount = sparkNameFee; - devPayout.scriptPubKey = GetScriptForDestination(CBitcoinAddress(payoutAddress).Get()); + devPayout.scriptPubKey = CSparkNameManager::GetSparkNameFeeScript(payoutAddress, nameData.name, nameData.sparkAddress); devPayout.fSubtractFeeFromAmount = false; CWalletTx wtxSparkSpend = CreateSparkSpendTransaction({devPayout}, {}, txFee, coinConrol, @@ -1683,13 +1698,12 @@ CWalletTx CSparkWallet::CreateSparkNameTransaction(CSparkNameTxData &nameData, C spark::SpendKey spendKey(params); try { spendKey = std::move(generateSpendKey(params)); - } catch (std::exception& e) { + } catch (const WalletLocked&) { + throw std::runtime_error(_("Unable to generate spend key, wallet is locked.")); + } catch (const std::exception&) { throw std::runtime_error(_("Unable to generate spend key.")); } - if (spendKey == spark::SpendKey(params)) - throw std::runtime_error(_("Unable to generate spend key, looks the wallet is locked.")); - spark::Address address(spark::Params::get_default()); try { address.decode(nameData.sparkAddress); @@ -1701,7 +1715,7 @@ CWalletTx CSparkWallet::CreateSparkNameTransaction(CSparkNameTxData &nameData, C throw std::runtime_error(_("Spark address doesn't belong to the wallet")); CMutableTransaction tx = CMutableTransaction(*wtxSparkSpend.tx); - sparkNameManager->AppendSparkNameTxData(tx, nameData, spendKey, fullViewKey); + sparkNameManager->AppendSparkNameTxData(tx, nameData, spendKey, fullViewKey, nHeight); wtxSparkSpend.tx = MakeTransactionRef(std::move(tx)); return wtxSparkSpend; diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 3b8f537377..4c417f9ca6 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1,10 +1,11 @@ -#include "../liblelantus/threadpool.h" +#include "threadpool.h" #include "state.h" #include "compat_layer.h" #include "sparkname.h" #include "../validation.h" #include "../batchproof_container.h" +#include #include namespace spark { @@ -102,7 +103,7 @@ unsigned char GetNetworkType() { } /* - * Util funtions + * Util functions */ size_t CountCoinInBlock(CBlockIndex *index, int id) { return index->sparkMintedCoins.count(id) > 0 @@ -274,7 +275,7 @@ bool ConnectBlockSpark( pindexNew->sparkSetHash.clear(); } - if (!CheckSparkBlock(state, *pblock)) { + if (!CheckSparkBlock(state, *pblock, pindexNew->nHeight)) { return false; } @@ -341,12 +342,26 @@ bool ConnectBlockSpark( for (const auto &sparkName : pblock->sparkTxInfo->sparkNames) { uint8_t opType = sparkName.second.nVersion >= 2 ? sparkName.second.operationType : CSparkNameTxData::opRegister; + // For V2.1+, renewals and transfers preserve remaining validity + int validityBlocks = sparkName.second.sparkNameValidityBlocks; + const auto& consensusParams = ::Params().GetConsensus(); + if (pindexNew->nHeight >= consensusParams.nSparkNamesV21StartBlock) { + try { + int existingExpirationHeight = sparkNameManager->GetSparkNameBlockHeight(sparkName.first); + int remainingBlocks = existingExpirationHeight - pindexNew->nHeight; + if (remainingBlocks > 0) + validityBlocks += remainingBlocks; + } catch (const std::runtime_error&) { + // name doesn't exist yet, no adjustment needed + } + } + switch (opType) { case CSparkNameTxData::opRegister: pindexNew->addedSparkNames[sparkName.first] = CSparkNameBlockIndexData(sparkName.second.name, sparkName.second.sparkAddress, - pindexNew->nHeight + sparkName.second.sparkNameValidityBlocks, + pindexNew->nHeight + validityBlocks, sparkName.second.additionalInfo); break; @@ -361,7 +376,7 @@ bool ConnectBlockSpark( pindexNew->addedSparkNames[sparkName.first] = CSparkNameBlockIndexData(sparkName.second.name, sparkName.second.sparkAddress, - pindexNew->nHeight + sparkName.second.sparkNameValidityBlocks, + pindexNew->nHeight + validityBlocks, sparkName.second.additionalInfo); break; @@ -458,27 +473,39 @@ void DisconnectTipSpark(CBlock& block, CBlockIndex *pindexDelete) { sparkState.RemoveBlock(pindexDelete); + // Invalidate proof cache for Spark spends in the disconnected block. After a reorg, + // those spends may be re-applied on the new fork where the anonymity set differs; + // they must be re-verified instead of using a stale cache hit. + { + LOCK(cs_checkedSparkSpendTransactions); + for (const auto& txRef : block.vtx) { + const CTransaction& tx = *txRef; + if (tx.IsSparkSpend()) + gCheckedSparkSpendTransactions.erase(tx.GetHash()); + } + } + // Also remove from mempool spends that reference given block hash. RemoveSpendReferencingBlock(mempool, pindexDelete); RemoveSpendReferencingBlock(txpools.getStemTxPool(), pindexDelete); } -bool CheckSparkBlock(CValidationState &state, const CBlock& block) { +bool CheckSparkBlock(CValidationState &state, const CBlock& block, int nBlockHeight) { auto& consensus = ::Params().GetConsensus(); - size_t blockSpendsValue = 0; + CAmount blockSpendsValue = 0; for (const auto& tx : block.vtx) { auto txSpendsValue = GetSpendTransparentAmount(*tx); - if (txSpendsValue > consensus.GetMaxValueSparkSpendPerTransaction(block.nHeight)) { + if (txSpendsValue > consensus.GetMaxValueSparkSpendPerTransaction(nBlockHeight)) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } blockSpendsValue += txSpendsValue; } - if (cmp::greater(blockSpendsValue, consensus.GetMaxValueSparkSpendPerBlock(block.nHeight))) { + if (cmp::greater(blockSpendsValue, consensus.GetMaxValueSparkSpendPerBlock(nBlockHeight))) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } @@ -762,6 +789,7 @@ bool CheckSparkSpendTransaction( batchProofContainer->add(*spend); } else { bool fChecked = false; + bool scheduledAsync = false; try { bool fRecheckNeeded; @@ -787,9 +815,17 @@ bool CheckSparkSpendTransaction( bool result = future->get(); cs_checkedSparkSpendTransactions.lock(); - checkState.fChecked = true; - checkState.fResult = result; - checkState.checkInProgress = nullptr; + // Entry may have been erased by DisconnectTipSpark during the unlock window + auto it = gCheckedSparkSpendTransactions.find(hashTx); + if (it == gCheckedSparkSpendTransactions.end()) { + fRecheckNeeded = true; + continue; + } + ProofCheckState& checkStateAfterWait = it->second; + + checkStateAfterWait.fChecked = true; + checkStateAfterWait.fResult = result; + checkStateAfterWait.checkInProgress = nullptr; if (!result) { // unfortunately, it's possible that the proof was checked and failed @@ -820,11 +856,12 @@ bool CheckSparkSpendTransaction( checkState.fChecked = false; checkState.fResult = false; checkState.checkInProgress = std::make_shared>(std::move(future)); + scheduledAsync = true; } } } while (fRecheckNeeded); - + if (fChecked) { // if we are here, then the proof was already checked and it passed passVerify = true; @@ -835,8 +872,13 @@ bool CheckSparkSpendTransaction( passVerify = spark::SpendTransaction::verify(*spend, cover_sets); } else { - // return true for now, the result will be processed later - return true; + if (scheduledAsync) { + // result will be processed later by the async task + passVerify = true; + } else { + // Pool was busy so verification was not scheduled; verify synchronously for defense-in-depth + passVerify = spark::SpendTransaction::verify(*spend, cover_sets); + } } } } @@ -847,8 +889,7 @@ bool CheckSparkSpendTransaction( } if (!fStatefulSigmaCheck) - // nothing more to do - return true; + return passVerify; if (passVerify) { const std::vector& lTags = spend->getUsedLTags(); @@ -862,7 +903,7 @@ bool CheckSparkSpendTransaction( if (!(sparkTxInfo && sparkTxInfo->spTransactions.count(hashTx) > 0)) { for (size_t i = 0; i < lTags.size(); ++i) { if (!CheckLTag(state, sparkTxInfo, lTags[i], nHeight, false)) { - LogPrintf("CheckSparkSpendTransaction: lTAg check failed, ltag=%s\n", lTags[i]); + LogPrintf("CheckSparkSpendTransaction: lTag check failed, ltag=%s\n", lTags[i]); return false; } } @@ -944,10 +985,10 @@ bool CheckSparkTransaction( // Check Spark Spend if (tx.IsSparkSpend()) { int nRealHeight = nHeight; - if (nRealHeight == INT_MAX) // if height is not set, use chainActive height + if (nRealHeight == INT_MAX) // mempool validation checks the next block height { LOCK(cs_main); - nRealHeight = chainActive.Height(); + nRealHeight = chainActive.Height() + 1; } if (GetSpendTransparentAmount(tx) > consensus.GetMaxValueSparkSpendPerTransaction(nRealHeight)) { return state.DoS(100, false, @@ -1734,4 +1775,4 @@ void CSparkMempoolState::Reset() { mempoolMints.clear(); } -} // namespace spark \ No newline at end of file +} // namespace spark diff --git a/src/spark/state.h b/src/spark/state.h index 7d3ad95962..1d7b168c31 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -59,7 +59,7 @@ std::vector GetSparkMintCoins(const CTransaction &tx); size_t GetSpendInputs(const CTransaction &tx); CAmount GetSpendTransparentAmount(const CTransaction& tx); -bool CheckSparkBlock(CValidationState &state, const CBlock& block); +bool CheckSparkBlock(CValidationState &state, const CBlock& block, int nBlockHeight); //void DisconnectTipLelantus(CBlock &block, CBlockIndex *pindexDelete); diff --git a/src/liblelantus/threadpool.h b/src/spark/threadpool.h similarity index 92% rename from src/liblelantus/threadpool.h rename to src/spark/threadpool.h index 1fa04369d4..8dcac043c4 100644 --- a/src/liblelantus/threadpool.h +++ b/src/spark/threadpool.h @@ -1,29 +1,29 @@ +// Copyright (c) The Firo Core Developers +// Thread pool for Spark wallet background tasks (same implementation as historical liblelantus/threadpool.h). + #ifndef THREADPOOL_H #define THREADPOOL_H +#include #include #include #include #include #include -//#include #define BOOST_THREAD_PROVIDES_FUTURE +#include #include #include #include #include #include - -// our code currently relies on boost disable_interruption. This will go away with core upgrade -//#include - // Number of seconds before thread shuts down if idle constexpr static int secondsBeforeThreadShutdown = 60; -// Simple thread pool class for using multiple cores effeciently +// Simple thread pool class for using multiple cores efficiently template class ParallelOpThreadPool { @@ -64,11 +64,11 @@ class ParallelOpThreadPool { } void StartThreads() { - // should be called with mutex aquired + // should be called with mutex acquired // start missing threads while (threads.size() < number_of_threads) threads.emplace_back(std::bind(&ParallelOpThreadPool::ThreadProc, this)); - } + } public: ParallelOpThreadPool(std::size_t thread_number) : shutdown(false), number_of_threads(thread_number) {} @@ -136,4 +136,4 @@ class DoNotDisturb { }; -#endif \ No newline at end of file +#endif diff --git a/src/sparkname.cpp b/src/sparkname.cpp index 02a0601782..33462a6964 100644 --- a/src/sparkname.cpp +++ b/src/sparkname.cpp @@ -53,6 +53,7 @@ bool CSparkNameManager::RemoveBlock(CBlockIndex *pindex) std::set CSparkNameManager::GetSparkNames() { std::set result; + LOCK(cs_spark_name); for (const auto &entry : sparkNames) result.insert(entry.second.name); @@ -62,6 +63,7 @@ std::set CSparkNameManager::GetSparkNames() std::vector CSparkNameManager::DumpSparkNameData() { std::vector result; + LOCK(cs_spark_name); result.reserve(sparkNames.size()); for (const auto &entry : sparkNames) result.push_back(entry.second); @@ -71,6 +73,7 @@ std::vector CSparkNameManager::DumpSparkNameData() bool CSparkNameManager::GetSparkAddress(const std::string &name, std::string &address) { + LOCK(cs_spark_name); auto it = sparkNames.find(ToUpper(name)); if (it != sparkNames.end()) { address = it->second.sparkAddress; @@ -83,6 +86,7 @@ bool CSparkNameManager::GetSparkAddress(const std::string &name, std::string &ad uint64_t CSparkNameManager::GetSparkNameBlockHeight(const std::string &name) const { + LOCK(cs_spark_name); auto it = sparkNames.find(ToUpper(name)); if (it == sparkNames.end()) throw std::runtime_error("Spark name not found: " + name); @@ -93,6 +97,7 @@ uint64_t CSparkNameManager::GetSparkNameBlockHeight(const std::string &name) con std::string CSparkNameManager::GetSparkNameAdditionalData(const std::string &name) const { + LOCK(cs_spark_name); auto it = sparkNames.find(ToUpper(name)); if (it == sparkNames.end()) throw std::runtime_error("Spark name not found: " + name); @@ -126,9 +131,13 @@ bool CSparkNameManager::ParseSparkNameTxData(const CTransaction &tx, spark::Spen bool CSparkNameManager::CheckPaymentToTransparentAddress(const CTransaction &tx, const std::string &address, CAmount amount) const { + CScript expectedScript = GetScriptForDestination(CBitcoinAddress(address).Get()); for (const CTxOut &txout : tx.vout) { - if (txout.scriptPubKey == GetScriptForDestination(CBitcoinAddress(address).Get()) && txout.nValue >= amount) + CScript baseScript = txout.scriptPubKey.IsSparkNameFee() + ? GetBaseScriptFromSparkNameFee(txout.scriptPubKey) + : txout.scriptPubKey; + if (baseScript == expectedScript && txout.nValue >= amount) return true; } return false; @@ -168,46 +177,105 @@ bool CSparkNameManager::CheckSparkNameTx(const CTransaction &tx, int nHeight, CV if (sparkNameData.nVersion >= 2 && nHeight < consensusParams.nSparkNamesV2StartBlock) return state.DoS(100, error("CheckSparkNameTx: spark name tx v2 is not allowed yet")); + if (sparkNameData.nVersion >= 2 && sparkNameData.operationType >= (uint8_t)CSparkNameTxData::opMaximumValue) + return state.DoS(100, error("CheckSparkNameTx: invalid operation type")); + + if (sparkNameData.nVersion >= 2 && sparkNameData.operationType == (uint8_t)CSparkNameTxData::opUnregister) + return state.DoS(100, error("CheckSparkNameTx: unregister operation is not supported yet")); + if (outSparkNameData) *outSparkNameData = sparkNameData; if (!IsSparkNameValid(sparkNameData.name)) return state.DoS(100, error("CheckSparkNameTx: invalid name")); + int existingExpirationHeight = -1; + bool fUpdateExistingRecord = false; + bool fSparkNameTransfer = sparkNameData.nVersion >= 2 && sparkNameData.operationType == (uint8_t)CSparkNameTxData::opTransfer; + const std::string normalizedName = ToUpper(sparkNameData.name); + + { + LOCK(cs_spark_name); + auto sparkNameIt = sparkNames.find(normalizedName); + if (sparkNameIt != sparkNames.end()) { + // it's possible to change any metadata of the existing name but if the spark address is being + // tranferred, new name shouldn't be already registered + if (!fSparkNameTransfer && sparkNameIt->second.sparkAddress != sparkNameData.sparkAddress) + return state.DoS(100, error("CheckSparkNameTx: name already exists")); + + fUpdateExistingRecord = true; + existingExpirationHeight = sparkNameIt->second.sparkNameValidityHeight; + } + } + constexpr int nBlockPerYear = 365*24*24; // 24 blocks per hour - int nYears = (sparkNameData.sparkNameValidityBlocks + nBlockPerYear-1) / nBlockPerYear; + if (sparkNameData.sparkNameValidityBlocks == 0) + return state.DoS(100, error("CheckSparkNameTx: validity period must be at least 1 block")); + // Explicit uint32_t check before narrowing to int to prevent integer overflow in the update path + if (sparkNameData.sparkNameValidityBlocks > (uint32_t)(nBlockPerYear * 15)) + return state.DoS(100, error("CheckSparkNameTx: can't be valid for more than 15 years")); + int validityBlocks = (int)sparkNameData.sparkNameValidityBlocks; + + if (nHeight >= consensusParams.nSparkNamesV21StartBlock) { + if (existingExpirationHeight != -1) + validityBlocks = std::max(validityBlocks, existingExpirationHeight - nHeight + validityBlocks); + // after nSparkNamesV21StartBlock, max validity is 15 years + if (validityBlocks > nBlockPerYear * 15) + return state.DoS(100, error("CheckSparkNameTx: can't be valid for more than 15 years")); + } + else { + if (validityBlocks > nBlockPerYear * 10) + return state.DoS(100, error("CheckSparkNameTx: can't be valid for more than 10 years")); + } - if (sparkNameData.sparkNameValidityBlocks > nBlockPerYear * 10) - return state.DoS(100, error("CheckSparkNameTx: can't be valid for more than 10 years")); + // fee is based on the new time being purchased, not including leftover time from a previous registration + int nYears = (sparkNameData.sparkNameValidityBlocks + nBlockPerYear-1) / nBlockPerYear; CAmount nameFee = consensusParams.nSparkNamesFee[sparkNameData.name.size()] * COIN * nYears; - bool payoutFound = false; - // Up until stage 4.1, the fee is paid to the development fund address. Afterwards, it is paid to the community fund address. - // Graceful period allows to register spark names with the old address for the payment - if (nHeight < consensusParams.stage41StartBlockDevFundAddressChange + consensusParams.stage41SparkNamesGracefulPeriod) - payoutFound = CheckPaymentToTransparentAddress(tx, consensusParams.stage3DevelopmentFundAddress, nameFee); - if (nHeight >= consensusParams.stage41StartBlockDevFundAddressChange) - payoutFound = payoutFound || CheckPaymentToTransparentAddress(tx, consensusParams.stage3CommunityFundAddress, nameFee); - - if (!payoutFound) - return state.DoS(100, error("CheckSparkNameTx: name fee is either missing or insufficient")); + // After v2.1, the fee output itself must carry the spark name and address. + if (nHeight >= consensusParams.nSparkNamesV21StartBlock) { + const CScript developmentFundScript = GetScriptForDestination(CBitcoinAddress(consensusParams.stage3DevelopmentFundAddress).Get()); + const CScript communityFundScript = GetScriptForDestination(CBitcoinAddress(consensusParams.stage3CommunityFundAddress).Get()); + bool payoutFound = false; + for (const CTxOut &txout : tx.vout) { + if (txout.scriptPubKey.IsSparkNameFee()) { + std::string embeddedName, embeddedAddress; + if (!ExtractSparkNameFromScript(txout.scriptPubKey, embeddedName, embeddedAddress)) + return state.DoS(100, error("CheckSparkNameTx: malformed spark name fee output")); + if (embeddedName != sparkNameData.name) + return state.DoS(100, error("CheckSparkNameTx: spark name in fee output does not match transaction data")); + if (embeddedAddress != sparkNameData.sparkAddress) + return state.DoS(100, error("CheckSparkNameTx: spark address in fee output does not match transaction data")); + + CScript baseScript = GetBaseScriptFromSparkNameFee(txout.scriptPubKey); + bool validPayoutAddress = false; + if (nHeight < consensusParams.stage41StartBlockDevFundAddressChange + consensusParams.stage41SparkNamesGracefulPeriod) + validPayoutAddress = baseScript == developmentFundScript; + if (nHeight >= consensusParams.stage41StartBlockDevFundAddressChange) + validPayoutAddress = validPayoutAddress || baseScript == communityFundScript; + if (validPayoutAddress && txout.nValue >= nameFee) + payoutFound = true; + } + } + if (!payoutFound) + return state.DoS(100, error("CheckSparkNameTx: spark name fee output with name/address tag is required after v2.1")); + } else { + bool payoutFound = false; + // Up until stage 4.1, the fee is paid to the development fund address. Afterwards, it is paid to the community fund address. + // Graceful period allows to register spark names with the old address for the payment + if (nHeight < consensusParams.stage41StartBlockDevFundAddressChange + consensusParams.stage41SparkNamesGracefulPeriod) + payoutFound = CheckPaymentToTransparentAddress(tx, consensusParams.stage3DevelopmentFundAddress, nameFee); + if (nHeight >= consensusParams.stage41StartBlockDevFundAddressChange) + payoutFound = payoutFound || CheckPaymentToTransparentAddress(tx, consensusParams.stage3CommunityFundAddress, nameFee); + + if (!payoutFound) + return state.DoS(100, error("CheckSparkNameTx: name fee is either missing or insufficient")); + } if (sparkNameData.additionalInfo.size() > 1024) return state.DoS(100, error("CheckSparkNameTx: additional info is too long")); - bool fUpdateExistingRecord = false; - bool fSparkNameTransfer = sparkNameData.nVersion >= 2 && sparkNameData.operationType == (uint8_t)CSparkNameTxData::opTransfer; - - if (sparkNames.count(ToUpper(sparkNameData.name)) > 0) { - // it's possible to change any metadata of the existing name but if the spark address is being - // tranferred, new name shouldn't be already registered - if (!fSparkNameTransfer && sparkNames[ToUpper(sparkNameData.name)].sparkAddress != sparkNameData.sparkAddress) - return state.DoS(100, error("CheckSparkNameTx: name already exists")); - - fUpdateExistingRecord = true; - } - { LOCK(cs_spark_name); if ((fSparkNameTransfer || !fUpdateExistingRecord) && sparkNameAddresses.count(sparkNameData.sparkAddress) > 0) @@ -258,6 +326,21 @@ bool CSparkNameManager::CheckSparkNameTx(const CTransaction &tx, int nHeight, CV // check the transfer ownership proof (if present) if (fSparkNameTransfer) { + // V2.1+: inputsHash must commit to the name's current expiration height, which is + // unique per registration cycle and known by all parties without tx coordination. + if (nHeight >= consensusParams.nSparkNamesV21StartBlock) { + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + hw << (uint64_t)existingExpirationHeight; + // Grace period: pre-v2.1 transfers were built without an inputsHash. Those that were + // already signed/broadcast before activation can still be sitting in the mempool when + // the fork takes effect, so accept the legacy (null inputsHash) form for a short window + // after activation to avoid dropping in-flight transfers. + bool fInGracePeriod = nHeight < consensusParams.nSparkNamesV21StartBlock + consensusParams.stage41SparkNamesGracefulPeriod; + bool fLegacyTransfer = fInGracePeriod && sparkNameData.inputsHash.IsNull(); + if (sparkNameData.inputsHash != hw.GetHash() && !fLegacyTransfer) + return state.DoS(100, error("CheckSparkNameTx: bad transfer proof inputs hash")); + } + spark::Address oldSparkAddress(spark::Params::get_default()); try { oldSparkAddress.decode(sparkNameData.oldSparkAddress); @@ -267,9 +350,12 @@ bool CSparkNameManager::CheckSparkNameTx(const CTransaction &tx, int nHeight, CV } // check if the old spark address is the one currently associated with the spark name - if (sparkNameAddresses.count(sparkNameData.oldSparkAddress) == 0 || - sparkNameAddresses[sparkNameData.oldSparkAddress] != ToUpper(sparkNameData.name)) - return state.DoS(100, error("CheckSparkNameTx: old spark address is not associated with the spark name")); + { + LOCK(cs_spark_name); + auto oldAddressIt = sparkNameAddresses.find(sparkNameData.oldSparkAddress); + if (oldAddressIt == sparkNameAddresses.end() || oldAddressIt->second != normalizedName) + return state.DoS(100, error("CheckSparkNameTx: old spark address is not associated with the spark name")); + } spark::OwnershipProof transferOwnershipProof; try { @@ -317,41 +403,96 @@ bool CSparkNameManager::GetSparkNameByAddress(const std::string& address, std::s return false; } +CScript CSparkNameManager::GetSparkNameFeeScript(const std::string &feeAddress, const std::string &sparkName, const std::string &sparkAddress) +{ + CTxDestination dest = CBitcoinAddress(feeAddress).Get(); + int nHeight; + { + LOCK(cs_main); + nHeight = chainActive.Height() + 1; + } + if (nHeight >= ::Params().GetConsensus().nSparkNamesV21StartBlock) + return GetScriptForSparkNameFee(dest, sparkName, sparkAddress); + return GetScriptForDestination(dest); +} + bool CSparkNameManager::ValidateSparkNameData(const CSparkNameTxData &sparkNameData, std::string &errorDescription) { errorDescription.clear(); - LOCK(cs_spark_name); - if (!IsSparkNameValid(sparkNameData.name)) - errorDescription = "invalid spark name"; + int nHeight; + { + LOCK(cs_main); + nHeight = chainActive.Height() + 1; + } + const std::string normalizedName = ToUpper(sparkNameData.name); + + int existingExpirationHeight = -1; + { + LOCK(cs_spark_name); + auto sparkNameIt = sparkNames.find(normalizedName); + auto sparkNameAddressIt = sparkNameAddresses.find(sparkNameData.sparkAddress); + if (sparkNameIt != sparkNames.end()) + existingExpirationHeight = sparkNameIt->second.sparkNameValidityHeight; - else if (sparkNameData.additionalInfo.size() > 1024) - errorDescription = "additional info is too long"; + if (!IsSparkNameValid(sparkNameData.name)) + errorDescription = "invalid spark name"; - else if (sparkNameData.sparkNameValidityBlocks > 365*24*24*10) - errorDescription = "transaction can't be valid for more than 10 years"; + else if (sparkNameData.additionalInfo.size() > 1024) + errorDescription = "additional info is too long"; - else if (sparkNames.count(ToUpper(sparkNameData.name)) > 0 && - sparkNames[ToUpper(sparkNameData.name)].sparkAddress != sparkNameData.sparkAddress && - (sparkNameData.nVersion < 2 || sparkNameData.operationType == CSparkNameTxData::opRegister)) - errorDescription = "name already exists with another spark address as a destination"; + else if (nHeight >= ::Params().GetConsensus().nSparkNamesV21StartBlock && sparkNameData.sparkNameValidityBlocks > 365*24*24*15) + errorDescription = "transaction can't be valid for more than 15 years"; - else if (sparkNameAddresses.count(sparkNameData.sparkAddress) > 0 && - sparkNameAddresses[sparkNameData.sparkAddress] != ToUpper(sparkNameData.name)) - errorDescription = "spark address is already used for another name"; + else if (nHeight < ::Params().GetConsensus().nSparkNamesV21StartBlock && sparkNameData.sparkNameValidityBlocks > 365*24*24*10) + errorDescription = "transaction can't be valid for more than 10 years"; - else if (sparkNameData.nVersion >= 2 && sparkNameData.operationType == CSparkNameTxData::opTransfer && - sparkNameData.oldSparkAddress.empty()) - errorDescription = "old spark address is required for transfer operation"; + else if (sparkNameData.sparkNameValidityBlocks == 0) + errorDescription = "validity period must be at least 1 block"; - else if (sparkNameData.nVersion >= 2 && sparkNameData.operationType == CSparkNameTxData::opUnregister) - errorDescription = "unregister operation is not supported yet"; + else if (sparkNameData.nVersion >= 2 && sparkNameData.operationType >= (uint8_t)CSparkNameTxData::opMaximumValue) + errorDescription = "invalid operation type"; - else { + else if (sparkNameIt != sparkNames.end() && + sparkNameIt->second.sparkAddress != sparkNameData.sparkAddress && + (sparkNameData.nVersion < 2 || sparkNameData.operationType == CSparkNameTxData::opRegister)) + errorDescription = "name already exists with another spark address as a destination"; + + else if (sparkNameAddressIt != sparkNameAddresses.end() && + sparkNameAddressIt->second != normalizedName) + errorDescription = "spark address is already used for another name"; + + else if (sparkNameData.nVersion >= 2 && sparkNameData.operationType == CSparkNameTxData::opTransfer && + sparkNameData.oldSparkAddress.empty()) + errorDescription = "old spark address is required for transfer operation"; + + else if (sparkNameData.nVersion >= 2 && sparkNameData.operationType == CSparkNameTxData::opUnregister) + errorDescription = "unregister operation is not supported yet"; + } + + if (errorDescription.empty()) { LOCK(mempool.cs); - if (mempool.sparkNames.count(ToUpper(sparkNameData.name)) > 0) + if (mempool.sparkNames.count(normalizedName) > 0) errorDescription = "spark name transaction with that name is already in the mempool"; } + // After V2.1, check that total validity (including remaining time from existing registration) doesn't exceed 15 years + if (errorDescription.empty() && nHeight >= ::Params().GetConsensus().nSparkNamesV21StartBlock) { + if (existingExpirationHeight != -1) { + constexpr int nBlockPerYear = 365*24*24; + // Explicit uint32_t check before narrowing to int to prevent integer overflow in the update path + if (sparkNameData.sparkNameValidityBlocks > (uint32_t)(nBlockPerYear * 15)) { + errorDescription = "validity blocks in sparkNameData exceed 15 years limit"; + } else { + int validityBlocks = (int)sparkNameData.sparkNameValidityBlocks; + int remainingBlocks = existingExpirationHeight - nHeight; + if (remainingBlocks > 0) + validityBlocks += remainingBlocks; + if (validityBlocks > nBlockPerYear * 15) + errorDescription = "total validity including remaining time can't exceed 15 years"; + } + } + } + return errorDescription.empty(); } @@ -373,8 +514,20 @@ size_t CSparkNameManager::GetSparkNameTxDataSize(const CSparkNameTxData &sparkNa return sparkNameDataStream.size(); } -void CSparkNameManager::AppendSparkNameTxData(CMutableTransaction &txSparkSpend, CSparkNameTxData &sparkNameData, const spark::SpendKey &spendKey, const spark::IncomingViewKey &incomingViewKey) +void CSparkNameManager::AppendSparkNameTxData(CMutableTransaction &txSparkSpend, CSparkNameTxData &sparkNameData, const spark::SpendKey &spendKey, const spark::IncomingViewKey &incomingViewKey, int nHeight) { + if (sparkNameData.operationType == (uint8_t)CSparkNameTxData::opTransfer && + nHeight >= ::Params().GetConsensus().nSparkNamesV21StartBlock) { + try { + uint64_t expirationHeight = GetSparkNameBlockHeight(sparkNameData.name); + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + hw << expirationHeight; + sparkNameData.inputsHash = hw.GetHash(); + } catch (const std::exception &) { + // Name not found; inputsHash stays zero and CheckSparkNameTx will reject the tx. + } + } + for (uint32_t n=0; ; n++) { sparkNameData.addressOwnershipProof.clear(); sparkNameData.hashFailsafe = n; diff --git a/src/sparkname.h b/src/sparkname.h index f02210ae2c..741b2eac24 100644 --- a/src/sparkname.h +++ b/src/sparkname.h @@ -176,7 +176,11 @@ class CSparkNameManager size_t GetSparkNameTxDataSize(const CSparkNameTxData &sparkNameData); // fill missing CSparkNameTxData fields and append spark name tx data to the transaction - void AppendSparkNameTxData(CMutableTransaction &txSparkSpend, CSparkNameTxData &sparkNameData, const spark::SpendKey &spendKey, const spark::IncomingViewKey &incomingViewKey); + void AppendSparkNameTxData(CMutableTransaction &txSparkSpend, CSparkNameTxData &sparkNameData, const spark::SpendKey &spendKey, const spark::IncomingViewKey &incomingViewKey, int nHeight); + + // Build a fee output scriptPubKey tagged with spark name and address (for v2.1+). + // Falls back to a plain P2PKH script if the current height is before nSparkNamesV21StartBlock. + static CScript GetSparkNameFeeScript(const std::string &feeAddress, const std::string &sparkName, const std::string &sparkAddress); // add and remove spark name bool AddSparkName(const std::string &name, const std::string &address, uint32_t validityBlocks, const std::string &additionalInfo); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index dad6ea0cca..62b481b552 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -47,9 +47,6 @@ add_executable(test_firo ${CMAKE_CURRENT_SOURCE_DIR}/hash_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/key_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dbwrapper_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantus_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantus_mintspend_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantus_state_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/limitedmap_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/main_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mbstring_tests.cpp @@ -98,20 +95,7 @@ add_executable(test_firo ${CMAKE_CURRENT_SOURCE_DIR}/progpow_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/bls_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sparkname_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../hdmint/test/lelantus_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/challenge_generator_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/coin_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/inner_product_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/joinsplit_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/lelantus_primitives_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/lelantus_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/lelantus_test_fixture.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/lelantus_test_fixture.h - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/range_proof_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/schnorr_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../libspark/test/ownership_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/serialize_test.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../liblelantus/test/sigma_extended_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../libspark/test/transcript_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../libspark/test/schnorr_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../libspark/test/chaum_test.cpp diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 787f69bbb9..b91d54d56e 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -908,7 +908,7 @@ ["1", "IF 0xdd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xde ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xdf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], -["1", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], +["1", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "EVAL_FALSE", "OP_SPARKNAMEID is a valid NOP"], ["1", "IF 0xe2 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe3 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe4 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index cd23b6736d..c9117d6a4d 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -16,7 +16,6 @@ #include "consensus/consensus.h" #include "consensus/validation.h" #include "key.h" -#include "liblelantus/openssl_context.h" #include "validation.h" #include "miner.h" #include "pubkey.h" @@ -36,7 +35,6 @@ #include #include #include -#include "lelantus.h" #include "../libspark/coin.h" @@ -203,106 +201,6 @@ CBlock MtpMalformedTestingSetup::CreateAndProcessBlock( return block; } -LelantusTestingSetup::LelantusTestingSetup() : - params(lelantus::Params::get_default()) { - CPubKey key; - { - LOCK(pwalletMain->cs_wallet); - key = pwalletMain->GenerateNewKey(); - } - - script = GetScriptForDestination(key.GetID()); -} - -CBlockIndex* LelantusTestingSetup::GenerateBlock(std::vector const &txns, CScript *script) { - auto last = chainActive.Tip(); - - CreateAndProcessBlock(txns, script ? *script : this->script); - auto block = chainActive.Tip(); - - if (block != last) { - pwalletMain->ScanForWalletTransactions(block, true); - } - - return block != last ? block : nullptr; -} - -void LelantusTestingSetup::GenerateBlocks(size_t blocks, CScript *script) { - while (blocks--) { - GenerateBlock({}, script); - } -} - -std::vector LelantusTestingSetup::GenerateMints( - std::vector const &amounts) { - - auto const &p = lelantus::Params::get_default(); - - std::vector coins; - for (auto a : amounts) { - std::vector k(32); - GetRandBytes(k.data(), k.size()); - - secp256k1_pubkey pubkey; - - if (!secp256k1_ec_pubkey_create(OpenSSLContext::get_context(), &pubkey, k.data())) { - throw std::runtime_error("Fail to create public key"); - } - - auto serial = lelantus::PrivateCoin::serialNumberFromSerializedPublicKey( - OpenSSLContext::get_context(), &pubkey); - - Scalar randomness; - randomness.randomize(); - - coins.emplace_back(p, serial, a, randomness, k, 0); - } - - return coins; -} - -std::vector LelantusTestingSetup::GenerateMints( - std::vector const &amounts, - std::vector &txs) { - - std::vector coins; - return GenerateMints(amounts, txs, coins); -} - -std::vector LelantusTestingSetup::GenerateMints( - std::vector const &amounts, - std::vector &txs, - std::vector &coins) { - - std::vector hdMints; - CWalletDB walletdb(pwalletMain->strWalletFile); - for (auto a : amounts) { - std::vector> wtxAndFee; - std::vector mints; - auto result = pwalletMain->MintAndStoreLelantus(a, wtxAndFee, mints); - - if (result != "") { - throw std::runtime_error(_("Fail to generate mints, ") + result); - } - - for(auto itr : wtxAndFee) - txs.emplace_back(itr.first); - - hdMints.insert(hdMints.end(), mints.begin(), mints.end()); - } - - return hdMints; -} - -CPubKey LelantusTestingSetup::GenerateAddress() { - LOCK(pwalletMain->cs_wallet); - return pwalletMain->GenerateNewKey(); -} - -LelantusTestingSetup::~LelantusTestingSetup() { - lelantus::CLelantusState::GetState()->Reset(); -} - // SparkTestingSetup SparkTestingSetup::SparkTestingSetup() : params(spark::Params::get_default()) { CPubKey key; diff --git a/src/test/fixtures.h b/src/test/fixtures.h index 7b21ab2a27..5f890a27ea 100644 --- a/src/test/fixtures.h +++ b/src/test/fixtures.h @@ -1,9 +1,7 @@ -#include "hdmint/hdmint.h" #include "primitives/transaction.h" #include "test/test_bitcoin.h" #include "test/testutil.h" #include "consensus/params.h" -#include "liblelantus/coin.h" #include @@ -72,35 +70,6 @@ struct MtpMalformedTestingSetup : public ZerocoinTestingSetupBase { const CScript&, bool); }; -struct LelantusTestingSetup : public TestChain100Setup { -public: - LelantusTestingSetup(); - -public: - CBlockIndex* GenerateBlock(std::vector const &txns = {}, CScript *script = nullptr); - void GenerateBlocks(size_t blocks, CScript *script = nullptr); - - std::vector GenerateMints( - std::vector const &amounts); - - std::vector GenerateMints( - std::vector const &amounts, - std::vector &txs); - - std::vector GenerateMints( - std::vector const &amounts, - std::vector &txs, - std::vector &coins); - - CPubKey GenerateAddress(); - - ~LelantusTestingSetup(); - -public: - lelantus::Params const *params; - CScript script; -}; - struct SparkTestingSetup : public TestChain100Setup { public: diff --git a/src/test/lelantus_mintspend_test.cpp b/src/test/lelantus_mintspend_test.cpp deleted file mode 100644 index 7cca76641a..0000000000 --- a/src/test/lelantus_mintspend_test.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "util.h" - -#include -#include - -#include "chainparams.h" -#include "key.h" -#include "validation.h" -#include "pubkey.h" -#include "txdb.h" -#include "txmempool.h" -#include "lelantus.h" - -#include "test/fixtures.h" -#include "test/testutil.h" - -#include "wallet/db.h" -#include "wallet/wallet.h" -#include "wallet/walletexcept.h" - -#include -#include -#include - -BOOST_FIXTURE_TEST_SUITE(lelantus_mintspend, LelantusTestingSetup) - -BOOST_AUTO_TEST_CASE(lelantus_mintspend_test) -{ - GenerateBlocks(110); - - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - - // Make sure that transactions get to mempool - pwalletMain->SetBroadcastTransactions(true); - - std::vector mintTxs; - auto hdMints = GenerateMints({50 * COIN, 60 * COIN}, mintTxs); - - // Verify Mint gets in the mempool - BOOST_CHECK_MESSAGE(mempool.size() == hdMints.size(), "Mints were not added to mempool"); - - int previousHeight = chainActive.Height(); - auto blockIdx1 = GenerateBlock(mintTxs); - BOOST_CHECK(blockIdx1); - - BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mints were not removed from mempool"); - previousHeight = chainActive.Height(); - - // Generate address - CPubKey newKey; - BOOST_CHECK_MESSAGE(pwalletMain->GetKeyFromPool(newKey), "Fail to get new address"); - - const CBitcoinAddress randomAddr(newKey.GetID()); - std::vector recipients = { - {GetScriptForDestination(randomAddr.Get()), 30 * COIN, true}, - }; - - GenerateBlock({}); - - BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool must be empty"); - - CWalletTx wtx; - { - BOOST_CHECK_NO_THROW(pwalletMain->JoinSplitLelantus(recipients, {}, wtx)); //this must pass - } - BOOST_CHECK_MESSAGE(mempool.size() == 1, "JoinSplit is not added into mempool"); - - previousHeight = chainActive.Height(); - GenerateBlock({CMutableTransaction(*wtx.tx)}); - BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "JoinSplit is not removed from mempool"); - GenerateBlocks(6); - std::vector spendCoins; //spends - CWalletTx result; - { - std::vector mintCoins; // new mints - CAmount fee = 0; - result = pwalletMain->CreateLelantusJoinSplitTransaction(recipients, fee, {}, spendCoins, mintCoins); - pwalletMain->CommitLelantusTransaction(result, spendCoins, mintCoins); - } - BOOST_CHECK_MESSAGE(mempool.size() == 1, "Joinsplit was not added to mempool"); - - //Set mints unused, and try to spend again - for(auto mint : spendCoins) - pwalletMain->zwallet->GetTracker().SetLelantusPubcoinNotUsed(primitives::GetPubCoinValueHash(mint.value)); - - wtx.Init(NULL); - //try double spend - BOOST_CHECK_NO_THROW(pwalletMain->JoinSplitLelantus(recipients, {}, wtx)); - //Verify spend got into mempool - BOOST_CHECK_MESSAGE(mempool.size() == 1, "Double spend was added into mempool, but was not supposed"); - - previousHeight = chainActive.Height(); - GenerateBlock({CMutableTransaction(*result.tx)}); - BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool not cleared"); - GenerateBlocks(2); - for(auto mint : spendCoins) - pwalletMain->zwallet->GetTracker().SetLelantusPubcoinUsed(primitives::GetPubCoinValueHash(mint.value), uint256()); - - spendCoins.clear(); //spends - result.Init(NULL); - { - std::vector mintCoins; // new mints - CAmount fee = 0; - result = pwalletMain->CreateLelantusJoinSplitTransaction(recipients, fee, {}, spendCoins, mintCoins); - pwalletMain->CommitLelantusTransaction(result, spendCoins, mintCoins); - } - - //Verify spend got into mempool - BOOST_CHECK_MESSAGE(mempool.size() == 1, "Spend was not added to mempool"); - - previousHeight = chainActive.Height(); - GenerateBlock({CMutableTransaction(*result.tx)}); - BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool not cleared"); - GenerateBlocks(2); - - //Set mints unused, and try to spend again - for(auto mint : spendCoins) - pwalletMain->zwallet->GetTracker().SetLelantusPubcoinNotUsed(primitives::GetPubCoinValueHash(mint.value)); - //try double spend - wtx.Init(NULL); - - BOOST_CHECK_NO_THROW(pwalletMain->JoinSplitLelantus(recipients, {}, wtx)); - BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool not empty although mempool should reject double spend"); - - //Temporary disable usedCoinSerials check to force double spend in mempool - auto tempSerials = lelantusState->containers.usedCoinSerials; - lelantusState->containers.usedCoinSerials.clear(); - - { - //Set mints unused, and try to spend again - for(auto mint : spendCoins) - pwalletMain->zwallet->GetTracker().SetLelantusPubcoinNotUsed(primitives::GetPubCoinValueHash(mint.value)); - wtx.Init(NULL); - BOOST_CHECK_NO_THROW(pwalletMain->JoinSplitLelantus(recipients, {}, wtx)); - BOOST_CHECK_MESSAGE(mempool.size() == 1, "Spend was not added to mempool"); - } - - lelantusState->containers.usedCoinSerials = tempSerials; - - BOOST_CHECK_EXCEPTION(CreateBlock({CMutableTransaction(*wtx.tx)}, script), std::runtime_error, no_check); - BOOST_CHECK_MESSAGE(mempool.size() == 1, "Mempool not set"); - tempSerials = lelantusState->containers.usedCoinSerials; - lelantusState->containers.usedCoinSerials.clear(); - CBlock b = CreateBlock({CMutableTransaction(*wtx.tx)}, script); - - lelantusState->containers.usedCoinSerials = tempSerials; - - mempool.clear(); - previousHeight = chainActive.Height(); - - const CChainParams& chainparams = Params(); - BOOST_CHECK_MESSAGE(ProcessNewBlock(chainparams, std::make_shared(b), true, NULL), "ProcessBlock failed"); - //This test confirms that a block containing a double spend is rejected and not added in the chain - BOOST_CHECK_MESSAGE(previousHeight == chainActive.Height(), "Double spend - Block added to chain even though same spend in previous block"); - - mempool.clear(); - lelantusState->Reset(); -} -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/test/lelantus_state_tests.cpp b/src/test/lelantus_state_tests.cpp deleted file mode 100644 index cea54173e2..0000000000 --- a/src/test/lelantus_state_tests.cpp +++ /dev/null @@ -1,591 +0,0 @@ -#include "../lelantus.h" -#include "../validation.h" - -#include "fixtures.h" -#include "test_bitcoin.h" - -#include - -namespace std { - -template -basic_ostream& operator<<(basic_ostream& os, const pair& p) -{ - return os << '(' << p.first << ", " << p.second << ')'; -} - -} - -namespace lelantus { - -class LelantusStateTests : public LelantusTestingSetup { -public: - LelantusStateTests() : LelantusTestingSetup(), - lelantusState(CLelantusState::GetState()) { - } - - ~LelantusStateTests() { - lelantusState->Reset(); - } - -public: - CBlock GetCBlock(CBlockIndex const *blockIdx) { - CBlock block; - if (!ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())) { - throw std::invalid_argument("No block index data"); - } - - return block; - } - - void PopulateLelantusTxInfo( - CBlock &block, - std::vector>> const &mints, - std::vector> const &serials) { - block.lelantusTxInfo = std::make_shared(); - block.lelantusTxInfo->mints.insert(block.lelantusTxInfo->mints.end(), mints.begin(), mints.end()); - - for (auto const &s : serials) { - block.lelantusTxInfo->spentSerials.emplace(s); - } - } - - std::vector GenerateMintsInBlocks(CLelantusState &state, std::initializer_list const &mintsInBlocks) { - - std::vector indexes; - for (auto mintsInBlock : mintsInBlocks) { - - std::vector amounts(mintsInBlock, 1); - auto mints = GenerateMints(amounts); - - std::vector>> coins; - for (auto const &m : mints) { - coins.emplace_back(std::make_pair(m.getPublicCoin(), std::make_pair(m.getV(), uint256()))); - } - - auto index = GenerateBlock({}); - auto block = GetCBlock(index); - - PopulateLelantusTxInfo(block, coins, {}); - - state.AddMintsToStateAndBlockIndex(index, &block); - indexes.push_back(index); - } - - return indexes; - } - - CBlockIndex* GenerateSpendGroups( - CLelantusState &state, std::initializer_list> const &serials) { - - std::vector indexes; - auto index = GenerateBlock({}); - - for (auto const& s : serials) { - for (size_t i = 0; i != s.second; i++) { - Scalar serial; - serial.randomize(); - - index->lelantusSpentSerials[serial] = s.first; - } - } - - state.AddBlock(index); - return index; - } - - void RemoveBlocks(CLelantusState &state, std::vector indexes) { - for (auto it = indexes.rbegin(); it != indexes.rend(); it++) { - state.RemoveBlock(*it); - } - } - -public: - CLelantusState *lelantusState; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_state_tests, LelantusStateTests) - -BOOST_AUTO_TEST_CASE(add_mints_to_state) -{ - // Try to add some mints to state. - - GenerateBlocks(110); - - std::vector txs; - auto mints = GenerateMints({1 * COIN, 2 * COIN, 1 * CENT}, txs); - - auto blockIdx1 = GenerateBlock({txs[0]}); - auto block1 = GetCBlock(blockIdx1); - PopulateLelantusTxInfo(block1, {{mints[0].GetPubcoinValue(), std::make_pair(mints[0].GetAmount(), uint256())}}, {}); - - auto blockIdx2 = GenerateBlock({txs[1]}); - auto block2 = GetCBlock(blockIdx2); - PopulateLelantusTxInfo(block2, {{mints[1].GetPubcoinValue(), std::make_pair(mints[1].GetAmount(), uint256())}}, {}); - - // verify heigh and id was assigned. - BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height() - 1, 1), lelantusState->GetMintedCoinHeightAndId(mints[0].GetPubcoinValue())); - BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height(), 1), lelantusState->GetMintedCoinHeightAndId(mints[1].GetPubcoinValue())); - BOOST_CHECK_EQUAL(std::make_pair(-1, -1), lelantusState->GetMintedCoinHeightAndId(mints[2].GetPubcoinValue())); - - // test has coin - BOOST_CHECK(lelantusState->HasCoin(mints[0].GetPubcoinValue())); - BOOST_CHECK(lelantusState->HasCoin(mints[1].GetPubcoinValue())); - BOOST_CHECK(!lelantusState->HasCoin(mints[2].GetPubcoinValue())); - - // test has coin hash - GroupElement received; - BOOST_CHECK(lelantusState->HasCoinHash(received, mints[0].GetPubCoinHash())); - BOOST_CHECK(mints[0].GetPubcoinValue() == received); - - BOOST_CHECK(!lelantusState->HasCoinHash(received, mints[2].GetPubCoinHash())); - - BOOST_CHECK_EQUAL(2, lelantusState->GetTotalCoins()); - - // check group info - CLelantusState::LelantusCoinGroupInfo group, fakeGroup; - BOOST_CHECK(lelantusState->GetCoinGroupInfo(1, group)); - BOOST_CHECK(!lelantusState->GetCoinGroupInfo(0, fakeGroup)); - BOOST_CHECK(!lelantusState->GetCoinGroupInfo(2, fakeGroup)); - - BOOST_CHECK(blockIdx1 == group.firstBlock); - BOOST_CHECK(blockIdx2 == group.lastBlock); - BOOST_CHECK_EQUAL(2, group.nCoins); - - BOOST_CHECK_EQUAL(1, lelantusState->GetLatestCoinID()); -} - -BOOST_AUTO_TEST_CASE(serial_adding) -{ - GenerateBlocks(110); - - std::vector txs; - auto mints = GenerateMints({1 * COIN, 2 * COIN, 1 * CENT}, txs); - - GenerateBlock(txs); - - auto blockIdx = chainActive.Tip(); - auto block = GetCBlock(blockIdx); - PopulateLelantusTxInfo(block, {{mints[0].GetPubcoinValue(), std::make_pair(mints[0].GetAmount(), uint256())}}, {}); - - lelantusState->AddMintsToStateAndBlockIndex(blockIdx, &block); - - Scalar serial1(1), serial2(2); - auto serialHash1 = primitives::GetSerialHash(serial1); - auto serialHash2 = primitives::GetSerialHash(serial2); - - lelantusState->AddSpend(serial1, 1); - - Scalar receivedSerial; - BOOST_CHECK(lelantusState->IsUsedCoinSerial(serial1)); - BOOST_CHECK(lelantusState->IsUsedCoinSerialHash(receivedSerial, serialHash1)); - BOOST_CHECK(serial1 == receivedSerial); - - BOOST_CHECK(!lelantusState->IsUsedCoinSerial(serial2)); - BOOST_CHECK(!lelantusState->IsUsedCoinSerialHash(receivedSerial, serialHash2)); -} - -BOOST_AUTO_TEST_CASE(mempool) -{ - GenerateBlocks(110); - - std::vector txs; - auto mint = GenerateMints({1 * COIN}, txs)[0]; - - GenerateBlock(txs); - - auto blockIdx = chainActive.Tip(); - auto block = GetCBlock(blockIdx); - PopulateLelantusTxInfo(block, {{mint.GetPubcoinValue(), std::make_pair(mint.GetAmount(), uint256())}}, {}); - - lelantusState->AddMintsToStateAndBlockIndex(blockIdx, &block); - - Scalar spendSerial(1); - lelantusState->AddSpend(spendSerial, 1); - - // test mint mempool - // - can not add on-chain coin - BOOST_CHECK(!lelantusState->CanAddMintToMempool(mint.GetPubcoinValue())); - - // - can not add duplicated coin - GroupElement randMint; - randMint.randomize(); - - BOOST_CHECK(lelantusState->CanAddMintToMempool(randMint)); - lelantusState->AddMintsToMempool({randMint}); - BOOST_CHECK(!lelantusState->CanAddMintToMempool(randMint)); - - // - remove from mempool then can add again - lelantusState->RemoveMintFromMempool(randMint); - BOOST_CHECK(lelantusState->CanAddMintToMempool(randMint)); - - // test spend mempool - // - can not add on-chain spend - BOOST_CHECK(!lelantusState->CanAddSpendToMempool(spendSerial)); - - // - can not add duplicated serial - Scalar anotherSerial(2); - auto txid = ArithToUint256(1); - - BOOST_CHECK(lelantusState->CanAddSpendToMempool(anotherSerial)); - lelantusState->AddSpendToMempool({anotherSerial}, txid); - BOOST_CHECK(!lelantusState->CanAddSpendToMempool(anotherSerial)); - - BOOST_CHECK(txid == - lelantusState->GetMempoolConflictingTxHash(anotherSerial)); - - Scalar fakeSerial(3); - BOOST_CHECK(uint256() == - lelantusState->GetMempoolConflictingTxHash(fakeSerial)); - - // - remove spend then can add again - lelantusState->RemoveSpendFromMempool({anotherSerial}); - BOOST_CHECK(lelantusState->CanAddSpendToMempool(anotherSerial)); - lelantusState->AddSpendToMempool({anotherSerial}, txid); - BOOST_CHECK(!lelantusState->CanAddSpendToMempool(anotherSerial)); -} - -BOOST_AUTO_TEST_CASE(add_remove_block) -{ - // No coins and serials - auto index1 = GenerateBlock({}); - auto block1 = GetCBlock(index1); - PopulateLelantusTxInfo(block1, {}, {}); - - lelantusState->AddBlock(index1); - - BOOST_CHECK_EQUAL(0, lelantusState->GetMints().size()); - BOOST_CHECK_EQUAL(0, lelantusState->GetSpends().size()); - - // some mints - GroupElement mint1, mint2; - mint1.randomize(); - mint2.randomize(); - - auto index2 = GenerateBlock({}); - auto block2 = GetCBlock(index2); - PopulateLelantusTxInfo(block2, {{mint1, {1, uint256()}}, {mint2, {1, uint256()}}}, {}); - - lelantusState->AddMintsToStateAndBlockIndex(index2, &block2); - lelantusState->AddBlock(index2); - - BOOST_CHECK_EQUAL(2, lelantusState->GetMints().size()); - BOOST_CHECK_EQUAL(0, lelantusState->GetSpends().size()); - - // some serials - Scalar serial1, serial2; - serial1.randomize(); - serial2.randomize(); - - auto index3 = GenerateBlock({}); - auto block3 = GetCBlock(index3); - PopulateLelantusTxInfo(block3, {}, {{serial1, 1}, {serial2, 1}}); - index3->lelantusSpentSerials = block3.lelantusTxInfo->spentSerials; - - lelantusState->AddBlock(index3); - - BOOST_CHECK_EQUAL(2, lelantusState->GetMints().size()); - BOOST_CHECK_EQUAL(2, lelantusState->GetSpends().size()); - - // both mint and serial - GroupElement mint3; - mint3.randomize(); - - Scalar serial3; - serial3.randomize(); - - auto index4 = GenerateBlock({}); - auto block4 = GetCBlock(index4); - PopulateLelantusTxInfo(block4, {{mint3, {1, uint256()}}}, {{serial3, 1}}); - lelantusState->AddMintsToStateAndBlockIndex(index4, &block4); - index4->lelantusSpentSerials = block4.lelantusTxInfo->spentSerials; - - lelantusState->AddBlock(index4); - - BOOST_CHECK_EQUAL(3, lelantusState->GetMints().size()); - BOOST_CHECK_EQUAL(3, lelantusState->GetSpends().size()); - - // remove last block - lelantusState->RemoveBlock(index4); - - BOOST_CHECK_EQUAL(2, lelantusState->GetMints().size()); - BOOST_CHECK_EQUAL(2, lelantusState->GetSpends().size()); - - // verify mints and spends on blocks - BOOST_CHECK(lelantusState->HasCoin(mint1)); - BOOST_CHECK(lelantusState->HasCoin(mint2)); - BOOST_CHECK(!lelantusState->HasCoin(mint3)); - - BOOST_CHECK(lelantusState->IsUsedCoinSerial(serial1)); - BOOST_CHECK(lelantusState->IsUsedCoinSerial(serial2)); - BOOST_CHECK(!lelantusState->IsUsedCoinSerial(serial3)); - - lelantusState->Reset(); -} - -BOOST_AUTO_TEST_CASE(get_coin_group) -{ - GenerateBlocks(120); - - std::vector amounts(12, COIN); - std::vector txs; - - auto mints = GenerateMints(amounts, txs); - - std::vector coins; - std::vector indexes; - std::vector blocks; - - for (size_t i = 0; i != mints.size(); i += 2) { - auto index = GenerateBlock({txs[i], txs[i + 1]}); - auto block = GetCBlock(index); - coins.push_back(mints[i + 1].GetPubcoinValue()); - coins.push_back(mints[i].GetPubcoinValue()); - - PopulateLelantusTxInfo( - block, - { - {mints[i].GetPubcoinValue(), {1, uint256()}}, - {mints[i + 1].GetPubcoinValue(), {1, uint256()}} - }, {}); - - indexes.push_back(index); - blocks.push_back(block); - - GenerateBlock({}); - } - - size_t maxSize = 6; - size_t startCoin = 2; - auto lelantusState = new CLelantusState(maxSize, startCoin); - - auto addMintsToState = [&](CBlockIndex *index, CBlock const &block) { - index->lelantusMintedPubCoins.clear(); - lelantusState->AddMintsToStateAndBlockIndex(index, &block); - }; - - auto verifyMints = [&](size_t i, size_t j, std::vector const &coinSet) { - std::vector expected(coins.begin() + i, coins.begin() + j); - std::reverse(expected.begin(), expected.end()); - - BOOST_CHECK(expected == coinSet); - }; - - auto verifyGroup = [&](int expectedId, size_t expectedCoins, CBlockIndex *expectedFirst, CBlockIndex *expectedLast, int testId = 0)->void { - if (!testId) { - testId = lelantusState->GetLatestCoinID(); - } - - CLelantusState::LelantusCoinGroupInfo group; - - BOOST_CHECK(lelantusState->GetCoinGroupInfo(testId, group)); - if (expectedId > 0) { // verify last Id - BOOST_CHECK_EQUAL(expectedId, testId); - } - - BOOST_CHECK_EQUAL(expectedCoins, group.nCoins); - BOOST_CHECK_EQUAL(expectedFirst, group.firstBlock); - BOOST_CHECK_EQUAL(expectedLast, group.lastBlock); - }; - - // 6 coins, 1(6), 2(0) - addMintsToState(indexes[0], blocks[0]); - addMintsToState(indexes[1], blocks[1]); - addMintsToState(indexes[2], blocks[2]); - - verifyGroup(1, 6, indexes[0], indexes[2]); - uint256 blockHashOut1; - std::vector coinOut1; - std::vector setHash; - BOOST_CHECK_EQUAL(6, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[2]->nHeight, - 1, - blockHashOut1, - coinOut1, - setHash)); - - verifyMints(0, 6, coinOut1); - BOOST_CHECK(indexes[2]->GetBlockHash() == blockHashOut1); - - // 8 coins, 1(6), 2(4) - addMintsToState(indexes[3], blocks[3]); - verifyGroup(2, 4, indexes[2], indexes[3]); - verifyGroup(1, 6, indexes[0], indexes[2], 1); - - uint256 blockHashOut2; - std::vector coinOut2; - BOOST_CHECK_EQUAL(4, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[3]->nHeight + 1, // specify limit with no mints block - 2, - blockHashOut2, - coinOut2, - setHash)); - - verifyMints(4, 8, coinOut2); - BOOST_CHECK(indexes[3]->GetBlockHash() == blockHashOut2); - - // 10 coins, 1(6), 2(6) - addMintsToState(indexes[4], blocks[4]); - - verifyGroup(2, 6, indexes[2], indexes[4]); - verifyGroup(1, 6, indexes[0], indexes[2], 1); - - uint256 blockHashOut3; - std::vector coinOut3; - BOOST_CHECK_EQUAL(6, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[4]->nHeight, - 2, - blockHashOut3, - coinOut3, - setHash)); - - verifyMints(4, 10, coinOut3); - BOOST_CHECK(indexes[4]->GetBlockHash() == blockHashOut3); - - // 12 coins, 1(6), 2(6), 3(4) - addMintsToState(indexes[5], blocks[5]); - - verifyGroup(3, 4, indexes[4], indexes[5]); - verifyGroup(2, 6, indexes[2], indexes[4], 2); - verifyGroup(1, 6, indexes[0], indexes[2], 1); - - uint256 blockHashOut4; - std::vector coinOut4; - BOOST_CHECK_EQUAL(4, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[5]->nHeight, - 3, - blockHashOut4, - coinOut4, - setHash)); - - verifyMints(8, 12, coinOut4); - - // Get first group - uint256 blockHashOut5; - std::vector coinOut5; - BOOST_CHECK_EQUAL(6, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[5]->nHeight, - 1, - blockHashOut5, - coinOut5, - setHash)); - - verifyMints(0, 6, coinOut5); - BOOST_CHECK(indexes[2]->GetBlockHash() == blockHashOut5); - - // Get first group with low max height - uint256 blockHashOut6; - std::vector coinOut6; - BOOST_CHECK_EQUAL(2, lelantusState->GetCoinSetForSpend( - &chainActive, - indexes[0]->nHeight, - 1, - blockHashOut6, - coinOut6, - setHash)); - - verifyMints(0, 2, coinOut6); - BOOST_CHECK(indexes[0]->GetBlockHash() == blockHashOut6); - - lelantusState->RemoveBlock(indexes[5]); - verifyGroup(2, 6, indexes[2], indexes[4]); - verifyGroup(1, 6, indexes[0], indexes[2], 1); - - lelantusState->Reset(); -} - -// Surge condition testing -#define Undetected BOOST_CHECK(!state.IsSurgeConditionDetected()) -#define Detected BOOST_CHECK(state.IsSurgeConditionDetected()) - -BOOST_AUTO_TEST_CASE(add_more_spend_and_trigger_surge_condition) -{ - size_t maxGroupSize = 6; - size_t startGroupSize = 2; - CLelantusState state(maxGroupSize, startGroupSize); - state.Reset(); - - // 6(1), 4(2), 4(3) - auto mintIndexes = GenerateMintsInBlocks(state, {2, 2, 2, 2, 2, 2, 2}); - Undetected; - - // spend 6 coins from 1, 4 coins from 2 and add one more should fail - auto index1 = GenerateSpendGroups(state, {{1, 6}, {2, 4}}); - Undetected; - - auto index2 = GenerateSpendGroups(state, {{1, 1}}); - Detected; - - RemoveBlocks(state, {index1, index2}); - Undetected; - - // spend 5 coins from 1, 5 coins from 2, 4 coins from 3, and add one more should fail - index1 = GenerateSpendGroups(state, {{1, 5}, {2, 5}, {3, 4}}); - Undetected; - - index2 = GenerateSpendGroups(state, {{1, 1}}); - Detected; - - RemoveBlocks(state, {index2}); - Undetected; - - index2 = GenerateSpendGroups(state, {{2, 1}}); - Detected; - - RemoveBlocks(state, {index2}); - Undetected; - - index2 = GenerateSpendGroups(state, {{3, 1}}); - Detected; - - RemoveBlocks(state, {index1}); - Undetected; - -} - -BOOST_AUTO_TEST_CASE(surge_condition_detected_and_no_serials_in_early_groups) -{ - size_t maxGroupSize = 6; - size_t startGroupSize = 2; - CLelantusState state(maxGroupSize, startGroupSize); - state.Reset(); - - // 0 + 6(1), 2 + 4(2), 2 + 4(3), 2 + 3(4), 3 + 3(5), 3 + 3(6), 3 + 3(7) - GenerateMintsInBlocks(state, {2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3}); - Undetected; - - auto index1 = GenerateSpendGroups(state, {{4, 5}}); - Undetected; - - auto index2 = GenerateSpendGroups(state, {{4, 1}}); - Detected; - - RemoveBlocks(state, {index1, index2}); - - index1 = GenerateSpendGroups(state, {{4, 5}, {5, 3}}); - Undetected; - - index1 = GenerateSpendGroups(state, {{4, 1}}); - Detected; - - RemoveBlocks(state, {index1}); - Undetected; - - index1 = GenerateSpendGroups(state, {{5, 1}}); - Detected; - - RemoveBlocks(state, {index1}); - Undetected; -} - -#undef Detected -#undef Undetected - -BOOST_AUTO_TEST_SUITE_END() - -} \ No newline at end of file diff --git a/src/test/lelantus_tests.cpp b/src/test/lelantus_tests.cpp deleted file mode 100644 index 6b4c6bc66a..0000000000 --- a/src/test/lelantus_tests.cpp +++ /dev/null @@ -1,962 +0,0 @@ -#include "../chainparams.h" -#include "../lelantus.h" -#include "../script/standard.h" -#include "../validation.h" -#include "../wallet/coincontrol.h" -#include "../wallet/wallet.h" -#include "../net.h" - -#include "test_bitcoin.h" -#include "fixtures.h" -#include -#include - -static bool CommitToMempool(const CTransaction &tx) -{ - CWallet *wallet = pwalletMain; - CWalletTx walletTx(wallet, MakeTransactionRef(tx)); - CReserveKey reserveKey(wallet); - CValidationState state; - wallet->CommitTransaction(walletTx, reserveKey, g_connman.get(), state); - return mempool.exists(tx.GetHash()); -} - -namespace lelantus { - -struct JoinSplitScriptGenerator { - Params const *params; - std::vector> coins; - std::map> anons; - CAmount vout; - std::vector coinsOut; - CAmount fee; - std::map groupBlockHashes; - uint256 txHash; - - std::pair Get() { - // auto p = params ? params : Params::get_default(); - auto p = Params::get_default(); - - CScript script; - - JoinSplit joinSplit(p, coins, anons, {}, vout, coinsOut, fee, groupBlockHashes, txHash, LELANTUS_TX_VERSION_4); - - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << joinSplit; - - script << OP_LELANTUSJOINSPLIT; - script.insert(script.end(), ss.begin(), ss.end()); - - return {script, joinSplit}; - } -}; - -class LelantusTests : public LelantusTestingSetup { -public: - LelantusTests() : - LelantusTestingSetup(), - lelantusState(CLelantusState::GetState()), - consensus(::Params().GetConsensus()) { - } - - ~LelantusTests() { - lelantusState->Reset(); - } - -public: - - std::vector ExtractCoins(std::vector const &coins) { - std::vector pubs; - pubs.reserve(coins.size()); - - for (auto &c : coins) { - pubs.push_back(c.getPublicCoin()); - } - - return pubs; - } - - void ExtractJoinSplit(CMutableTransaction const &tx, - std::vector &newMints, - std::vector &serials) { - for (auto const &in : tx.vin) { - if (in.IsLelantusJoinSplit()) { - auto js = ParseLelantusJoinSplit(tx); - auto const &s = js->getCoinSerialNumbers(); - serials.insert(serials.end(), s.begin(), s.end()); - } - } - - for (auto const &out : tx.vout) { - if (out.scriptPubKey.IsLelantusJMint()) { - GroupElement coin; - ParseLelantusMintScript(out.scriptPubKey, coin); - - newMints.push_back(coin); - } - } - } - - CBlock GetCBlock(CBlockIndex const *blockIdx) { - CBlock block; - if (!ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())) { - throw std::invalid_argument("No block index data"); - } - - return block; - } - - void PopulateLelantusTxInfo( - CBlock &block, - std::vector>> const &mints, - std::vector> const &serials) { - block.lelantusTxInfo = std::make_shared(); - block.lelantusTxInfo->mints.insert(block.lelantusTxInfo->mints.end(), mints.begin(), mints.end()); - - for (auto const &s : serials) { - block.lelantusTxInfo->spentSerials.emplace(s); - } - - block.lelantusTxInfo->Complete(); - } - - CTransaction GenerateJoinSplit( - std::vector const &outs, - std::vector const &mints, - CCoinControl const *coinControl = nullptr) { - - std::vector vecs; - for (auto const &out : outs) { - LOCK(pwalletMain->cs_wallet); - auto pub = pwalletMain->GenerateNewKey(); - - vecs.push_back( - { - GetScriptForDestination(pub.GetID()), - out, - false - }); - } - - std::vector spendCoins; - std::vector mintCoins; - - CAmount fee; - auto result = pwalletMain->CreateLelantusJoinSplitTransaction( - vecs, fee, mints, spendCoins, mintCoins, coinControl); - - if (!pwalletMain->CommitLelantusTransaction( - result, spendCoins, mintCoins)) { - throw std::runtime_error("Fail to commit transaction"); - } - - return result; - } - -public: - CLelantusState *lelantusState; - Consensus::Params const &consensus; -}; - -BOOST_FIXTURE_TEST_SUITE(lelantus_tests, LelantusTests) - -BOOST_AUTO_TEST_CASE(schnorr_proof) -{ - auto params = Params::get_default(); - - PrivateCoin coin(params, 1); - - CDataStream serializedSchnorrProof(SER_NETWORK, PROTOCOL_VERSION); - GenerateMintSchnorrProof(coin, serializedSchnorrProof); - - auto commitment = coin.getPublicCoin(); - SchnorrProof proof; - serializedSchnorrProof >> proof; - - BOOST_CHECK(VerifyMintSchnorrProof(1, commitment.getValue(), proof)); -} - -BOOST_AUTO_TEST_CASE(is_lelantus_allowed) -{ - auto start = ::Params().GetConsensus().nLelantusStartBlock; - BOOST_CHECK(!IsLelantusAllowed(0)); - BOOST_CHECK(!IsLelantusAllowed(start - 1)); - BOOST_CHECK(IsLelantusAllowed(start)); - BOOST_CHECK(IsLelantusAllowed(start + 1)); -} - -BOOST_AUTO_TEST_CASE(parse_lelantus_mintscript) -{ - // payload: op_code + pubcoin + schnorrproof - PrivateCoin priv(params, 1); - auto &pub = priv.getPublicCoin(); - - CDataStream proofSerialized(SER_NETWORK, PROTOCOL_VERSION); - - GenerateMintSchnorrProof(priv, proofSerialized); - - CScript script(OP_LELANTUSMINT); - - auto vch = pub.getValue().getvch(); - script.insert(script.end(), vch.begin(), vch.end()); - script.insert(script.end(), proofSerialized.begin(), proofSerialized.end()); - - // verify - secp_primitives::GroupElement parsedCoin; - ParseLelantusMintScript(script, parsedCoin); - - BOOST_CHECK(pub.getValue() == parsedCoin); - - SchnorrProof proof; - uint256 mintTag; - ParseLelantusMintScript(script, parsedCoin, proof, mintTag); - - BOOST_CHECK(pub.getValue() == parsedCoin); - BOOST_CHECK(VerifyMintSchnorrProof(1, parsedCoin, proof)); - - CDataStream parsedProof(SER_NETWORK, PROTOCOL_VERSION); - parsedProof << proof; - - BOOST_CHECK(proofSerialized.vch == parsedProof.vch); - - GroupElement parsedCoin2; - ParseLelantusMintScript(script, parsedCoin2); - - BOOST_CHECK(pub.getValue() == parsedCoin2); - - script.resize(script.size() - 1); - BOOST_CHECK_THROW(ParseLelantusMintScript(script, parsedCoin, proof, mintTag), std::invalid_argument); -} - -BOOST_AUTO_TEST_CASE(parse_lelantus_jmint) -{ - GroupElement val; - val.randomize(); - - CScript script(OP_LELANTUSJMINT); - - auto vch = val.getvch(); - script.insert(script.end(), vch.begin(), vch.end()); - - std::vector encrypted; - encrypted.resize(16); - - std::fill(encrypted.begin(), encrypted.end(), 0xff); - script.insert(script.end(), encrypted.begin(), encrypted.end()); - - // parse and verify - GroupElement outCoin; - std::vector outEnc; - ParseLelantusJMintScript(script, outCoin, outEnc); - - BOOST_CHECK(val == outCoin); - BOOST_CHECK(encrypted == outEnc); - - GroupElement outCoin2; - ParseLelantusMintScript(script, outCoin2); - - BOOST_CHECK(val == outCoin2); - - // parse invalid - script.resize(script.size() - 1); - BOOST_CHECK_THROW(ParseLelantusJMintScript(script, outCoin, outEnc), std::invalid_argument); -} - -BOOST_AUTO_TEST_CASE(get_outpoint) -{ - GenerateBlocks(110); - - // generate mints - std::vector txs; - auto mints = GenerateMints({2, 10}, txs); - auto mint = mints[0]; - auto nonCommitted = mints[1]; - auto tx = txs[0]; - size_t mintIdx = 0; - for (; mintIdx < tx.vout.size(); mintIdx++) { - if (tx.vout[mintIdx].scriptPubKey.IsLelantusMint()) { - break; - } - } - - auto blockIdx = GenerateBlock({txs[0]}); - - CBlock block; - BOOST_CHECK(ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())); - - block.lelantusTxInfo = std::make_shared(); - block.lelantusTxInfo->mints.emplace_back(std::make_pair(mint.GetPubcoinValue(), std::make_pair(mint.GetAmount(), uint256()))); - - lelantusState->AddMintsToStateAndBlockIndex(blockIdx, &block); - lelantusState->AddBlock(blockIdx); - - // verify - COutPoint expectedOut(tx.GetHash(), mintIdx); - - // GetOutPointFromBlock - COutPoint out; - BOOST_CHECK(GetOutPointFromBlock(out, mint.GetPubcoinValue(), block)); - BOOST_CHECK(expectedOut == out); - - BOOST_CHECK(!GetOutPointFromBlock(out, nonCommitted.GetPubcoinValue(), block)); - - // GetOutPoint - // by pubcoin - out = COutPoint(); - BOOST_CHECK(GetOutPoint(out, PublicCoin(mint.GetPubcoinValue()))); - BOOST_CHECK(expectedOut == out); - - BOOST_CHECK(!GetOutPoint(out, PublicCoin(nonCommitted.GetPubcoinValue()))); - - // by pubcoin value - out = COutPoint(); - BOOST_CHECK(GetOutPoint(out, mint.GetPubcoinValue())); - BOOST_CHECK(expectedOut == out); - - BOOST_CHECK(!GetOutPoint(out, nonCommitted.GetPubcoinValue())); - - // by pubcoin hash - out = COutPoint(); - BOOST_CHECK(GetOutPoint(out, mint.GetPubCoinHash())); - BOOST_CHECK(expectedOut == out); - - BOOST_CHECK(!GetOutPoint(out, nonCommitted.GetPubCoinHash())); -} - -BOOST_AUTO_TEST_CASE(build_lelantus_state) -{ - GenerateBlocks(110); - - // generate mints - std::vector txs; - auto mints = GenerateMints({1 * COIN, 2 * COIN, 10 * COIN, 100 * COIN}, txs); - - GenerateBlock({txs[0], txs[1]}); - auto blockIdx1 = chainActive.Tip(); - auto block1 = GetCBlock(blockIdx1); - - GenerateBlock({txs[2], txs[3]}); - auto blockIdx2 = chainActive.Tip(); - auto block2 = GetCBlock(blockIdx2); - - block1.lelantusTxInfo = std::make_shared(); - block2.lelantusTxInfo = std::make_shared(); - - block1.lelantusTxInfo->mints.emplace_back(std::make_pair(mints[0].GetPubcoinValue(), std::make_pair(mints[0].GetAmount(), uint256()))); - block1.lelantusTxInfo->mints.emplace_back(std::make_pair(mints[1].GetPubcoinValue(), std::make_pair(mints[1].GetAmount(), uint256()))); - block2.lelantusTxInfo->mints.emplace_back(std::make_pair(mints[2].GetPubcoinValue(), std::make_pair(mints[2].GetAmount(), uint256()))); - block2.lelantusTxInfo->mints.emplace_back(std::make_pair(mints[3].GetPubcoinValue(), std::make_pair(mints[3].GetAmount(), uint256()))); - lelantusState->Reset(); - lelantusState->AddMintsToStateAndBlockIndex(blockIdx1, &block1); - lelantusState->AddMintsToStateAndBlockIndex(blockIdx2, &block2); - - BOOST_CHECK(BuildLelantusStateFromIndex(&chainActive)); - BOOST_CHECK(lelantusState->HasCoin(mints[0].GetPubcoinValue())); - BOOST_CHECK(lelantusState->HasCoin(mints[1].GetPubcoinValue())); - BOOST_CHECK(lelantusState->HasCoin(mints[2].GetPubcoinValue())); - BOOST_CHECK(lelantusState->HasCoin(mints[3].GetPubcoinValue())); -} - -BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) -{ - // util function - auto reconnect = [](CBlock const &block) { - LOCK(cs_main); - - std::shared_ptr sharedBlock = - std::make_shared(block); - - CValidationState state; - ActivateBestChain(state, ::Params(), sharedBlock); - }; - - GenerateBlocks(110); - - std::vector mintTxs; - auto hdMints = GenerateMints({3 * COIN, 3 * COIN, 3 * COIN}, mintTxs); - - struct { - // expected state. - std::vector coins; - std::vector serials; - - // first group - CBlockIndex *first = nullptr; - CBlockIndex *last = nullptr; - - int lastId = 0; - - // real state - CLelantusState *state; - - void Verify() const { - auto const &spends = state->GetSpends(); - BOOST_CHECK_EQUAL(serials.size(), spends.size()); - for (auto const &s : serials) { - BOOST_CHECK_MESSAGE(spends.count(s), "serial is not found on state"); - } - - auto const &mints = state->GetMints(); - BOOST_CHECK_EQUAL(coins.size(), mints.size()); - for (auto const &c : coins) { - BOOST_CHECK_MESSAGE(mints.count(c), "public is not found on state"); - } - - auto retrievedId = state->GetLatestCoinID(); - - CLelantusState::LelantusCoinGroupInfo group; - state->GetCoinGroupInfo(retrievedId, group); - BOOST_CHECK_EQUAL(lastId, retrievedId); - BOOST_CHECK_EQUAL(first, group.firstBlock); - BOOST_CHECK_EQUAL(last, group.lastBlock); - BOOST_CHECK_EQUAL(coins.size(), group.nCoins); - } - } checker; - checker.state = lelantusState; - - // Cache empty checker - auto emptyChecker = checker; - - // Generate some txs which contain mints - auto blockIdx1 = GenerateBlock({mintTxs[0], mintTxs[1]}); - BOOST_CHECK(blockIdx1); - auto block1 = GetCBlock(blockIdx1); - - checker.coins.push_back(hdMints[0].GetPubcoinValue()); - checker.coins.push_back(hdMints[1].GetPubcoinValue()); - checker.first = blockIdx1; - checker.last = blockIdx1; - checker.lastId = 1; - checker.Verify(); - - // Generate empty blocks should not effect state - GenerateBlocks(10); - checker.Verify(); - - // Add spend tx - - // Create two txs which contains same serial. - CCoinControl coinControl; - - { - auto tx = mintTxs[0]; - auto it = std::find_if(tx.vout.begin(), tx.vout.end(), [](CTxOut const &out) -> bool { - return out.scriptPubKey.IsLelantusMint(); - }); - BOOST_CHECK(it != tx.vout.end()); - - coinControl.Select(COutPoint(tx.GetHash(), std::distance(tx.vout.begin(), it))); - } - - auto jsTx1 = GenerateJoinSplit({1 * COIN}, {}, &coinControl); - - // Update isused status - { - auto mint = hdMints[0]; - auto hash = primitives::GetPubCoinValueHash(mint.GetPubcoinValue()); - - CLelantusMintMeta meta; - BOOST_CHECK(pwalletMain->zwallet->GetTracker() - .GetLelantusMetaFromPubcoin(hash, meta)); - - BOOST_CHECK(meta.isUsed); - - meta.isUsed = false; - BOOST_CHECK(pwalletMain->zwallet->GetTracker().UpdateState(meta)); - - meta = CLelantusMintMeta(); - BOOST_CHECK(pwalletMain->zwallet->GetTracker() - .GetLelantusMetaFromPubcoin(hash, meta)); - BOOST_CHECK(!meta.isUsed); - } - - // Create duplicated serial tx and test this at the bottom - auto dupJsTx1 = GenerateJoinSplit({1 * COIN}, {}, &coinControl); - - std::vector dupNewCoins1; - std::vector dupSerials1; - ExtractJoinSplit(dupJsTx1, dupNewCoins1, dupSerials1); - - std::vector newCoins1; - std::vector serials1; - ExtractJoinSplit(jsTx1, newCoins1, serials1); - BOOST_CHECK_EQUAL(1, newCoins1.size()); - BOOST_CHECK_EQUAL(1, serials1.size()); - BOOST_CHECK(dupSerials1[0] == serials1[0]); - - auto blockIdx2 = GenerateBlock({jsTx1}); - BOOST_CHECK(blockIdx2); - auto block2 = GetCBlock(blockIdx2); - - auto cacheChecker = checker; - checker.coins.push_back(newCoins1.front()); - checker.serials.push_back(serials1.front()); - checker.last = blockIdx2; - - checker.Verify(); - - // state should be rolled back - DisconnectBlocks(1); - BOOST_CHECK_EQUAL(chainActive.Tip()->nHeight, blockIdx2->nHeight - 1); - cacheChecker.Verify(); - - // reconnect - reconnect(block2); - checker.Verify(); - - // add more block contain both mint and serial - auto jsTx2 = GenerateJoinSplit({1 * COIN}, {CENT}); - std::vector newCoins2; - std::vector serials2; - ExtractJoinSplit(jsTx2, newCoins2, serials2); - BOOST_CHECK_EQUAL(2, newCoins2.size()); - BOOST_CHECK_EQUAL(1, serials2.size()); - - auto blockIdx3 = GenerateBlock({mintTxs[2], jsTx2}); - BOOST_CHECK(blockIdx3); - auto block3 = GetCBlock(blockIdx3); - - checker.coins.insert(checker.coins.end(), newCoins2.begin(), newCoins2.end()); - checker.coins.push_back(hdMints[2].GetPubcoinValue()); - checker.serials.push_back(serials2[0]); - checker.last = blockIdx3; - - checker.Verify(); - - // Clear state and rebuild - lelantusState->Reset(); - emptyChecker.Verify(); - - BuildLelantusStateFromIndex(&chainActive); - checker.Verify(); - - // Disconnect all and reconnect - std::vector blocks; - while (chainActive.Tip() != chainActive.Genesis()) { - blocks.push_back(GetCBlock(chainActive.Tip())); - DisconnectBlocks(1); - } - - emptyChecker.Verify(); - - for (auto const &block : blocks) { - reconnect(block); - } - - checker.Verify(); - - // double spend - auto currentBlock = chainActive.Tip()->nHeight; - BOOST_CHECK(!GenerateBlock({dupJsTx1})); - BOOST_CHECK_EQUAL(currentBlock, chainActive.Tip()->nHeight); - mempool.clear(); - lelantusState->Reset(); -} - -BOOST_AUTO_TEST_CASE(checktransaction) -{ - GenerateBlocks(110); - - // mints - std::vector txs; - auto mints = GenerateMints({1 * CENT}, txs); - auto &tx = txs[0]; - - CValidationState state; - CLelantusTxInfo info; - BOOST_CHECK(CheckLelantusTransaction( - txs[0], state, tx.GetHash(), false, chainActive.Height(), true, true, &info)); - - std::vector>> expectedCoins = {{mints[0].GetPubcoinValue(), {1 * CENT, info.mints[0].second.second}}}; - - BOOST_CHECK(expectedCoins == info.mints); - - // join split - txs.clear(); - mints = GenerateMints({10 * CENT, 11 * CENT, 100 * CENT}, txs); - GenerateBlock(txs); - GenerateBlocks(10); - - auto outputAmount = 8 * CENT; - auto mintAmount = 2 * CENT - CENT; // a cent as fee - - CWalletTx wtx; - pwalletMain->JoinSplitLelantus( - {{script, outputAmount, false}}, - {mintAmount}, - wtx); - - CMutableTransaction joinsplitTx(wtx); - auto joinsplit = ParseLelantusJoinSplit(joinsplitTx); - - // test get join split amounts - BOOST_CHECK_EQUAL(1, GetSpendInputs(joinsplitTx)); - BOOST_CHECK_EQUAL(1, GetSpendInputs(joinsplitTx, joinsplitTx.vin[0])); - - info = CLelantusTxInfo(); - - BOOST_CHECK(CheckLelantusTransaction( - joinsplitTx, state, joinsplitTx.GetHash(), false, chainActive.Height(), false, true, &info)); - - auto &serials = joinsplit->getCoinSerialNumbers(); - auto &ids = joinsplit->getCoinGroupIds(); - - for (size_t i = 0; i != serials.size(); i++) { - bool hasSerial = false; - BOOST_CHECK_MESSAGE(hasSerial = (info.spentSerials.count(serials[i]) > 0), "No serial as expected"); - if (hasSerial) { - BOOST_CHECK_MESSAGE(cmp::equal(ids[i], info.spentSerials[serials[i]]), "Serials group id is invalid"); - } - } - - info = CLelantusTxInfo(); - BOOST_CHECK(CheckLelantusTransaction( - joinsplitTx, state, joinsplitTx.GetHash(), false, chainActive.Height(), false, true, &info)); - - // test surge dection. - while (!lelantusState->IsSurgeConditionDetected()) { - Scalar s; - s.randomize(); - - lelantusState->AddSpend(s, 1); - } - - BOOST_CHECK(!CheckLelantusTransaction( - joinsplitTx, state, joinsplitTx.GetHash(), false, chainActive.Height(), false, true, &info)); -} - -BOOST_AUTO_TEST_CASE(move_to_v3_payload) -{ - int prevHeight; - pwalletMain->SetBroadcastTransactions(true); - - for (int n=chainActive.Height(); n<300; n++) - GenerateBlock({}); - - std::vector lelantusMints; - GenerateMints({1*COIN, 2*COIN, 3*COIN, 4*COIN, 5*COIN}, lelantusMints); - GenerateBlock(lelantusMints); - - for (int i=0; i<6; i++) - GenerateBlock({}); - - // Shift HF block number to create joinsplit with v3 payload early - Consensus::Params &mutableParams = const_cast(::Params().GetConsensus()); - int v3PayloadHFBackup = mutableParams.nLelantusV3PayloadStartBlock; - mutableParams.nLelantusV3PayloadStartBlock = chainActive.Height(); - - CWalletTx jsWalletTx1, jsWalletTx2, jsWalletTx3; - - pwalletMain->JoinSplitLelantus({{script, COIN/10, false}}, {}, jsWalletTx1); - ::mempool.clear(); - - // Test transaction structure - BOOST_ASSERT(jsWalletTx1.tx->vin[0].scriptSig.size() == 1); - - CBlock blockWithPayloadJS = CreateBlock({*jsWalletTx1.tx}, coinbaseKey); - - mutableParams.nLelantusV3PayloadStartBlock = v3PayloadHFBackup; - - // transaction shouldn't get accepted into the mempool - BOOST_ASSERT(!::CommitToMempool(*jsWalletTx1.tx)); - // block with this transaction shouldn't be accepted either - prevHeight = chainActive.Height(); - ProcessNewBlock(::Params(), std::make_shared(blockWithPayloadJS), true, nullptr); - BOOST_ASSERT(chainActive.Height() == prevHeight); - - // Create wallet transaction with lelantus data in scriptSig - pwalletMain->JoinSplitLelantus({{script, COIN/10, false}}, {}, jsWalletTx2); - // ensure it has lelantus data in scriptSig - BOOST_ASSERT(jsWalletTx2.tx->vin[0].scriptSig.size() > 1); - // should get into the mempool - BOOST_ASSERT(::mempool.size() == 1); - // should get into the block - GenerateBlock({*jsWalletTx2.tx}); - BOOST_ASSERT(::mempool.size() == 0); - - // Create another wallet transaction with lelantus data in scriptSig - pwalletMain->JoinSplitLelantus({{script, COIN/10, false}}, {}, jsWalletTx3); - ::mempool.clear(); - - // fast forward to HF - for (int n=chainActive.Height(); n(blockWithNonpayloadJS), true, nullptr); - BOOST_ASSERT(chainActive.Height() == prevHeight); -} - -BOOST_AUTO_TEST_CASE(spend_limitation_per_tx) -{ - PrivateCoin coinOut(params, 0); - JoinSplitScriptGenerator invalidG, validG; - invalidG.fee = 0; - invalidG.vout = 0; - invalidG.coinsOut = {coinOut}; - invalidG.groupBlockHashes[1] = {ArithToUint256(0)}; - invalidG.txHash = ArithToUint256(0); - - validG.fee = 0; - validG.vout = 0; - validG.coinsOut = {coinOut}; - validG.groupBlockHashes[1] = ArithToUint256(0); - validG.txHash = ArithToUint256(0); - - for (size_t i = 0; i != consensus.nMaxLelantusInputPerTransaction + 1; i++) { - PrivateCoin coin(params, 0); - invalidG.coins.emplace_back(coin, 1); - invalidG.anons[1].push_back(coin.getPublicCoin()); - - if (i != 0) { // skip first - validG.coins.emplace_back(coin, 1); - validG.anons[1].push_back(coin.getPublicCoin()); - } - } - - CMutableTransaction invalidTx, validTx; - invalidTx.vin.resize(1); - invalidTx.vin[0].scriptSig = invalidG.Get().first; - - validTx.vin.resize(1); - validTx.vin[0].scriptSig = validG.Get().first; - - CBlock invalidBlock, validBlock; - invalidBlock.vtx.push_back(MakeTransactionRef(invalidTx)); - validBlock.vtx.push_back(MakeTransactionRef(validTx)); - - CValidationState state; - BOOST_CHECK(!CheckLelantusBlock(state, invalidBlock)); - BOOST_CHECK(CheckLelantusBlock(state, validBlock)); -} - -BOOST_AUTO_TEST_CASE(spend_limitation_per_block) -{ - CBlock block; - size_t spends = 0; - - for (size_t i = 0; spends <= consensus.nMaxLelantusInputPerBlock; i++) { - PrivateCoin coinOut(params, 0); - JoinSplitScriptGenerator g; - g.fee = 0; - g.vout = 0; - g.coinsOut = {coinOut}; - for (size_t i = 0; i != consensus.nMaxLelantusInputPerTransaction; i++) { - PrivateCoin coin(params, 0); - g.coins.emplace_back(coin, 1); - g.anons[1].push_back(coin.getPublicCoin()); - - spends++; - } - - g.groupBlockHashes[1] = ArithToUint256(i); - g.txHash = ArithToUint256(i); - - CMutableTransaction tx; - tx.vin.resize(1); - tx.vin[0].scriptSig = g.Get().first; - - - block.vtx.push_back(MakeTransactionRef(tx)); - } - - CValidationState state; - BOOST_CHECK(!CheckLelantusBlock(state, block)); - - block.vtx.pop_back(); - BOOST_CHECK(CheckLelantusBlock(state, block)); -} - -BOOST_AUTO_TEST_CASE(parse_joinsplit) -{ - auto coins = GenerateMints({1 * COIN, 10 * COIN, 1 * COIN, 1 * COIN}); - - JoinSplitScriptGenerator g; - g.params = params; - g.coins = {{coins[0], 1}, {coins[1], 1}, {coins[2], 2}}; - for (auto id : {1, 2}) { - for (size_t i = 0; i != 10; i++) { - GroupElement e; - e.randomize(); - - g.anons[id].emplace_back(e); - } - } - - g.anons[1][0] = coins[0].getPublicCoin(); - g.anons[1][1] = coins[1].getPublicCoin(); - g.anons[2][0] = coins[2].getPublicCoin(); - - g.vout = 11 * COIN - CENT; - g.coinsOut.push_back(coins[3]); - g.fee = CENT; - g.groupBlockHashes[1] = ArithToUint256(1); - g.groupBlockHashes[2] = ArithToUint256(2); - g.txHash = ArithToUint256(3); - - auto gs = g.Get(); - CTxIn inp(COutPoint(), gs.first); - CMutableTransaction inpTx; - inpTx.vin.push_back(inp); - - auto result = ParseLelantusJoinSplit(inpTx); - - BOOST_CHECK(gs.second.getCoinSerialNumbers() == result->getCoinSerialNumbers()); - BOOST_CHECK(gs.second.getFee() == result->getFee()); - BOOST_CHECK(gs.second.getCoinGroupIds() == result->getCoinGroupIds()); - BOOST_CHECK(gs.second.getIdAndBlockHashes() == result->getIdAndBlockHashes()); - BOOST_CHECK(gs.second.getVersion() == result->getVersion()); - BOOST_CHECK(gs.second.HasValidSerials() == result->HasValidSerials()); - - BOOST_CHECK(gs.second.Verify(g.anons, {}, ExtractCoins(g.coinsOut), g.vout, g.txHash)); - BOOST_CHECK(result->Verify(g.anons, {}, ExtractCoins(g.coinsOut), g.vout, g.txHash)); -} - -BOOST_AUTO_TEST_CASE(coingroup) -{ - GenerateBlocks(210); - - // util function - auto reconnect = [](CBlock const &block) { - LOCK2(cs_main, pwalletMain->cs_wallet); - LOCK(mempool.cs); - - std::shared_ptr sharedBlock = - std::make_shared(block); - - CValidationState state; - ActivateBestChain(state, ::Params(), sharedBlock); - }; - - struct { - // expected state. - std::vector coins; - - // first group - CBlockIndex *first = nullptr; - CBlockIndex *last = nullptr; - - int lastId = 0; - size_t lastGroupCoins = 0; - - // real state - CLelantusState *state; - - void Verify(std::string stateName = "") const { - auto const &mints = state->GetMints(); - BOOST_CHECK_EQUAL(coins.size(), mints.size()); - for (auto const &c : coins) { - BOOST_CHECK_MESSAGE(mints.count(c), "public is not found on state : " + stateName); - } - - auto retrievedId = state->GetLatestCoinID(); - - CLelantusState::LelantusCoinGroupInfo group; - state->GetCoinGroupInfo(retrievedId, group); - - BOOST_CHECK_EQUAL(lastId, retrievedId); - BOOST_CHECK_EQUAL(first, group.firstBlock); - BOOST_CHECK_EQUAL(last, group.lastBlock); - BOOST_CHECK_EQUAL(lastGroupCoins, group.nCoins); - } - } checker; - checker.state = lelantusState; - - lelantusState->~CLelantusState(); - new (lelantusState) CLelantusState(65, 16); - lelantusState->Reset(); - - // logic - std::vector txs; - std::vector coins; - auto hdMints = GenerateMints(std::vector(66, 1), txs, coins); - - auto txRange = [&](size_t start, size_t end) -> std::vector { - std::vector rangeTxs; - for (auto i = start; i < end && i < txs.size(); i++) { - rangeTxs.push_back(txs[i]); - } - - return rangeTxs; - }; - - std::vector pubCoins; - for (auto const &hdMint : hdMints) { - pubCoins.push_back(hdMint.GetPubcoinValue()); - } - - auto emptyChecker = checker; - emptyChecker.Verify(); - - // add one block - auto idx1 = GenerateBlock(txRange(0, 1)); - auto block1 = GetCBlock(idx1); - - checker.coins.push_back(pubCoins[0]); - checker.lastId = 1; - checker.first = idx1; - checker.last = idx1; - checker.lastGroupCoins = 1; - checker.Verify(); - - // add more - auto idx2 = GenerateBlock(txRange(1, 32)); - auto block2 = GetCBlock(idx2); - - checker.coins.insert(checker.coins.end(), pubCoins.begin() + 1, pubCoins.begin() + 32); - checker.last = idx2; - checker.lastGroupCoins = 32; - checker.Verify(); - - auto cacheIdx2Checker = checker; - - // add more to fill group - auto idx3 = GenerateBlock(txRange(32, 65)); - auto block3 = GetCBlock(idx3); - - checker.coins.insert(checker.coins.end(), pubCoins.begin() + 32, pubCoins.begin() + 65); - checker.last = idx3; - checker.lastGroupCoins = 65; - checker.Verify(); - - auto cacheIdx3Checker = checker; - - // add one more to create new group - auto idx4 = GenerateBlock(txRange(65, 66)); - auto block4 = GetCBlock(idx4); - - checker.coins.push_back(pubCoins[65]); - checker.lastId = 2; - checker.lastGroupCoins = 34; - checker.first = idx3; - checker.last = idx4; - - checker.Verify(); - - // remove last block check coingroup - DisconnectBlocks(1); - cacheIdx3Checker.Verify(); - - // remove one more block - DisconnectBlocks(1); - cacheIdx2Checker.Verify(); - - // reconnect them all and check state - reconnect(block2); - reconnect(block3); - checker.Verify(); - - lelantusState->~CLelantusState(); - new (lelantusState) CLelantusState(); - lelantusState->Reset(); -} - -BOOST_AUTO_TEST_SUITE_END() - -}; diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 34117dd6fd..1e1853f2ae 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -38,6 +38,27 @@ BOOST_AUTO_TEST_CASE(netbase_networks) } +BOOST_AUTO_TEST_CASE(netbase_parse_network) +{ + BOOST_CHECK_EQUAL(ParseNetwork("ipv4"), NET_IPV4); + BOOST_CHECK_EQUAL(ParseNetwork("ipv6"), NET_IPV6); + BOOST_CHECK_EQUAL(ParseNetwork("onion"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("tor"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("Tor"), NET_ONION); + BOOST_CHECK_EQUAL(ParseNetwork("bogus"), NET_UNROUTABLE); + + BOOST_CHECK_EQUAL(GetNetworkName(NET_UNROUTABLE), "not_publicly_routable"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_IPV4), "ipv4"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_IPV6), "ipv6"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_ONION), "onion"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_I2P), "i2p"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_CJDNS), "cjdns"); + BOOST_CHECK_EQUAL(GetNetworkName(NET_INTERNAL), "internal"); +#ifdef NDEBUG + BOOST_CHECK_EQUAL(GetNetworkName(static_cast(NET_MAX)), "unknown"); +#endif +} + BOOST_AUTO_TEST_CASE(netbase_properties) { diff --git a/src/test/sparkname_tests.cpp b/src/test/sparkname_tests.cpp index 5f2b1eccba..979c768364 100644 --- a/src/test/sparkname_tests.cpp +++ b/src/test/sparkname_tests.cpp @@ -3,6 +3,7 @@ #include "../validation.h" #include "../wallet/coincontrol.h" #include "../wallet/wallet.h" +#include "../wallet/walletexcept.h" #include "../net.h" #include "../sparkname.h" @@ -16,8 +17,10 @@ namespace spark { class SparkNameTests : public SparkTestingSetup { -private: +public: Consensus::Params &mutableConsensus; + +private: Consensus::Params oldConsensus; public: @@ -28,9 +31,11 @@ class SparkNameTests : public SparkTestingSetup consensus(::Params().GetConsensus()), sparkNameManager(CSparkNameManager::GetInstance()) { oldConsensus = mutableConsensus; + mempool.clear(); } ~SparkNameTests() { + mempool.clear(); sparkState->Reset(); sparkNameManager->Reset(); mutableConsensus = oldConsensus; @@ -145,7 +150,12 @@ class SparkNameTests : public SparkTestingSetup spark::Address sparkAddress(spark::Params::get_default()); spark::OwnershipProof ownershipProof; - spark::SpendKey spendKey = pwalletMain->sparkWallet->generateSpendKey(spark::Params::get_default()); + spark::SpendKey spendKey(spark::Params::get_default()); + try { + spendKey = std::move(pwalletMain->sparkWallet->generateSpendKey(spark::Params::get_default())); + } catch (const WalletLocked&) { + BOOST_FAIL("Spark wallet is locked; unlock wallet to run this test"); + } spark::IncomingViewKey incomingViewKey(spendKey); sparkAddress.decode(sparkNameData.sparkAddress); sparkAddress.prove_own(m, spendKey, incomingViewKey, ownershipProof); @@ -317,6 +327,10 @@ BOOST_AUTO_TEST_CASE(hfblocknumber) { Initialize(1000); // stay below HF block number for a time being + // Push V2.1 activation past the end of the graceful period so fee-tag + // requirements don't interfere with the address-transition test below. + mutableConsensus.nSparkNamesV21StartBlock = INT_MAX; + int oldHeight = chainActive.Height(); std::string txaddress = GenerateSparkAddress(); @@ -399,4 +413,682 @@ BOOST_AUTO_TEST_CASE(hfblocknumber) BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight+1); } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_CASE(transfer) +{ + constexpr int nBlockPerYear = 365*24*24; + + // regtest: nSparkNamesV2StartBlock = 2500, need to be past it for transfers + Initialize(2500); + + // --- Register "xfername" with address A --- + std::string addrA = GenerateSparkAddress(); + CMutableTransaction txReg = CreateSparkNameTx("xfername", addrA, nBlockPerYear * 5, "original", true); + BOOST_CHECK(lastState.IsValid()); + GenerateBlock({txReg}); + BOOST_CHECK(IsSparkNamePresent("xfername")); + + std::string resolvedAddr; + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrA); + + GenerateBlocks(5); + + // --- Transfer "xfername" from address A to address B --- + std::string addrB = GenerateSparkAddress(); + + CSparkNameTxData transferData; + transferData.nVersion = CSparkNameTxData::CURRENT_VERSION; + transferData.name = "xfername"; + transferData.sparkAddress = addrB; + transferData.oldSparkAddress = addrA; + transferData.sparkNameValidityBlocks = nBlockPerYear; + transferData.operationType = (uint8_t)CSparkNameTxData::opTransfer; + transferData.additionalInfo = "transferred"; + + // Compute transfer request hash (mirrors requestsparknametransfer RPC) + { + CHashWriter nameHash(SER_GETHASH, PROTOCOL_VERSION); + nameHash << transferData; + + CHashWriter hashStream(SER_GETHASH, PROTOCOL_VERSION); + hashStream << "SparkNameTransferProof"; + hashStream << transferData.oldSparkAddress << transferData.sparkAddress; + hashStream << nameHash.GetHash(); + + // Create transfer ownership proof using spend key (mirrors transfersparkname RPC) + const spark::Params *sparkParams = spark::Params::get_default(); + spark::SpendKey spendKey = pwalletMain->sparkWallet->generateSpendKey(sparkParams); + + spark::Address oldAddress(sparkParams); + oldAddress.decode(addrA); + + spark::Scalar mTransfer; + mTransfer.SetHex(hashStream.GetHash().ToString()); + + spark::OwnershipProof transferProof; + oldAddress.prove_own(mTransfer, spendKey, spark::FullViewKey(spendKey), transferProof); + + CDataStream proofStream(SER_NETWORK, PROTOCOL_VERSION); + proofStream << transferProof; + transferData.transferOwnershipProof.assign(proofStream.begin(), proofStream.end()); + } + + CMutableTransaction txTransfer = CreateSparkNameTx(transferData, true); + BOOST_CHECK(lastState.IsValid()); + GenerateBlock({txTransfer}); + + // Verify name is now at address B + BOOST_CHECK(IsSparkNamePresent("xfername")); + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + BOOST_CHECK_EQUAL(GetSparkNameAdditionalData("xfername"), "transferred"); + + // Verify old address A is freed and new address B is associated + std::string nameByAddr; + BOOST_CHECK(!sparkNameManager->GetSparkNameByAddress(addrA, nameByAddr)); + BOOST_CHECK(sparkNameManager->GetSparkNameByAddress(addrB, nameByAddr)); + BOOST_CHECK_EQUAL(nameByAddr, "xfername"); + + // --- Test rollback reverting the transfer --- + DisconnectBlocks(1); + + BOOST_CHECK(IsSparkNamePresent("xfername")); + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrA); + BOOST_CHECK_EQUAL(GetSparkNameAdditionalData("xfername"), "original"); + + BOOST_CHECK(sparkNameManager->GetSparkNameByAddress(addrA, nameByAddr)); + BOOST_CHECK(!sparkNameManager->GetSparkNameByAddress(addrB, nameByAddr)); + + // Re-apply the block and verify transfer is restored + ReprocessBlocks(1); + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + BOOST_CHECK_EQUAL(GetSparkNameAdditionalData("xfername"), "transferred"); + + // --- Test that invalid transfer proof is rejected --- + GenerateBlocks(5); + std::string addrC = GenerateSparkAddress(); + + CSparkNameTxData badTransferData; + badTransferData.nVersion = CSparkNameTxData::CURRENT_VERSION; + badTransferData.name = "xfername"; + badTransferData.sparkAddress = addrC; + badTransferData.oldSparkAddress = addrB; + badTransferData.sparkNameValidityBlocks = nBlockPerYear; + badTransferData.operationType = (uint8_t)CSparkNameTxData::opTransfer; + badTransferData.additionalInfo = "bad"; + // Use wrong proof (from previous transfer, bound to a different hash) + badTransferData.transferOwnershipProof = transferData.transferOwnershipProof; + + CMutableTransaction txBadTransfer = CreateSparkNameTx(badTransferData, false); + int oldHeight = chainActive.Height(); + GenerateBlock({txBadTransfer}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); + + // Name should still be at address B + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + + // --- Test that transfer with wrong old address is rejected --- + CSparkNameTxData wrongOldAddrData; + wrongOldAddrData.nVersion = CSparkNameTxData::CURRENT_VERSION; + wrongOldAddrData.name = "xfername"; + wrongOldAddrData.sparkAddress = addrC; + wrongOldAddrData.oldSparkAddress = addrA; // addrA no longer owns the name + wrongOldAddrData.sparkNameValidityBlocks = nBlockPerYear; + wrongOldAddrData.operationType = (uint8_t)CSparkNameTxData::opTransfer; + wrongOldAddrData.additionalInfo = "wrong old addr"; + + // Create a valid-looking proof for addrA (but addrA doesn't own the name anymore) + { + CHashWriter nameHash(SER_GETHASH, PROTOCOL_VERSION); + nameHash << wrongOldAddrData; + + CHashWriter hashStream(SER_GETHASH, PROTOCOL_VERSION); + hashStream << "SparkNameTransferProof"; + hashStream << wrongOldAddrData.oldSparkAddress << wrongOldAddrData.sparkAddress; + hashStream << nameHash.GetHash(); + + const spark::Params *sparkParams = spark::Params::get_default(); + spark::SpendKey spendKey = pwalletMain->sparkWallet->generateSpendKey(sparkParams); + + spark::Address addrAObj(sparkParams); + addrAObj.decode(addrA); + + spark::Scalar mTransfer; + mTransfer.SetHex(hashStream.GetHash().ToString()); + + spark::OwnershipProof wrongProof; + addrAObj.prove_own(mTransfer, spendKey, spark::FullViewKey(spendKey), wrongProof); + + CDataStream proofStream(SER_NETWORK, PROTOCOL_VERSION); + proofStream << wrongProof; + wrongOldAddrData.transferOwnershipProof.assign(proofStream.begin(), proofStream.end()); + } + + CMutableTransaction txWrongOldAddr = CreateSparkNameTx(wrongOldAddrData, false); + oldHeight = chainActive.Height(); + GenerateBlock({txWrongOldAddr}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); + + // Name should still be at address B + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("xfername", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); +} + +BOOST_AUTO_TEST_CASE(extension_v21) +{ + // regtest: spark names start at 2000, V2.1 starts at 2700 + constexpr int nBlockPerYear = 365*24*24; + + Initialize(); + // we're now at block ~2001 + + std::string addr1 = GenerateSparkAddress(); + + // Register "exttest" with 1 year validity + CMutableTransaction txReg = CreateSparkNameTx("exttest", addr1, nBlockPerYear, "initial", true); + GenerateBlock({txReg}); + BOOST_CHECK(IsSparkNamePresent("exttest")); + + int registrationHeight = chainActive.Height(); + uint64_t originalExpiration = sparkNameManager->GetSparkNameBlockHeight("exttest"); + BOOST_CHECK_EQUAL(originalExpiration, registrationHeight + nBlockPerYear); + + // --- Pre-V2.1: extend the name by 1 year --- + // Advance some blocks so there's meaningful remaining validity + GenerateBlocks(100); + int preV21Height = chainActive.Height(); + BOOST_CHECK(preV21Height < consensus.nSparkNamesV21StartBlock); + int remainingBeforeExtend = (int)originalExpiration - preV21Height; + BOOST_CHECK(remainingBeforeExtend > 0); + + CMutableTransaction txExtPre = CreateSparkNameTx("exttest", addr1, nBlockPerYear, "extended-pre", true); + GenerateBlock({txExtPre}); + BOOST_CHECK(IsSparkNamePresent("exttest")); + + int extendHeightPre = chainActive.Height(); + uint64_t expirationAfterPreV21Extend = sparkNameManager->GetSparkNameBlockHeight("exttest"); + + // Before V2.1, remaining validity is NOT preserved — new expiration = extendHeight + newBlocks + BOOST_CHECK_EQUAL(expirationAfterPreV21Extend, extendHeightPre + nBlockPerYear); + // The remaining blocks from original registration are lost + BOOST_CHECK(expirationAfterPreV21Extend < (uint64_t)(extendHeightPre + nBlockPerYear + remainingBeforeExtend)); + + // --- Advance to V2.1 --- + int blocksToV21 = consensus.nSparkNamesV21StartBlock - chainActive.Height(); + BOOST_CHECK(blocksToV21 > 0); + GenerateBlocks(blocksToV21); + BOOST_CHECK(chainActive.Height() >= consensus.nSparkNamesV21StartBlock); + + // Name should still be valid (we registered for 1 year = 210240 blocks and only advanced ~700 blocks) + BOOST_CHECK(IsSparkNamePresent("exttest")); + uint64_t expirationBeforeV21Extend = sparkNameManager->GetSparkNameBlockHeight("exttest"); + int preV21ExtendHeight = chainActive.Height(); + int remainingBeforeV21Extend = (int)expirationBeforeV21Extend - preV21ExtendHeight; + BOOST_CHECK(remainingBeforeV21Extend > 0); + + // --- Post-V2.1: extend the name by 1 year --- + CMutableTransaction txExtPost = CreateSparkNameTx("exttest", addr1, nBlockPerYear, "extended-post", true); + GenerateBlock({txExtPost}); + BOOST_CHECK(IsSparkNamePresent("exttest")); + + int extendHeightPost = chainActive.Height(); + uint64_t expirationAfterV21Extend = sparkNameManager->GetSparkNameBlockHeight("exttest"); + + // After V2.1, remaining validity IS preserved — new expiration = extendHeight + newBlocks + remaining + int expectedRemaining = (int)expirationBeforeV21Extend - extendHeightPost; + BOOST_CHECK(expectedRemaining > 0); + BOOST_CHECK_EQUAL(expirationAfterV21Extend, (uint64_t)(extendHeightPost + nBlockPerYear + expectedRemaining)); + + // Verify rollback restores old expiration + DisconnectBlocks(1); + BOOST_CHECK(IsSparkNamePresent("exttest")); + BOOST_CHECK_EQUAL(sparkNameManager->GetSparkNameBlockHeight("exttest"), expirationBeforeV21Extend); + + // Reprocess and verify extension is restored + ReprocessBlocks(1); + BOOST_CHECK_EQUAL(sparkNameManager->GetSparkNameBlockHeight("exttest"), expirationAfterV21Extend); + BOOST_CHECK_EQUAL(GetSparkNameAdditionalData("exttest"), "extended-post"); +} + +BOOST_AUTO_TEST_CASE(extension_max_validity) +{ + constexpr int nBlockPerYear = 365*24*24; + + // Initialize past V2.1 (regtest V2.1 starts at block 2700) + Initialize(2700); + + // --- Test 1: Register for exactly 15 years - should succeed --- + std::string addr1 = GenerateSparkAddress(); + CMutableTransaction txReg15 = CreateSparkNameTx("maxval1", addr1, nBlockPerYear * 15, "", false); + int oldHeight = chainActive.Height(); + GenerateBlock({txReg15}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + BOOST_CHECK(IsSparkNamePresent("maxval1")); + int regHeight = chainActive.Height(); + uint64_t exp15 = sparkNameManager->GetSparkNameBlockHeight("maxval1"); + BOOST_CHECK_EQUAL(exp15, (uint64_t)(regHeight + nBlockPerYear * 15)); + + // --- Test 2: Try to include a 16-year registration in a block - should be rejected --- + std::string addr2 = GenerateSparkAddress(); + CMutableTransaction txReg16 = CreateSparkNameTx("maxval2", addr2, nBlockPerYear * 16, "", false); + oldHeight = chainActive.Height(); + GenerateBlock({txReg16}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block rejected + BOOST_CHECK(!IsSparkNamePresent("maxval2")); + + // --- Test 3: Register for 10 years, extend by 5 years -> total ~15 years -> should succeed --- + std::string addr3 = GenerateSparkAddress(); + CMutableTransaction txReg10a = CreateSparkNameTx("maxval3", addr3, nBlockPerYear * 10, "", false); + oldHeight = chainActive.Height(); + GenerateBlock({txReg10a}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + BOOST_CHECK(IsSparkNamePresent("maxval3")); + + GenerateBlocks(5); + + CMutableTransaction txExt5 = CreateSparkNameTx("maxval3", addr3, nBlockPerYear * 5, "ext5", false); + oldHeight = chainActive.Height(); + GenerateBlock({txExt5}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + BOOST_CHECK(IsSparkNamePresent("maxval3")); + + // Verify extended expiration preserves remaining time (post-V21 behavior) + int extHeight = chainActive.Height(); + uint64_t expExt = sparkNameManager->GetSparkNameBlockHeight("maxval3"); + // Total from extend height should be close to 15 years (minus the few blocks advanced) + BOOST_CHECK(expExt > (uint64_t)(extHeight + nBlockPerYear * 14)); + BOOST_CHECK(expExt <= (uint64_t)(extHeight + nBlockPerYear * 15)); + + // --- Test 4: Register for 10 years, try extending by 6 years -> total > 15 years -> should fail --- + std::string addr4 = GenerateSparkAddress(); + CMutableTransaction txReg10b = CreateSparkNameTx("maxval4", addr4, nBlockPerYear * 10, "", false); + oldHeight = chainActive.Height(); + GenerateBlock({txReg10b}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + BOOST_CHECK(IsSparkNamePresent("maxval4")); + + CMutableTransaction txExt6 = CreateSparkNameTx("maxval4", addr4, nBlockPerYear * 6, "ext6", false); + oldHeight = chainActive.Height(); + GenerateBlock({txExt6}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block rejected - total exceeds 15 years +} + +BOOST_AUTO_TEST_CASE(tagged_fee_output_must_pay_fee) +{ + constexpr int nBlockPerYear = 365*24*24; + + Initialize(2700); + + std::string addr = GenerateSparkAddress(); + CMutableTransaction tx = CreateSparkNameTx("tagfee", addr, nBlockPerYear, "", false); + CAmount nameFee = consensus.nSparkNamesFee[std::string("tagfee").size()] * COIN; + + bool modifiedFeeOutput = false; + for (size_t i = 0; i < tx.vout.size(); ++i) { + if (tx.vout[i].scriptPubKey.IsSparkNameFee()) { + CScript baseScript = GetBaseScriptFromSparkNameFee(tx.vout[i].scriptPubKey); + tx.vout[i].nValue = 0; + tx.vout.push_back(CTxOut(nameFee, baseScript)); + modifiedFeeOutput = true; + break; + } + } + BOOST_REQUIRE(modifiedFeeOutput); + ModifySparkNameTx(tx, [](CSparkNameTxData &) {}, true); + + int oldHeight = chainActive.Height(); + GenerateBlock({tx}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); + BOOST_CHECK(!IsSparkNamePresent("tagfee")); +} + +BOOST_AUTO_TEST_CASE(extension_mempool_uses_next_block_height) +{ + constexpr int nBlockPerYear = 365*24*24; + + Initialize(2700); + + std::string addr = GenerateSparkAddress(); + CMutableTransaction txReg = CreateSparkNameTx("nextheight", addr, nBlockPerYear * 15, "", false); + int oldHeight = chainActive.Height(); + GenerateBlock({txReg}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + + uint64_t originalExpiration = sparkNameManager->GetSparkNameBlockHeight("nextheight"); + CMutableTransaction txExt = CreateSparkNameTx("nextheight", addr, 1, "one-block-extension", true); + BOOST_CHECK(lastState.IsValid()); + + oldHeight = chainActive.Height(); + GenerateBlock({txExt}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + BOOST_CHECK_EQUAL(sparkNameManager->GetSparkNameBlockHeight("nextheight"), originalExpiration + 1); +} + +BOOST_AUTO_TEST_CASE(validity_overflow_protection) +{ + constexpr int nBlockPerYear = 365*24*24; + + // Initialize past V2.1 (regtest V2.1 starts at 2700) + Initialize(2700); + + std::string addr1 = GenerateSparkAddress(); + + // Register "overtest" with 1 year to have an existing name available for renewal + CMutableTransaction txReg = CreateSparkNameTx("overtest", addr1, nBlockPerYear, "", true); + BOOST_CHECK(lastState.IsValid()); + GenerateBlock({txReg}); + BOOST_CHECK(IsSparkNamePresent("overtest")); + + // sparkNameValidityBlocks is uint32_t. Without the explicit uint32_t check, a value + // above INT_MAX narrows to a negative int, causing the 15-year cap comparison to + // silently pass. The tests below verify this is now rejected. + + const uint32_t overflowBlocks = (uint32_t)INT_MAX + 1u; // minimal case that triggers the bug + + // --- Fresh registration with sparkNameValidityBlocks = INT_MAX + 1 --- + std::string addr2 = GenerateSparkAddress(); + CMutableTransaction txNewOverflow = CreateSparkNameTx("newname1", addr2, nBlockPerYear, "", false); + ModifySparkNameTx(txNewOverflow, [overflowBlocks](CSparkNameTxData &data) { + data.sparkNameValidityBlocks = overflowBlocks; + }); + int oldHeight = chainActive.Height(); + GenerateBlock({txNewOverflow}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block must be rejected + + // --- Renewal of existing name with sparkNameValidityBlocks = INT_MAX + 1 --- + CMutableTransaction txUpdateOverflow = CreateSparkNameTx("overtest", addr1, nBlockPerYear, "", false); + ModifySparkNameTx(txUpdateOverflow, [overflowBlocks](CSparkNameTxData &data) { + data.sparkNameValidityBlocks = overflowBlocks; + }); + oldHeight = chainActive.Height(); + GenerateBlock({txUpdateOverflow}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block must be rejected + + // --- UINT32_MAX variant --- + CMutableTransaction txMaxOverflow = CreateSparkNameTx("overtest", addr1, nBlockPerYear, "", false); + ModifySparkNameTx(txMaxOverflow, [](CSparkNameTxData &data) { + data.sparkNameValidityBlocks = UINT32_MAX; + }); + oldHeight = chainActive.Height(); + GenerateBlock({txMaxOverflow}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block must be rejected + + // Sanity: a valid 1-year renewal still goes through after all the failed attempts + CMutableTransaction txValid = CreateSparkNameTx("overtest", addr1, nBlockPerYear, "ok", false); + oldHeight = chainActive.Height(); + GenerateBlock({txValid}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); // block must be accepted + BOOST_CHECK(IsSparkNamePresent("overtest")); +} + +BOOST_AUTO_TEST_CASE(transfer_replay_protection_v21) +{ + constexpr int nBlockPerYear = 365*24*24; + + // regtest: V2.1 starts at block 2700. Past it, every transfer must carry an + // inputsHash committing to the name's current expiration height. The expiration + // height is unique per registration cycle, so a transfer proof captured in one + // cycle becomes unusable once the name's expiration height changes. + Initialize(2700); + BOOST_CHECK(chainActive.Height() >= consensus.nSparkNamesV21StartBlock); + + auto inputsHashFor = [](uint64_t expirationHeight) { + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + hw << expirationHeight; + return hw.GetHash(); + }; + + // Sign a transfer ownership proof for `addrFrom` over `data`. This mirrors the + // message construction in CheckSparkNameTx (both ownership proofs cleared, then + // "SparkNameTransferProof" || oldAddr || newAddr || hash(data)). Because the + // message commits to inputsHash, callers must set inputsHash before signing. + auto signTransfer = [&](CSparkNameTxData &data, const std::string &addrFrom) { + CSparkNameTxData copy = data; + copy.addressOwnershipProof.clear(); + copy.transferOwnershipProof.clear(); + + CHashWriter nameHash(SER_GETHASH, PROTOCOL_VERSION); + nameHash << copy; + + CHashWriter hashStream(SER_GETHASH, PROTOCOL_VERSION); + hashStream << "SparkNameTransferProof"; + hashStream << data.oldSparkAddress << data.sparkAddress; + hashStream << nameHash.GetHash(); + + const spark::Params *sparkParams = spark::Params::get_default(); + spark::SpendKey spendKey = pwalletMain->sparkWallet->generateSpendKey(sparkParams); + + spark::Address from(sparkParams); + from.decode(addrFrom); + + spark::Scalar m; + m.SetHex(hashStream.GetHash().ToString()); + + spark::OwnershipProof proof; + from.prove_own(m, spendKey, spark::FullViewKey(spendKey), proof); + + CDataStream proofStream(SER_NETWORK, PROTOCOL_VERSION); + proofStream << proof; + data.transferOwnershipProof.assign(proofStream.begin(), proofStream.end()); + }; + + // --- Register "replayname" at address A --- + std::string addrA = GenerateSparkAddress(); + CMutableTransaction txReg = CreateSparkNameTx("replayname", addrA, nBlockPerYear, "original", true); + BOOST_CHECK(lastState.IsValid()); + GenerateBlock({txReg}); + BOOST_CHECK(IsSparkNamePresent("replayname")); + + uint64_t expirationA = sparkNameManager->GetSparkNameBlockHeight("replayname"); + + // --- Valid transfer A -> B with inputsHash bound to the current expiration height --- + std::string addrB = GenerateSparkAddress(); + + CSparkNameTxData xfer; + xfer.nVersion = CSparkNameTxData::CURRENT_VERSION; + xfer.name = "replayname"; + xfer.sparkAddress = addrB; + xfer.oldSparkAddress = addrA; + xfer.sparkNameValidityBlocks = nBlockPerYear; + xfer.operationType = (uint8_t)CSparkNameTxData::opTransfer; + xfer.additionalInfo = "transferred"; + xfer.inputsHash = inputsHashFor(expirationA); + signTransfer(xfer, addrA); + + CMutableTransaction txXfer = CreateSparkNameTx(xfer, true); + BOOST_CHECK(lastState.IsValid()); + int oldHeight = chainActive.Height(); + GenerateBlock({txXfer}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); + + std::string resolvedAddr; + BOOST_CHECK(sparkNameManager->GetSparkAddress("replayname", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + + uint64_t expirationB = sparkNameManager->GetSparkNameBlockHeight("replayname"); + std::string addrC = GenerateSparkAddress(); + + // Template for a transfer B -> C. Built via the wallet, which fills in inputsHash + // (bound to the current expiration height) and the destination ownership proof. + CSparkNameTxData xfer2; + xfer2.nVersion = CSparkNameTxData::CURRENT_VERSION; + xfer2.name = "replayname"; + xfer2.sparkAddress = addrC; + xfer2.oldSparkAddress = addrB; + xfer2.sparkNameValidityBlocks = nBlockPerYear; + xfer2.operationType = (uint8_t)CSparkNameTxData::opTransfer; + xfer2.additionalInfo = "second"; + xfer2.inputsHash = inputsHashFor(expirationB); + signTransfer(xfer2, addrB); + + // --- A transfer proof bound to a different registration cycle must be rejected --- + // Build a transfer that is internally consistent (its transfer ownership proof is + // re-signed over its own inputsHash) but whose inputsHash commits to a *different* + // expiration height than the name's current one — exactly what a proof captured + // from another registration cycle would look like. Without the inputsHash check + // this would be accepted, allowing the replay; with it, it is rejected. + CMutableTransaction txReplay = CreateSparkNameTx(xfer2, false); + ModifySparkNameTx(txReplay, [&](CSparkNameTxData &data) { + data.inputsHash = inputsHashFor(expirationB + 1); // wrong cycle + signTransfer(data, data.oldSparkAddress); // keep the proof consistent + }, true); + + oldHeight = chainActive.Height(); + GenerateBlock({txReplay}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // block rejected + // name must still be at B + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("replayname", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + + // --- Sanity: the same transfer with inputsHash bound to the current cycle passes --- + // Confirms the wrong-cycle inputsHash above was the sole reason for rejection. + CMutableTransaction txValid = CreateSparkNameTx(xfer2, false); + oldHeight = chainActive.Height(); + GenerateBlock({txValid}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); // block accepted + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("replayname", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrC); +} + +BOOST_AUTO_TEST_CASE(transfer_replay_grace_period_v21) +{ + constexpr int nBlockPerYear = 365*24*24; + + // regtest: V2.1 starts at block 2700 with a 100-block grace period (stage41SparkNamesGracefulPeriod). + // Transfers built before activation carry no inputsHash; if they were broadcast just before the fork + // they can still be sitting in the mempool when it activates. During [2700, 2800) such legacy (null + // inputsHash) transfers remain acceptable so they aren't dropped; from 2800 on, inputsHash is mandatory. + // The fixture starts at height 100 (TestChain100Setup), so Initialize(2600) lands exactly on the V2.1 + // activation height (2700), at the start of the grace window. + Initialize(2600); + BOOST_CHECK(chainActive.Height() >= consensus.nSparkNamesV21StartBlock); + BOOST_CHECK(chainActive.Height() < consensus.nSparkNamesV21StartBlock + consensus.stage41SparkNamesGracefulPeriod); + + auto inputsHashFor = [](uint64_t expirationHeight) { + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + hw << expirationHeight; + return hw.GetHash(); + }; + + // Re-sign the transfer ownership proof for `addrFrom` over `data` (see transfer_replay_protection_v21). + // Because the message commits to inputsHash, this must run after inputsHash has been set on `data`. + auto signTransfer = [&](CSparkNameTxData &data, const std::string &addrFrom) { + CSparkNameTxData copy = data; + copy.addressOwnershipProof.clear(); + copy.transferOwnershipProof.clear(); + + CHashWriter nameHash(SER_GETHASH, PROTOCOL_VERSION); + nameHash << copy; + + CHashWriter hashStream(SER_GETHASH, PROTOCOL_VERSION); + hashStream << "SparkNameTransferProof"; + hashStream << data.oldSparkAddress << data.sparkAddress; + hashStream << nameHash.GetHash(); + + const spark::Params *sparkParams = spark::Params::get_default(); + spark::SpendKey spendKey = pwalletMain->sparkWallet->generateSpendKey(sparkParams); + + spark::Address from(sparkParams); + from.decode(addrFrom); + + spark::Scalar m; + m.SetHex(hashStream.GetHash().ToString()); + + spark::OwnershipProof proof; + from.prove_own(m, spendKey, spark::FullViewKey(spendKey), proof); + + CDataStream proofStream(SER_NETWORK, PROTOCOL_VERSION); + proofStream << proof; + data.transferOwnershipProof.assign(proofStream.begin(), proofStream.end()); + }; + + // Build a legacy (pre-v2.1) transfer of `name` from `addrFrom` to `addrTo`: the wallet would normally + // fill in inputsHash for v2.1+, so we null it out and re-sign to reproduce a transfer created before + // the fork. + auto buildLegacyTransfer = [&](const std::string &name, const std::string &addrFrom, const std::string &addrTo, const std::string &info) { + CSparkNameTxData data; + data.nVersion = CSparkNameTxData::CURRENT_VERSION; + data.name = name; + data.sparkAddress = addrTo; + data.oldSparkAddress = addrFrom; + data.sparkNameValidityBlocks = nBlockPerYear; + data.operationType = (uint8_t)CSparkNameTxData::opTransfer; + data.additionalInfo = info; + + CMutableTransaction tx = CreateSparkNameTx(data, false); + ModifySparkNameTx(tx, [&](CSparkNameTxData &d) { + d.inputsHash.SetNull(); // pre-v2.1: no inputsHash + signTransfer(d, d.oldSparkAddress); // keep the transfer proof consistent + }, true); + return tx; + }; + + // --- Register "graced" at address A --- + std::string addrA = GenerateSparkAddress(); + CMutableTransaction txReg = CreateSparkNameTx("graced", addrA, nBlockPerYear, "original", true); + BOOST_CHECK(lastState.IsValid()); + GenerateBlock({txReg}); + BOOST_CHECK(IsSparkNamePresent("graced")); + + // --- Legacy transfer A -> B (null inputsHash) is accepted while inside the grace period --- + std::string addrB = GenerateSparkAddress(); + CMutableTransaction txLegacy = buildLegacyTransfer("graced", addrA, addrB, "legacy"); + + BOOST_CHECK(chainActive.Height() < consensus.nSparkNamesV21StartBlock + consensus.stage41SparkNamesGracefulPeriod); + int oldHeight = chainActive.Height(); + GenerateBlock({txLegacy}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); // accepted during grace + + std::string resolvedAddr; + BOOST_CHECK(sparkNameManager->GetSparkAddress("graced", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); + + // --- Advance past the grace period; a legacy (null inputsHash) transfer must now be rejected --- + GenerateBlocks(consensus.nSparkNamesV21StartBlock + consensus.stage41SparkNamesGracefulPeriod - chainActive.Height()); + BOOST_CHECK(chainActive.Height() >= consensus.nSparkNamesV21StartBlock + consensus.stage41SparkNamesGracefulPeriod); + + std::string addrC = GenerateSparkAddress(); + CMutableTransaction txLegacyLate = buildLegacyTransfer("graced", addrB, addrC, "legacy-late"); + + oldHeight = chainActive.Height(); + GenerateBlock({txLegacyLate}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight); // rejected after grace + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("graced", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrB); // name unchanged, still at B + + // --- Sanity: a proper v2.1 transfer (inputsHash bound to the current expiration height) is still + // accepted after grace. inputsHash must be set before signing the transfer proof, and matches the + // value the wallet recomputes in AppendSparkNameTxData. --- + uint64_t expirationB = sparkNameManager->GetSparkNameBlockHeight("graced"); + CSparkNameTxData xferValid; + xferValid.nVersion = CSparkNameTxData::CURRENT_VERSION; + xferValid.name = "graced"; + xferValid.sparkAddress = addrC; + xferValid.oldSparkAddress = addrB; + xferValid.sparkNameValidityBlocks = nBlockPerYear; + xferValid.operationType = (uint8_t)CSparkNameTxData::opTransfer; + xferValid.additionalInfo = "valid"; + xferValid.inputsHash = inputsHashFor(expirationB); + signTransfer(xferValid, addrB); + CMutableTransaction txValid = CreateSparkNameTx(xferValid, false); + + oldHeight = chainActive.Height(); + GenerateBlock({txValid}); + BOOST_CHECK_EQUAL(chainActive.Height(), oldHeight + 1); // accepted after grace + resolvedAddr.clear(); + BOOST_CHECK(sparkNameManager->GetSparkAddress("graced", resolvedAddr)); + BOOST_CHECK_EQUAL(resolvedAddr, addrC); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 5f3dd92a0a..1fd82d6b1b 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -120,12 +120,7 @@ TestingSetup::TestingSetup(const std::string& chainName, std::string suf) : Basi pwalletMain->SetBestChain(chainActive.GetLocator()); - pwalletMain->zwallet = std::make_unique(pwalletMain->strWalletFile); pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile); - - pwalletMain->zwallet->GetTracker().Init(); - pwalletMain->zwallet->LoadMintPoolFromDB(); - pwalletMain->zwallet->SyncWithChain(); } TestingSetup::~TestingSetup() diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index c76badbac9..14a8c51027 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -48,6 +48,8 @@ static const float RECONNECT_TIMEOUT_EXP = 1.5; * this is belt-and-suspenders sanity limit to prevent memory exhaustion. */ static const int MAX_LINE_LENGTH = 100000; +/** Default Tor SOCKS port */ +static const int DEFAULT_TOR_SOCKS_PORT = 9050; /****** Low-level TorControlConnection ********/ @@ -354,7 +356,7 @@ static bool WriteBinaryFile(const std::string &filename, const std::string &data class TorController { public: - TorController(struct event_base* base, const std::string& target); + TorController(struct event_base* base, const std::string& target, unsigned short onion_local_port); ~TorController(); /** Get name fo file to store private key in */ @@ -368,6 +370,13 @@ class TorController TorControlConnection conn; std::string private_key; std::string service_id; + /** Local 127.0.0.1 port that Tor should forward the hidden service to. + * Bound at init time on a listener flagged is_onion_listener=true so that + * accepted connections are classified as NET_ONION. 0 means no dedicated + * listener was bound and we will fall back to the shared listen port (in + * which case inbound onion peers are misclassified as IPv4, the + * pre-fix behavior). */ + const unsigned short onion_local_port; bool reconnect; struct event *reconnect_ev; float reconnect_timeout; @@ -376,9 +385,15 @@ class TorController std::vector cookie; /** ClientNonce for SAFECOOKIE auth */ std::vector clientNonce; + /** Whether this controller auto-configured name resolution via Tor. */ + bool name_proxy_configured; /** Callback for ADD_ONION result */ void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for GETINFO net/listeners/socks result */ + void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Apply the discovered (or fallback) Tor SOCKS endpoint as the onion proxy. */ + void ConfigureOnionProxy(const CService& resolved); /** Callback for AUTHENTICATE result */ void auth_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for AUTHCHALLENGE result */ @@ -394,10 +409,10 @@ class TorController static void reconnect_cb(evutil_socket_t fd, short what, void *arg); }; -TorController::TorController(struct event_base* _base, const std::string& _target): +TorController::TorController(struct event_base* _base, const std::string& _target, unsigned short _onion_local_port): base(_base), - target(_target), conn(base), reconnect(true), reconnect_ev(0), - reconnect_timeout(RECONNECT_TIMEOUT_START) + target(_target), conn(base), onion_local_port(_onion_local_port), reconnect(true), reconnect_ev(0), + reconnect_timeout(RECONNECT_TIMEOUT_START), name_proxy_configured(false) { reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); if (!reconnect_ev) @@ -459,29 +474,142 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& if (reply.code == 250) { LogPrint("tor", "tor: Authentication successful\n"); - // Now that we know Tor is running setup the proxy for onion addresses - // if -onion isn't set to something else. + // Now that we know Tor is running, discover its actual SOCKS port and + // set up the onion proxy, unless -onion is already set to something else. + // Querying the control port avoids hardcoding 9050 (Bitcoin Core pattern). if (GetArg("-onion", "") == "") { - CService resolved(LookupNumeric("127.0.0.1", 9050)); - proxyType addrOnion = proxyType(resolved, true); - SetProxy(NET_ONION, addrOnion); - SetLimited(NET_ONION, false); + if (!_conn.Command("GETINFO net/listeners/socks", + boost::bind(&TorController::get_socks_cb, this, _1, _2))) { + // Queueing the command failed (e.g. control connection lost), + // so get_socks_cb will never run. Apply the conventional + // default synchronously so onion outbound is still configured, + // matching the pre-discovery behaviour. + LogPrintf("tor: Error sending GETINFO net/listeners/socks; falling back to 127.0.0.1:%d\n", + DEFAULT_TOR_SOCKS_PORT); + ConfigureOnionProxy(LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT)); + } } // Finally - now create the service if (private_key.empty()) { // No private key, generate one private_key = "NEW:ED25519-V3"; // Explicitly request key type - see issue #9214 Bitcoin } + + // Forward inbound hidden-service traffic to the dedicated 127.0.0.1 + // listener bound at init time (see AppInitMain). Accepted connections + // on that listener are classified as NET_ONION so peers reaching us + // over Tor don't show up as IPv4 127.0.0.1 in the peer list. If init + // could not bind the dedicated listener (onion_local_port == 0), fall + // back to the shared listen port -- this preserves the pre-fix + // behavior (peers visible as IPv4) but keeps the hidden service + // working. + const unsigned short forward_port = onion_local_port != 0 ? onion_local_port : GetListenPort(); + // Request hidden service, redirect port. // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient // choice. TODO; refactor the shutdown sequence some day. - _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), + _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), forward_port), boost::bind(&TorController::add_onion_cb, this, _1, _2)); } else { LogPrintf("tor: Authentication failed\n"); } } +static CService LookupTorSocksListener(const std::string& listener) +{ + CService candidate; + if (!Lookup(listener.c_str(), candidate, DEFAULT_TOR_SOCKS_PORT, false)) { + return CService(); + } + if (candidate.IsValid()) { + return candidate; + } + if (!candidate.IsBindAny()) { + return CService(); + } + + // Tor may report wildcard SocksPort listeners such as 0.0.0.0:9050 or + // [::]:9050. They are not valid proxy destinations, but they mean the + // SOCKS listener is reachable locally on the same port. + return LookupNumeric(candidate.IsIPv6() ? "::1" : "127.0.0.1", candidate.GetPort()); +} + +void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply) +{ + // NOTE: We can only get here if -onion is unset + std::string socks_location; + CService resolved; + if (reply.code == 250) { + static const std::string SOCKS_PREFIX{"net/listeners/socks="}; + for (const std::string& line : reply.lines) { + if (line.starts_with(SOCKS_PREFIX)) { + const std::string port_list_str = line.substr(SOCKS_PREFIX.size()); + std::vector port_list; + boost::split(port_list, port_list_str, boost::is_any_of(" ")); + for (auto& portstr : port_list) { + if (portstr.empty()) continue; + // Strip surrounding quotes if present + if ((portstr.front() == '"' || portstr.front() == '\'') && + portstr.size() >= 2 && portstr.back() == portstr.front()) { + portstr = portstr.substr(1, portstr.size() - 2); + if (portstr.empty()) continue; + } + // Skip entries Tor may report that we can't use as a TCP + // proxy (e.g. "unix:/path/to/socket"), so they don't + // overwrite a usable TCP listener seen earlier. + const CService candidate = LookupTorSocksListener(portstr); + if (!candidate.IsValid()) continue; + if (!resolved.IsValid() || candidate.IsLocal()) { + socks_location = portstr; + resolved = candidate; + } + if (candidate.IsLocal()) + break; // prefer localhost over other interfaces + } + if (resolved.IsValid() && resolved.IsLocal()) + break; + } + } + if (!socks_location.empty()) { + LogPrint("tor", "tor: Get SOCKS port yielded %s\n", socks_location); + } else { + LogPrintf("tor: Get SOCKS port command returned nothing\n"); + } + } else if (reply.code == 510) { // 510 Unrecognized command + LogPrintf("tor: Get SOCKS port command failed with unrecognized command (You probably need to upgrade Tor)\n"); + } else { + LogPrintf("tor: Get SOCKS port command failed; error code %d\n", reply.code); + } + + if (!resolved.IsValid()) { + // No usable port from Tor; fall back to the conventional default. + resolved = LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT); + } + + ConfigureOnionProxy(resolved); +} + +void TorController::ConfigureOnionProxy(const CService& resolved) +{ + if (!resolved.IsValid()) { + LogPrintf("tor: Refusing to configure onion proxy: invalid SOCKS address\n"); + return; + } + LogPrint("tor", "tor: Configuring onion proxy for %s\n", resolved.ToString()); + proxyType addrOnion = proxyType(resolved, true); + SetProxy(NET_ONION, addrOnion); + if (!IsNetworkExplicitlyLimited(NET_ONION)) + SetLimited(NET_ONION, false); + // When clearnet is unreachable (e.g. -onlynet=onion), route name lookups + // through Tor so hostname resolution doesn't leak over clearnet. Refresh + // only the name proxy that this controller auto-configured, preserving + // explicit -proxy/-onion/-torsetup name proxies. + if (!IsReachable(NET_IPV4) && !IsReachable(NET_IPV6) && + (!HaveNameProxy() || name_proxy_configured)) { + name_proxy_configured = SetNameProxy(addrOnion); + } +} + /** Compute Tor SAFECOOKIE response. * * ServerHash is computed as: @@ -665,15 +793,19 @@ void TorController::reconnect_cb(evutil_socket_t fd, short what, void *arg) /****** Thread ********/ struct event_base *base; boost::thread torControlThread; +/** Port for the dedicated onion-forwarded local listener, captured by + * StartTorControl() and consumed by TorControlThread() when it constructs + * the TorController. 0 means no dedicated listener was bound at init. */ +static unsigned short g_tor_onion_local_port = 0; static void TorControlThread() { - TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); + TorController ctrl(base, GetArg("-torcontrol", DEFAULT_TOR_CONTROL), g_tor_onion_local_port); event_base_dispatch(base); } -void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) +void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler, unsigned short onion_local_port) { assert(!base); #ifdef WIN32 @@ -687,6 +819,7 @@ void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler) return; } + g_tor_onion_local_port = onion_local_port; torControlThread = boost::thread(boost::bind(&TraceThread, "torcontrol", &TorControlThread)); } diff --git a/src/torcontrol.h b/src/torcontrol.h index 72dc82c5b1..13dd0d94bd 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -13,7 +13,21 @@ extern const std::string DEFAULT_TOR_CONTROL; static const bool DEFAULT_LISTEN_ONION = true; -void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler); +/** + * Start the Tor control thread. + * + * @param onion_local_port Local 127.0.0.1 port that Tor should forward + * inbound hidden-service traffic to. Must be a port + * on a listener bound with is_onion_listener=true so + * that accepted connections are classified as + * NET_ONION. Pass 0 if no dedicated listener could be + * bound; in that case the Tor controller will fall + * back to forwarding to the shared listen port and + * inbound peers will be classified as IPv4 (the + * pre-fix behavior). + */ +void StartTorControl(boost::thread_group& threadGroup, CScheduler& scheduler, + unsigned short onion_local_port = 0); void InterruptTorControl(); void StopTorControl(); diff --git a/src/txdb.cpp b/src/txdb.cpp index c57ebaf3b9..492c401e71 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -575,7 +575,7 @@ std::pair classifyAddress(txnouttype type, std::vector(addresses.front().begin(), addresses.front().end())); - } else if(type == TX_PUBKEYHASH) { + } else if(type == TX_PUBKEYHASH || type == TX_SPARKNAMEFEE) { result.first = AddressType::payToPubKeyHash; result.second = uint160(std::vector(addresses.front().begin(), addresses.front().end())); } else if(type == TX_EXCHANGEADDRESS) { diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 93e8cf9d07..c965416b32 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -518,6 +518,10 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { NotifyEntryRemoved(it->GetSharedTx(), reason); const uint256 hash = it->GetTx().GetHash(); + + removeAddressIndex(hash); + removeSpentIndex(hash); + if (!it->GetTx().HasPrivateInputs()) { LogPrintf("removeUnchecked txHash=%s (no private inputs)\n", hash.ToString()); BOOST_FOREACH(const CTxIn& txin, it->GetTx().vin) @@ -579,39 +583,6 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) sporkManager.RemovedFromMemoryPool(it->GetTx()); } - else if (it->GetTx().IsLelantusTransaction()) { - // Remove mints and spend serials from lelantus mempool state - const CTransaction &tx = it->GetTx(); - if (tx.IsLelantusJoinSplit()) { - std::vector serials; - try { - serials = lelantus::GetLelantusJoinSplitSerialNumbers(tx, tx.vin[0]); - for (const Scalar &serial: serials) - lelantusState.RemoveSpendFromMempool(serial); - } - catch (CBadTxIn&) { - } - } - - BOOST_FOREACH(const CTxOut &txout, tx.vout) - { - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - GroupElement pubCoinValue; - try { - if (txout.scriptPubKey.IsLelantusMint()) { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pubCoinValue); - } else { - std::vector encryptedValue; - lelantus::ParseLelantusJMintScript(txout.scriptPubKey, pubCoinValue, encryptedValue); - } - lelantusState.RemoveMintFromMempool(pubCoinValue); - } - catch (std::invalid_argument&) { - } - } - } - } - else if (it->GetTx().IsSparkTransaction()) { // Remove mints and spends from spark mempool state const CTransaction &tx = it->GetTx(); @@ -680,7 +651,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); mapAddress.insert(std::make_pair(key, delta)); inserted.push_back(key); - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash() || prevout.scriptPubKey.IsSparkNameFee()) { std::vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); CMempoolAddressDeltaKey key(AddressType::payToPubKeyHash, uint160(hashBytes), txhash, j, 1); CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); @@ -702,7 +673,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC CMempoolAddressDeltaKey key(AddressType::payToScriptHash, uint160(hashBytes), txhash, k, 0); mapAddress.insert(std::make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); inserted.push_back(key); - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + } else if (out.scriptPubKey.IsPayToPublicKeyHash() || out.scriptPubKey.IsSparkNameFee()) { std::vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); std::pair ret; CMempoolAddressDeltaKey key(AddressType::payToPubKeyHash, uint160(hashBytes), txhash, k, 0); @@ -771,7 +742,7 @@ void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCac if (prevout.scriptPubKey.IsPayToScriptHash()) { addressHash = uint160(std::vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); addressType = AddressType::payToScriptHash; - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash() || prevout.scriptPubKey.IsSparkNameFee()) { addressHash = uint160(std::vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); addressType = AddressType::payToPubKeyHash; } else if (prevout.scriptPubKey.IsPayToExchangeAddress()) { @@ -1128,7 +1099,6 @@ void CTxMemPool::_clear() lastRollingFeeUpdate = GetTime(); blockSinceLastRollingFeeBump = false; rollingMinimumFeeRate = 0; - lelantusState.Reset(); sparkState.Reset(); ++nTransactionsUpdated; } diff --git a/src/txmempool.h b/src/txmempool.h index 1eb257ec70..09ff7a3dee 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -6,6 +6,10 @@ #ifndef BITCOIN_TXMEMPOOL_H #define BITCOIN_TXMEMPOOL_H +// Include C++ before any other headers to avoid macro conflicts with +// project headers (e.g. util.h, random.h) that break standard library parsing. +#include + #include #include #include "addressindex.h" @@ -23,7 +27,6 @@ #include "random.h" #include "netaddress.h" #include "bls/bls.h" -#include "lelantus.h" #include "spark/state.h" #include "evo/spork.h" @@ -522,7 +525,6 @@ class CTxMemPool const setEntries & GetMemPoolParents(txiter entry) const; const setEntries & GetMemPoolChildren(txiter entry) const; - lelantus::CLelantusMempoolState lelantusState; spark::CSparkMempoolState sparkState; std::map> sparkNames; // used to rule out duplicate names diff --git a/src/util.cpp b/src/util.cpp index e8567d3449..c4502e332d 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -39,6 +39,7 @@ #endif // __linux__ #include +#include // for INT_MAX #include #include #include @@ -776,6 +777,10 @@ int RaiseFileDescriptorLimit(int nMinFD) { setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } + // Clamp to INT_MAX to prevent truncation when rlim_cur is + // RLIM_INFINITY or any value exceeding the int return type. + if (limitFD.rlim_cur > (rlim_t)INT_MAX) + return INT_MAX; return limitFD.rlim_cur; } return nMinFD; // getrlimit failed, assume it's fine diff --git a/src/validation.cpp b/src/validation.cpp index 3f89eea07d..616fbf6e39 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -42,7 +42,6 @@ #include "wallet/walletdb.h" #endif // ENABLE_WALLET #include "batchproof_container.h" -#include "lelantus.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include "validationinterface.h" @@ -113,6 +112,7 @@ bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; size_t nCoinCacheUsage = 5000 * 300; uint64_t nPruneTarget = 0; int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; +int64_t nMinimumInputValue = 0; bool fEnableReplacement = DEFAULT_ENABLE_REPLACEMENT; uint256 hashAssumeValid; @@ -134,7 +134,6 @@ static void CheckBlockIndex(const Consensus::Params& consensusParams); CScript COINBASE_FLAGS; const std::string strMessageMagic = "Zcoin Signed Message:\n"; -const std::string strLelantusMessageMagic = "Lelantus signed Message:\n"; // Internal stuff namespace { @@ -327,74 +326,6 @@ bool CheckFinalTx(const CTransaction &tx, int flags) return IsFinalTx(tx, nBlockHeight, nBlockTime); } -bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& vchSig, const std::string& message) -{ - CTransactionRef tx; - uint256 hashBlock; - if(!GetTransaction(txid, tx, Params().GetConsensus(), hashBlock, true)) - return false; - - if (tx->IsLelantusJoinSplit()) { - CHashWriter ss(SER_GETHASH, 0); - ss << strLelantusMessageMagic; - ss << message; - - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(*tx); - } catch (const std::exception&) { - return false; - } - const auto& pubKeys = joinsplit->GetEcdsaPubkeys(); - - if((pubKeys.size() *64) != vchSig.size()) { - LogPrintf("Verification to serialNumbers and ecdsaSignatures/ecdsaPubkeys number mismatch."); - return false; - } - - uint32_t count = 0; - - for (const auto& pub : pubKeys) { - ss << count; - uint256 metahash = ss.GetHash(); - - // Check sizes - if (pub.size() != 33 ) { - LogPrintf("Verification failed due to incorrect size of ecdsaSignature."); - return false; - } - - // Verify signature - secp256k1_pubkey pubkey; - secp256k1_ecdsa_signature signature; - - if (!secp256k1_ec_pubkey_parse(OpenSSLContext::get_context(), &pubkey, pub.data(), 33)) { - LogPrintf("Verification failed due to unable to parse ecdsaPubkey."); - return false; - } - - if (1 != secp256k1_ecdsa_signature_parse_compact(OpenSSLContext::get_context(), &signature, &vchSig[count * 64]) ) { - LogPrintf("Verification failed due to signature cannot be parsed."); - return false; - } - - if (!secp256k1_ecdsa_verify( - OpenSSLContext::get_context(), &signature, metahash.begin(), &pubkey)) { - LogPrintf("Verification failed due to signature cannot be verified."); - return false; - } - - count++; - } - } else if (tx->IsCoinBase()) { - throw std::runtime_error("This is a coinbase transaction and not a private transaction"); - } else { - throw std::runtime_error("Currently this is allowed only for Lelantus transactions"); - } - - return true; -} - /** * Calculates the block height and previous block's median time past at * which the transaction will be considered final in the context of BIP 68. @@ -642,7 +573,7 @@ int GetUTXOConfirmations(const COutPoint& outpoint) return (nPrevoutHeight > -1 && chainActive.Tip()) ? chainActive.Height() - nPrevoutHeight + 1 : -1; } -bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight, bool isCheckWallet, bool fStatefulZerocoinCheck, lelantus::CLelantusTxInfo* lelantusTxInfo, spark::CSparkTxInfo* sparkTxInfo) +bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight, bool isCheckWallet, bool fStatefulZerocoinCheck, spark::CSparkTxInfo* sparkTxInfo) { LogPrintf("CheckTransaction nHeight=%d, isVerifyDB=%d, isCheckWallet=%d, txHash=%s\n", nHeight, (int)isVerifyDB, (int)isCheckWallet, tx.GetHash().ToString()); @@ -727,6 +658,23 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe if (hasExchangeUTXOs && !isVerifyDB && nTxHeight < ::Params().GetConsensus().nExchangeAddressStartBlock) return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + // Spark name fee outputs (with OP_SPARKNAMEID) are only allowed after nSparkNamesV21StartBlock + for (const auto &vout : tx.vout) { + if (vout.scriptPubKey.IsSparkNameFee()) { + if (!isVerifyDB && nTxHeight < ::Params().GetConsensus().nSparkNamesV21StartBlock) + return state.DoS(100, false, REJECT_INVALID, "bad-sparkname-fee-output"); + break; + } + } + + // After Spark Names v2.1, Spark SMint (OP_SPARKSMINT) outputs must have nValue zero — value is committed only in the script. + if (nTxHeight >= ::Params().GetConsensus().nSparkNamesV21StartBlock) { + for (const auto &vout : tx.vout) { + if (vout.scriptPubKey.IsSparkSMint() && vout.nValue != 0) + return state.DoS(100, false, REJECT_INVALID, "bad-spark-smint-nvalue"); + } + } + if (tx.IsCoinBase()) { size_t minCbSize = 2; @@ -748,13 +696,6 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe || tx.IsSparkSpend())) return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); - if (tx.IsLelantusTransaction()) { - if (hasExchangeUTXOs) - return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); - if (!CheckLelantusTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, lelantusTxInfo)) - return false; - } - if (tx.IsSparkTransaction()) { if (hasExchangeUTXOs) return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); @@ -763,25 +704,32 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe } const auto ¶ms = ::Params().GetConsensus(); + // Use nTxHeight (not nHeight) so mempool calls with nHeight==INT_MAX use chain tip like + // nExchangeAddressStartBlock checks above — same idea as Sigma/Lelantus mempool gates. if (tx.IsZerocoinSpend() || tx.IsZerocoinMint()) { - if (!isVerifyDB && nHeight >= params.nDisableZerocoinStartBlock) + if (!isVerifyDB && nTxHeight >= params.nDisableZerocoinStartBlock) return state.DoS(1, error("Zerocoin is disabled at this point")); } if (tx.IsSigmaSpend() || tx.IsSigmaMint()) { - if (!isVerifyDB && nHeight >= (::Params().GetConsensus().nLelantusStartBlock + 5)) - return state.DoS(1, error( "Sigma already is not available, start using Lelantus.")); + if (!isVerifyDB && nTxHeight >= params.nLelantusStartBlock) + return state.DoS(1, error("Sigma already is not available, start using Lelantus.")); + } + + if (tx.IsLelantusJoinSplit() || tx.IsLelantusMint()) { + if (!isVerifyDB && nTxHeight >= params.nLelantusGracefulPeriod) + return state.DoS(1, error("Lelantus already is not available, start using Spark.")); } if (tx.IsZerocoinRemint()) { - if (!isVerifyDB && (nHeight < params.nSigmaStartBlock || nHeight >= params.nSigmaStartBlock + params.nZerocoinToSigmaRemintWindowSize)) + if (!isVerifyDB && (nTxHeight < params.nSigmaStartBlock || nTxHeight >= params.nSigmaStartBlock + params.nZerocoinToSigmaRemintWindowSize)) // we allow transactions of remint type only during specific window return false; } } bool isInWhitelist = Params().GetConsensus().txidWhitelist.count(tx.GetHash()) > 0; - if (nHeight >= ::Params().GetConsensus().nStartBlacklist && !isInWhitelist) { + if (nTxHeight >= ::Params().GetConsensus().nStartBlacklist && !isInWhitelist) { for (const auto& vin : tx.vin) { if (txid_blacklist.count(vin.prevout.hash.GetHex()) > 0) { return state.DoS(100, error("Spending this tx is temporarily disabled"), @@ -818,8 +766,6 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, if (tx.nType == TRANSACTION_SPORK && !(nHeight >= consensusParams.nEvoSporkStartBlock && nHeight < consensusParams.nEvoSporkStopBlock)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); - if (tx.nType == TRANSACTION_LELANTUS && nHeight < consensusParams.nLelantusV3PayloadStartBlock) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); if (tx.nType == TRANSACTION_SPARK && nHeight < consensusParams.nSparkStartBlock) return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } @@ -872,8 +818,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C bool isCheckWalletTransaction, bool markFiroSpendTransactionSerial) { bool fTestNet = Params().GetConsensus().IsTestnet(); - LogPrintf("AcceptToMemoryPoolWorker(), lelantusJoinSplit=%d, fTestNet=%d\n", - (int)ptx->IsLelantusJoinSplit(), (int)fTestNet); const CTransaction& tx = *ptx; const uint256 hash = tx.GetHash(); @@ -915,13 +859,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C REJECT_INVALID, "bad-txns-zerocoin"); } } - - //lelantus - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - std::vector lelantusSpendSerials; - std::vector lelantusMintPubcoins; - std::vector lelantusAmounts; - // Spark spark::CSparkState *sparkState = spark::CSparkState::GetState(); std::vector sparkMintCoins; @@ -931,45 +868,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C { LOCK(pool.cs); - if (tx.IsLelantusJoinSplit()) { - if (tx.vin.size() > 1) { - return state.Invalid(false, REJECT_CONFLICT, "txn-invalid-lelantus-joinsplit"); - } - std::unique_ptr joinsplit; - - try { - joinsplit = lelantus::ParseLelantusJoinSplit(tx); - } - catch (CBadTxIn&) { - return state.Invalid(false, REJECT_CONFLICT, "txn-invalid-lelantus-joinsplit"); - } - catch (const std::exception &) { - return state.Invalid(false, REJECT_CONFLICT, "failed to deserialize joinsplit"); - } - - const std::vector &ids = joinsplit->getCoinGroupIds(); - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - - if (joinsplit->isSigmaToLelantus() && chainActive.Height() >= consensus.nSigmaEndBlock) { - return state.DoS(100, error("Sigma pool already closed."), - REJECT_INVALID, "txn-invalid-lelantus-joinsplit"); - } - - if (serials.size() != ids.size()) - return state.Invalid(false, REJECT_CONFLICT, "txn-invalid-lelantus-joinsplit"); - - for (size_t i = 0; i < serials.size(); ++i) { - if (!serials[i].isMember() || serials[i].isZero()) - return state.Invalid(false, REJECT_INVALID, "txn-invalid-lelantus-joinsplit-serial"); - - if (lelantusState->IsUsedCoinSerial(serials[i]) || pool.lelantusState.HasCoinSerial(serials[i])) { - LogPrintf("AcceptToMemoryPool(): lelantus serial number %s has been used\n", - serials[i].tostring()); - return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); - } - lelantusSpendSerials.push_back(serials[i]); - } - } else if (tx.IsSparkSpend()) { + if (tx.IsSparkSpend()) { if (tx.vin.size() > 1) { return state.Invalid(false, REJECT_CONFLICT, "txn-invalid-spark-spend"); } @@ -990,7 +889,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } CSparkNameManager *sparkNameManager = CSparkNameManager::GetInstance(); - if (!sparkNameManager->CheckSparkNameTx(tx, chainActive.Height(), state, &sparkNameData)) + if (!sparkNameManager->CheckSparkNameTx(tx, chainActive.Height() + 1, state, &sparkNameData)) return false; if (!sparkNameData.name.empty() && @@ -1001,23 +900,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - BOOST_FOREACH(const CTxOut &txout, tx.vout) - { - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - GroupElement pubCoinValue; - try { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pubCoinValue); - } catch (std::invalid_argument&) { - return state.DoS(100, false, PUBCOIN_NOT_VALIDATE, "bad-txns-zerocoin"); - } - if (lelantusState->HasCoin(pubCoinValue) || pool.lelantusState.HasMint(pubCoinValue)) { - LogPrintf("AcceptToMemoryPool(): lelantus mint with the same value %s is already in the mempool\n", pubCoinValue.tostring()); - return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); - } - lelantusMintPubcoins.push_back(pubCoinValue); - } - } - if (tx.IsSparkTransaction()) { try { sparkMintCoins = spark::GetSparkMintCoins(tx); @@ -1208,16 +1090,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount nFees; if (!tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { nFees = nValueIn - nValueOut; - } else if (tx.IsLelantusJoinSplit()) { - try { - nFees = lelantus::ParseLelantusJoinSplit(tx)->getFee(); - } - catch (CBadTxIn&) { - return state.DoS(0, false, REJECT_INVALID, "unable to parse joinsplit"); - } - catch (const std::exception &) { - return state.DoS(0, false, REJECT_INVALID, "failed to deserialize joinsplit"); - } } else { try { nFees = spark::ParseSparkSpend(tx).getFee(); @@ -1556,20 +1428,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - if (tx.IsLelantusJoinSplit()) { - if(markFiroSpendTransactionSerial) { - for (const auto &spendSerial: lelantusSpendSerials) - pool.lelantusState.AddSpendToMempool(spendSerial, hash); - } - LogPrintf("Updating mint tracker state from Mempool..\n"); -#ifdef ENABLE_WALLET - if (!GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - LogPrintf("Updating spend state from Mempool..\n"); - pwalletMain->zwallet->GetTracker().UpdateJoinSplitStateFromMempool(lelantusSpendSerials); - } -#endif - } - if (tx.IsSparkSpend()) { if(markFiroSpendTransactionSerial) { @@ -1596,32 +1454,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C LogPrintf("Adding Spark mints to Mempool..\n"); pwalletMain->sparkWallet->UpdateMintStateFromMempool(sparkMintCoins, hash); } - - if(tx.IsLelantusMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - LogPrintf("Updating mint state from Mempool..\n"); - BOOST_FOREACH(const CTxOut &txout, tx.vout) - { - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - GroupElement pubCoinValue; - uint64_t amount = 0; - try { - if (txout.scriptPubKey.IsLelantusMint()) { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pubCoinValue); - amount = txout.nValue; - } else { - std::vector encryptedValue; - lelantus::ParseLelantusJMintScript(txout.scriptPubKey, pubCoinValue, encryptedValue); - if(!pwalletMain->DecryptMintAmount(encryptedValue, pubCoinValue, amount)) - amount = 0; - } - } catch (std::invalid_argument&) { - return state.DoS(100, false, PUBCOIN_NOT_VALIDATE, "bad-txns-zerocoin"); - } - lelantusAmounts.push_back(amount); - } - } - pwalletMain->zwallet->GetTracker().UpdateLelantusMintStateFromMempool(lelantusMintPubcoins, lelantusAmounts); - } #endif GetMainSignals().SyncTransaction(tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); @@ -2119,24 +1951,11 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins } CAmount nTxFee; - if(!tx.IsLelantusJoinSplit()) { - // at Lelantus JoinSplit we check balance inside cryptographic proof verification - if (nValueIn < tx.GetValueOut()) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, - strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut()))); - // Tally transaction fees - nTxFee = nValueIn - tx.GetValueOut(); - } else { - try { - nTxFee = lelantus::ParseLelantusJoinSplit(tx)->getFee(); - } - catch (CBadTxIn&) { - return state.DoS(0, false, REJECT_INVALID, "unable to parse joinsplit"); - } - catch (const std::exception &) { - return state.DoS(0, false, REJECT_INVALID, "failed to deserialize joinsplit"); - } - } + if (nValueIn < tx.GetValueOut()) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, + strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut()))); + // Tally transaction fees + nTxFee = nValueIn - tx.GetValueOut(); if (nTxFee < 0) return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-negative"); nFees += nTxFee; @@ -2328,13 +2147,6 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi } } } - } else if (tx.IsLelantusJoinSplit()) { - if(tx.vin.size() > 1 || !tx.vin[0].scriptSig.IsLelantusJoinSplit()) { - return state.DoS( - 100, false, - REJECT_MALFORMED, - " Can't mix Lelantus joinsplit input with regular ones or have more than one input"); - } } else if (tx.IsSparkSpend()) { if(tx.vin.size() > 1) { return state.DoS( @@ -2541,17 +2353,9 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s // At this point, all of txundo.vprevout should have been moved out. } - if (tx.IsLelantusJoinSplit()) { - try { - nFees += lelantus::ParseLelantusJoinSplit(tx)->getFee(); - } - catch (const std::exception &) { - // do nothing - } - } - else if (tx.IsSparkSpend()) { + if (tx.IsSparkSpend()) { try { - nFees = spark::ParseSparkSpend(tx).getFee(); + nFees += spark::ParseSparkSpend(tx).getFee(); } catch (const std::exception &) { // do nothing @@ -2885,8 +2689,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin batchProofContainer->fCollectProofs = ((GetSystemTimeInSeconds() - pindex->GetBlockTime()) > 86400) && GetBoolArg("-batching", true); batchProofContainer->init(); std::size_t nSigma = 0; + std::size_t nLelantus = 0; - block.lelantusTxInfo = std::make_shared(); block.sparkTxInfo = std::make_shared(); for (unsigned int i = 0; i < block.vtx.size(); i++) { @@ -2936,15 +2740,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if(tx.IsSigmaSpend()) ++nSigma; if(tx.IsLelantusJoinSplit()) { - try { - nFees += lelantus::ParseLelantusJoinSplit(tx)->getFee(); - } - catch (CBadTxIn&) { - return state.DoS(0, false, REJECT_INVALID, "unable to parse joinsplit"); - } - catch (const std::exception &) { - return state.DoS(0, false, REJECT_INVALID, "failed to deserialize joinsplit"); - } + ++nLelantus; } if(tx.IsSparkSpend()) { @@ -2959,8 +2755,11 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } } + if (!MoneyRange(nFees)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); + // Check transaction against signa/lelantus state - if (!CheckTransaction(tx, state, false, txHash, false, pindex->nHeight, false, true, block.lelantusTxInfo.get(), block.sparkTxInfo.get())) + if (!CheckTransaction(tx, state, false, txHash, false, pindex->nHeight, false, true, block.sparkTxInfo.get())) return state.DoS(100, error("stateful zerocoin check failed"), REJECT_INVALID, "bad-txns-zerocoin"); } @@ -3005,7 +2804,6 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } - block.lelantusTxInfo->Complete(); block.sparkTxInfo->Complete(); int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; @@ -3019,9 +2817,16 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin //btzc: Add time to check CAmount blockSubsidy = GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus(), pindex->nTime); CAmount blockReward = nFees + blockSubsidy; - // as we removed sigma lib, are are not able to get the fee, and as blocks are historical, we just skip fee check on blocks containing sigma tx - if(nSigma > 0 && pindex->nHeight >= ::Params().GetConsensus().nSigmaStartBlock && pindex->nHeight < ::Params().GetConsensus().nLelantusStartBlock) + const auto& consensusParams = ::Params().GetConsensus(); + // Sigma: use coinbase as limit when block has Sigma tx; on strip (nLelantusStartBlock<=1) any height, else only Sigma era (height < Lelantus) + if (nSigma > 0 && pindex->nHeight >= consensusParams.nSigmaStartBlock && + (consensusParams.nLelantusStartBlock <= 1 || pindex->nHeight < consensusParams.nLelantusStartBlock)) + blockReward = block.vtx[0]->GetValueOut(); + + // Lelantus: use coinbase as limit (Lelantus fees are not in nFees), including in Spark era + if (nLelantus > 0 && pindex->nHeight >= consensusParams.nLelantusStartBlock) blockReward = block.vtx[0]->GetValueOut(); + if (block.vtx[0]->GetValueOut() > blockReward) return state.DoS(100, error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)", @@ -3095,8 +2900,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } } - if (!lelantus::ConnectBlockLelantus(state, chainparams, pindex, &block, fJustCheck) || - !spark::ConnectBlockSpark(state, chainparams, pindex, &block, fJustCheck)) + if (!spark::ConnectBlockSpark(state, chainparams, pindex, &block, fJustCheck)) return false; if (!sporkManager->IsBlockAllowed(block, pindex, state)) @@ -3178,38 +2982,9 @@ void static RemoveConflictingPrivacyTransactionsFromMempool(const CBlock &block) LOCK(mempool.cs); // Erase conflicting sigma/lelantus txs from the mempool - - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); spark::CSparkState *sparkState = spark::CSparkState::GetState(); BOOST_FOREACH(CTransactionRef tx, block.vtx) { - if (tx->IsLelantusJoinSplit()) { - std::vector serials; - try { - serials = lelantus::GetLelantusJoinSplitSerialNumbers(*tx, tx->vin[0]); - } catch (CBadTxIn&) { - // nothing - } - - uint256 thisTxHash = tx->GetHash(); - uint256 conflictingTxHash; - for(const auto& serial : serials) { - conflictingTxHash = lelantusState->GetMempoolConflictingTxHash(serial); - if(!conflictingTxHash.IsNull()) - break; - } - if (!conflictingTxHash.IsNull() && conflictingTxHash != thisTxHash) { - std::list removed; - auto pTx = mempool.get(conflictingTxHash); - if (pTx) - mempool.removeRecursive(*pTx); - LogPrintf("ConnectBlock: removed conflicting lelantus joinsplit tx %s from the mempool\n", - conflictingTxHash.ToString()); - } - - // In any case we need to remove serial from mempool set - lelantusState->RemoveSpendFromMempool(serials); - } - else if (tx->IsSparkSpend()) { + if (tx->IsSparkSpend()) { std::vector lTags; try { lTags = spark::GetSparkUsedTags(*tx); @@ -3238,16 +3013,6 @@ void static RemoveConflictingPrivacyTransactionsFromMempool(const CBlock &block) } BOOST_FOREACH(const CTxOut &txout, tx->vout) { - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - GroupElement pubCoinValue; - try { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pubCoinValue); - } catch (std::invalid_argument&) { - // nothing - } - lelantusState->RemoveMintFromMempool(pubCoinValue); - } - if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { try { const spark::Params* params = spark::Params::get_default(); @@ -3468,39 +3233,14 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara // retrieve all mints - block.lelantusTxInfo = std::make_shared(); block.sparkTxInfo = std::make_shared(); - std::unordered_map lelantusSerialsToRemove; - std::vector rangeProofsToRemove; std::vector sparkTransactionsToRemove; for (CTransactionRef tx : block.vtx) { CheckTransaction(*tx, state, false, tx->GetHash(), false, pindexDelete->pprev->nHeight, - false, false, block.lelantusTxInfo.get(), block.sparkTxInfo.get()); + false, false, block.sparkTxInfo.get()); if(GetBoolArg("-batching", true)) { - if (tx->IsLelantusJoinSplit()) { - std::unique_ptr joinsplit; - - try { - joinsplit = lelantus::ParseLelantusJoinSplit(*tx); - } - catch (const std::exception &) { - continue; - } - - const std::vector &ids = joinsplit->getCoinGroupIds(); - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - - if (serials.size() != ids.size()) { - continue; - } - - for (size_t i = 0; i < serials.size(); i++) { - lelantusSerialsToRemove.insert(std::make_pair(serials[i], ids[i])); - } - - rangeProofsToRemove.push_back(joinsplit->getLelantusProof().bulletproofs); - } else if (tx->IsSparkSpend()) { + if (tx->IsSparkSpend()) { try { spark::SpendTransaction spendTransaction = spark::ParseSparkSpend(*tx); sparkTransactionsToRemove.push_back(spendTransaction); @@ -3526,17 +3266,9 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara } LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); - lelantus::DisconnectTipLelantus(block, pindexDelete); spark::DisconnectTipSpark(block, pindexDelete); BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); - if (lelantusSerialsToRemove.size() > 0) { - batchProofContainer->removeLelantus(lelantusSerialsToRemove); - } - - if (rangeProofsToRemove.size() > 0) { - batchProofContainer->remove(rangeProofsToRemove); - } for (auto& sparkTransaction : sparkTransactionsToRemove) { batchProofContainer->remove(sparkTransaction); @@ -3590,15 +3322,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara #ifdef ENABLE_WALLET // update mint/spend wallet - if (!GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { - if (block.lelantusTxInfo->spentSerials.size() > 0) { - pwalletMain->zwallet->GetTracker().UpdateSpendStateFromBlock(block.lelantusTxInfo->spentSerials); - } - - if (block.lelantusTxInfo->mints.size() > 0) { - pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(block.lelantusTxInfo->mints); - } - + if (!GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet && block.sparkTxInfo) { if (block.sparkTxInfo->spentLTags.size() > 0) { pwalletMain->sparkWallet->RemoveSparkSpends(block.sparkTxInfo->spentLTags); } @@ -3666,6 +3390,8 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, RemoveConflictingPrivacyTransactionsFromMempool(blockConnecting); GetMainSignals().BlockChecked(blockConnecting, state); if (!rv) { + LogPrintf("ConnectTip(): ConnectBlock failed at height=%d, hash=%s: %s\n", + pindexNew->nHeight, pindexNew->GetBlockHash().ToString(), FormatStateMessage(state)); if (state.IsInvalid()) InvalidBlockFound(pindexNew, state); return error("ConnectTip(): ConnectBlock %s failed", pindexNew->GetBlockHash().ToString()); @@ -3691,19 +3417,10 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, UpdateTip(pindexNew, chainparams); #ifdef ENABLE_WALLET - // Sync with HDMint wallet + // Sync with Spark wallet - if (!GetBoolArg("-disablewallet", false) && pwalletMain->zwallet && blockConnecting.lelantusTxInfo) { + if (!GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet && blockConnecting.sparkTxInfo) { LogPrintf("Checking if block contains wallet mints..\n"); - if (blockConnecting.lelantusTxInfo->spentSerials.size() > 0) { - LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); - pwalletMain->zwallet->GetTracker().UpdateSpendStateFromBlock(blockConnecting.lelantusTxInfo->spentSerials); - } - - if (blockConnecting.lelantusTxInfo->mints.size() > 0) { - LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); - pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(blockConnecting.lelantusTxInfo->mints); - } if (blockConnecting.sparkTxInfo->spentLTags.size() > 0) { LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); @@ -3711,7 +3428,7 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, } if (blockConnecting.sparkTxInfo->mints.size() > 0) { - LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); + LogPrintf("SparkWallet: UpdateMintStateFromBlock. [height: %d]\n", GetHeight()); pwalletMain->sparkWallet->UpdateMintStateFromBlock(blockConnecting); } } @@ -4404,6 +4121,8 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const uint256 final_hash; if (block.IsProgPow()) { + if (block.mix_hash.IsNull()) + return state.DoS(50, false, REJECT_INVALID, "invalid-mixhash", false, "mix_hash cannot be null"); // If we use GetProgPowHashFull user may experience very slow header sync // We use simplified function for header check and then will use full check in ConnectBlock() // This won't make sync faster but it will give user a better experience @@ -4424,14 +4143,10 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const } bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot, int nHeight, bool isVerifyDB) { - // CheckBlock not only checks the block, but also fills up sparkTxInfo, lelantusTxInfo and sigmaTxInfo. - if (!block.lelantusTxInfo) - block.lelantusTxInfo = std::make_shared(); + // CheckBlock not only checks the block, but also fills up sparkTxInfo. if (!block.sparkTxInfo) block.sparkTxInfo = std::make_shared(); - LogPrintf("CheckBlock() nHeight=%d, blockHash= %s, isVerifyDB = %d\n", nHeight, block.GetHash().ToString(), isVerifyDB); - // These are checks that are independent of context. if (block.fChecked) @@ -4480,13 +4195,14 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P if (block.vtx[i]->IsCoinBase()) return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); - // Check transactions + // Check transactions (when called from ProcessNewBlock, nHeight is INT_MAX and we derive it here) if (nHeight == INT_MAX) nHeight = GetNHeight(block.GetBlockHeader()); + LogPrintf("CheckBlock() nHeight=%d, blockHash=%s, isVerifyDB=%d\n", nHeight, block.GetHash().ToString(), isVerifyDB); for (CTransactionRef tx : block.vtx) { // We don't check transactions against sigma/lelantus state here, we'll check it again later in ConnectBlock - if (!CheckTransaction(*tx, state, false, tx->GetHash(), isVerifyDB, nHeight, false, false, NULL, NULL)) + if (!CheckTransaction(*tx, state, false, tx->GetHash(), isVerifyDB, nHeight, false, false, NULL)) return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); @@ -4499,14 +4215,11 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", false, "out-of-bounds SigOpCount"); - if (fCheckPOW && fCheckMerkleRoot) - block.fChecked = true; - - if (!lelantus::CheckLelantusBlock(state, block)) + if (!spark::CheckSparkBlock(state, block, nHeight)) return false; - if (!spark::CheckSparkBlock(state, block)) - return false; + if (fCheckPOW && fCheckMerkleRoot) + block.fChecked = true; return true; } @@ -4975,6 +4688,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptrGetHash().ToString(), FormatStateMessage(state)); GetMainSignals().BlockChecked(*pblock, state); return error("%s: AcceptBlock FAILED", __func__); } @@ -4983,8 +4697,10 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptrnHeight % 1000 == 0) { LogPrint("reindex", "Block Import: already had block %s at height %d\n", hash.ToString(), mapBlockIndex[hash]->nHeight); } diff --git a/src/validation.h b/src/validation.h index 5e71dd92d8..48f1bd776d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -189,7 +189,6 @@ extern uint64_t nLastBlockTx; extern uint64_t nLastBlockSize; extern uint64_t nLastBlockWeight; extern const std::string strMessageMagic; -extern const std::string strLelantusMessageMagic; extern CWaitableCriticalSection csBestBlock; extern CConditionVariable cvBlockChange; extern std::atomic_bool fImporting; @@ -419,7 +418,7 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight); /** Transaction validation functions */ /** Context-independent validity checks */ -bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight = INT_MAX, bool isCheckWallet = false, bool fStatefulZerocoinCheck = true, lelantus::CLelantusTxInfo* lelantusTxInfo = NULL, spark::CSparkTxInfo* sparkTxInfo = NULL); +bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight = INT_MAX, bool isCheckWallet = false, bool fStatefulZerocoinCheck = true, spark::CSparkTxInfo* sparkTxInfo = NULL); namespace Consensus { @@ -449,9 +448,6 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime); */ bool CheckFinalTx(const CTransaction &tx, int flags = -1); - -bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& vchSig, const std::string& message); - /** * Test whether the LockPoints height and time are still valid on the current chain */ diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index d21c2acb01..d585589319 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -7,11 +7,6 @@ add_library(firo_wallet STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/../activemasternode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../masternode-sync.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../lelantus.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../hdmint/hdmint.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../hdmint/mintpool.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../hdmint/wallet.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../hdmint/tracker.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../spark/state.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../spark/sparkwallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../spark/primitives.cpp @@ -28,8 +23,6 @@ add_library(firo_wallet STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/db.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rpcdump.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rpcwallet.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/txbuilder.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantusjoinsplitbuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/walletexcept.cpp ${CMAKE_CURRENT_SOURCE_DIR}/wallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/walletdb.cpp diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 52eb4aeebb..ca63701be7 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -224,13 +224,6 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, const bool& fF return false; vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; - if(!fFirstUnlock && pwalletMain->zwallet){ - auto &hdChain = pwalletMain->GetHDChain(); - uint160 hashSeedMaster = hdChain.masterKeyID; - pwalletMain->SetHDChain(hdChain, false); // Used to upgrade normal keys to BIP44 - pwalletMain->zwallet->SetupWallet(hashSeedMaster, false); - pwalletMain->zwallet->SyncWithChain(); - } } NotifyStatusChanged(this); return true; diff --git a/src/wallet/lelantusjoinsplitbuilder.cpp b/src/wallet/lelantusjoinsplitbuilder.cpp deleted file mode 100644 index e1747e179f..0000000000 --- a/src/wallet/lelantusjoinsplitbuilder.cpp +++ /dev/null @@ -1,491 +0,0 @@ -#include "lelantusjoinsplitbuilder.h" -#include "walletexcept.h" - -#include "../primitives/transaction.h" - -#include "validation.h" -#include "../policy/policy.h" - - -#include "../lelantus.h" - -#include -#include - -struct CoinCompare -{ - bool operator()( const std::pair& left, const std::pair& right ) const { - return left.second < right.second; - } -}; - -LelantusJoinSplitBuilder::LelantusJoinSplitBuilder(CWallet& wallet, CHDMintWallet& mintWallet, const CCoinControl *coinControl) : - wallet(wallet), - mintWallet(mintWallet) -{ - cs_main.lock(); - - try { - wallet.cs_wallet.lock(); - } catch (...) { - cs_main.unlock(); - throw; - } - - this->coinControl = coinControl; -} - -LelantusJoinSplitBuilder::~LelantusJoinSplitBuilder() -{ - wallet.cs_wallet.unlock(); - cs_main.unlock(); -} - -CWalletTx LelantusJoinSplitBuilder::Build( - const std::vector& recipients, - CAmount &fee, - const std::vector& newMints, - std::function outModifier) -{ - if (recipients.empty() && newMints.empty()) { - throw std::runtime_error(_("Either recipients or newMints has to be nonempty.")); - } - - // calculate total value to spend - CAmount vOut = 0; - CAmount mint = 0; - unsigned recipientsToSubtractFee = 0; - - for (size_t i = 0; i < recipients.size(); i++) { - auto& recipient = recipients[i]; - - if (recipient.scriptPubKey.IsPayToExchangeAddress()) { - throw std::runtime_error("Exchange addresses cannot receive private funds. Please transfer your funds to a transparent address first before sending to an Exchange address"); - } - - if (!MoneyRange(recipient.nAmount)) { - throw std::runtime_error(boost::str(boost::format(_("Recipient has invalid amount")) % i)); - } - - vOut += recipient.nAmount; - - if (recipient.fSubtractFeeFromAmount) { - recipientsToSubtractFee++; - } - } - - for(const auto& mintValue : newMints) { - mint += mintValue; - } - - CWalletTx result; - CMutableTransaction tx; - - result.fTimeReceivedIsTxTime = true; - result.BindWallet(&wallet); - - - // Discourage fee sniping. - // - // For a large miner the value of the transactions in the best block and - // the mempool can exceed the cost of deliberately attempting to mine two - // blocks to orphan the current best block. By setting nLockTime such that - // only the next block can include the transaction, we discourage this - // practice as the height restricted and limited blocksize gives miners - // considering fee sniping fewer options for pulling off this attack. - // - // A simple way to think about this is from the wallet's point of view we - // always want the blockchain to move forward. By setting nLockTime this - // way we're basically making the statement that we only want this - // transaction to appear in the next block; we don't want to potentially - // encourage reorgs by allowing transactions to appear at lower heights - // than the next block in forks of the best chain. - // - // Of course, the subsidy is high enough, and transaction volume low - // enough, that fee sniping isn't a problem yet, but by implementing a fix - // now we ensure code won't be written that makes assumptions about - // nLockTime that preclude a fix later. - tx.nLockTime = chainActive.Height(); - - // Secondly occasionally randomly pick a nLockTime even further back, so - // that transactions that are delayed after signing for whatever reason, - // e.g. high-latency mix networks and some CoinJoin implementations, have - // better privacy. - if (GetRandInt(10) == 0) { - tx.nLockTime = std::max(0, static_cast(tx.nLockTime) - GetRandInt(100)); - } - - assert(tx.nLockTime <= static_cast(chainActive.Height())); - assert(tx.nLockTime < LOCKTIME_THRESHOLD); - - // Start with no fee and loop until there is enough fee; - uint32_t nCountNextUse = 0; - if (pwalletMain->zwallet) { - nCountNextUse = pwalletMain->zwallet->GetCount(); - } - - std::list coins = pwalletMain->GetAvailableLelantusCoins(coinControl); - std::tie(fee, std::ignore) = wallet.EstimateJoinSplitFee(vOut + mint, recipientsToSubtractFee, coins, coinControl); - - for (;;) { - // In case of not enough fee, reset mint seed counter - if (pwalletMain->zwallet) { - pwalletMain->zwallet->SetCount(nCountNextUse); - } - CAmount required = vOut + mint; - CAmount currentVout = vOut; - tx.vin.clear(); - tx.vout.clear(); - - result.fFromMe = true; - result.changes.clear(); - - // If no any recipients to subtract fee then the sender need to pay by themself. - if (!recipientsToSubtractFee) { - required += fee; - } else { - currentVout -= fee; - } - // fill outputs - bool remainderSubtracted = false; - - for (size_t i = 0; i < recipients.size(); i++) { - auto& recipient = recipients[i]; - CTxOut vout(recipient.nAmount, recipient.scriptPubKey); - - if (recipient.fSubtractFeeFromAmount) { - // Subtract fee equally from each selected recipient. - vout.nValue -= fee / recipientsToSubtractFee; - - if (!remainderSubtracted) { - // First receiver pays the remainder not divisible by output count. - vout.nValue -= fee % recipientsToSubtractFee; - remainderSubtracted = true; - } - } - - if (vout.IsDust(minRelayTxFee)) { - std::string err; - - if (recipient.fSubtractFeeFromAmount && fee > 0) { - if (vout.nValue < 0) { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to pay the fee")) % i); - } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to send after the fee has been deducted")) % i); - } - } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small")) % i); - } - - throw std::runtime_error(err); - } - - tx.vout.push_back(vout); - } - - // get coins - spendCoins.clear(); - - const auto& consensusParams = Params().GetConsensus(); - CAmount changeToMint = 0; - - if(required > 0) { - if (!wallet.GetCoinsToJoinSplit(required, spendCoins, changeToMint, coins, - consensusParams.nMaxLelantusInputPerTransaction, - consensusParams.nMaxValueLelantusSpendPerTransaction, coinControl)) { - throw InsufficientFunds(); - } - } - - if ((spendCoins.size()) > consensusParams.nMaxLelantusInputPerTransaction) - throw std::invalid_argument( - _("Number of inputs is bigger then limit.")); - - - CAmount input(0); - for (const auto &spend : spendCoins) { - input += spend.amount; - } - - changeToMint += (input - currentVout - fee - changeToMint - mint); - - if(changeToMint > consensusParams.nMaxValueLelantusMint) { - throw std::invalid_argument( - _("Value of change exceeds the limit")); - } - - // get outputs - mintCoins.clear(); - std::vector outputMints; - std::vector Cout; - { - CWalletDB walletdb(pwalletMain->strWalletFile); - pwalletMain->zwallet->ResetCount(walletdb); - } - GenerateMints(newMints, changeToMint, Cout, outputMints); - - // shuffle outputs to provide some privacy - std::vector> outputs; - outputs.reserve(outputMints.size()); - - for (auto& output : outputMints) { - outputs.push_back(std::ref(output)); - } - - std::shuffle(outputs.begin(), outputs.end(), std::random_device()); - - // replace outputs with shuffled one - size_t coinIdx = 0; - for (size_t i = 0; i < outputs.size(); i++) { - auto& output = outputs[i]; - - result.changes.insert(static_cast(tx.vout.size() + i)); - - CScript script; - if ((script = output.get().scriptPubKey).IsLelantusJMint()) { - GroupElement g; - std::vector enc; - lelantus::ParseLelantusJMintScript(script, g, enc); - - for (size_t i = coinIdx; i != Cout.size(); i++) { - if (Cout[i].getPublicCoin() == g) { - std::swap(Cout[i], Cout[coinIdx++]); - break; - } - } - } - } - - tx.vout.insert(tx.vout.end(), outputs.begin(), outputs.end()); - - // fill inputs - uint32_t sequence = CTxIn::SEQUENCE_FINAL; - tx.vin.emplace_back(COutPoint(), CScript(), sequence); - - if(outModifier) { - for(CTxOut & out : tx.vout) { - outModifier(out, *this); - } - } - - // clear vExtraPayload to calculate metadata hash correctly - tx.vExtraPayload.clear(); - - // set correct type of transaction (this affects metadata hash) - if (chainActive.Height() >= Params().GetConsensus().nLelantusV3PayloadStartBlock) { - tx.nVersion = 3; - tx.nType = TRANSACTION_LELANTUS; - } - - // now every fields is populated then we can sign transaction - uint256 sig = tx.GetHash(); - - CreateJoinSplit(sig, Cout, currentVout, fee, tx); - - // check fee - result.SetTx(MakeTransactionRef(tx)); - - if (GetTransactionWeight(tx) >= MAX_NEW_TX_WEIGHT) { - throw std::runtime_error(_("Transaction is too large (size limit: 100Kb). Select less inputs or consolidate your UTXOs")); - } - - // check fee - unsigned size = GetVirtualTransactionSize(tx); - CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); - - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (feeNeeded < minRelayTxFee.GetFee(size)) { - throw std::invalid_argument(_("Transaction too large for fee policy")); - } - - if (fee >= feeNeeded) { - break; - } - - fee = feeNeeded; - } - - if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { - // Lastly, ensure this tx will pass the mempool's chain limits - LockPoints lp; - CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); - CTxMemPool::setEntries setAncestors; - size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; - std::string errString; - if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, - nLimitDescendants, nLimitDescendantSize, errString)) { - throw std::runtime_error(_("Transaction has too long of a mempool chain")); - } - } - - return result; - -} - -void LelantusJoinSplitBuilder::GenerateMints(const std::vector& newMints, const CAmount& changeToMint, std::vector& Cout, std::vector& outputs) { - mintCoins.clear(); - Cout.clear(); - Cout.reserve(newMints.size() + 1); - CHDMint hdMint; - auto params = lelantus::Params::get_default(); - std::vector newMintsAndChange(newMints); - newMintsAndChange.push_back(changeToMint); - for (CAmount mintVal : newMintsAndChange) { - while (true) { - hdMint.SetNull(); - lelantus::PrivateCoin newCoin(params, mintVal); - newCoin.setVersion(LELANTUS_TX_VERSION_4); - CWalletDB walletdb(pwalletMain->strWalletFile); - - uint160 seedID; - mintWallet.GenerateLelantusMint(walletdb, newCoin, hdMint, seedID, boost::none, true); - - auto &pubCoin = newCoin.getPublicCoin(); - - if (!pubCoin.validate()) { - throw std::runtime_error("Unable to mint a lelantus coin."); - } - - // Create script for coin - CScript scriptSerializedCoin; - scriptSerializedCoin << OP_LELANTUSJMINT; - std::vector vch = pubCoin.getValue().getvch(); - scriptSerializedCoin.insert(scriptSerializedCoin.end(), vch.begin(), vch.end()); - - std::vector encryptedValue = pwalletMain->EncryptMintAmount(mintVal, pubCoin.getValue()); - scriptSerializedCoin.insert(scriptSerializedCoin.end(), encryptedValue.begin(), encryptedValue.end()); - - auto pubcoin = hdMint.GetPubcoinValue() + - lelantus::Params::get_default()->get_h1() * Scalar(hdMint.GetAmount()).negate(); - uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); - CDataStream ss(SER_GETHASH, 0); - ss << hashPub; - ss << seedID; - uint256 hashForRecover = Hash(ss.begin(), ss.end()); - // Check if there is a mint with same private data in chain, most likely Hd mint state corruption, - // If yes, try with new counter - GroupElement dummyValue; - if (lelantus::CLelantusState::GetState()->HasCoinTag(dummyValue, hashForRecover)) - continue; - - CDataStream serializedHash(SER_NETWORK, 0); - serializedHash << hashForRecover; - scriptSerializedCoin.insert(scriptSerializedCoin.end(), serializedHash.begin(), serializedHash.end()); - - Cout.emplace_back(newCoin); - outputs.push_back(CTxOut(0, scriptSerializedCoin)); - mintCoins.push_back(hdMint); - break; - } - } -} - -void LelantusJoinSplitBuilder::CreateJoinSplit( - const uint256& txHash, - const std::vector& Cout, - const uint64_t& Vout, - const uint64_t& fee, - CMutableTransaction& tx) { - - lelantus::CLelantusState* state = lelantus::CLelantusState::GetState(); - auto params = lelantus::Params::get_default(); - - std::vector> coins; - coins.reserve(spendCoins.size()); - std::map> anonymity_sets; - std::map groupBlockHashes; - int version = 0; - - // after nLelantusFixesStartBlock set new transaction version, - { - if (chainActive.Height() >= Params().GetConsensus().nLelantusV3PayloadStartBlock) - version = LELANTUS_TX_TPAYLOAD; - else - version = LELANTUS_TX_VERSION_4_5; - } - - std::vector> anonymity_set_hashes; - for (const auto &spend : spendCoins) { - // construct public part of the mint - lelantus::PublicCoin pub(spend.value); - // construct private part of the mint - lelantus::PrivateCoin priv(params, spend.amount); - priv.setVersion(version); - priv.setSerialNumber(spend.serialNumber); - priv.setRandomness(spend.randomness); - priv.setEcdsaSeckey(spend.ecdsaSecretKey); - priv.setPublicCoin(pub); - - // get coin group - int groupId; - int mintHeight; - std::tie(mintHeight, groupId) = state->GetMintedCoinHeightAndId(pub); - - if (groupId < 0) { - throw std::runtime_error(_("One of the lelantus coins has not been found in the chain!")); - } - - // Check if the coin is at overlapping parts of sets, use next set for proof creation if it is also in next set. - lelantus::CLelantusState::LelantusCoinGroupInfo nextCoinGroupInfo; - if (state->GetLatestCoinID() > groupId && state->GetCoinGroupInfo(groupId + 1, nextCoinGroupInfo)) { - if (nextCoinGroupInfo.firstBlock->nHeight <= mintHeight) - groupId += 1; - } - - coins.emplace_back(std::make_pair(priv, groupId)); - std::vector setHash; - if (anonymity_sets.count(groupId) == 0) { - std::vector set; - uint256 blockHash; - if (state->GetCoinSetForSpend( - &chainActive, - chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), // required 1 confirmation for mint to spend - groupId, - blockHash, - set, - setHash) < 2) - throw std::runtime_error( - _("Has to have at least two mint coins with at least 1 confirmation in order to spend a coin")); - groupBlockHashes[groupId] = blockHash; - anonymity_sets[groupId] = set; - if (!setHash.empty()) - anonymity_set_hashes.push_back(setHash); - } - } - - std::sort(coins.begin(), coins.end(), CoinCompare()); - - lelantus::JoinSplit joinSplit(params, coins, anonymity_sets, anonymity_set_hashes, Vout, Cout, fee, groupBlockHashes, txHash, version); - - std::vector pCout; - pCout.reserve(Cout.size()); - for(const auto& coin : Cout) - pCout.emplace_back(coin.getPublicCoin()); - - if (!joinSplit.Verify(anonymity_sets, anonymity_set_hashes, pCout, Vout, txHash)) { - throw std::runtime_error(_("The joinsplit transaction failed to verify")); - } - - // construct spend script - CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << joinSplit; - - CScript script; - - if (chainActive.Height() >= Params().GetConsensus().nLelantusV3PayloadStartBlock) { - script << OP_LELANTUSJOINSPLITPAYLOAD; - tx.nVersion = 3; - tx.nType = TRANSACTION_LELANTUS; - tx.vExtraPayload.assign(serialized.begin(), serialized.end()); - } - else { - script << OP_LELANTUSJOINSPLIT; - script.insert(script.end(), serialized.begin(), serialized.end()); - } - - tx.vin[0].scriptSig = script; -} diff --git a/src/wallet/lelantusjoinsplitbuilder.h b/src/wallet/lelantusjoinsplitbuilder.h deleted file mode 100644 index 22c8ba3d34..0000000000 --- a/src/wallet/lelantusjoinsplitbuilder.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef FIRO_WALLET_LELANTUSJOINSPLITBUILDER_H -#define FIRO_WALLET_LELANTUSJOINSPLITBUILDER_H - -#include "wallet.h" - -#include "../amount.h" -#include "../script/script.h" -#include "../primitives/transaction.h" - -#include "../hdmint/wallet.h" - - -class LelantusJoinSplitBuilder { -public: - LelantusJoinSplitBuilder(CWallet& wallet, CHDMintWallet& mintWallet, const CCoinControl *coinControl = nullptr); - ~LelantusJoinSplitBuilder(); - - CWalletTx Build( - const std::vector& recipients, - CAmount &fee, - const std::vector& newMintss, - std::function outModifier = nullptr); - -private: - void GenerateMints(const std::vector& newMints, const CAmount& changeToMint, std::vector& Cout, std::vector& outputs); - void CreateJoinSplit( - const uint256& txHash, - const std::vector& Cout, - const uint64_t& Vout, - const uint64_t& fee, - CMutableTransaction& tx); - -public: - std::vector spendCoins; - std::vector mintCoins; - - CWallet& wallet; - const CCoinControl *coinControl; - - CAmount fee = 0; - -private: - CHDMintWallet& mintWallet; -}; - - -#endif //FIRO_WALLET_LELANTUSJOINSPLITBUILDER_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 8ddd535777..6d2ad1871f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -477,8 +477,6 @@ UniValue importwallet(const JSONRPCRequest& request) bool fGood = true; - bool fMintUpdate = false; - int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); @@ -554,14 +552,6 @@ UniValue importwallet(const JSONRPCRequest& request) fGood = false; continue; } - - if(!masterKeyID.IsNull() && fHd){ - // If change component in HD path is 2, this is a mint seed key. Add to mintpool. (Have to call after key addition) - if(pwallet->mapKeyMetadata[keyid].nChange.first==2){ - pwallet->zwallet->RegenerateMintPoolEntry(walletdb, hdMasterKeyID, keyid, pwallet->mapKeyMetadata[keyid].nChild.first); - fMintUpdate = true; - } - } if (fLabel) pwallet->SetAddressBook(keyid, strLabel, "receive"); nTimeBegin = std::min(nTimeBegin, nTime); @@ -576,11 +566,6 @@ UniValue importwallet(const JSONRPCRequest& request) pwallet->ScanForWalletTransactions(pindex); pwallet->MarkDirty(); - if(fMintUpdate){ - pwallet->zwallet->SyncWithChain(); - pwallet->zwallet->GetTracker().ListLelantusMints(false, false); - } - if (!fGood) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 220870222b..8e06e67af2 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -10,7 +10,6 @@ #include "core_io.h" #include "init.h" #include "validation.h" -#include "lelantus.h" #include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include "net.h" @@ -23,10 +22,8 @@ #include "utilmoneystr.h" #include "wallet.h" #include "walletdb.h" -#include "hdmint/tracker.h" #include "walletexcept.h" #include "masternode-payments.h" -#include "lelantusjoinsplitbuilder.h" #include "bip47/paymentchannel.h" #include "bip47/account.h" #include "wallet/coincontrol.h" @@ -64,17 +61,11 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) return true; } -void EnsureLelantusWalletIsAvailable() -{ - if (!pwalletMain || !pwalletMain->zwallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "lelantus mint/joinsplit is not allowed for legacy wallet"); - } -} void EnsureSparkWalletIsAvailable() { - if (!pwalletMain || !pwalletMain->zwallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "lelantus mint/joinsplit is not allowed for legacy wallet"); + if (!pwalletMain || !pwalletMain->sparkWallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Spark wallet is not available for this wallet (legacy or disabled)"); } } @@ -712,6 +703,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request) } return wtx.GetHash().GetHex(); + } catch (const WalletLocked&) { + LogPrintf("Exception when sending to Spark address (simple format): wallet is locked\n"); + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Failed to send to Spark address: wallet is locked"); } catch (const std::exception &e) { LogPrintf("Exception when sending to Spark address (simple format): %s\n", e.what()); throw JSONRPCError(RPC_WALLET_ERROR, @@ -1009,8 +1003,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) std::list reservekeys; int nChangePosRet = -1; std::string strError; - - + // Debug logging LogPrintf("Attempting to create Spark mint transaction for %zu recipients\n", outputs.size()); for (size_t i = 0; i < outputs.size(); i++) { @@ -1058,6 +1051,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request) txids.push_back(wtx.GetHash().GetHex()); } + } catch (const WalletLocked&) { + LogPrintf("Exception when sending to Spark address (JSON format): wallet is locked\n"); + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Failed to send to Spark address: wallet is locked"); } catch (const std::exception &e) { LogPrintf("Exception when sending to Spark address (JSON format): %s\n", e.what()); throw JSONRPCError(RPC_WALLET_ERROR, @@ -1370,6 +1366,7 @@ UniValue signmessage(const JSONRPCRequest& request) return EncodeBase64(&vchSig[0], vchSig.size()); } + UniValue signmessagewithsparkaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -1432,13 +1429,12 @@ UniValue signmessagewithsparkaddress(const JSONRPCRequest& request) spark::SpendKey spendKey(params); try { spendKey = std::move(pwallet->sparkWallet->generateSpendKey(params)); + } catch (const WalletLocked&) { + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Unable to generate spend key, wallet may be locked"); } catch (const std::exception&) { throw JSONRPCError(RPC_WALLET_ERROR, "Unable to generate spend key"); } - if (spendKey == spark::SpendKey(params)) - throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Unable to generate spend key, wallet may be locked"); - spark::OwnershipProof proof; spark::FullViewKey fullViewKey(spendKey); address.prove_own(m, spendKey, fullViewKey, proof); @@ -1449,52 +1445,6 @@ UniValue signmessagewithsparkaddress(const JSONRPCRequest& request) return HexStr(proofStream.begin(), proofStream.end()); } -UniValue proveprivatetxown(const JSONRPCRequest& request) -{ - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( - "proveprivatetxown \"txid\" \"message\"\n" - "\nCreated a proof by signing the message with private key of each spent coin." - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - "1. \"strTxId\" (string, required) Txid, in which we spend lelantus coins.\n" - "2. \"message\" (string, required) The message to create a signature of.\n" - "\nResult:\n" - "\"proof\" (string) The signatures of the message encoded in base 64\n" - "\nExamples:\n" - "\nUnlock the wallet for 30 seconds\n" - + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + - "\nCreate the signature\n" - + HelpExampleCli("proveprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \" \"my message\"") + - "\nVerify the signature\n" - + HelpExampleCli("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \" \"proof\" \"my message\"") + - "\nAs json rpc\n" - + HelpExampleRpc("proveprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \", \"my message\"") - ); - - EnsureLelantusWalletIsAvailable(); - - LOCK2(cs_main, pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); - - std::string strTxId = request.params[0].get_str(); - std::string strMessage = request.params[1].get_str(); - - uint256 txid = uint256S(strTxId); - std::vector vchSig = pwallet->ProvePrivateTxOwn(txid, strMessage); - - if (vchSig.empty()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Something went wrong, may be you are not the owner of provided tx"); - - return EncodeBase64(&vchSig[0], vchSig.size()); -} - - UniValue getreceivedbyaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -1690,7 +1640,7 @@ UniValue getprivatebalance(const JSONRPCRequest& request) throw std::runtime_error( "getprivatebalance\n" "\nReturns private balance.\n" - "Private balance is the sum of all confirmed sigma/lelantus mints which are created by the wallet.\n" + "Private balance is the sum of all confirmed spark mints which are created by the wallet.\n" "\nResult:\n" "amount (numeric) The confirmed private balance in " + CURRENCY_UNIT + ".\n" "\nExamples:\n" @@ -1699,10 +1649,10 @@ UniValue getprivatebalance(const JSONRPCRequest& request) + HelpExampleRpc("getprivatebalance", "") ); - EnsureLelantusWalletIsAvailable(); + EnsureSparkWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); + return ValueFromAmount(pwallet->sparkWallet->getAvailableBalance()); } UniValue gettotalbalance(const JSONRPCRequest& request) @@ -1716,9 +1666,9 @@ UniValue gettotalbalance(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( "gettotalbalance\n" - "\nReturns total (transparent + private) balance.\n" - "Transparent balance is the sum of coin amounts received as utxo.\n" - "Private balance is the sum of all confirmed sigma/lelantus/spark mints which are created by the wallet.\n" + "\nReturns total (transparent + Spark private) balance.\n" + "Transparent balance is the sum of UTXO amounts.\n" + "Private balance is confirmed Spark funds when Spark wallet is enabled; otherwise 0.\n" "\nResult:\n" "amount (numeric) The total balance in " + CURRENCY_UNIT + " for the wallet.\n" "\nExamples:\n" @@ -1727,12 +1677,14 @@ UniValue gettotalbalance(const JSONRPCRequest& request) + HelpExampleRpc("gettotalbalance", "") ); - EnsureLelantusWalletIsAvailable(); - EnsureSparkWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); - - return ValueFromAmount(pwallet->GetBalance() + pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); + const CAmount transparent = pwallet->GetBalance(); + CAmount spark = 0; + if (pwallet->sparkWallet) { + spark = pwallet->sparkWallet->getAvailableBalance(); + } + return ValueFromAmount(transparent + spark); } UniValue getunconfirmedbalance(const JSONRPCRequest &request) @@ -2903,14 +2855,7 @@ UniValue gettransaction(const JSONRPCRequest& request) CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); - if (wtx.tx->vin[0].IsLelantusJoinSplit()) { - try { - nFee = (0 - lelantus::ParseLelantusJoinSplit(*wtx.tx)->getFee()); - } - catch (const std::exception &) { - // do nothing - } - } else if (wtx.tx->IsSparkSpend()) { + if (wtx.tx->IsSparkSpend()) { try { nFee = (0 - spark::ParseSparkSpend(*wtx.tx).getFee()); } @@ -3815,131 +3760,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) return result; } -UniValue regeneratemintpool(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( - "regeneratemintpool\n" - "\nIf issues exist with the keys that map to mintpool entries in the DB, this function corrects them.\n" - "\nExamples:\n" - + HelpExampleCli("regeneratemintpool", "") - + HelpExampleRpc("regeneratemintpool", "") - ); - - if (pwallet->IsLocked()) - throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, - "Error: Please enter the wallet passphrase with walletpassphrase first."); - - if (!pwallet->IsHDSeedAvailable() || !pwallet->zwallet) { - throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, - "Error: Can only regenerate mintpool on a HD-enabled wallet."); - } - - CWalletDB walletdb(pwallet->strWalletFile); - std::vector> listMintPool = walletdb.ListMintPool(); - std::vector> serialPubcoinPairs = walletdb.ListSerialPubcoinPairs(); - - // - std::pair nIndexes; - - uint256 oldHashSerial; - uint256 oldHashPubcoin; - - bool reindexRequired = false; - - for (auto& mintPoolPair : listMintPool){ - oldHashPubcoin = mintPoolPair.first; - bool hasSerial = pwallet->zwallet->GetSerialForPubcoin(serialPubcoinPairs, oldHashPubcoin, oldHashSerial); - - MintPoolEntry entry = mintPoolPair.second; - nIndexes = pwallet->zwallet->RegenerateMintPoolEntry(walletdb, std::get<0>(entry),std::get<1>(entry),std::get<2>(entry)); - - if(nIndexes.first != oldHashPubcoin){ - walletdb.EraseMintPoolPair(oldHashPubcoin); - reindexRequired = true; - } - - if(!hasSerial || nIndexes.second != oldHashSerial){ - walletdb.ErasePubcoin(oldHashSerial); - reindexRequired = true; - } - } - - if(reindexRequired) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Mintpool issue corrected. Please shutdown firo and restart with -reindex flag."); - - return true; -} - -UniValue listunspentlelantusmints(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( - "listunspentsigmamints [minconf=1] [maxconf=9999999] \n" - "Returns array of unspent transaction outputs\n" - "with between minconf and maxconf (inclusive) confirmations.\n" - "Results are an array of Objects, each of which has:\n" - "{txid, vout, scriptPubKey, amount, confirmations}"); - } - - if (pwallet->IsLocked()) { - throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, - "Error: Please enter the wallet passphrase with walletpassphrase first."); - } - - EnsureLelantusWalletIsAvailable(); - - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR)); - - int nMinDepth = 1; - if (request.params.size() > 0) - nMinDepth = request.params[0].get_int(); - - int nMaxDepth = 9999999; - if (request.params.size() > 1) - nMaxDepth = request.params[1].get_int(); - - UniValue results(UniValue::VARR); - std::vector vecOutputs; - assert(pwallet != NULL); - pwallet->ListAvailableLelantusMintCoins(vecOutputs, false); - LogPrintf("vecOutputs.size()=%zu\n", vecOutputs.size()); - BOOST_FOREACH(const COutput &out, vecOutputs) - { - if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) - continue; - - int64_t nValue = out.tx->tx->vout[out.i].nValue; - const CScript &pk = out.tx->tx->vout[out.i].scriptPubKey; - UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); - entry.push_back(Pair("vout", out.i)); - entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); - if (pk.IsPayToScriptHash()) { - CTxDestination address; - if (ExtractDestination(pk, address)) { - const CScriptID &hash = boost::get(address); - CScript redeemScript; - if (pwallet->GetCScript(hash, redeemScript)) - entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); - } - } - entry.push_back(Pair("amount", ValueFromAmount(nValue))); - entry.push_back(Pair("confirmations", out.nDepth)); - results.push_back(entry); - } - - return results; -} - UniValue listunspentsparkmints(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { @@ -4851,7 +4671,7 @@ UniValue registersparkname(const JSONRPCRequest& request) { std::string additionalData; int numberOfYears = request.params[2].get_int(); - if (numberOfYears < 1 || numberOfYears > 10) + if (numberOfYears < 1 || numberOfYears > 15) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid number of years"); std::size_t additionalDataIndex = fTransfer ? 5 : 3; @@ -4892,22 +4712,15 @@ UniValue registersparkname(const JSONRPCRequest& request) { throw JSONRPCError(RPC_WALLET_ERROR, std::string("Spark name registration failed: ") + x.what()); } - // commit - try { + { CValidationState state; CReserveKey reserveKey(pwallet); - if (!pwallet->CommitTransaction(wtx, reserveKey, g_connman.get(), state)) - throw JSONRPCError(RPC_WALLET_ERROR, "CommitTransaction failed: " + FormatStateMessage(state)); - } - catch (const std::exception &) { - auto error = _( - "Error: The transaction was rejected! This might happen if some of " - "the coins in your wallet were already spent, such as if you used " - "a copy of wallet.dat and coins were spent in the copy but not " - "marked as spent here." - ); - - std::throw_with_nested(std::runtime_error(error)); + if (!pwallet->CommitTransaction(wtx, reserveKey, g_connman.get(), state, true)) { + std::string rejectReason = FormatStateMessage(state); + throw JSONRPCError(RPC_WALLET_ERROR, + "Spark name transaction was rejected" + + (rejectReason.empty() ? std::string() : ": " + rejectReason)); + } } return wtx.GetHash().GetHex(); @@ -4916,17 +4729,20 @@ UniValue registersparkname(const JSONRPCRequest& request) { UniValue requestsparknametransfer(const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 4 || request.params.size() > 5) { throw std::runtime_error( - "requestsparknametransfer \"name\" \"sparkaddress\" \"oldsparkaddress\" years [\"additionalData\"]\n" + "requestsparknametransfer \"name\" \"newsparkaddress\" years \"oldsparkaddress\" [\"additionalData\"]\n" ); } if (request.params.size() < 4 || request.params.size() > 5) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters"); + // Use the next-block height: the resulting transfer transaction will be validated at + // chainActive.Height() + 1, so the v2.1 inputsHash decision must be made against that + // height (matching GetSparkNameFeeScript) to stay valid across the activation boundary. int chainHeight; { LOCK(cs_main); - chainHeight = chainActive.Height(); + chainHeight = chainActive.Height() + 1; } const auto &consensusParams = Params().GetConsensus(); if (chainHeight < consensusParams.nSparkNamesV2StartBlock) { @@ -4935,13 +4751,14 @@ UniValue requestsparknametransfer(const JSONRPCRequest &request) { std::string sparkName = request.params[0].get_str(); std::string sparkAddress = request.params[1].get_str(); - std::string oldSparkAddress = request.params[2].get_str(); std::string additionalData; - int numberOfYears = request.params[3].get_int(); - if (numberOfYears < 1 || numberOfYears > 10) + int numberOfYears = request.params[2].get_int(); + if (numberOfYears < 1 || numberOfYears > 15) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid number of years"); + std::string oldSparkAddress = request.params[3].get_str(); + if (request.params.size() >= 5) additionalData = request.params[4].get_str(); @@ -4957,6 +4774,22 @@ UniValue requestsparknametransfer(const JSONRPCRequest &request) { sparkNameData.sparkNameValidityBlocks = numberOfYears * 365*24*24; sparkNameData.operationType = CSparkNameTxData::opTransfer; + // V2.1+: bind inputsHash to the name's current expiration height so the proof + // is specific to this registration cycle and cannot be replayed after the name + // expires and is re-registered at the same address. + if (chainHeight >= consensusParams.nSparkNamesV21StartBlock) { + CSparkNameManager *sparkNameManager = CSparkNameManager::GetInstance(); + try { + uint64_t expirationHeight = sparkNameManager->GetSparkNameBlockHeight(sparkName); + CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); + hw << expirationHeight; + sparkNameData.inputsHash = hw.GetHash(); + } catch (const std::exception &x) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + std::string("Spark name not found or not active: ") + x.what()); + } + } + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << sparkNameData; @@ -4987,13 +4820,12 @@ UniValue transfersparkname(const JSONRPCRequest &request) { spark::SpendKey spendKey(params); try { spendKey = std::move(pwallet->sparkWallet->generateSpendKey(params)); - } catch (std::exception& e) { + } catch (const WalletLocked&) { + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Unable to generate spend key, wallet may be locked"); + } catch (const std::exception&) { throw std::runtime_error(_("Unable to generate spend key.")); } - if (spendKey == spark::SpendKey(params)) - throw std::runtime_error(_("Unable to generate spend key, looks the wallet is locked.")); - std::string oldSparkAddress = request.params[0].get_str(); std::string requestHash = request.params[1].get_str(); @@ -5024,40 +4856,6 @@ UniValue transfersparkname(const JSONRPCRequest &request) { return HexStr(ownStream.begin(), ownStream.end()); } -UniValue lelantustospark(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() > 0) { - throw std::runtime_error( - "lelantustospark \n" - "Takes all your lelantus mints, spends all to transparent layer, takes all that UTX's and mints to Spark"); - } - - if (!lelantus::IsLelantusGraceFulPeriod()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus spends are not allowed anymore"); - } - - - EnsureWalletIsUnlocked(pwallet); - EnsureSparkWalletIsAvailable(); - - assert(pwallet != NULL); - std::string strFailReason = ""; - bool passed = false; - try { - passed = pwallet->LelantusToSpark(strFailReason); - } catch (const std::exception &) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed!"); - } - if (!passed || strFailReason != "") - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed. " + strFailReason); - - return NullUniValue; -} - UniValue identifysparkcoins(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -5142,464 +4940,6 @@ UniValue getsparkcoinaddr(const JSONRPCRequest& request) return results; } -UniValue mintlelantus(const JSONRPCRequest& request) -{ - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( - "mintlelantus amount\n" - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - "1. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to mint, must be not less than 0.05\n" - "\nResult:\n" - "\"transactionid\" (string) The transaction id.\n" - "\nExamples:\n" - + HelpExampleCli("mintlelantus", "0.15") - + HelpExampleCli("mintlelantus", "100.9") - + HelpExampleRpc("mintlelantus", "0.15") - ); - - EnsureWalletIsUnlocked(pwallet); - EnsureLelantusWalletIsAvailable(); - - // Ensure Lelantus mints is already accepted by network so users will not lost their coins - // due to other nodes will treat it as garbage data. - if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); - } - - CAmount nAmount = AmountFromValue(request.params[0]); - LogPrintf("rpcWallet.mintlelantus() nAmount = %d \n", nAmount); - - std::vector> wtxAndFee; - std::vector mints; - std::string strError = pwallet->MintAndStoreLelantus(nAmount, wtxAndFee, mints); - - if (strError != "") - throw JSONRPCError(RPC_WALLET_ERROR, strError); - - UniValue result(UniValue::VARR); - for(const auto& wtx : wtxAndFee) { - result.push_back(wtx.first.GetHash().GetHex()); - } - - return result; -} - -UniValue autoMintlelantus(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - "autoMintlelantus\n" - "This function automatically mints all unspent transparent funds to Lelantus.\n" - ); - - EnsureWalletIsUnlocked(pwallet); - EnsureLelantusWalletIsAvailable(); - - // Ensure Lelantus mints is already accepted by network so users will not lost their coins - // due to other nodes will treat it as garbage data. - if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); - } - - std::vector> wtxAndFee; - std::vector mints; - std::string strError = pwallet->MintAndStoreLelantus(0, wtxAndFee, mints, true); - - if (strError != "") - throw JSONRPCError(RPC_WALLET_ERROR, strError); - - UniValue result(UniValue::VARR); - for(const auto& wtx : wtxAndFee) { - result.push_back(wtx.first.GetHash().GetHex()); - } - - return result; -} - -UniValue joinsplit(const JSONRPCRequest& request) { - - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( - "joinsplit {\"address\":amount,...} ([\"address\",...] )\n" - "\nSpend lelantus and mint in one transaction, you need at least provide one of 1-st or 3-rd arguments." - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - "1. \"amounts\" (string, optional) A json object with addresses and amounts\n" - " {\n" - " \"address\":amount (numeric or string) The Firo address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" - " ,...\n" - " }\n" - "2. subtractfeefromamount (string, optional) A json array with addresses.\n" - " The fee will be equally deducted from the amount of each selected address.\n" - " Those recipients will receive less firos than you enter in their corresponding amount field.\n" - " If no addresses are specified here, the sender pays the fee.\n" - " [\n" - " \"address\" (string) Subtract fee from this address\n" - " ,...\n" - " ]\n" - "3. output mints (numeric, optional) A json object with amounts to mint\n" - " {\n" - " \"mint\"\n" - " ,...\n" - " }\n" - "\nResult:\n" - "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" - " the number of addresses.\n" - "\nExamples:\n" - "\nSend two amounts to two different addresses:\n" - + HelpExampleCli("joinsplit", "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + - "\nSend two amounts to two different addresses and subtract fee from amount:\n" - + HelpExampleCli("joinsplit", "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") - ); - - if (!lelantus::IsLelantusGraceFulPeriod()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus spends are not allowed anymore"); - } - - EnsureLelantusWalletIsAvailable(); - - LOCK2(cs_main, pwallet->cs_wallet); - - - UniValue sendTo = request.params[0].get_obj(); - - std::unordered_set subtractFeeFromAmountSet; - UniValue subtractFeeFromAmount(UniValue::VARR); - if (request.params.size() > 1) { - try { - subtractFeeFromAmount = request.params[1].get_array(); - } catch (std::runtime_error const &) { - //may be empty - } - for (int i = subtractFeeFromAmount.size(); i--;) { - subtractFeeFromAmountSet.insert(subtractFeeFromAmount[i].get_str()); - } - } - - UniValue mintAmounts; - if(request.params.size() > 2) { - try { - mintAmounts = request.params[2].get_obj(); - } catch (std::runtime_error const &) { - //may be empty - } - } - - std::set setAddress; - std::vector vecSend; - std::vector vMints; - - FIRO_UNUSED CAmount totalAmount = 0; - - auto keys = sendTo.getKeys(); - std::vector mints = mintAmounts.empty() ? std::vector() : mintAmounts.getValues(); - - if(keys.empty() && mints.empty()) - throw JSONRPCError(RPC_TYPE_ERROR, "You have to provide at least public addressed or amount to mint"); - - for (const auto& strAddr : keys) { - CBitcoinAddress address(strAddr); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Firo address: " + strAddr); - - if (!setAddress.insert(address).second) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, duplicated address: " + strAddr); - - CScript scriptPubKey = GetScriptForDestination(address.Get()); - CAmount nAmount = AmountFromValue(sendTo[strAddr]); - if (nAmount <= 0) { - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - } - totalAmount += nAmount; - - bool fSubtractFeeFromAmount = - subtractFeeFromAmountSet.find(strAddr) != subtractFeeFromAmountSet.end(); - - vecSend.push_back({scriptPubKey, nAmount, fSubtractFeeFromAmount, {}, {}}); - } - - for(const auto& mint : mints) { - auto val = mint.get_int64(); - if (!lelantus::IsAvailableToMint(val) || val <= 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Amount to mint is invalid.\n"); - } - - vMints.push_back(val); - } - - EnsureWalletIsUnlocked(pwallet); - - CWalletTx wtx; - - try { - pwallet->JoinSplitLelantus(vecSend, vMints, wtx); - } - catch (const InsufficientFunds& e) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, e.what()); - } - catch (const std::exception& e) { - throw JSONRPCError(RPC_WALLET_ERROR, e.what()); - } - - return wtx.GetHash().GetHex(); -} - -UniValue resetlelantusmint(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - "resetlelantusmint" - + HelpRequiringPassphrase(pwallet)); - - EnsureLelantusWalletIsAvailable(); - - std::vector listMints; - CWalletDB walletdb(pwallet->strWalletFile); - listMints = pwallet->zwallet->GetTracker().ListLelantusMints(false, false); - - BOOST_FOREACH(const CLelantusMintMeta& mint, listMints) { - CHDMint dMint; - if (!walletdb.ReadHDMint(mint.GetPubCoinValueHash(), true, dMint)) { - continue; - } - dMint.SetUsed(false); - dMint.SetHeight(-1); - pwallet->zwallet->GetTracker().AddLelantus(walletdb, dMint, true); - } - - return NullUniValue; -} - -UniValue listlelantusmints(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( - "listlelantusmints (false/true)\n" - "\nArguments:\n" - "1. (boolean, optional) false (default) to return real listlelantusmints. true to return every listlelantusmints.\n" - "\nResults are an array of Objects, each of which has:\n" - "{id, IsUsed, amount, value, serialNumber, nHeight, randomness}"); - - EnsureLelantusWalletIsAvailable(); - - bool fAllStatus = false; - if (request.params.size() > 0) { - fAllStatus = request.params[0].get_bool(); - } - - // Mint secret data encrypted in wallet - EnsureWalletIsUnlocked(pwallet); - - std::list listCoin; - CWalletDB walletdb(pwallet->strWalletFile); - listCoin = pwallet->zwallet->GetTracker().MintsAsLelantusEntries(false, false); - UniValue results(UniValue::VARR); - - BOOST_FOREACH(const CLelantusEntry &lelantusItem, listCoin) { - if ((fAllStatus || lelantusItem.amount != uint64_t(0)) && (lelantusItem.IsUsed || (lelantusItem.randomness != uint64_t(0) && lelantusItem.serialNumber != uint64_t(0)))) { - UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("id", lelantusItem.id)); - entry.push_back(Pair("isUsed", lelantusItem.IsUsed)); - entry.push_back(Pair("amount", lelantusItem.amount)); - entry.push_back(Pair("value", lelantusItem.value.GetHex())); - entry.push_back(Pair("serialNumber", lelantusItem.serialNumber.GetHex())); - entry.push_back(Pair("nHeight", lelantusItem.nHeight)); - entry.push_back(Pair("randomness", lelantusItem.randomness.GetHex())); - results.push_back(entry); - } - } - - return results; -} - -UniValue setlelantusmintstatus(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( - "setlelantusmintstatus \"coinserial\" (true/false)\n" - "Set lelantus mint IsUsed status to True or False\n" - "Results are an array of one or no Objects, each of which has:\n" - "{id, IsUsed, amount, value, serialNumber, nHeight, randomness}"); - - EnsureLelantusWalletIsAvailable(); - - Scalar coinSerial; - coinSerial.SetHex(request.params[0].get_str()); - - bool fStatus = true; - fStatus = request.params[1].get_bool(); - - EnsureWalletIsUnlocked(pwallet); - - std::vector listMints; - listMints = pwallet->zwallet->GetTracker().ListLelantusMints(false, false, false); - CWalletDB walletdb(pwallet->strWalletFile); - - UniValue results(UniValue::VARR); - - BOOST_FOREACH(const CLelantusMintMeta& mint, listMints) { - CLelantusEntry lelantusItem; - if(!pwallet->GetMint(mint.hashSerial, lelantusItem)) - continue; - - CHDMint dMint; - if (!walletdb.ReadHDMint(mint.GetPubCoinValueHash(), true, dMint)){ - continue; - } - - if (!lelantusItem.serialNumber.isZero()) { - LogPrintf("lelantusItem.serialNumber = %s\n", lelantusItem.serialNumber.GetHex()); - if (lelantusItem.serialNumber == coinSerial) { - LogPrintf("setmintlelantusstatus Found!\n"); - - const std::string& isUsedAmountStr = - fStatus - ? "Used (" + std::to_string((double)lelantusItem.amount / COIN) + " mint)" - : "New (" + std::to_string((double)lelantusItem.amount / COIN) + " mint)"; - pwallet->NotifyZerocoinChanged(pwallet, lelantusItem.value.GetHex(), isUsedAmountStr, CT_UPDATED); - - dMint.SetUsed(fStatus); - pwallet->zwallet->GetTracker().AddLelantus(walletdb, dMint, true); - - if (!fStatus) { - // erase lelantus spend entry - CLelantusSpendEntry spendEntry; - spendEntry.coinSerial = coinSerial; - walletdb.EraseLelantusSpendSerialEntry(spendEntry); - } - - UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("id", lelantusItem.id)); - entry.push_back(Pair("isUsed", fStatus)); - entry.push_back(Pair("amount", lelantusItem.amount)); - entry.push_back(Pair("value", lelantusItem.value.GetHex())); - entry.push_back(Pair("serialNumber", lelantusItem.serialNumber.GetHex())); - entry.push_back(Pair("nHeight", lelantusItem.nHeight)); - entry.push_back(Pair("randomness", lelantusItem.randomness.GetHex())); - results.push_back(entry); - break; - } - } - } - - return results; -} - -UniValue listlelantusjoinsplits(const JSONRPCRequest& request) { - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( - "listlelantusjoinsplits\n" - "Return up to \"count\" saved lelantus joinsplit transactions\n" - "\nArguments:\n" - "1. count (numeric) The number of transactions to return, <=0 means no limit\n" - "2. onlyunconfirmed (bool, optional, default=false) If true return only unconfirmed transactions\n" - "\nResult:\n" - "[\n" - " {\n" - " \"txid\": \"transactionid\", (string) The transaction hash\n" - " \"confirmations\": n, (numeric) The number of confirmations for the transaction\n" - " \"abandoned\": xxx, (bool) True if the transaction was already abandoned\n" - " \"joinsplits\": \n" - " [\n" - " {\n" - " \"spendid\": id, (numeric) Spend group id\n" - " \"serial\": \"s\", (string) Serial number of the coin\n" - " }\n" - " ]\n" - " }\n" - "]\n"); - - EnsureLelantusWalletIsAvailable(); - - int count = request.params[0].get_int(); - bool fOnlyUnconfirmed = request.params.size()>=2 && request.params[1].get_bool(); - - LOCK2(cs_main, pwallet->cs_wallet); - - UniValue ret(UniValue::VARR); - const CWallet::TxItems& txOrdered = pwallet->wtxOrdered; - - for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); - it != txOrdered.rend(); - ++it) { - CWalletTx *const pwtx = (*it).second.first; - - if (!pwtx || !pwtx->tx->IsLelantusJoinSplit()) - continue; - - UniValue entry(UniValue::VOBJ); - - int confirmations = pwtx->GetDepthInMainChain(); - if (confirmations > 0 && fOnlyUnconfirmed) - continue; - - entry.push_back(Pair("txid", pwtx->GetHash().GetHex())); - entry.push_back(Pair("confirmations", confirmations)); - entry.push_back(Pair("abandoned", pwtx->isAbandoned())); - - UniValue spends(UniValue::VARR); - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(*pwtx->tx); - } catch (const std::exception &) { - continue; - } - - std::vector spentSerials = joinsplit->getCoinSerialNumbers(); - std::vector ids = joinsplit->getCoinGroupIds(); - - if(spentSerials.size() != ids.size()) { - continue; - } - - for(size_t i = 0; i < spentSerials.size(); i++) { - UniValue spendEntry(UniValue::VOBJ); - spendEntry.push_back(Pair("spendid", int64_t(ids[i]))); - spendEntry.push_back(Pair("serial", spentSerials[i].GetHex())); - spends.push_back(spendEntry); - } - - entry.push_back(Pair("spent_coins", spends)); - ret.push_back(entry); - - if (count > 0 && (int)ret.size() >= count) - break; - } - - return ret; -} UniValue removetxmempool(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -6112,54 +5452,6 @@ UniValue createrapaddress(const JSONRPCRequest& request) return result; } -UniValue setupchannel(const JSONRPCRequest& request) -{ - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( - "setupchannel \"RapAddress\"\n" - "\nSets up a payment channel for the RAP address. Sends a notification transaction to the RAP address notification address.\n" - "It __will__ use Lelantus facilities to send the notification tx. The tx cost is " + std::to_string(1.0 * bip47::NotificationTxValue / COIN ) + " for the JoinSplit tx + fees\n" - + HelpRequiringPassphrase(pwallet) + - "\nArguments:\n" - "1. \"RapAddress\" (string, required) The RAP address to send to.\n" - "\nResult:\n" - "\"txid\" (string) The notification transaction id.\n" - "\nExamples:\n" - + HelpExampleCli("setupchannel", "\"PM8TJTLJbPRGxSbc8EJi42Wrr6QbNSaSSVJ5Y3E4pbCYiTHUskHg13935Ubb7q8tx9GVbh2UuRnBc3WSyJHhUrw8KhprKnn9eDznYGieTzFcwQRya4GA\"") - ); - - bip47::CPaymentCode theirPcode(request.params[0].get_str()); - - if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); - } - - EnsureLelantusWalletIsAvailable(); - - LOCK2(cs_main, pwallet->cs_wallet); - - EnsureWalletIsUnlocked(pwallet); - - try { - CWalletTx wtx = pwallet->PrepareAndSendNotificationTx(theirPcode); - return wtx.GetHash().GetHex(); - - } - catch (InsufficientFunds const & e) - { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, std::string(e.what())+" Please check your Lelantus balance is greater than " + std::to_string(1.0 * bip47::NotificationTxValue / COIN)); - } - catch (std::runtime_error const & e) - { - throw JSONRPCError(RPC_WALLET_ERROR, e.what()); - } -} - UniValue sendtorapaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -6322,25 +5614,14 @@ static const CRPCCommand commands[] = { "wallet", "settxfee", &settxfee, true, {"amount"} }, { "wallet", "signmessage", &signmessage, true, {"address","message"} }, { "wallet", "signmessagewithsparkaddress", &signmessagewithsparkaddress, true, {"sparkaddress","message"} }, - { "wallet", "proveprivatetxown", &proveprivatetxown, true, {"txid","message"} }, { "wallet", "walletlock", &walletlock, true, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} }, { "wallet", "removeprunedfunds", &removeprunedfunds, true, {"txid"} }, - { "wallet", "listunspentlelantusmints", &listunspentlelantusmints, false, {} }, - { "wallet", "mintlelantus", &mintlelantus, false, {} }, - { "wallet", "autoMintlelantus", &autoMintlelantus, false, {} }, - { "wallet", "joinsplit", &joinsplit, false, {} }, - { "wallet", "resetlelantusmint", &resetlelantusmint, false, {} }, - { "wallet", "setlelantusmintstatus", &setlelantusmintstatus, false, {} }, - { "wallet", "listlelantusmints", &listlelantusmints, false, {} }, - { "wallet", "setmininput", &setmininput, false, {} }, - { "wallet", "regeneratemintpool", ®eneratemintpool, false, {} }, { "wallet", "removetxmempool", &removetxmempool, false, {} }, { "wallet", "removetxwallet", &removetxwallet, false, {} }, - { "wallet", "listlelantusjoinsplits", &listlelantusjoinsplits, false, {} }, //spark { "wallet", "listunspentsparkmints", &listunspentsparkmints, false, {} }, @@ -6358,7 +5639,6 @@ static const CRPCCommand commands[] = { "wallet", "spendspark", &spendspark, false, {} }, { "wallet", "sendspark", &sendspark, false, {} }, { "wallet", "sendsparkmany", &sendsparkmany, false, {"fromaccount","amounts","comment","subtractfeefrom"} }, - { "wallet", "lelantustospark", &lelantustospark, false, {} }, { "wallet", "identifysparkcoins", &identifysparkcoins, false, {} }, { "wallet", "getsparkcoinaddr", &getsparkcoinaddr, false, {} }, { "wallet", "registersparkname", ®istersparkname, false, {} }, @@ -6367,7 +5647,6 @@ static const CRPCCommand commands[] = //bip47 { "bip47", "createrapaddress", &createrapaddress, true, {} }, - { "bip47", "setupchannel", &setupchannel, true, {} }, { "bip47", "sendtorapaddress", &sendtorapaddress, true, {} }, { "bip47", "listrapaddresses", &listrapaddresses, true, {} }, { "bip47", "setusednumber", &setusednumber, true, {} } diff --git a/src/wallet/test/CMakeLists.txt b/src/wallet/test/CMakeLists.txt index 2b714929fa..3f1344abc1 100644 --- a/src/wallet/test/CMakeLists.txt +++ b/src/wallet/test/CMakeLists.txt @@ -8,10 +8,8 @@ target_sources(test_firo PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/crypto_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lelantus_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mnemonic_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/spark_wallet_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/txbuilder_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/wallet_test_fixture.cpp ${CMAKE_CURRENT_SOURCE_DIR}/wallet_tests.cpp ) diff --git a/src/wallet/test/lelantus_tests.cpp b/src/wallet/test/lelantus_tests.cpp deleted file mode 100644 index 5ad17cea8d..0000000000 --- a/src/wallet/test/lelantus_tests.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#include "../../test/fixtures.h" -#include "../../validation.h" -#include "../../lelantus.h" -#include "../walletexcept.h" -#include - -#include "../wallet.h" - -#include - -BOOST_FIXTURE_TEST_SUITE(wallet_lelantus_tests, LelantusTestingSetup) - -BOOST_AUTO_TEST_CASE(create_mint_recipient) -{ - lelantus::PrivateCoin coin(params, 1); - CHDMint m; - - auto r = CWallet::CreateLelantusMintRecipient(coin, m); - - // payload is commentment and schnorr proof - size_t expectedSize = 1 // op code - + lelantus::PublicCoin().GetSerializeSize() - + lelantus::SchnorrProof().memoryRequired() - + 32; - - BOOST_CHECK(r.scriptPubKey.IsLelantusMint()); - BOOST_CHECK_EQUAL(expectedSize, r.scriptPubKey.size()); - - // assert HDMint - BOOST_CHECK_EQUAL(0, m.GetCount()); -} - -BOOST_AUTO_TEST_CASE(mint_and_store_lelantus) -{ - bool oldFRequireStandard = fRequireStandard; - fRequireStandard = true; // to verify mainnet can accept lelantus mint - pwalletMain->SetBroadcastTransactions(true); - - GenerateBlocks(110); - auto amount = 1 * COIN; - - std::vector> wtxAndFee; - std::vector mints; - auto result = pwalletMain->MintAndStoreLelantus(amount, wtxAndFee, mints); - - BOOST_CHECK_EQUAL("", result); - size_t mintAmount = 0; - for(const auto& wtx : wtxAndFee) { - auto tx = wtx.first.tx.get(); - - BOOST_CHECK(tx->IsLelantusMint()); - BOOST_CHECK(tx->IsLelantusTransaction()); - BOOST_CHECK(mempool.exists(tx->GetHash())); - - - for (auto const &out : tx->vout) { - if (out.scriptPubKey.IsLelantusMint()) { - mintAmount += out.nValue; - } - } - - // verify tx - CMutableTransaction mtx(*tx); - BOOST_CHECK(GenerateBlock({mtx})); - } - - BOOST_CHECK_EQUAL(amount, mintAmount); - - auto lelantusState = lelantus::CLelantusState::GetState(); - lelantusState->Reset(); - fRequireStandard = oldFRequireStandard; -} - -BOOST_AUTO_TEST_CASE(get_and_list_mints) -{ - GenerateBlocks(120); - std::vector confirmedAmounts = {1, 2 * COIN}; - std::vector unconfirmedAmounts = {10 * COIN}; - std::vector allAmounts(confirmedAmounts); - allAmounts.insert(allAmounts.end(), unconfirmedAmounts.begin(), unconfirmedAmounts.end()); - - // Generate all coins - std::vector txs; - auto mints = GenerateMints(allAmounts, txs); - GenerateBlock(std::vector(txs.begin(), txs.begin() + txs.size() - 1)); - - std::vector>> pubCoins; - pubCoins.reserve(mints.size() - 1); - for (size_t i = 0; i != mints.size() - 1; i++) { - pubCoins.emplace_back(mints[i].GetPubcoinValue(), std::make_pair(mints[i].GetAmount(), uint256())); - } - - pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(pubCoins); - - auto extractAmountsFromOutputs = [](std::vector const &outs) -> std::vector { - std::vector amounts; - for (auto const &out : outs) { - amounts.push_back(out.tx->tx->vout[out.i].nValue); - } - - return amounts; - }; - - std::vector confirmedCoins, allCoins; - pwalletMain->ListAvailableLelantusMintCoins(confirmedCoins, true); - pwalletMain->ListAvailableLelantusMintCoins(allCoins, false); - auto confirmed = extractAmountsFromOutputs(confirmedCoins); - auto all = extractAmountsFromOutputs(allCoins); - - BOOST_CHECK(std::is_permutation(confirmed.begin(), confirmed.end(), confirmedAmounts.begin())); - BOOST_CHECK(std::is_permutation(all.begin(), all.end(), allAmounts.begin())); - - // get mints - CLelantusEntry entry; - BOOST_CHECK(pwalletMain->GetMint(mints.front().GetSerialHash(), entry)); - BOOST_CHECK(entry.value == mints.front().GetPubcoinValue()); - - uint256 fakeSerial; - std::fill(fakeSerial.begin(), fakeSerial.end(), 1); - BOOST_CHECK(!pwalletMain->GetMint(fakeSerial, entry)); -} - -BOOST_AUTO_TEST_CASE(mintlelantus_and_mint_all) -{ - // utils - auto countMintsBalance = [&]( - std::vector> const &wtxs, - bool includeFee = false) -> CAmount { - - CAmount s = 0; - for (auto const &w : wtxs) { - for (auto const &out : w.first.tx->vout) { - if (out.scriptPubKey.IsLelantusMint()) { - s += out.nValue; - } - } - - if (includeFee) { - s += w.second; - } - } - - return s; - }; - - auto getAvialableCoinForLMintBalance = [&]() -> CAmount { - std::vector>> valueAndUTXO; - pwalletMain->AvailableCoinsForLMint(valueAndUTXO, nullptr); - CAmount s = 0; - - for (auto const &v : valueAndUTXO) { - s += v.first; - } - - return s; - }; - - CScript externalScript; - { - uint160 seed; - GetRandBytes(seed.begin(), seed.size()); - - externalScript = GetScriptForDestination(CKeyID(seed)); - } - - auto generateBlocksPerScripts = [&](size_t blocks, size_t blocksPerScript) -> std::vector { - LOCK2(cs_main, pwalletMain->cs_wallet); - std::vector scripts; - while (blocks != 0) { - CPubKey key; - key = pwalletMain->GenerateNewKey(); - scripts.push_back(GetScriptForDestination(key.GetID())); - - auto blockCount = std::min(blocksPerScript, blocks); - - GenerateBlocks(blockCount, &scripts.back()); - - blocks -= blockCount; - } - - return scripts; - }; - - auto scripts = generateBlocksPerScripts(200, 10); - GenerateBlocks(100, &externalScript); - - std::vector> wtxAndFee; - std::vector hdMints; - - // Produce just one txs - auto result = pwalletMain->MintAndStoreLelantus(10 * COIN, wtxAndFee, hdMints); - BOOST_CHECK_EQUAL("", result); - BOOST_CHECK_EQUAL(1, wtxAndFee.size()); - BOOST_CHECK_EQUAL(10 * COIN, countMintsBalance(wtxAndFee)); - - // Produce more than one txs - wtxAndFee.clear(); - hdMints.clear(); - - result = pwalletMain->MintAndStoreLelantus(600 * COIN, wtxAndFee, hdMints); - BOOST_CHECK_EQUAL("", result); - BOOST_CHECK_GT(wtxAndFee.size(), 1); - BOOST_CHECK_EQUAL(600 * COIN, countMintsBalance(wtxAndFee)); - - // Mint all and each address contain no larger than mint limit - wtxAndFee.clear(); - hdMints.clear(); - - auto balance = getAvialableCoinForLMintBalance(); - BOOST_CHECK_GT(balance, 0); - - result = pwalletMain->MintAndStoreLelantus(0, wtxAndFee, hdMints, true); - BOOST_CHECK_EQUAL("", result); - BOOST_CHECK_GE(wtxAndFee.size(), scripts.size() - 2); - BOOST_CHECK_GT(balance, countMintsBalance(wtxAndFee)); - BOOST_CHECK_EQUAL(balance, countMintsBalance(wtxAndFee, true)); - BOOST_CHECK_EQUAL(0, getAvialableCoinForLMintBalance()); - - // Mint all and have address that contain balance larger mint limit per tx - scripts = generateBlocksPerScripts(500, 200); - GenerateBlocks(100, &externalScript); - - wtxAndFee.clear(); - hdMints.clear(); - - balance = getAvialableCoinForLMintBalance(); - BOOST_CHECK_GT(balance, 0); - - result = pwalletMain->MintAndStoreLelantus(0, wtxAndFee, hdMints, true); - BOOST_CHECK_EQUAL("", result); - BOOST_CHECK_GE(wtxAndFee.size(), scripts.size()); - BOOST_CHECK_GT(balance, countMintsBalance(wtxAndFee)); - BOOST_CHECK_EQUAL(balance, countMintsBalance(wtxAndFee, true)); - BOOST_CHECK_EQUAL(0, pwalletMain->GetBalance()); - - // Scripts of all changes should unique - std::set changeScripts; - for (auto const &wtx : wtxAndFee) { - for (auto const &out : wtx.first.tx->vout) { - if (!out.scriptPubKey.IsLelantusMint()) { - BOOST_CHECK(!changeScripts.count(out.scriptPubKey)); - changeScripts.insert(out.scriptPubKey); - } - } - } -} - -BOOST_AUTO_TEST_CASE(spend) -{ - fRequireStandard = true; // to verify mainnet can accept lelantus mint - pwalletMain->SetBroadcastTransactions(true); - GenerateBlocks(150); - - std::vector> wtxAndFee; - std::vector mints; - auto result = pwalletMain->MintAndStoreLelantus(10 * COIN, wtxAndFee, mints); - BOOST_CHECK_EQUAL("", result); - CMutableTransaction mutableTx(*(wtxAndFee[0].first.tx)); - GenerateBlock({mutableTx}, &script); - GenerateBlocks(5); - BOOST_CHECK_EQUAL(1, wtxAndFee.size()); - wtxAndFee.clear(); - mints.clear(); - - CWalletTx tx; - std::vector recipients; - - CPubKey pub; - { - LOCK(pwalletMain->cs_wallet); - pub = pwalletMain->GenerateNewKey(); - } - recipients.push_back(CRecipient{ - .scriptPubKey = GetScriptForDestination(pub.GetID()), - .nAmount = 5 * COIN, - .fSubtractFeeFromAmount = false - }); - - BOOST_CHECK_EXCEPTION( - pwalletMain->JoinSplitLelantus(recipients, {0}, tx), - InsufficientFunds, - [](const InsufficientFunds& e) { return e.what() == std::string("Insufficient funds"); }); - - result = pwalletMain->MintAndStoreLelantus(10 * COIN, wtxAndFee, mints); - BOOST_CHECK_EQUAL("", result); - mutableTx = (*(wtxAndFee[0].first.tx)); - GenerateBlock({mutableTx}, &script); - GenerateBlocks(6); - BOOST_CHECK_EQUAL(1, wtxAndFee.size()); - wtxAndFee.clear(); - mints.clear(); - - BOOST_CHECK_NO_THROW(pwalletMain->JoinSplitLelantus(recipients, {0}, tx)); - - lelantus::CLelantusState::GetState()->Reset(); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/txbuilder_tests.cpp b/src/wallet/test/txbuilder_tests.cpp deleted file mode 100644 index 3f97f4cc45..0000000000 --- a/src/wallet/test/txbuilder_tests.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include "../txbuilder.h" -#include "../../amount.h" -#include "../../random.h" - -#include "wallet_test_fixture.h" - -#include - -#include -#include - -static const CBitcoinAddress randomAddr1("aHEog3QYDGa8wH4Go9igKLDFkpaMsi3btq"); -static const CBitcoinAddress randomAddr2("aLTSv7QbTZbkgorYEhbNx2gH4hGYNLsoGv"); - -class TestInputSigner : public InputSigner -{ -public: - CScript signature; - -public: - TestInputSigner() - { - } - - explicit TestInputSigner(const CScript& sig, const COutPoint& outputParam = COutPoint(), uint32_t seq = CTxIn::SEQUENCE_FINAL) : - InputSigner(outputParam, seq), - signature(sig) - { - } - - CScript Sign(const CMutableTransaction& tx, const uint256& sig) override - { - return signature; - } -}; - -class TestTxBuilder : public TxBuilder -{ -public: - std::vector amountsRequested; - std::vector changesRequested; - std::vector> adjustFeeRequested; - - std::function>& signers, CAmount required)> getInputs; - std::function& outputs, CAmount amount, CWalletDB& walletdb)> getChanges; - std::function adjustFee; - -public: - explicit TestTxBuilder(CWallet& walletParam) : TxBuilder(walletParam) - { - } - -protected: - CAmount GetInputs(std::vector>& signers, CAmount required) override - { - amountsRequested.push_back(required); - - return getInputs ? getInputs(signers, required) : required; - } - - CAmount GetChanges(std::vector& outputs, CAmount amount, CWalletDB& walletdb) override - { - changesRequested.push_back(amount); - - return getChanges ? getChanges(outputs, amount, walletdb) : amount; - } - - CAmount AdjustFee(CAmount needed, unsigned txSize) override - { - adjustFeeRequested.push_back(std::make_pair(needed, txSize)); - - return adjustFee ? adjustFee(needed, txSize) : TxBuilder::AdjustFee(needed, txSize); - } -}; - -BOOST_FIXTURE_TEST_SUITE(wallet_txbuilder_tests, WalletTestingSetup) - -BOOST_AUTO_TEST_CASE(build_with_empty_recipients) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - BOOST_CHECK_EXCEPTION( - builder.Build({}, fee, fChangeAddedToFee, walletdb), - std::invalid_argument, - [](const std::invalid_argument& e) { return e.what() == std::string("No recipients"); } - ); -} - -BOOST_AUTO_TEST_CASE(build_with_some_recipients_have_negative_amount) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - - std::vector recipients = { - {.scriptPubKey = GetScriptForDestination(randomAddr1.Get()), .nAmount = 10, .fSubtractFeeFromAmount = false}, - {.scriptPubKey = GetScriptForDestination(randomAddr2.Get()), .nAmount = -5, .fSubtractFeeFromAmount = false} - }; - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - BOOST_CHECK_EXCEPTION( - builder.Build(recipients, fee, fChangeAddedToFee, walletdb), - std::invalid_argument, - [](const std::invalid_argument& e) { return e.what() == std::string("Recipient 1 has invalid amount"); } - ); -} - -BOOST_AUTO_TEST_CASE(build_with_some_recipients_have_amount_exceed_limit) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - - std::vector recipients = { - {.scriptPubKey = GetScriptForDestination(randomAddr1.Get()), .nAmount = MAX_MONEY + 1, .fSubtractFeeFromAmount = false}, - {.scriptPubKey = GetScriptForDestination(randomAddr2.Get()), .nAmount = 1, .fSubtractFeeFromAmount = false} - }; - - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - BOOST_CHECK_EXCEPTION( - builder.Build(recipients, fee, fChangeAddedToFee, walletdb), - std::invalid_argument, - [](const std::invalid_argument& e) { return e.what() == std::string("Recipient 0 has invalid amount"); } - ); -} - -BOOST_AUTO_TEST_CASE(build_with_no_subtract_fee) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - - std::vector recipients = { - {.scriptPubKey = GetScriptForDestination(randomAddr1.Get()), .nAmount = 10, .fSubtractFeeFromAmount = false}, - {.scriptPubKey = GetScriptForDestination(randomAddr2.Get()), .nAmount = 20, .fSubtractFeeFromAmount = false} - }; - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - auto tx = builder.Build(recipients, fee, fChangeAddedToFee, walletdb); - - BOOST_CHECK_GT(fee, 0); - BOOST_CHECK_GT(builder.amountsRequested.size(), 0); - BOOST_CHECK_EQUAL(builder.amountsRequested.back(), 30 + fee); - - BOOST_CHECK_EQUAL(tx.tx->vout.size(), 2); - BOOST_CHECK(tx.tx->vout[0].scriptPubKey == GetScriptForDestination(randomAddr1.Get())); - BOOST_CHECK_EQUAL(tx.tx->vout[0].nValue, 10); - BOOST_CHECK(tx.tx->vout[1].scriptPubKey == GetScriptForDestination(randomAddr2.Get())); - BOOST_CHECK_EQUAL(tx.tx->vout[1].nValue, 20); -} - -BOOST_AUTO_TEST_CASE(build_with_subtract_fee) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - - std::vector recipients = { - {.scriptPubKey = GetScriptForDestination(randomAddr1.Get()), .nAmount = 10, .fSubtractFeeFromAmount = true}, - {.scriptPubKey = GetScriptForDestination(randomAddr2.Get()), .nAmount = 20, .fSubtractFeeFromAmount = true} - }; - - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - auto tx = builder.Build(recipients, fee, fChangeAddedToFee, walletdb); - - BOOST_CHECK_GT(fee, 0); - BOOST_CHECK_GT(builder.amountsRequested.size(), 0); - BOOST_CHECK_EQUAL(builder.amountsRequested.back(), 30); - - BOOST_CHECK_EQUAL(tx.tx->vout.size(), 2); - BOOST_CHECK(tx.tx->vout[0].scriptPubKey == GetScriptForDestination(randomAddr1.Get())); - BOOST_CHECK_EQUAL(tx.tx->vout[0].nValue, 10 - (fee / 2 + fee % 2)); - BOOST_CHECK(tx.tx->vout[1].scriptPubKey == GetScriptForDestination(randomAddr2.Get())); - BOOST_CHECK_EQUAL(tx.tx->vout[1].nValue, 20 - fee / 2); -} - -BOOST_AUTO_TEST_CASE(build_with_changes) -{ - TestTxBuilder builder(*pwalletMain); - CAmount fee; - CScript in1, in2; - COutPoint out1(GetRandHash(), 0), out2(GetRandHash(), 1); - - in1 << std::vector({ 0x21, 0xe3, 0xad, 0x9a, 0xec, 0x5b, 0x70, 0xcb, 0x4c, 0xc1, 0xd8, 0xe2, 0x95, 0x27, 0xe3, 0x7c }); - in2 << std::vector({ 0xac, 0xd9, 0x86, 0x7d, 0xd7, 0x6e, 0xc1, 0xb7, 0x9d, 0xde, 0xdc, 0xbd, 0x91, 0xc1, 0x8e, 0xed }); - - builder.getInputs = [&in1, &in2, &out1, &out2](std::vector>& signers, CAmount required) { - signers.push_back(std::unique_ptr(new TestInputSigner(in1, out1, 1))); - signers.push_back(std::unique_ptr(new TestInputSigner(in2, out2, 2))); - return required + 5; - }; - - builder.getChanges = [](std::vector& outputs, CAmount amount, CWalletDB& walletdb) { - outputs.emplace_back(amount - 1, CScript()); - return 1; - }; - - std::vector recipients = { - {.scriptPubKey = GetScriptForDestination(randomAddr1.Get()), .nAmount = 10, .fSubtractFeeFromAmount = false}, - {.scriptPubKey = GetScriptForDestination(randomAddr2.Get()), .nAmount = 20, .fSubtractFeeFromAmount = false} - }; - CWalletDB walletdb(pwalletMain->strWalletFile); - bool fChangeAddedToFee; - auto tx = builder.Build(recipients, fee, fChangeAddedToFee, walletdb); - - BOOST_CHECK_GT(fee, 0); - BOOST_CHECK_GT(builder.amountsRequested.size(), 0); - BOOST_CHECK_EQUAL(builder.amountsRequested.back(), 30 + fee - 1); - - BOOST_CHECK_GT(builder.changesRequested.size(), 0); - - for (auto& call : builder.changesRequested) { - BOOST_CHECK_EQUAL(call, 5); - } - - BOOST_CHECK_GT(builder.adjustFeeRequested.size(), 0); - - for (auto& call : builder.adjustFeeRequested) { - BOOST_CHECK_GT(call.first, 0); - BOOST_CHECK_GT(call.second, 0); - } - - BOOST_CHECK_EQUAL(tx.tx->vin.size(), 2); - BOOST_CHECK(tx.tx->vin[0].scriptSig == in1); - BOOST_CHECK(tx.tx->vin[0].prevout == out1); - BOOST_CHECK_EQUAL(tx.tx->vin[0].nSequence, 1); - BOOST_CHECK(tx.tx->vin[1].scriptSig == in2); - BOOST_CHECK(tx.tx->vin[1].prevout == out2); - BOOST_CHECK_EQUAL(tx.tx->vin[1].nSequence, 2); - - BOOST_CHECK_EQUAL(tx.tx->vout.size(), 3); - BOOST_CHECK(std::find_if(tx.tx->vout.begin(), tx.tx->vout.end(), [](const CTxOut& o) { return o.nValue == 4; }) != tx.tx->vout.end()); - BOOST_CHECK(std::find_if(tx.tx->vout.begin(), tx.tx->vout.end(), [](const CTxOut& o) { return o.nValue == 10; }) != tx.tx->vout.end()); - BOOST_CHECK(std::find_if(tx.tx->vout.begin(), tx.tx->vout.end(), [](const CTxOut& o) { return o.nValue == 20; }) != tx.tx->vout.end()); -} - -BOOST_AUTO_TEST_SUITE_END() - diff --git a/src/wallet/txbuilder.cpp b/src/wallet/txbuilder.cpp deleted file mode 100644 index cb853516ba..0000000000 --- a/src/wallet/txbuilder.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include "txbuilder.h" - -#include "../amount.h" -#include "../validation.h" -#include "../policy/policy.h" -#include "../random.h" -#include "../script/script.h" -#include "../txmempool.h" -#include "../uint256.h" -#include "../util.h" - -#include - -#include -#include -#include -#include - -#include -#include - -InputSigner::InputSigner() : InputSigner(COutPoint()) -{ -} - -InputSigner::InputSigner(const COutPoint& output, uint32_t seq) : output(output), sequence(seq) -{ -} - -InputSigner::~InputSigner() -{ -} - -TxBuilder::TxBuilder(CWallet& wallet) noexcept : wallet(wallet) -{ -} - -TxBuilder::~TxBuilder() -{ -} - -CWalletTx TxBuilder::Build(const std::vector& recipients, CAmount& fee, bool& fChangeAddedToFee, CWalletDB& walletdb) -{ - if (recipients.empty()) { - throw std::invalid_argument(_("No recipients")); - } - - // calculate total value to spend - CAmount spend = 0; - unsigned recipientsToSubtractFee = 0; - - for (size_t i = 0; i < recipients.size(); i++) { - auto& recipient = recipients[i]; - - if (!MoneyRange(recipient.nAmount)) { - throw std::invalid_argument(boost::str(boost::format(_("Recipient %1% has invalid amount")) % i)); - } - - spend += recipient.nAmount; - - if (recipient.fSubtractFeeFromAmount) { - recipientsToSubtractFee++; - } - } - - CWalletTx result; - CMutableTransaction tx; - - result.fTimeReceivedIsTxTime = true; - result.BindWallet(&wallet); - - // Discourage fee sniping. - // - // For a large miner the value of the transactions in the best block and - // the mempool can exceed the cost of deliberately attempting to mine two - // blocks to orphan the current best block. By setting nLockTime such that - // only the next block can include the transaction, we discourage this - // practice as the height restricted and limited blocksize gives miners - // considering fee sniping fewer options for pulling off this attack. - // - // A simple way to think about this is from the wallet's point of view we - // always want the blockchain to move forward. By setting nLockTime this - // way we're basically making the statement that we only want this - // transaction to appear in the next block; we don't want to potentially - // encourage reorgs by allowing transactions to appear at lower heights - // than the next block in forks of the best chain. - // - // Of course, the subsidy is high enough, and transaction volume low - // enough, that fee sniping isn't a problem yet, but by implementing a fix - // now we ensure code won't be written that makes assumptions about - // nLockTime that preclude a fix later. - tx.nLockTime = chainActive.Height(); - - // Secondly occasionally randomly pick a nLockTime even further back, so - // that transactions that are delayed after signing for whatever reason, - // e.g. high-latency mix networks and some CoinJoin implementations, have - // better privacy. - if (GetRandInt(10) == 0) { - tx.nLockTime = std::max(0, static_cast(tx.nLockTime) - GetRandInt(100)); - } - - assert(tx.nLockTime <= static_cast(chainActive.Height())); - assert(tx.nLockTime < LOCKTIME_THRESHOLD); - - // Start with no fee and loop until there is enough fee; - uint32_t nCountNextUse = 0; - if (pwalletMain->zwallet) { - nCountNextUse = pwalletMain->zwallet->GetCount(); - } - for (fee = payTxFee.GetFeePerK();;) { - // In case of not enough fee, reset mint seed counter - if (pwalletMain->zwallet) { - pwalletMain->zwallet->SetCount(nCountNextUse); - } - CAmount required = spend; - - tx.vin.clear(); - tx.vout.clear(); - - result.fFromMe = true; - result.changes.clear(); - - // If no any recipients to subtract fee then the sender need to pay by themself. - if (!recipientsToSubtractFee) { - required += fee; - } - - // fill outputs - bool remainderSubtracted = false; - - for (size_t i = 0; i < recipients.size(); i++) { - auto& recipient = recipients[i]; - CTxOut vout(recipient.nAmount, recipient.scriptPubKey); - - if (recipient.fSubtractFeeFromAmount) { - // Subtract fee equally from each selected recipient. - vout.nValue -= fee / recipientsToSubtractFee; - - if (!remainderSubtracted) { - // First receiver pays the remainder not divisible by output count. - vout.nValue -= fee % recipientsToSubtractFee; - remainderSubtracted = true; - } - } - - if (vout.IsDust(minRelayTxFee)) { - std::string err; - - if (recipient.fSubtractFeeFromAmount && fee > 0) { - if (vout.nValue < 0) { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to pay the fee")) % i); - } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to send after the fee has been deducted")) % i); - } - } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small")) % i); - } - - throw std::invalid_argument(err); - } - - tx.vout.push_back(vout); - } - - // get inputs - std::vector> signers; - CAmount total = GetInputs(signers, required); - - // add changes - CAmount change = total - required; - - if (change > 0) { - // get changes outputs - std::vector changes; - CAmount addToFee = GetChanges(changes, change, walletdb); - if(addToFee > 0) - fChangeAddedToFee = true; - fee += addToFee; - - // shuffle changes to provide some privacy - std::vector, bool>> outputs; - outputs.reserve(tx.vout.size() + changes.size()); - - for (auto& output : tx.vout) { - outputs.push_back(std::make_pair(std::ref(output), false)); - } - - for (auto& output : changes) { - outputs.push_back(std::make_pair(std::ref(output), true)); - } - - std::shuffle(outputs.begin(), outputs.end(), std::random_device()); - - // replace outputs with shuffled one - std::vector shuffled; - shuffled.reserve(outputs.size()); - - for (size_t i = 0; i < outputs.size(); i++) { - auto& output = outputs[i]; - - shuffled.push_back(output.first); - - if (output.second) { - result.changes.insert(static_cast(i)); - } - } - - tx.vout = std::move(shuffled); - } - - // fill inputs - for (auto& signer : signers) { - tx.vin.emplace_back(signer->output, CScript(), signer->sequence); - } - - // now every fields is populated then we can sign transaction - uint256 sig = tx.GetHash(); - - for (size_t i = 0; i < tx.vin.size(); i++) { - tx.vin[i].scriptSig = signers[i]->Sign(tx, sig); - } - - // check fee - result.SetTx(MakeTransactionRef(tx)); - - if (GetTransactionWeight(tx) >= MAX_STANDARD_TX_WEIGHT) { - throw std::runtime_error(_("Transaction is too large (size limit: 100Kb). Select less inputs or consolidate your UTXOs")); - } - - // check fee - unsigned size = GetVirtualTransactionSize(tx); - CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); - feeNeeded = AdjustFee(feeNeeded, size); - - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (feeNeeded < minRelayTxFee.GetFee(size)) { - throw std::runtime_error(_("Transaction too large for fee policy")); - } - - if (fee >= feeNeeded) { - break; - } - - fee = feeNeeded; - } - - if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { - // Lastly, ensure this tx will pass the mempool's chain limits - LockPoints lp; - CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); - CTxMemPool::setEntries setAncestors; - size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; - std::string errString; - if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, - nLimitDescendants, nLimitDescendantSize, errString)) { - throw std::runtime_error(_("Transaction has too long of a mempool chain")); - } - } - - return result; -} - -CAmount TxBuilder::AdjustFee(CAmount needed, unsigned txSize) -{ - return needed; -} diff --git a/src/wallet/txbuilder.h b/src/wallet/txbuilder.h deleted file mode 100644 index 7266904a18..0000000000 --- a/src/wallet/txbuilder.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef FIRO_WALLET_TXBUILDER_H -#define FIRO_WALLET_TXBUILDER_H - -#include "wallet.h" - -#include "../amount.h" -#include "../script/script.h" -#include "../primitives/transaction.h" -#include "../uint256.h" - -#include -#include - -#include - -class InputSigner -{ -public: - COutPoint output; - uint32_t sequence; - -public: - InputSigner(); - explicit InputSigner(const COutPoint& output, uint32_t seq = CTxIn::SEQUENCE_FINAL); - virtual ~InputSigner(); - - virtual CScript Sign(const CMutableTransaction& tx, const uint256& sig) = 0; -}; - -class TxBuilder -{ -public: - CWallet& wallet; - const CCoinControl *coinControl; - -public: - explicit TxBuilder(CWallet& wallet) noexcept; - virtual ~TxBuilder(); - - CWalletTx Build(const std::vector& recipients, CAmount& fee, bool& fChangeAddedToFee, CWalletDB& walletdb); - -protected: - virtual CAmount GetInputs(std::vector>& signers, CAmount required) = 0; - virtual CAmount GetChanges(std::vector& outputs, CAmount amount, CWalletDB& walletdb) = 0; - virtual CAmount AdjustFee(CAmount needed, unsigned txSize); -}; - -#endif diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c9d71264da..4480d146a8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -10,7 +10,6 @@ #include "support/allocators/secure.h" #include "sync.h" #include "walletexcept.h" -#include "lelantusjoinsplitbuilder.h" #include "amount.h" #include "base58.h" #include "checkpoints.h" @@ -21,7 +20,6 @@ #include "key.h" #include "keystore.h" #include "validation.h" -#include "lelantus.h" #include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include "net.h" @@ -39,15 +37,12 @@ #include "masternode-sync.h" #include "random.h" #include "init.h" -#include "hdmint/wallet.h" #include "rpc/protocol.h" #include "wallet/bip39.h" #include "crypto/hmac_sha512.h" #include "crypto/aes.h" -#include "hdmint/tracker.h" - #include "evo/deterministicmns.h" #include @@ -109,13 +104,6 @@ struct CompareByAmount } }; -static void EnsureMintWalletAvailable() -{ - if (!pwalletMain || !pwalletMain->zwallet) { - throw std::logic_error("Sigma feature requires HD wallet"); - } -} - static void EnsureSparkWalletAvailable() { if (!pwalletMain || !pwalletMain->sparkWallet) { @@ -772,22 +760,11 @@ bool CWallet::IsSpent(const uint256 &hash, unsigned int n) const if (script.IsZerocoinMint()) { return true; - } else if (zwallet && script.IsSigmaMint()) { + } else if (pwalletMain && script.IsSigmaMint()) { return true; - } else if (zwallet && (script.IsLelantusMint() || script.IsLelantusJMint())) { - secp_primitives::GroupElement pubcoin; - try { - lelantus::ParseLelantusMintScript(script, pubcoin); - } catch (std::invalid_argument &) { - return false; - } - uint256 hashPubcoin = primitives::GetPubCoinValueHash(pubcoin); - CLelantusMintMeta meta; - if(!zwallet->GetTracker().GetLelantusMetaFromPubcoin(hashPubcoin, meta)){ - return false; - } - return meta.isUsed; - } else if (zwallet && (script.IsSparkMint() || script.IsSparkSMint())) { + } else if (pwalletMain && (script.IsLelantusMint() || script.IsLelantusJMint())) { + return true; + } else if (pwalletMain && pwalletMain->sparkWallet && (script.IsSparkMint() || script.IsSparkSMint())) { std::vector serialContext; for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx *pcoin = &(*it).second; @@ -1455,35 +1432,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) } } - if (wtx.tx->IsLelantusJoinSplit()) { - // find out coin serial number - assert(wtx.tx->vin.size() == 1); - - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(*wtx.tx); - } - catch (const std::exception &) { - continue; - } - - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - - for (const auto& serial : serials) { - // mark corresponding mint as unspent - uint256 hashSerial = primitives::GetSerialHash(serial); - CLelantusMintMeta meta; - if(zwallet->GetTracker().GetMetaFromSerial(hashSerial, meta)){ - meta.isUsed = false; - zwallet->GetTracker().UpdateState(meta); - - // erase lelantus spend entry - CLelantusSpendEntry spendEntry; - spendEntry.coinSerial = serial; - walletdb.EraseLelantusSpendSerialEntry(spendEntry); - } - } - } else if (wtx.tx->IsSparkSpend()) { + if (wtx.tx->IsSparkSpend()) { std::vector lTags; try { spark::SpendTransaction spend = spark::ParseSparkSpend(*wtx.tx); @@ -1496,25 +1445,6 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) sparkWallet->AbandonSpends(lTags); } - if (wtx.tx->IsLelantusMint()) { - for (const CTxOut &txout: wtx.tx->vout) { - if (!txout.scriptPubKey.IsLelantusMint() && !txout.scriptPubKey.IsLelantusJMint()) - continue; - - try { - secp_primitives::GroupElement groupElement; - lelantus::ParseLelantusMintScript(txout.scriptPubKey, groupElement); - uint256 hashPubcoin = primitives::GetPubCoinValueHash(groupElement); - CLelantusMintMeta meta; - if (zwallet->GetTracker().GetLelantusMetaFromPubcoin(hashPubcoin, meta)) - zwallet->GetTracker().Archive(meta); - } - catch (std::invalid_argument &) { - continue; - } - } - } - if (wtx.tx->IsSparkTransaction()) { std::vector coins = spark::GetSparkMintCoins(*wtx.tx); sparkWallet->AbandonSparkMints(coins); @@ -1605,21 +1535,8 @@ isminetype CWallet::IsMine(const CTxIn &txin, const CTransaction& tx) const { LOCK(cs_wallet); - if (txin.IsZerocoinSpend() || txin.IsSigmaSpend()) { + if (txin.IsZerocoinSpend() || txin.IsSigmaSpend() || txin.IsLelantusJoinSplit()) { return ISMINE_NO; - } else if (txin.IsLelantusJoinSplit()) { - CWalletDB db(strWalletFile); - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(tx); - } - catch (const std::exception &) { - return ISMINE_NO; - } - - if (db.HasLelantusSpendSerialEntry(joinsplit->getCoinSerialNumbers()[0])) { - return ISMINE_SPENDABLE; - } } else if (txin.IsZerocoinRemint()) { return ISMINE_NO; } else if (tx.IsSparkSpend()) { @@ -1654,34 +1571,10 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const CTransaction& tx, const ismin { LOCK(cs_wallet); - if (txin.IsZerocoinSpend() || txin.IsSigmaSpend()) { - // Reverting it to its pre-lelantus state. + if (txin.IsZerocoinSpend() || txin.IsSigmaSpend() || txin.IsLelantusJoinSplit()) { goto end; } else if (txin.IsZerocoinRemint()) { return 0; - } else if (txin.IsLelantusJoinSplit()) { - if (!(filter & ISMINE_SPENDABLE)) { - goto end; - } - - CWalletDB db(strWalletFile); - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(tx); - } - catch (const std::exception &) { - goto end; - } - - CAmount amount = 0; - - const std::vector& serials = joinsplit->getCoinSerialNumbers(); - for (const auto& serial : serials) { - CLelantusSpendEntry lelantusSpend; - if(db.ReadLelantusSpendSerialEntry(serial, lelantusSpend)) - amount += lelantusSpend.amount; - } - return amount; } else if (tx.IsSparkSpend()) { if (!(filter & ISMINE_SPENDABLE)) { goto end; @@ -1719,16 +1612,7 @@ isminetype CWallet::IsMine(const CTxOut &txout) const { LOCK(cs_wallet); - if (txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) { - CWalletDB db(strWalletFile); - secp_primitives::GroupElement pub; - try { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pub); - } catch (std::invalid_argument &) { - return ISMINE_NO; - } - return db.HasHDMint(pub) ? ISMINE_SPENDABLE : ISMINE_NO; - } else if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { std::vector serialContext; for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx *pcoin = &(*it).second; @@ -1767,24 +1651,6 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons { if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); - if (txout.scriptPubKey.IsLelantusJMint()) { - if (!(filter & ISMINE_SPENDABLE)) - return 0; - CWalletDB db(strWalletFile); - secp_primitives::GroupElement pub; - try { - std::vector encryptedValue; - lelantus::ParseLelantusJMintScript(txout.scriptPubKey, pub, encryptedValue); - } catch (std::invalid_argument&) { - return ISMINE_NO; - } - uint256 hashPubcoin = primitives::GetPubCoinValueHash(pub); - CHDMint dMint; - if (db.ReadHDMint(hashPubcoin, true, dMint)) { - return dMint.GetAmount(); - } - return 0; - } if (txout.scriptPubKey.IsSparkSMint()) { if (!(filter & ISMINE_SPENDABLE)) @@ -2006,7 +1872,7 @@ void CWallet::GenerateNewMnemonic() if (!mnContainer.SetMnemonic(secureMnemonic, securePassphrase)) throw std::runtime_error(std::string(__func__) + ": SetMnemonic failed"); - newHdChain.masterKeyID = CKeyID(Hash160(mnContainer.seed.begin(), mnContainer.seed.end())); + newHdChain.masterKeyID = CKeyID(Hash160(mnContainer.seed.begin(), mnContainer.seed.end())); } if (!SetHDChain(newHdChain, false)) @@ -2198,14 +2064,7 @@ void CWalletTx::GetAmounts(std::list& listReceived, CAmount nDebit = GetDebit(filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - if (tx->IsLelantusJoinSplit()) { - try { - nFee = lelantus::ParseLelantusJoinSplit(*tx)->getFee(); - } - catch (const std::exception &) { - // do nothing - } - } else if (tx->IsSparkSpend()) { + if (tx->IsSparkSpend()) { try { nFee = spark::ParseSparkSpend(*tx).getFee(); } @@ -2252,7 +2111,7 @@ void CWalletTx::GetAmounts(std::list& listReceived, } CAmount nValue; - if(txout.scriptPubKey.IsLelantusJMint() || txout.scriptPubKey.IsSparkSMint()) { + if(txout.scriptPubKey.IsSparkSMint()) { LOCK(pwalletMain->cs_wallet); nValue = pwallet->GetCredit(txout, ISMINE_SPENDABLE); } else { @@ -2824,100 +2683,6 @@ CAmount CWallet::GetBalance(bool fExcludeLocked) const return nTotal; } -std::pair CWallet::GetPrivateBalance() -{ - if (cachedLelantusBalance.first >= 0) - return {cachedLelantusBalance.first, cachedLelantusBalance.second}; - - std::pair balance = {0, 0}; - - auto zwallet = pwalletMain->zwallet.get(); - - if(!zwallet) - return balance; - - auto lelantusCoins = zwallet->GetTracker().ListLelantusMints(true, false, false); - for (auto const &c : lelantusCoins) { - - if (c.isUsed || c.isArchived || !c.isSeedCorrect) { - continue; - } - - auto conf = c.nHeight > 0 - ? chainActive.Height() - c.nHeight + 1 : 0; - - if (conf >= ZC_MINT_CONFIRMATIONS) { - balance.first += c.amount; - } else { - balance.second += c.amount; - } - } - cachedLelantusBalance.first = balance.first; - cachedLelantusBalance.second = balance.second; - - return balance; -} - -CRecipient CWallet::CreateLelantusMintRecipient( - lelantus::PrivateCoin& coin, - CHDMint& vDMint, - bool generate) -{ - EnsureMintWalletAvailable(); - - while (true) { - CWalletDB walletdb(pwalletMain->strWalletFile); - uint160 seedID; - if (generate) { - // Generate and store secrets deterministically in the following function. - pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin, vDMint, seedID); - } - - // Get a copy of the 'public' portion of the coin. You should - // embed this into a Lelantus 'MINT' transaction along with a series of currency inputs - auto &pubCoin = coin.getPublicCoin(); - - if (!pubCoin.validate()) { - throw std::runtime_error("Unable to mint a lelantus coin."); - } - - // Create script for coin - CScript script; - // opcode is inserted as 1 byte according to file script/script.h - script << OP_LELANTUSMINT; - - // and this one will write the size in different byte lengths depending on the length of vector. If vector size is <0.4c, which is 76, will write the size of vector in just 1 byte. In our case the size is always 34, so must write that 34 in 1 byte. - std::vector vch = pubCoin.getValue().getvch(); - script.insert(script.end(), vch.begin(), vch.end()); //this uses 34 byte - - // generating schnorr proof - CDataStream serializedSchnorrProof(SER_NETWORK, PROTOCOL_VERSION); - lelantus::GenerateMintSchnorrProof(coin, serializedSchnorrProof); - script.insert(script.end(), serializedSchnorrProof.begin(), serializedSchnorrProof.end()); //this uses 98 byte - - auto pubcoin = vDMint.GetPubcoinValue() + - lelantus::Params::get_default()->get_h1() * Scalar(vDMint.GetAmount()).negate(); - uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); - CDataStream ss(SER_GETHASH, 0); - ss << hashPub; - ss << seedID; - uint256 hashForRecover = Hash(ss.begin(), ss.end()); - - // Check if there is a mint with same private data in chain, most likely Hd mint state corruption, - // If yes, try with new counter - GroupElement dummyValue; - if (lelantus::CLelantusState::GetState()->HasCoinTag(dummyValue, hashForRecover)) - continue; - - CDataStream serializedHash(SER_NETWORK, 0); - serializedHash << hashForRecover; - script.insert(script.end(), serializedHash.begin(), serializedHash.end()); - - // overall Lelantus mint script size is 1 + 34 + 98 + 32 = 165 byte - return {script, CAmount(coin.getV()), false, {}, {}}; - } -} - std::list CWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { EnsureSparkWalletAvailable(); @@ -2968,84 +2733,6 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons return balance; } -std::list CWallet::GetAvailableLelantusCoins(const CCoinControl *coinControl, bool includeUnsafe, bool forEstimation) const { - EnsureMintWalletAvailable(); - - LOCK2(cs_main, cs_wallet); - CWalletDB walletdb(strWalletFile); - std::list coins; - std::vector vecMints = zwallet->GetTracker().ListLelantusMints(true, true, false); - for (const CLelantusMintMeta& mint : vecMints) { - CLelantusEntry entry; - GetMint(mint.hashSerial, entry, forEstimation); - if(entry.amount != 0) // ignore 0 mints which where created to increase privacy - coins.push_back(entry); - } - - std::set lockedCoins = setLockedCoins; - - // Filter out coins which are not confirmed, I.E. do not have at least 2 blocks - // above them, after they were minted. - // Also filter out used coins. - // Finally filter out coins that have not been selected from CoinControl should that be used - coins.remove_if([lockedCoins, coinControl, includeUnsafe](const CLelantusEntry& coin) { - lelantus::CLelantusState* state = lelantus::CLelantusState::GetState(); - if (coin.IsUsed) - return true; - - COutPoint outPoint; - lelantus::PublicCoin pubCoin(coin.value); - lelantus::GetOutPoint(outPoint, pubCoin); - - if(lockedCoins.count(outPoint) > 0){ - return true; - } - - if(coinControl != NULL){ - if(coinControl->HasSelected()){ - if(!coinControl->IsSelected(outPoint)){ - return true; - } - } - } - - int coinHeight, coinId; - std::tie(coinHeight, coinId) = state->GetMintedCoinHeightAndId(lelantus::PublicCoin(coin.value)); - - // Check group size - uint256 hashOut; - std::vector coinOuts; - std::vector setHash; - state->GetCoinSetForSpend( - &chainActive, - chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), // required 1 confirmation for mint to spend - coinId, - hashOut, - coinOuts, - setHash - ); - - if (!includeUnsafe && coinOuts.size() < 2) { - return true; - } - - if (coinHeight == -1) { - // Coin still in the mempool. - return true; - } - - if (coinHeight + (ZC_MINT_CONFIRMATIONS - 1) > chainActive.Height()) { - // Remove the coin from the candidates list, since it does not have the - // required number of confirmations. - return true; - } - - return false; - }); - - return coins; -} - std::vector GetAESKey(const secp_primitives::GroupElement& pubcoin) { uint32_t keyPath = primitives::GetPubCoinValueHash(pubcoin).GetFirstUint32(); CKey secret; @@ -3095,154 +2782,6 @@ static CAmount CalculateCoinsBalance(Iterator begin, Iterator end) { return balance; } -template -static CAmount CalculateLelantusCoinsBalance(Iterator begin, Iterator end) { - CAmount balance(0); - for (auto start = begin; start != end; start++) { - balance += start->amount; - } - return balance; -} - -bool CWallet::GetCoinsToJoinSplit( - CAmount required, - std::vector& coinsToSpend_out, - CAmount& changeToMint, - std::list coins, - const size_t coinsToSpendLimit, - const CAmount amountToSpendLimit, - const CCoinControl *coinControl) const -{ - - EnsureMintWalletAvailable(); - const Consensus::Params &consensusParams = Params().GetConsensus(); - - if (required > consensusParams.nMaxValueLelantusSpendPerTransaction) { - throw std::invalid_argument(_("The required amount exceeds spend limit")); - } - - CAmount availableBalance = CalculateLelantusCoinsBalance(coins.begin(), coins.end()); - - if (required > availableBalance) { - throw InsufficientFunds(); - } - - // sort by biggest amount. if it is same amount we will prefer the older block - auto comparer = [](const CLelantusEntry& a, const CLelantusEntry& b) -> bool { - return a.amount != b.amount ? a.amount > b.amount : a.nHeight < b.nHeight; - }; - coins.sort(comparer); - - CAmount spend_val(0); - - std::list coinsToSpend; - - // If coinControl, want to use all inputs - bool coinControlUsed = false; - if(coinControl != NULL) { - if(coinControl->HasSelected()) { - auto coinIt = coins.rbegin(); - for (; coinIt != coins.rend(); coinIt++) { - spend_val += coinIt->amount; - } - coinControlUsed = true; - coinsToSpend.insert(coinsToSpend.begin(), coins.begin(), coins.end()); - } - } - - if(!coinControlUsed) { - while (spend_val < required) { - if(coins.empty()) - break; - - CLelantusEntry choosen; - CAmount need = required - spend_val; - - auto itr = coins.begin(); - if(need >= itr->amount) { - choosen = *itr; - coins.erase(itr); - } else { - for (auto coinIt = coins.rbegin(); coinIt != coins.rend(); coinIt++) { - auto nextItr = coinIt; - nextItr++; - - if (coinIt->amount >= need && (nextItr == coins.rend() || nextItr->amount != coinIt->amount)) { - choosen = *coinIt; - coins.erase(std::next(coinIt).base()); - break; - } - } - } - - spend_val += choosen.amount; - coinsToSpend.push_back(choosen); - } - } - - // sort by group id ay ascending order. it is mandatory for creting proper joinsplit - auto idComparer = [](const CLelantusEntry& a, const CLelantusEntry& b) -> bool { - return a.id < b.id; - }; - coinsToSpend.sort(idComparer); - - changeToMint = spend_val - required; - coinsToSpend_out.insert(coinsToSpend_out.begin(), coinsToSpend.begin(), coinsToSpend.end()); - - return true; -} - -std::vector CWallet::ProvePrivateTxOwn(const uint256& txid, const std::string& message) const { - std::vector result; - if (!mapWallet.count(txid)) - return result; - - const CWalletTx& wtx = mapWallet.at(txid); - - if (wtx.tx->IsLelantusJoinSplit()) { - CHashWriter ss(SER_GETHASH, 0); - ss << strLelantusMessageMagic; - ss << message; - - std::unique_ptr joinsplit; - try { - joinsplit = lelantus::ParseLelantusJoinSplit(*wtx.tx); - } catch (const std::exception&) { - return result; - } - const auto& serials = joinsplit->getCoinSerialNumbers(); - uint32_t count = 0; - result.resize(serials.size() * 64); - - for (const auto& serial : serials) { - CLelantusEntry mint; - uint256 hashSerial = primitives::GetSerialHash(serial); - std::vector ecdsaSecretKey; - ecdsaSecretKey = mint.ecdsaSecretKey; - - ss << count; - uint256 metahash = ss.GetHash(); - secp256k1_ecdsa_signature sig; - if (1 != secp256k1_ecdsa_sign( - OpenSSLContext::get_context(), &sig, - metahash.begin(), &ecdsaSecretKey[0], NULL, NULL)) { - return std::vector(); - } - if (1 != secp256k1_ecdsa_signature_serialize_compact( - OpenSSLContext::get_context(), &result[count * 64], &sig)) { - return std::vector(); - } - - count++; - } - } else if (wtx.tx->IsCoinBase()) { - throw std::runtime_error("This is a coinbase transaction and not a private transaction"); - } else { - throw std::runtime_error("Currently this operation is allowed only for Lelantus transactions"); - } - - return result; -} bool CWallet::TryGetBalances(CAmount& balance, CAmount& unconfirmedBalance, @@ -3430,7 +2969,7 @@ void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlyConfirmed, if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && (!IsLockedCoin((*it).first, i) || nCoinType == CoinType::ONLY_1000) && - (pcoin->tx->vout[i].nValue > 0 || fIncludeZeroValue || ((pcoin->tx->vout[i].scriptPubKey.IsLelantusJMint() || pcoin->tx->vout[i].scriptPubKey.IsSparkSMint()) && GetCredit(pcoin->tx->vout[i], ISMINE_SPENDABLE) > 0)) && + (pcoin->tx->vout[i].nValue > 0 || fIncludeZeroValue || ((pcoin->tx->vout[i].scriptPubKey.IsSparkSMint()) && GetCredit(pcoin->tx->vout[i], ISMINE_SPENDABLE) > 0)) && (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i)))) { vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || @@ -3533,63 +3072,6 @@ bool CWallet::GetVinAndKeysFromOutput(COutput out, CTxIn &txinRet, CPubKey &pubK return true; } -void CWallet::ListAvailableLelantusMintCoins(std::vector &vCoins, bool fOnlyConfirmed) const { - EnsureMintWalletAvailable(); - - vCoins.clear(); - LOCK2(cs_main, cs_wallet); - std::list listOwnCoins; - CWalletDB walletdb(pwalletMain->strWalletFile); - listOwnCoins = zwallet->GetTracker().MintsAsLelantusEntries(true, false); - LogPrintf("listOwnCoins.size()=%zu\n", listOwnCoins.size()); - for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { - const CWalletTx *pcoin = &(*it).second; -// LogPrintf("pcoin=%s\n", pcoin->GetHash().ToString()); - if (!CheckFinalTx(*pcoin)) { - LogPrintf("!CheckFinalTx(*pcoin)=%d\n", !CheckFinalTx(*pcoin)); - continue; - } - - if (fOnlyConfirmed && !pcoin->IsTrusted()) { - LogPrintf("fOnlyConfirmed = %d, !pcoin->IsTrusted() = %d\n", fOnlyConfirmed, !pcoin->IsTrusted()); - continue; - } - - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) { - LogPrintf("Not trusted\n"); - continue; - } - - int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth < 0) { - LogPrintf("nDepth=%d\n", nDepth); - continue; - } - LogPrintf("pcoin->tx->vout.size()=%zu\n", pcoin->tx->vout.size()); - - for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { - if (pcoin->tx->vout[i].scriptPubKey.IsLelantusMint() || pcoin->tx->vout[i].scriptPubKey.IsLelantusJMint()) { - CTxOut txout = pcoin->tx->vout[i]; - secp_primitives::GroupElement pubCoin; - try { - lelantus::ParseLelantusMintScript(txout.scriptPubKey, pubCoin); - } catch (std::invalid_argument &) { - continue; - } - LogPrintf("Pubcoin=%s\n", pubCoin.tostring()); - // CHECKING PROCESS - BOOST_FOREACH(const CLelantusEntry& ownCoinItem, listOwnCoins) { - if (ownCoinItem.value == pubCoin && ownCoinItem.IsUsed == false && - !ownCoinItem.randomness.isZero() && !ownCoinItem.serialNumber.isZero()) { - vCoins.push_back(COutput(pcoin, i, nDepth, true, true)); - LogPrintf("-->OK\n"); - } - } - } - } - } -} - static void ApproximateBestSubset(std::vector > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, std::vector& vfBest, CAmount& nBest, int iterations = 1000) { @@ -3742,6 +3224,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin return true; } + bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl, bool fForUseInInstantSend) const { std::vector vCoins(vAvailableCoins); @@ -4265,11 +3748,19 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state) +bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state, bool fCheckTransaction) { { LOCK2(cs_main, cs_wallet); LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); + + // When fCheckTransaction is set, validate against the mempool before touching + // the wallet. If rejected, return false immediately without adding the transaction. + if (fCheckTransaction && !wtxNew.AcceptToMemoryPool(maxTxFee, state)) { + LogPrintf("CommitTransaction(): Transaction rejected: %s\n", state.GetRejectReason()); + return false; + } + { // Take key pair from key pool so it won't be used again reservekey.KeepKey(); @@ -4294,15 +3785,18 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon // Track how many getdata requests our transaction gets mapRequestCount[wtxNew.GetHash()] = 0; - if (fBroadcastTransactions) - { - // Broadcast - if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) { - LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason()); - // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. - } else { + if (fBroadcastTransactions) { + // When fCheckTransaction is true the tx is already in the mempool; just relay. + // Otherwise attempt mempool acceptance now and relay on success. + if (fCheckTransaction || wtxNew.AcceptToMemoryPool(maxTxFee, state)) { wtxNew.RelayWalletTransaction(connman); + } else { + LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason()); } + } else if (fCheckTransaction) { + // The tx was explicitly validated and accepted; relay it regardless of + // the fBroadcastTransactions flag. + wtxNew.RelayWalletTransaction(connman); } } return true; @@ -4331,490 +3825,28 @@ bool CWallet::EraseFromWallet(uint256 hash) { * @param sign Whether to sign the transaction immediately * @return true if transaction creation succeeded, false otherwise */ -bool CWallet::CreateLelantusMintTransactions( - CAmount valueToMint, - std::vector>& wtxAndFee, - CAmount& nAllFeeRet, - std::vector& dMints, - std::list& reservekeys, - int& nChangePosInOut, - std::string& strFailReason, - const CCoinControl *coinControl, - bool autoMintAll, - bool sign) -{ - const auto& lelantusParams = lelantus::Params::get_default(); - int nChangePosRequest = nChangePosInOut; +std::string CWallet::MintAndStoreSpark( + const std::vector& outputs, + std::vector>& wtxAndFee, + bool subtractFeeFromAmount, + bool fSplit, + bool autoMintAll, + bool fAskFee, + const CCoinControl *coinControl) { + std::string strError; - // Create transaction template - CWalletTx wtxNew; - wtxNew.fTimeReceivedIsTxTime = true; - wtxNew.BindWallet(this); + EnsureSparkWalletAvailable(); - CMutableTransaction txNew; - int nHeight = 0; - { - LOCK(cs_main); - nHeight = chainActive.Height(); + if (IsLocked()) { + strError = _("Error: Wallet locked, unable to create transaction!"); + LogPrintf("MintSpark() : %s", strError); + return strError; } - txNew.nLockTime = nHeight; - assert(txNew.nLockTime <= static_cast(nHeight)); - assert(txNew.nLockTime < LOCKTIME_THRESHOLD); - - { - LOCK2(cs_main, cs_wallet); - { - std::list cacheWtxs; - std::vector>> valueAndUTXO; - AvailableCoinsForLMint(valueAndUTXO, coinControl); - - Shuffle(valueAndUTXO.begin(), valueAndUTXO.end(), FastRandomContext()); - { - CWalletDB walletdb(pwalletMain->strWalletFile); - pwalletMain->zwallet->ResetCount(walletdb); - } - while (!valueAndUTXO.empty()) { - - // initialize - CWalletTx wtx = wtxNew; - CMutableTransaction tx = txNew; - - reservekeys.emplace_back(this); - auto &reservekey = reservekeys.back(); - - if (GetRandInt(10) == 0) - tx.nLockTime = std::max(0, (int) tx.nLockTime - GetRandInt(100)); - - CHDMint dMint; - - CAmount nFeeRet = 0; - LogPrintf("nFeeRet=%d\n", nFeeRet); - - auto itr = valueAndUTXO.begin(); - - CAmount valueToMintInTx = std::min( - ::Params().GetConsensus().nMaxValueLelantusMint, - itr->first); - - if (!autoMintAll) { - valueToMintInTx = std::min(valueToMintInTx, valueToMint); - } - - CAmount nValueToSelect, mintedValue; - - std::set> setCoins; - bool skipCoin = false; - // Start with no fee and loop until there is enough fee - while (true) { - mintedValue = valueToMintInTx; - nValueToSelect = mintedValue + nFeeRet; - - // if have no enough coins in this group then subtract fee from mint - if (nValueToSelect > itr->first) { - mintedValue -= nFeeRet; - nValueToSelect = mintedValue + nFeeRet; - } - - if (!MoneyRange(mintedValue) || mintedValue == 0) { - valueAndUTXO.erase(itr); - skipCoin = true; - break; - } - - nChangePosInOut = nChangePosRequest; - tx.vin.clear(); - tx.vout.clear(); - - wtx.fFromMe = true; - wtx.changes.clear(); - - setCoins.clear(); - - // create recipient using random private coin to mock script sig - lelantus::PrivateCoin privCoin(lelantusParams, mintedValue); - auto recipient = CWallet::CreateLelantusMintRecipient(privCoin, dMint, false); - - double dPriority = 0; - - // vout to create mint - CTxOut txout(recipient.nAmount, recipient.scriptPubKey); - - if (txout.IsDust(::minRelayTxFee)) { - strFailReason = _("Transaction amount too small"); - return false; - } - - tx.vout.push_back(txout); - - // Choose coins to use - - CAmount nValueIn = 0; - if (!SelectCoins(itr->second, nValueToSelect, setCoins, nValueIn, coinControl)) { - - if (nValueIn < nValueToSelect) { - strFailReason = _("Insufficient funds"); - } - return false; - } - - for (auto const &pcoin : setCoins) { - CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue; - //The coin age after the next block (depth+1) is used instead of the current, - //reflecting an assumption the user would accept a bit more delay for - //a chance at a free transaction. - //But mempool inputs might still be in the mempool, so their age stays 0 - int age = pcoin.first->GetDepthInMainChain(); - assert(age >= 0); - if (age != 0) - age += 1; - dPriority += (double) nCredit * age; - } - - CAmount nChange = nValueIn - nValueToSelect; - - if (nChange > 0) { - // Fill a vout to ourself - // TODO: pass in scriptChange instead of reservekey so - // change transaction isn't always pay-to-bitcoin-address - CScript scriptChange; - - // coin control: send change to custom address - if (coinControl && !boost::get(&coinControl->destChange)) - scriptChange = GetScriptForDestination(coinControl->destChange); - - // send change to one of the specified change addresses - else if (IsArgSet("-change") && mapMultiArgs.at("-change").size() > 0) { - CBitcoinAddress address( - mapMultiArgs.at("-change")[GetRandInt(mapMultiArgs.at("-change").size())]); - CKeyID keyID; - if (!address.GetKeyID(keyID)) { - strFailReason = _("Bad change address"); - return false; - } - scriptChange = GetScriptForDestination(keyID); - } - - // no coin control: send change to newly generated address - else { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey); - if (!ret) { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); - return false; - } - - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - } - - CTxOut newTxOut(nChange, scriptChange); - - // Never create dust outputs; if we would, just - // add the dust to the fee. - if (newTxOut.IsDust(::minRelayTxFee)) { - nChangePosInOut = -1; - nFeeRet += nChange; - reservekey.ReturnKey(); - } else { - - if (nChangePosInOut == -1) { - - // Insert change txn at random position: - nChangePosInOut = GetRandInt(tx.vout.size() + 1); - } else if ((unsigned int) nChangePosInOut > tx.vout.size()) { - - strFailReason = _("Change index out of range"); - return false; - } - - std::vector::iterator position = tx.vout.begin() + nChangePosInOut; - tx.vout.insert(position, newTxOut); - wtx.changes.insert(static_cast(nChangePosInOut)); - } - } else { - reservekey.ReturnKey(); - } - - // Fill vin - // - // Note how the sequence number is set to max()-1 so that the - // nLockTime set above actually works. - for (const auto &coin : setCoins) { - tx.vin.push_back(CTxIn( - coin.first->GetHash(), - coin.second, - CScript(), - std::numeric_limits::max() - 1)); - } - - // Fill in dummy signatures for fee calculation. - if (!DummySignTx(tx, setCoins)) { - strFailReason = _("Signing transaction failed"); - return false; - } - - unsigned int nBytes = GetVirtualTransactionSize(tx); - - // Limit size - CTransaction txConst(tx); - if (GetTransactionWeight(txConst) >= MAX_STANDARD_TX_WEIGHT) { - strFailReason = _("Transaction is too large (size limit: 100Kb). Select less inputs or consolidate your UTXOs"); - return false; - } - dPriority = txConst.ComputePriority(dPriority, nBytes); - - // Remove scriptSigs to eliminate the fee calculation dummy signatures - for (auto &vin : tx.vin) { - vin.scriptSig = CScript(); - vin.scriptWitness.SetNull(); - } - - // Can we complete this as a free transaction? - if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) { - // Not enough fee: enough priority? - double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget); - // Require at least hard-coded AllowFree. - if (dPriority >= dPriorityNeeded && AllowFree(dPriority)) - break; - } - CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - - if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { - nFeeNeeded = coinControl->nMinimumTotalFee; - } - - if (coinControl && coinControl->fOverrideFeeRate) - nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); - - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { - strFailReason = _("Transaction too large for fee policy"); - return false; - } - - if (cmp::greater_equal(nFeeRet, nFeeNeeded)) { - for (auto &usedCoin : setCoins) { - for (auto coin = itr->second.begin(); coin != itr->second.end(); coin++) { - if (usedCoin.first == coin->tx && cmp::equal(usedCoin.second, coin->i)) { - itr->first -= coin->tx->tx->vout[coin->i].nValue; - itr->second.erase(coin); - break; - } - } - } - - if (itr->second.empty()) { - valueAndUTXO.erase(itr); - } - - // Generate hdMint - recipient = CWallet::CreateLelantusMintRecipient(privCoin, dMint); - - // vout to mint - txout = CTxOut(recipient.nAmount, recipient.scriptPubKey); - LogPrintf("txout: %s\n", txout.ToString()); - - for (size_t i = 0; i != tx.vout.size(); i++) { - if (tx.vout[i].scriptPubKey.IsLelantusMint()) { - tx.vout[i] = txout; - } - } - - break; // Done, enough fee included. - } - - // Include more fee and try again. - nFeeRet = nFeeNeeded; - continue; - } - - if(skipCoin) - continue; - - if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { - // Lastly, ensure this tx will pass the mempool's chain limits - LockPoints lp; - CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); - CTxMemPool::setEntries setAncestors; - size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - size_t nLimitDescendantSize = - GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; - std::string errString; - if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, - nLimitDescendants, nLimitDescendantSize, errString)) { - strFailReason = _("Transaction has too long of a mempool chain"); - return false; - } - } - - // Sign - int nIn = 0; - CTransaction txNewConst(tx); - for (const auto &coin : setCoins) { - bool signSuccess = false; - const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; - SignatureData sigdata; - if (sign) - signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, - coin.first->tx->vout[coin.second].nValue, - SIGHASH_ALL), scriptPubKey, - sigdata); - else - signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata); - - if (!signSuccess) { - strFailReason = _("Signing transaction failed"); - return false; - } else { - UpdateTransaction(tx, nIn, sigdata); - } - nIn++; - } - - wtx.SetTx(MakeTransactionRef(std::move(tx))); - - wtxAndFee.push_back(std::make_pair(wtx, nFeeRet)); - - if (nChangePosInOut >= 0) { - // Cache wtx to somewhere because COutput use pointer of it. - cacheWtxs.push_back(wtx); - auto &wtx = cacheWtxs.back(); - - COutput out(&wtx, nChangePosInOut, wtx.GetDepthInMainChain(false), true, true); - auto val = wtx.tx->vout[nChangePosInOut].nValue; - - bool added = false; - for (auto &utxos : valueAndUTXO) { - auto const &o = utxos.second.front(); - if (o.tx->tx->vout[o.i].scriptPubKey == wtx.tx->vout[nChangePosInOut].scriptPubKey) { - utxos.first += val; - utxos.second.push_back(out); - - added = true; - } - } - - if (!added) { - valueAndUTXO.push_back({val, {out}}); - } - } - - nAllFeeRet += nFeeRet; - dMints.push_back(dMint); - if(!autoMintAll) { - valueToMint -= mintedValue; - if (valueToMint == 0) - break; - } - } - } - } - - if (!autoMintAll && valueToMint > 0) { - return false; - } - - return true; -} - -std::string CWallet::MintAndStoreLelantus(const CAmount& value, - std::vector>& wtxAndFee, - std::vector& mints, - bool autoMintAll, - bool fAskFee, - const CCoinControl *coinControl) { - std::string strError; - - EnsureMintWalletAvailable(); - - if (IsLocked()) { - strError = _("Error: Wallet locked, unable to create transaction!"); - LogPrintf("MintLelantus() : %s", strError); - return strError; - } - - - if ((value + payTxFee.GetFeePerK()) > GetBalance()) - return _("Insufficient funds"); - - LogPrintf("payTxFee.GetFeePerK()=%d\n", payTxFee.GetFeePerK()); - int64_t nFeeRequired = 0; - - int nChangePosRet = -1; - - std::vector dMints; - std::list reservekeys; - if (!CreateLelantusMintTransactions(value, wtxAndFee, nFeeRequired, dMints, reservekeys, nChangePosRet, strError, coinControl, autoMintAll)) { - return strError; - } - - if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired)){ - LogPrintf("MintLelantus: returning aborted..\n"); - return "ABORTED"; - } - - CValidationState state; - CWalletDB walletdb(pwalletMain->strWalletFile); - - auto reservekey = reservekeys.begin(); - for(size_t i = 0; i < wtxAndFee.size(); i++) { - if (!CommitTransaction(wtxAndFee[i].first, *reservekey++, g_connman.get(), state)) { - return _( - "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); - } else { - LogPrintf("CommitTransaction success!\n"); - } - - //update mints with full transaction hash and then database them - CHDMint dMintTmp = dMints[i]; - mints.push_back(dMints[i]); - dMintTmp.SetTxHash(wtxAndFee[i].first.GetHash()); - zwallet->GetTracker().AddLelantus(walletdb, dMintTmp, true); - NotifyZerocoinChanged(this, - dMintTmp.GetPubcoinValue().GetHex(), - "New (" + std::to_string(dMintTmp.GetAmount()) + " mint)", - CT_NEW); - } - // Update nCountNextUse in HDMint wallet database - zwallet->UpdateCountDB(walletdb); - - return ""; -} - -std::string CWallet::MintAndStoreSpark( - const std::vector& outputs, - std::vector>& wtxAndFee, - bool subtractFeeFromAmount, - bool fSplit, - bool autoMintAll, - bool fAskFee, - const CCoinControl *coinControl) { - std::string strError; - - EnsureSparkWalletAvailable(); - - if (IsLocked()) { - strError = _("Error: Wallet locked, unable to create transaction!"); - LogPrintf("MintSpark() : %s", strError); - return strError; - } - - uint64_t value = 0; - for (auto& output : outputs) - value += output.v; + uint64_t value = 0; + for (auto& output : outputs) + value += output.v; if (cmp::greater((value + payTxFee.GetFeePerK()), GetBalance())) return _("Insufficient funds"); @@ -4848,44 +3880,6 @@ std::string CWallet::MintAndStoreSpark( return ""; } -std::vector CWallet::JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result, const CCoinControl *coinControl) { - // create transaction - std::vector spendCoins; //spends - std::vector mintCoins; // new mints - CAmount fee; - result = CreateLelantusJoinSplitTransaction(recipients, fee, newMints, spendCoins, mintCoins, coinControl); - - CommitLelantusTransaction(result, spendCoins, mintCoins); - - return spendCoins; -} - -CWalletTx CWallet::CreateLelantusJoinSplitTransaction( - const std::vector& recipients, - CAmount &fee, - const std::vector& newMints, - std::vector& spendCoins, - std::vector& mintCoins, - const CCoinControl *coinControl, - std::function modifier) -{ - // sanity check - EnsureMintWalletAvailable(); - - if (IsLocked()) { - throw std::runtime_error(_("Wallet locked")); - } - - // create transaction - LelantusJoinSplitBuilder builder(*this, *zwallet, coinControl); - - CWalletTx tx = builder.Build(recipients, fee, newMints, modifier); - spendCoins = builder.spendCoins; - mintCoins = builder.mintCoins; - - return tx; -} - CWalletTx CWallet::CreateSparkSpendTransaction( const std::vector& recipients, const std::vector>& privateRecipients, @@ -4893,7 +3887,7 @@ CWalletTx CWallet::CreateSparkSpendTransaction( const CCoinControl *coinControl) { // sanity check - EnsureMintWalletAvailable(); + EnsureSparkWalletAvailable(); if (IsLocked()) { throw std::runtime_error(_("Wallet locked")); @@ -4909,7 +3903,7 @@ CWalletTx CWallet::CreateSparkNameTransaction( const CCoinControl *coinControl) { // sanity check - EnsureMintWalletAvailable(); + EnsureSparkWalletAvailable(); if (IsLocked()) { throw std::runtime_error(_("Wallet locked")); @@ -4946,223 +3940,6 @@ CWalletTx CWallet::SpendAndStoreSpark( return result; } -bool CWallet::LelantusToSpark(std::string& strFailReason) { - std::list coins = GetAvailableLelantusCoins(); - CScript scriptChange; - { - // Reserve a new key pair from key pool - CPubKey vchPubKey; - bool ret; - ret = CReserveKey(this).GetReservedKey(vchPubKey); - if (!ret) - { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); - return false; - } - - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - } - - while (coins.size() > 0) { - std::size_t selectedNum = 0; - CCoinControl coinControl; - CAmount spendValue = 0; - while (true) { - COutPoint outPoint; - if (coins.size() > 0) { - auto coin = coins.begin(); - lelantus::GetOutPoint(outPoint, coin->value); - coinControl.Select(outPoint); - spendValue += coin->amount; - selectedNum++; - coins.erase(coin); - } else - break; - - if ((spendValue + coins.begin()->amount) > Params().GetConsensus().nMaxValueLelantusSpendPerTransaction) - break; - - if (selectedNum == Params().GetConsensus().nMaxLelantusInputPerTransaction) - break; - } - CRecipient recipient = {scriptChange, spendValue, true, {}, {}}; - - CWalletTx result; - JoinSplitLelantus({recipient}, {}, result, &coinControl); - coinControl.UnSelectAll(); - - uint32_t i = 0; - for (; i < result.tx->vout.size(); ++i) { - if (result.tx->vout[i].scriptPubKey == recipient.scriptPubKey) - break; - } - - COutPoint outPoint(result.GetHash(), i); - coinControl.Select(outPoint); - std::vector> wtxAndFee; - MintAndStoreSpark({}, wtxAndFee, true, true, false, false, &coinControl); - } - - return true; -} - - -std::pair CWallet::EstimateJoinSplitFee( - CAmount required, - bool subtractFeeFromAmount, - std::list coins, - const CCoinControl *coinControl) { - CAmount fee; - unsigned size; - std::vector spendCoins; - - for (fee = payTxFee.GetFeePerK();;) { - CAmount currentRequired = required; - - if (!subtractFeeFromAmount) - currentRequired += fee; - - spendCoins.clear(); - const auto &consensusParams = Params().GetConsensus(); - CAmount changeToMint = 0; - - try { - if (currentRequired > 0) { - if (!this->GetCoinsToJoinSplit(currentRequired, spendCoins, changeToMint, coins, - consensusParams.nMaxLelantusInputPerTransaction, - consensusParams.nMaxValueLelantusSpendPerTransaction, coinControl)) { - return std::make_pair(0, 0); - } - } - } catch (std::runtime_error const &) { - } - - // 1054 is constant part, mainly Schnorr and Range proofs, 2560 is for each sigma/aux data - // 179 other parts of tx, assuming 1 utxo and 1 jmint - size = 1054 + 2560 * (spendCoins.size()) + 179; - CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); - - if (fee >= feeNeeded) { - break; - } - - fee = feeNeeded; - - if(subtractFeeFromAmount) - break; - } - - return std::make_pair(fee, size); -} - -bool CWallet::CommitLelantusTransaction(CWalletTx& wtxNew, std::vector& spendCoins, std::vector& mintCoins) { - EnsureMintWalletAvailable(); - - // commit - try { - CValidationState state; - CReserveKey reserveKey(this); - CommitTransaction(wtxNew, reserveKey, g_connman.get(), state); - } catch (const std::exception &) { - auto error = _( - "Error: The transaction was rejected! This might happen if some of " - "the coins in your wallet were already spent, such as if you used " - "a copy of wallet.dat and coins were spent in the copy but not " - "marked as spent here." - ); - - std::throw_with_nested(std::runtime_error(error)); - } - - // mark selected coins as used - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - CWalletDB db(strWalletFile); - - for (auto& coin : spendCoins) { - // get coin id & height - int height, id; - - std::tie(height, id) = lelantusState->GetMintedCoinHeightAndId(lelantus::PublicCoin(coin.value)); - - // add CLelantusSpendEntry - CLelantusSpendEntry spend; - - spend.coinSerial = coin.serialNumber; - spend.hashTx = wtxNew.GetHash(); - spend.pubCoin = coin.value; - spend.id = id; - spend.amount = coin.amount; - - if (!db.WriteLelantusSpendSerialEntry(spend)) { - throw std::runtime_error(_("Failed to write coin serial number into wallet")); - } - - //Set spent mint as used in memory - uint256 hashPubcoin = primitives::GetPubCoinValueHash(coin.value); - zwallet->GetTracker().SetLelantusPubcoinUsed(hashPubcoin, wtxNew.GetHash()); - CLelantusMintMeta metaCheck; - zwallet->GetTracker().GetLelantusMetaFromPubcoin(hashPubcoin, metaCheck); - if (!metaCheck.isUsed) { - std::string strError = "Error, mint with pubcoin hash " + hashPubcoin.GetHex() + " did not get marked as used"; - LogPrintf("SpendLelantus() : %s\n", strError.c_str()); - } - - //Set spent mint as used in DB - zwallet->GetTracker().UpdateState(metaCheck); - - // update CLelantusEntry - coin.IsUsed = true; - coin.id = id; - coin.nHeight = height; - - // raise event - NotifyZerocoinChanged( - this, - coin.value.GetHex(), - "Used (" + std::to_string(coin.amount) + " mint)", - CT_UPDATED); - } - - for (auto& coin : mintCoins) { - coin.SetTxHash(wtxNew.GetHash()); - zwallet->GetTracker().AddLelantus(db, coin, true); - - // raise event - NotifyZerocoinChanged(this, - coin.GetPubcoinValue().GetHex(), - "New (" + std::to_string(coin.GetAmount()) + " mint)", - CT_NEW); - } - - // Update nCountNextUse in HDMint wallet database - zwallet->UpdateCountDB(db); - - return true; -} - -bool CWallet::GetMint(const uint256& hashSerial, CLelantusEntry& mint, bool forEstimation) const -{ - EnsureMintWalletAvailable(); - - if (IsLocked() && !forEstimation) { - return false; - } - - CLelantusMintMeta meta; - if(!zwallet->GetTracker().GetMetaFromSerial(hashSerial, meta)) - return error("%s: serialhash %s is not in tracker", __func__, hashSerial.GetHex()); - - CWalletDB walletdb(strWalletFile); - - CHDMint dMint; - if (!walletdb.ReadHDMint(meta.GetPubCoinValueHash(), true, dMint)) - return error("%s: failed to read deterministic Lelantus mint", __func__); - if (!zwallet->RegenerateMint(walletdb, dMint, mint, forEstimation)) - return error("%s: failed to generate Lelantus mint", __func__); - return true; - -} - void CWallet::ListAccountCreditDebit(const std::string& strAccount, std::list& entries) { CWalletDB walletdb(strWalletFile); return walletdb.ListAccountCreditDebit(strAccount, entries); @@ -5332,18 +4109,6 @@ DBErrors CWallet::ZapWalletTx(std::vector& vWtx) return DB_LOAD_OK; } -DBErrors CWallet::ZapLelantusMints() { - if (!fFileBacked) - return DB_LOAD_OK; - DBErrors nZapLelantusMintRet = CWalletDB(strWalletFile, "cr+").ZapLelantusMints(this); - if (nZapLelantusMintRet != DB_LOAD_OK){ - LogPrintf("Failed to remove Lelantus mints from CWalletDB"); - return nZapLelantusMintRet; - } - - return DB_LOAD_OK; -} - DBErrors CWallet::ZapSparkMints() { if (!fFileBacked) return DB_LOAD_OK; @@ -6039,7 +4804,6 @@ std::string CWallet::GetWalletHelpString(bool showDebug) std::string strUsage = HelpMessageGroup(_("Wallet options:")); strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); strUsage += HelpMessageOpt("-keypool=", strprintf(_("Set key pool size to (default: %u)"), DEFAULT_KEYPOOL_SIZE)); - strUsage += HelpMessageOpt("-mintpoolsize=", strprintf(_("Set mint pool size to (default: %u)"), DEFAULT_MINTPOOL_SIZE)); strUsage += HelpMessageOpt("-fallbackfee=", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE))); strUsage += HelpMessageOpt("-mintxfee=", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)"), @@ -6058,7 +4822,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-mnemonic=", _("User defined mnemonic for HD wallet (bip39). Only has effect during wallet creation/first start (default: randomly generated)")); strUsage += HelpMessageOpt("-mnemonicpassphrase=", _("User defined mnemonic passphrase for HD wallet (BIP39). Only has effect during wallet creation/first start (default: empty string)")); strUsage += HelpMessageOpt("-hdseed=", _("User defined seed for HD wallet (should be in hex). Only has effect during wallet creation/first start (default: randomly generated)")); - strUsage += HelpMessageOpt("-batching", _("In case of sync/reindex verifies sigma/lelantus proofs with batch verification, default: true")); + strUsage += HelpMessageOpt("-batching", _("In case of sync/reindex verifies privacy (Spark) proofs with batch verification, default: true")); strUsage += HelpMessageOpt("-mobile", _("Use this argument when you want to keep additional data in block index for mobile api, default: false")); strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (default: %u)"), DEFAULT_WALLET_RBF)); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); @@ -6088,9 +4852,8 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) uiInterface.InitMessage(_("Zapping all Sigma mints from wallet...")); CWallet *tempWallet = new CWallet(walletFile); - DBErrors nZapLelantusMintRet = tempWallet->ZapLelantusMints(); DBErrors nZapSparkMintRet = tempWallet->ZapSparkMints(); - if (nZapLelantusMintRet != DB_LOAD_OK || nZapSparkMintRet != DB_LOAD_OK) { + if (nZapSparkMintRet != DB_LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return NULL; } @@ -6219,8 +4982,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); if (pwalletMain->IsHDSeedAvailable()) { - walletInstance->zwallet = std::make_unique(pwalletMain->strWalletFile); - // if it is first run, we need to generate the full key set for spark, if not we are loading spark wallet from db walletInstance->sparkWallet = std::make_unique(pwalletMain->strWalletFile); @@ -6543,71 +5304,6 @@ bip47::CPaymentCode CWallet::GeneratePcode(std::string const & label) return newAcc.getMyPcode(); } -CWalletTx CWallet::PrepareAndSendNotificationTx(bip47::CPaymentCode const & theirPcode) -{ - bip47::CPaymentChannel pchannel = SetupPchannel(theirPcode); - - CWalletTx wtxNew; - - if (GetBroadcastTransactions() && !g_connman) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - } - - CBitcoinAddress const notifAddr = pchannel.getTheirPcode().getNotificationAddress(); - - std::vector recipients; - std::vector newMints; - - CRecipient receiver; - receiver.scriptPubKey = GetScriptForDestination(notifAddr.Get()); - receiver.nAmount = bip47::NotificationTxValue; - receiver.fSubtractFeeFromAmount = false; - - recipients.emplace_back(receiver); - CScript opReturnScript = CScript() << OP_RETURN << std::vector(80); // Passing empty array to calc fees - recipients.push_back({opReturnScript, 0, false, {}, {}}); - - auto throwSigma = - [](){throw std::runtime_error(std::string("There are unspent Sigma coins in your wallet. Using Sigma coins for BIP47 is not supported. Please spend your Sigma coins before establishing a BIP47 channel."));}; - - try { - std::vector spendCoins; - std::vector mintCoins; - CAmount fee; - - wtxNew = CreateLelantusJoinSplitTransaction(recipients, fee, newMints, spendCoins, mintCoins, nullptr, - [&pchannel, &throwSigma](CTxOut & out, LelantusJoinSplitBuilder const & builder) { - if(out.scriptPubKey[0] == OP_RETURN) { - CKey spendPrivKey; - if (builder.spendCoins.empty()) - throwSigma(); - spendPrivKey.Set(builder.spendCoins[0].ecdsaSecretKey.begin(), builder.spendCoins[0].ecdsaSecretKey.end(), false); - CDataStream ds(SER_NETWORK, 0); - ds << builder.spendCoins[0].serialNumber; - bip47::Bytes const pcode = pchannel.getMaskedPayload((unsigned char const *)ds.vch.data(), ds.vch.size(), spendPrivKey); - out.scriptPubKey = CScript() << OP_RETURN << pcode; - } - }); - - if (spendCoins.empty()) - throw std::runtime_error(std::string("Cannot create a Lelantus spend to address: " + notifAddr.ToString()).c_str()); - - CommitLelantusTransaction(wtxNew, spendCoins, mintCoins); - LogBip47("Paymentcode %s was sent to notification address: %s\n", pchannel.getMyPcode().toString().c_str(), notifAddr.ToString().c_str() ); - } - catch (const InsufficientFunds& e) - { - throw e; - } - catch (const std::exception& e) - { - throw WalletError(e.what()); - } - - SetNotificationTxId(theirPcode, wtxNew.GetHash()); - return wtxNew; -} - std::vector CWallet::ListPcodes() { std::vector result; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 623a425173..bb58bf273f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -7,7 +7,6 @@ #define BITCOIN_WALLET_WALLET_H #include "amount.h" -#include "../liblelantus/coin.h" #include "libspark/keys.h" #include "streams.h" #include "tinyformat.h" @@ -27,9 +26,6 @@ #include "firo_params.h" #include "univalue.h" -#include "hdmint/tracker.h" -#include "hdmint/wallet.h" - #include "primitives/mint_spend.h" #include "bip47/paymentcode.h" @@ -641,8 +637,6 @@ class CAccountingEntry std::vector _ssExtra; }; -class LelantusJoinSplitBuilder; - /**Open unlock wallet window**/ //static boost::signals2::signal UnlockWallet; @@ -760,8 +754,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID; - std::unique_ptr zwallet; - std::unique_ptr sparkWallet; std::atomic fUnlockRequested; @@ -799,7 +791,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface fAnonymizableTallyCachedNonDenom = false; vecAnonymizableTallyCached.clear(); vecAnonymizableTallyCachedNonDenom.clear(); - zwallet = NULL; bip47wallet.reset(); } @@ -822,8 +813,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::set setLockedCoins; - std::pair cachedLelantusBalance = {-1, -1}; - const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature @@ -937,7 +926,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; std::vector ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); CAmount GetBalance(bool fExcludeLocked = false) const; - std::pair GetPrivateBalance(); bool TryGetBalances(CAmount& balance, CAmount& unconfirmedBalance, CAmount& newImmatureBalance, CAmount& mintableBalance) const; CAmount GetUnconfirmedBalance() const; CAmount GetImmatureBalance() const; @@ -946,15 +934,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetImmatureWatchOnlyBalance() const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account, bool fAddLocked = false) const; - static CRecipient CreateLelantusMintRecipient( - lelantus::PrivateCoin& coin, - CHDMint& vDMint, - bool generate = true); - - // Returns a list of unspent and verified coins, I.E. coins which are ready - // to be spent. - std::list GetAvailableLelantusCoins(const CCoinControl *coinControl = NULL, bool includeUnsafe = false, bool forEstimation = false) const; - // Returns the list of pairs of coins and meta data for that coin, std::list GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; @@ -970,17 +949,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * \returns true, if it was possible to spend exactly required(rounded up to 0.1 firo) amount using coins we have. */ - bool GetCoinsToJoinSplit( - CAmount required, - std::vector& coinsToSpend_out, - CAmount& changeToMint, - std::list coins, - const size_t coinsToSpendLimit = SIZE_MAX, - const CAmount amountToSpendLimit = MAX_MONEY, - const CCoinControl *coinControl = NULL) const; - - std::vector ProvePrivateTxOwn(const uint256& txid, const std::string& message) const; - /** * Insert additional inputs into the transaction by * calling CreateTransaction(); @@ -998,13 +966,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** * Add Mint and Spend functions */ - void ListAvailableLelantusMintCoins(std::vector &vCoins, bool fOnlyConfirmed) const; - - bool CreateLelantusMintTransactions(CAmount valueToMint, std::vector>& wtxAndFee, - CAmount& nAllFeeRet, std::vector& dMints, - std::list& reservekeys, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false, bool sign = true); - std::pair GetSparkBalance(); bool IsSparkAddressMine(const std::string& address); @@ -1020,28 +981,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CCoinControl *coinControl, bool autoMintAll = false); - - CWalletTx CreateLelantusJoinSplitTransaction( - const std::vector& recipients, - CAmount& fee, - const std::vector& newMints, - std::vector& spendCoins, - std::vector& mintCoins, - const CCoinControl *coinControl = NULL, - std::function modifier = nullptr); - - bool CommitLelantusTransaction(CWalletTx& wtxNew, std::vector& spendCoins, std::vector& mintCoins); std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); - std::string MintAndStoreLelantus( - const CAmount& value, - std::vector>& wtxAndFee, - std::vector& mints, - bool autoMintAll = false, - bool fAskFee = false, - const CCoinControl *coinControl = NULL); - std::string MintAndStoreSpark( const std::vector& outputs, std::vector>& wtxAndFee, @@ -1069,16 +1011,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount &fee, const CCoinControl *coinControl = NULL); - bool LelantusToSpark(std::string& strFailReason); - - std::vector JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result, const CCoinControl *coinControl = NULL); - - std::pair EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, std::list coins, const CCoinControl *coinControl); - - bool GetMint(const uint256& hashSerial, CLelantusEntry& mint, bool forEstimation = false) const; - - bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); - + bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state, bool fCheckTransaction = false); bool CreateCollateralTransaction(CMutableTransaction& txCollateral, std::string& strReason); bool ConvertList(std::vector vecTxIn, std::vector& vecAmounts); @@ -1153,8 +1086,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface DBErrors ZapWalletTx(std::vector& vWtx); DBErrors ZapSelectTx(std::vector& vHashIn, std::vector& vHashOut); - // Remove all Lelantus HDMint objects from WalletDB - DBErrors ZapLelantusMints(); // Remove all Spark Mint objects from WalletDB DBErrors ZapSparkMints(); @@ -1238,10 +1169,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface */ boost::signals2::signal NotifyTransactionChanged; - /** - * sigma/lelantus entry changed. - * @note called with lock cs_wallet held. - */ + boost::signals2::signal NotifyZerocoinChanged; @@ -1322,9 +1250,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /* Generates and strores a new payment code for receiving*/ bip47::CPaymentCode GeneratePcode(std::string const & label); - /*Prepares and sends a notification tx using Lelantus facilities*/ - CWalletTx PrepareAndSendNotificationTx(bip47::CPaymentCode const & theirPcode); - /* Lists all receiving pcodes as tuples of (pcode, label, notification address) */ std::vector ListPcodes(); @@ -1458,6 +1383,4 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins return true; } -CWalletTx PrepareAndSendNotificationTx(CWallet* pwallet, bip47::CPaymentCode const & theirPcode); - #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 7a34e38d5a..3d3dd1e054 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -271,61 +271,10 @@ void CWalletDB::ListAccountCreditDebit(const std::string& strAccount, std::list< pcursor->close(); } -bool CWalletDB::WriteLelantusSpendSerialEntry(const CLelantusSpendEntry& lelantusSpend) { - return Write(std::make_pair(std::string("lelantus_spend"), lelantusSpend.coinSerial), lelantusSpend, true); -} - -bool CWalletDB::HasLelantusSpendSerialEntry(const secp_primitives::Scalar& serial) { - return Exists(std::make_pair(std::string("lelantus_spend"), serial)); -} - -bool CWalletDB::EraseLelantusSpendSerialEntry(const CLelantusSpendEntry& lelantusSpend) { - return Erase(std::make_pair(std::string("lelantus_spend"), lelantusSpend.coinSerial)); -} - -bool CWalletDB::ReadLelantusSpendSerialEntry(const secp_primitives::Scalar& serial, CLelantusSpendEntry& lelantusSpend) { - return Read(std::make_pair(std::string("lelantus_spend"), serial), lelantusSpend); -} - bool CWalletDB::WriteCalculatedZCBlock(int height) { return Write(std::string("calculatedzcblock"), height); } -void CWalletDB::ListLelantusSpendSerial(std::list & listLelantusSpendSerial) { - Dbc *pcursor = GetCursor(); - if (!pcursor) - throw std::runtime_error("CWalletDB::ListLelantusSpendSerial() : cannot create DB cursor"); - bool setRange = true; - while (true) { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - if (setRange) - ssKey << std::make_pair(std::string("lelantus_spend"), secp_primitives::Scalar()); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange); - setRange = false; - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) { - pcursor->close(); - throw std::runtime_error("CWalletDB::ListLelantusSpendSerial() : error scanning DB"); - } - - // Unserialize - std::string strType; - ssKey >> strType; - if (strType != "lelantus_spend") - break; - Scalar value; - ssKey >> value; - CLelantusSpendEntry lelantusSpendItem; - ssValue >> lelantusSpendItem; - listLelantusSpendSerial.push_back(lelantusSpendItem); - } - - pcursor->close(); -} - DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet) { LOCK(pwallet->cs_wallet); @@ -402,36 +351,6 @@ DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet) return DB_LOAD_OK; } -bool CWalletDB::WriteHDMint(const uint256& hashPubcoin, const CHDMint& dMint, bool isLelantus) -{ - std::string name; - if(!isLelantus) - name = "hdmint"; - else - name = "hdmint_lelantus"; - return Write(std::make_pair(name, hashPubcoin), dMint, true); -} - -bool CWalletDB::ReadHDMint(const uint256& hashPubcoin, bool isLelantus, CHDMint& dMint) -{ - std::string name; - if(!isLelantus) - name = "hdmint"; - else - name = "hdmint_lelantus"; - return Read(std::make_pair(name, hashPubcoin), dMint); -} - -bool CWalletDB::EraseHDMint(const CHDMint& dMint) { - nWalletDBUpdateCounter++; - uint256 hash = dMint.GetPubCoinHash(); - return Erase(std::make_pair(std::string("hdmint"), hash)) || Erase(std::make_pair(std::string("hdmint_lelantus"), hash)); -} - -bool CWalletDB::HasHDMint(const secp_primitives::GroupElement& pub) { - return Exists(std::make_pair(std::string("hdmint"), primitives::GetPubCoinValueHash(pub))) || Exists(std::make_pair(std::string("hdmint_lelantus"), primitives::GetPubCoinValueHash(pub))); -} - bool CWalletDB::WritePubcoinHashes(const uint256& fullHash, const uint256& reducedHash) { return Write(std::make_pair(std::string("pubhash"), fullHash), reducedHash, true); } @@ -984,20 +903,6 @@ DBErrors CWalletDB::ZapSelectTx(CWallet* pwallet, std::vector& vTxHashI return DB_LOAD_OK; } -DBErrors CWalletDB::ZapLelantusMints(CWallet *pwallet) { - // get list of HD Mints - std::list lelantusHDMints = ListHDMints(true); - - // erase each HD Mint - BOOST_FOREACH(CHDMint & hdMint, lelantusHDMints) - { - if (!EraseHDMint(hdMint)) - return DB_CORRUPT; - } - - return DB_LOAD_OK; -} - DBErrors CWalletDB::ZapSparkMints(CWallet *pwallet) { // get list of spark Mints std::unordered_map sparkMints = ListSparkMints(); @@ -1449,143 +1354,6 @@ bool CWalletDB::ReadMintPoolPair(const uint256& hashPubcoin, uint160& hashSeedMa return true; } -//! list of MintPoolEntry objects mapped with pubCoin hash, returned as pairs -std::vector> CWalletDB::ListMintPool() -{ - std::vector> listPool; - Dbc* pcursor = GetCursor(); - if (!pcursor) - throw std::runtime_error(std::string(__func__)+" : cannot create DB cursor"); - bool setRange = true; - for (;;) - { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - if (setRange) - ssKey << std::make_pair(std::string("mintpool"), ArithToUint256(arith_uint256(0))); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange); - setRange = false; - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) - { - pcursor->close(); - throw std::runtime_error(std::string(__func__)+" : error scanning DB"); - } - - // Unserialize - - try { - std::string strType; - ssKey >> strType; - if (strType != "mintpool") - break; - - uint256 hashPubcoin; - ssKey >> hashPubcoin; - - uint160 hashSeedMaster; - ssValue >> hashSeedMaster; - - CKeyID seedId; - ssValue >> seedId; - - int32_t nCount; - ssValue >> nCount; - - MintPoolEntry mintPoolEntry(hashSeedMaster, seedId, nCount); - - listPool.push_back(std::make_pair(hashPubcoin, mintPoolEntry)); - } catch (std::ios_base::failure const &) { - // There maybe some old entries that don't conform to the latest version. Just skipping those. - } - } - - pcursor->close(); - - return listPool; -} - -std::list CWalletDB::ListHDMints(bool isLelantus) -{ - std::list listMints; - Dbc* pcursor = GetCursor(); - if (!pcursor) - throw std::runtime_error(std::string(__func__)+" : cannot create DB cursor"); - - std::string mintName; - - if(isLelantus) - mintName = "hdmint_lelantus"; - else - mintName = "hdmint"; - - bool setRange = true; - for (;;) - { - // Read next record - CDataStream ssKey(SER_DISK, CLIENT_VERSION); - if (setRange) - ssKey << std::make_pair(mintName, ArithToUint256(arith_uint256(0))); - CDataStream ssValue(SER_DISK, CLIENT_VERSION); - int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange); - setRange = false; - if (ret == DB_NOTFOUND) - break; - else if (ret != 0) - { - pcursor->close(); - throw std::runtime_error(std::string(__func__)+" : error scanning DB"); - } - - // Unserialize - std::string strType; - ssKey >> strType; - if (strType != mintName) - break; - - uint256 hashPubcoin; - ssKey >> hashPubcoin; - - CHDMint mint; - ssValue >> mint; - - listMints.emplace_back(mint); - } - - pcursor->close(); - return listMints; -} - -bool CWalletDB::ArchiveDeterministicOrphan(const CHDMint& dMint) -{ - if (!Write(std::make_pair(std::string("dzco"), dMint.GetPubCoinHash()), dMint)) - return error("%s: write failed", __func__); - - if (!Erase(std::make_pair(std::string("hdmint"), dMint.GetPubCoinHash()))) - return error("%s: failed to erase", __func__); - - if (!Erase(std::make_pair(std::string("hdmint_lelantus"), dMint.GetPubCoinHash()))) - return error("%s: failed to erase lelantus", __func__); - - return true; -} - -bool CWalletDB::UnarchiveHDMint(const uint256& hashPubcoin, bool isLelantus, CHDMint& dMint) -{ - if (!Read(std::make_pair(std::string("dzco"), hashPubcoin), dMint)) - return error("%s: failed to retrieve deterministic mint from archive", __func__); - - if (!WriteHDMint(hashPubcoin, dMint, isLelantus)) - return error("%s: failed to write deterministic mint", __func__); - - if (!Erase(std::make_pair(std::string("dzco"), dMint.GetPubCoinHash()))) - return error("%s : failed to erase archived deterministic mint", __func__); - - return true; -} - void CWalletDB::IncrementUpdateCounter() { nWalletDBUpdateCounter++; diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 01ac024713..89749e2b73 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -14,8 +14,6 @@ #include "streams.h" #include "key.h" -#include "hdmint/hdmint.h" -#include "hdmint/mintpool.h" #include "../secp256k1/include/GroupElement.h" #include "../secp256k1/include/Scalar.h" #include "../libspark/keys.h" @@ -238,12 +236,6 @@ class CWalletDB : public CDB CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); - void ListLelantusSpendSerial(std::list& listLelantusSpendSerial); - bool WriteLelantusSpendSerialEntry(const CLelantusSpendEntry& lelantusSpend); - bool ReadLelantusSpendSerialEntry(const secp_primitives::Scalar& serial, CLelantusSpendEntry& lelantusSpend); - bool HasLelantusSpendSerialEntry(const secp_primitives::Scalar& serial); - bool EraseLelantusSpendSerialEntry(const CLelantusSpendEntry& lelantusSpend); - bool ReadCalculatedZCBlock(int& height); bool WriteCalculatedZCBlock(int height); @@ -252,7 +244,6 @@ class CWalletDB : public CDB DBErrors FindWalletTx(CWallet* pwallet, std::vector& vTxHash, std::vector& vWtx); DBErrors ZapWalletTx(CWallet* pwallet, std::vector& vWtx); DBErrors ZapSelectTx(CWallet* pwallet, std::vector& vHashIn, std::vector& vHashOut); - DBErrors ZapLelantusMints(CWallet *pwallet); DBErrors ZapSparkMints(CWallet *pwallet); static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename); @@ -269,19 +260,10 @@ class CWalletDB : public CDB bool readFullViewKey(spark::FullViewKey& viewKey); bool writeFullViewKey(const spark::FullViewKey& viewKey); - bool ArchiveDeterministicOrphan(const CHDMint& dMint); - bool UnarchiveHDMint(const uint256& hashPubcoin, bool isLelantus, CHDMint& dMint); - - bool WriteHDMint(const uint256& hashPubcoin, const CHDMint& dMint, bool isLelantus); - bool ReadHDMint(const uint256& hashPubcoin, bool isLelantus, CHDMint& dMint); - bool EraseHDMint(const CHDMint& dMint); - bool HasHDMint(const secp_primitives::GroupElement& pub); - bool WritePubcoinHashes(const uint256& fullHash, const uint256& reducedHash); bool ReadPubcoinHashes(const uint256& fullHash, uint256& reducedHash); bool ErasePubcoinHashes(const uint256& fullHash); - std::list ListHDMints(bool isLelantus); bool WritePubcoin(const uint256& hashSerial, const GroupElement& hashPubcoin); bool ReadPubcoin(const uint256& hashSerial, GroupElement& hashPubcoin); bool ErasePubcoin(const uint256& hashSerial); @@ -289,7 +271,6 @@ class CWalletDB : public CDB bool EraseMintPoolPair(const uint256& hashPubcoin); bool WriteMintPoolPair(const uint256& hashPubcoin, const std::tuple& hashSeedMintPool); bool ReadMintPoolPair(const uint256& hashPubcoin, uint160& hashSeedMaster, CKeyID& seedId, int32_t& nCount); - std::vector> ListMintPool(); std::unordered_map ListSparkMints(); bool WriteSparkOutputTx(const CScript& scriptPubKey, const CSparkOutputTx& output);